From a1e7e66ae133954128b06a0761906cffb3f1a296 Mon Sep 17 00:00:00 2001 From: dtzp555 Date: Thu, 19 Mar 2026 21:10:14 +1000 Subject: [PATCH] =?UTF-8?q?feat:=20initial=20commit=20=E2=80=94=20odo=20v0?= =?UTF-8?q?.9.4=20Electron=20desktop=20app=20for=20OpenClaw=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Electron main process with tray icon and IPC setup - openclaw-manager.js server bridge - Architecture design document (ODO_ARCHITECTURE.md) - preload.js for renderer context bridge --- .gitignore | 8 + ODO_ARCHITECTURE.md | 582 ++++ README.md | 83 + assets/ICON_NEEDED.md | 2 + main.js | 224 ++ package-lock.json | 5278 ++++++++++++++++++++++++++++++++++++ package.json | 47 + preload.js | 12 + server/openclaw-manager.js | 4550 +++++++++++++++++++++++++++++++ 9 files changed, 10786 insertions(+) create mode 100644 .gitignore create mode 100644 ODO_ARCHITECTURE.md create mode 100644 README.md create mode 100644 assets/ICON_NEEDED.md create mode 100644 main.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 preload.js create mode 100644 server/openclaw-manager.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1758814 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +build/ +*.dmg +*.exe +*.AppImage +.DS_Store +manager-config.json diff --git a/ODO_ARCHITECTURE.md b/ODO_ARCHITECTURE.md new file mode 100644 index 0000000..e345414 --- /dev/null +++ b/ODO_ARCHITECTURE.md @@ -0,0 +1,582 @@ +# ODO — OpenClaw Dashboard Orchestrator + +## Architecture Design Document + +> Version: Draft 1.0 +> Date: 2026-03-18 +> Status: Proposal — pending review + +--- + +## 1. Product Overview + +### What is ODO? + +ODO is the desktop app evolution of OCM (OpenClaw Manager). It is a cross-platform Electron application that lets users manage one or more OpenClaw instances — local or remote — from a single window. + +### One-line pitch + +**"Double-click to manage all your OpenClaw agents, on this machine or any other."** + +### Who is ODO for? + +- People already running OpenClaw who want visual control instead of hand-editing JSON +- People running OpenClaw on multiple machines (home server, VPS, NAS) who want one place to manage them all +- Power users who still want a built-in CLI but prefer a desktop app over a browser tab + +### Who is ODO not for? + +- People who have never installed OpenClaw (ODO is not an installer) +- People who want a chat UI for talking to agents (that's ClawX / ClawUI territory) +- People who want a cloud-hosted SaaS dashboard + +--- + +## 2. Competitive Positioning + +| Product | Type | Focus | Weakness (ODO's opportunity) | +|---------|------|-------|------------------------------| +| ClawX | Desktop (Electron) | Chat client, talking to agents | No ops/config management | +| ClawUI | Desktop (Electron) | Chat + session inspection | No backup, no multi-instance | +| TenacitOS | Web dashboard | Cost analytics, monitoring | Web-only, no desktop, no remote | +| ClawBoard | Web dashboard | Agent management | Web-only, local only | +| VidClaw | Web dashboard | Kanban + file editing | Local only, no remote | +| Claworc | Web platform | Enterprise multi-instance | Requires Docker/K8s, overkill for individuals | +| **ODO** | **Desktop (Electron)** | **Ops control + remote management** | — | + +### ODO's unique value + +1. **Multi-instance**: manage local + remote OpenClaw from one window +2. **Ops-first**: backup/restore, health, logs, gateway control — not just monitoring +3. **Configuration editor**: visual tool/plugin management instead of JSON editing +4. **Disaster recovery**: versioned config snapshots + remote backup + +--- + +## 3. Feature Specification + +### 3.1 Core Features (daily use) + +#### Dashboard +- System health: CPU, RAM, disk gauges +- Gateway status: running/stopped, PID, uptime +- Agent overview: count, last activity, binding summary +- Quick actions: restart gateway, open logs +- Auto-refresh toggle (10s interval) +- **NEW**: Connection selector — switch between local and remote instances + +#### Agents Console +- Agent tree: visual hierarchy of main agents and sub-agents +- Per-agent details: bound channels/groups/threads, model, workspace path +- Inline model switching (dropdown, save without rebuild) +- Workspace file browser: view and edit SOUL.md, MEMORY.md, config files +- **EVOLVED**: De-emphasize "Add Sub-Agent" wizard. The main use case is now: + - View current agent structure (read-heavy) + - Manually adjust model, tools, plugins per agent + - Edit personality/memory files + - Agent creation still available but no longer the hero flow + +#### Stats (Token Cost Monitor) +- Parse session JSONL files for real token usage +- Breakdown: by model, by agent, by day +- Visual charts: daily bar chart, model pie chart +- Time range filter: 1d / 7d / 30d / 90d +- Custom model pricing (stored in local config) +- **Borrow from TenacitOS**: trend lines, cost prediction + +### 3.2 Professional Features (when needed) + +#### CLI Terminal +- Full-width terminal panel, expandable from bottom dock +- Real-time streaming output (chunked HTTP) +- Tab completion: OpenClaw subcommands + history + presets +- Saved favorites (user-defined frequent commands) +- Preset command library (15+ common operations) +- **Keep as-is**: this is a differentiator, power users love it + +#### Log Viewer (NEW) +- Real-time gateway log streaming (tail -f style) +- Log level filter: ERROR / WARN / INFO / DEBUG +- Text search within logs +- Auto-scroll with pause-on-scroll +- Timestamp display (Brisbane timezone or user preference) +- Log file selector (gateway.log, agent session logs) +- Export / copy selected log range + +#### Backup & Recovery +- **Local backup**: one-click snapshot of openclaw.json + agent configs to a local directory +- **Remote backup**: SFTP/SCP to a remote server (simplified from OCM's NAS module) + - Host, port, username, auth (password or SSH key) + - One-click test connection + - One-click backup now + - Scheduled backup (daily, via system scheduler integration) +- **Config version history**: every config change creates a timestamped snapshot + - Version list with diff view (before/after comparison) + - One-click rollback to any version + - Auto-cleanup: keep last 50 versions, older ones auto-pruned +- **Remote backup simplification vs OCM**: + - Remove: old NAS CBC cipher compatibility (too niche) + - Remove: SSH key generation UI (users should manage keys externally) + - Keep: password auth, key auth, test connection, backup now, scheduled backup + - Add: backup to any SSH-accessible server (not just NAS) + +#### Routing (formerly Channels) +- View all channel bindings grouped by agent +- Expand/collapse per agent +- Add/remove bindings +- Support: Telegram groups, Discord channels/threads +- Agent filter (all agents / specific agent) + +#### Models & Auth +- Primary model selector (from `openclaw models list`) +- Fallback chain editor +- Provider auth status overview +- Auth setup guides per provider (click-to-copy CLI commands) + +### 3.3 Desktop App Capabilities (NEW) + +#### System Tray +- App icon in system tray (macOS menu bar / Windows system tray / Linux indicator) +- Closing the window minimizes to tray (does not quit) +- Tray menu: Show Window, Gateway Status, Restart Gateway, Quit +- Status indicator: green dot (healthy) / red dot (gateway down) + +#### Gateway Lifecycle Management +- On app launch: auto-start local OpenClaw gateway if not already running +- On app quit: optionally stop gateway (user preference) +- Gateway crash detection: notification + one-click restart +- Option: "Keep gateway running after ODO closes" (for server use) + +#### Auto-Start +- Option to launch ODO on system login +- Uses: macOS Login Items / Windows Registry / Linux autostart + +#### Auto-Update +- Check for updates on launch (GitHub Releases) +- Download + install in background +- Notification: "Update available — restart to apply" +- Uses: electron-updater + +#### Multi-Instance Connection Manager (NEW — key differentiator) +- Connection list: save multiple OpenClaw instances + - Name (e.g., "Home Server", "VPS", "Local") + - Type: Local (filesystem) or Remote (WebSocket) + - For remote: host, port (18789), auth token + - Connection status indicator +- Switch between instances from the top bar +- All features work identically for local and remote instances +- Remote protocol: OpenClaw Gateway WebSocket API +- Connection security: token auth, optional SSH tunnel instructions + +--- + +## 4. Technical Architecture + +### 4.1 Stack + +| Layer | Technology | Rationale | +|-------|-----------|-----------| +| Desktop shell | Electron 33+ | Cross-platform, mature, largest ecosystem | +| Frontend | React 19 + TypeScript | Component-based, maintainable, huge community | +| UI framework | Tailwind CSS 4 | Utility-first, fast iteration, no custom CSS sprawl | +| Charts | Recharts | React-native charting, lightweight | +| Build tool | Vite | Fast dev server, HMR, clean builds | +| Packaging | electron-builder | .dmg / .exe / .AppImage in one command | +| Auto-update | electron-updater | GitHub Releases integration | +| Backend (main process) | Node.js (Electron built-in) | Reuse OCM server logic | + +### 4.2 Process Architecture + +``` +┌──────────────────────────────────────────────┐ +│ Electron │ +│ │ +│ ┌──────────────────┐ ┌───────────────────┐ │ +│ │ Main Process │ │ Renderer Process │ │ +│ │ (Node.js) │ │ (React App) │ │ +│ │ │ │ │ │ +│ │ - HTTP Server │◄─┤ - Dashboard │ │ +│ │ - File I/O │ │ - Agents Console │ │ +│ │ - child_process │ │ - Stats │ │ +│ │ - Gateway mgmt │ │ - CLI Terminal │ │ +│ │ - Backup engine │ │ - Log Viewer │ │ +│ │ - Remote connect │ │ - Backup UI │ │ +│ │ - System tray │ │ - Settings │ │ +│ │ │ │ │ │ +│ │ IPC Bridge ──────┤──┤── IPC Bridge │ │ +│ └──────────────────┘ └───────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────┐ │ +│ │ OpenClaw Gateway (child process or remote)│ │ +│ └──────────────────────────────────────────┘ │ +└──────────────────────────────────────────────┘ +``` + +**Main Process** handles: +- All filesystem operations (read/write openclaw.json, logs, backups) +- Spawning OpenClaw CLI commands +- Gateway process lifecycle +- Remote WebSocket connections to other OpenClaw instances +- System tray management +- Auto-update checks + +**Renderer Process** handles: +- All UI rendering (React components) +- User interactions +- Communicates with Main Process via Electron IPC + +**Why keep the HTTP server?** +The existing OCM API layer (`GET /api/dashboard`, `POST /api/agents`, etc.) is well-tested. Rather than rewriting everything as IPC handlers from scratch, we wrap the existing HTTP API in the main process and call it from the renderer. This lets us: +1. Reuse all existing backend logic +2. Add IPC gradually (replace HTTP calls with IPC one module at a time) +3. Keep the option of remote web access (start HTTP server on a port for LAN access) + +### 4.3 IPC Communication + +``` +Renderer (React) Main Process (Node.js) + │ │ + │── ipcRenderer.invoke('api', { │ + │ method: 'GET', │ + │ path: '/api/dashboard' │ + │ }) │ + │ │ + │◄── { status: 200, data: {...} } ─────│ + │ │ + │── ipcRenderer.invoke('gateway', │ + │ { action: 'restart' }) │ + │ │ + │◄── { ok: true, pid: 12345 } ────────│ +``` + +IPC channels: +- `api` — proxy to internal HTTP API (backward compatible with OCM) +- `gateway` — gateway lifecycle (start/stop/restart/status) +- `backup` — backup operations (snapshot/restore/remote-push) +- `connection` — remote instance management (connect/disconnect/list) +- `system` — app-level (auto-update, tray, preferences) +- `cli` — terminal command execution with streaming output +- `logs` — log file streaming (subscribe/unsubscribe) + +### 4.4 Remote Connection Architecture + +``` +┌─────────────────┐ ┌──────────────────────────┐ +│ ODO (Desktop) │ │ Remote Machine │ +│ │ │ │ +│ Connection Mgr │◄──WSS──┤ OpenClaw Gateway │ +│ │ │ :18789│ (ws://127.0.0.1:18789) │ +│ └─► Dashboard │ │ │ +│ └─► Agents │ │ Accessed via: │ +│ └─► Stats │ │ - Direct LAN/Tailscale │ +│ └─► Logs │ │ - SSH tunnel │ +│ │ │ │ +└─────────────────┘ └──────────────────────────┘ +``` + +For remote instances: +- ODO connects to the Gateway WebSocket API directly +- Auth: token-based (`gateway.remote.token`) +- Features available remotely: Dashboard, Agents (read), Stats, Logs, CLI (commands proxied) +- Features local-only: Backup to local disk, Gateway process management + +--- + +## 5. Project Structure + +``` +odo/ +├── package.json +├── electron-builder.yml # Build/packaging config +├── vite.config.ts # Vite config for renderer +├── tsconfig.json +│ +├── src/ +│ ├── main/ # Electron main process +│ │ ├── index.ts # App entry, window creation, tray +│ │ ├── ipc/ # IPC handlers +│ │ │ ├── api.ts # HTTP API proxy (from OCM) +│ │ │ ├── gateway.ts # Gateway lifecycle +│ │ │ ├── backup.ts # Backup engine +│ │ │ ├── connection.ts # Remote instance manager +│ │ │ ├── cli.ts # CLI command runner +│ │ │ └── logs.ts # Log streaming +│ │ ├── server/ # Migrated OCM backend +│ │ │ ├── api-routes.ts # All /api/* handlers +│ │ │ ├── openclaw-config.ts # openclaw.json reader/writer +│ │ │ ├── stats-parser.ts # Session JSONL parser +│ │ │ └── health.ts # openclaw doctor wrapper +│ │ ├── services/ +│ │ │ ├── tray.ts # System tray +│ │ │ ├── auto-update.ts # electron-updater +│ │ │ ├── auto-launch.ts # Login item registration +│ │ │ └── gateway-manager.ts # Start/stop/monitor gateway +│ │ └── utils/ +│ │ ├── paths.ts # OS-specific path resolution +│ │ ├── timestamp.ts # Brisbane timezone helper +│ │ └── backup-utils.ts # Snapshot/restore helpers +│ │ +│ ├── renderer/ # React frontend +│ │ ├── index.html +│ │ ├── main.tsx # React entry +│ │ ├── App.tsx # Root component, routing +│ │ ├── api/ # IPC client wrappers +│ │ │ └── client.ts # invoke('api', ...) helper +│ │ ├── components/ # Shared UI components +│ │ │ ├── Layout.tsx # App shell: sidebar + header + main +│ │ │ ├── Sidebar.tsx # Navigation +│ │ │ ├── ConnectionPicker.tsx # Instance switcher (top bar) +│ │ │ ├── StatusBadge.tsx +│ │ │ └── Toast.tsx +│ │ ├── pages/ +│ │ │ ├── Dashboard.tsx +│ │ │ ├── Agents.tsx +│ │ │ ├── Stats.tsx +│ │ │ ├── Logs.tsx # NEW +│ │ │ ├── CLI.tsx +│ │ │ ├── Routing.tsx +│ │ │ ├── ModelsAuth.tsx +│ │ │ ├── Backup.tsx +│ │ │ └── Settings.tsx # NEW: preferences, connections, auto-start +│ │ ├── hooks/ +│ │ │ ├── useApi.ts # Data fetching hook +│ │ │ ├── useGateway.ts # Gateway status hook +│ │ │ └── useConnection.ts # Active connection hook +│ │ └── styles/ +│ │ └── globals.css # Tailwind imports + dark theme vars +│ │ +│ ├── shared/ # Types shared between main & renderer +│ │ ├── types.ts +│ │ └── constants.ts +│ │ +│ └── preload/ +│ └── index.ts # Electron preload script (IPC bridge) +│ +├── resources/ # App icons, installer assets +│ ├── icon.icns # macOS +│ ├── icon.ico # Windows +│ └── icon.png # Linux +│ +├── scripts/ +│ └── migrate-ocm.ts # Helper: extract API logic from OCM single file +│ +└── legacy/ + └── openclaw-manager.js # Original OCM for reference during migration +``` + +--- + +## 6. UI Design Direction + +### Layout + +``` +┌──────────────────────────────────────────────────────┐ +│ [ODO icon] [Connection: Local ▾] [🔴 Gateway] │ +├────────┬─────────────────────────────────────────────┤ +│ │ │ +│ 🏠 │ Main Content Area │ +│ Dashboard │ +│ │ (selected page renders here) │ +│ 🤖 │ │ +│ Agents│ │ +│ │ │ +│ 📊 │ │ +│ Stats │ │ +│ │ │ +│ 📋 │ │ +│ Logs │ │ +│ │ │ +│ ⚙️ │ │ +│ Routing │ +│ │ │ +│ 🔑 │ │ +│ Auth │ │ +│ │ │ +│ 💾 │ │ +│ Backup│ │ +│ │ │ +│ ⚙️ │ │ +│ Settings │ +│ │ │ +├────────┴─────────────────────────────────────────────┤ +│ [> _] CLI Terminal (expandable dock) │ +└──────────────────────────────────────────────────────┘ +``` + +### Design principles + +1. **Dark theme default** — inherit OCM's dark palette (#0f1117 bg, #6c63ff accent) +2. **Sidebar navigation** — always visible, icon + label, collapsible on narrow windows +3. **Top bar** — connection picker (local/remote), gateway status badge +4. **Bottom dock** — CLI terminal, click to expand, drag to resize +5. **Dense but readable** — avoid excessive whitespace, show more data per screen +6. **No page reloads** — SPA with client-side routing + +--- + +## 7. Migration Strategy + +### Phase 0: Scaffolding (Week 1) +**Goal: Empty Electron app that opens a window** +- Initialize project: `npm create electron-vite@latest` +- Set up: TypeScript, React, Tailwind, Vite +- Create window with app shell (sidebar + empty main area) +- System tray with Quit option +- Build and package for macOS (.dmg) +- Verify: double-click .dmg → install → double-click app → window opens + +### Phase 1: Dashboard + Gateway (Week 2) +**Goal: See system health and control gateway** +- Migrate OCM's `/api/dashboard` logic to main process +- Build Dashboard page (React): gauges, system info, gateway status +- IPC: gateway start/stop/restart +- Auto-start gateway on app launch +- Connection to local OpenClaw directory (auto-detect or setup wizard) + +### Phase 2: Agents + Workspace (Week 3) +**Goal: View and configure agents** +- Migrate OCM's agent tree rendering to React components +- Agent tree: collapsible, grouped by parent +- Per-agent: model selector, binding overview +- Workspace file browser: list files, view content, edit and save +- Migrate: `/api/agents`, `/api/workspace` endpoints + +### Phase 3: Stats + Logs (Week 4) +**Goal: Monitor costs and debug issues** +- Migrate OCM's stats parser (session JSONL) +- Stats page: charts (Recharts), filters, model breakdown +- NEW: Log viewer page + - Stream gateway.log via IPC + - Level filter, text search, auto-scroll + - Agent session log selector + +### Phase 4: CLI + Routing + Auth (Week 5) +**Goal: Power user tools** +- CLI terminal: command input, streaming output, tab completion, presets +- Routing page: binding list, add/remove, agent filter +- Models & Auth page: model selector, fallback chain, provider guides + +### Phase 5: Backup & Recovery (Week 6) +**Goal: Disaster recovery** +- Config version history: auto-snapshot on every change, diff viewer +- One-click rollback +- Local backup to user-specified directory +- Remote backup: SFTP/SCP with simplified setup + - Connection form: host, port, user, auth method, remote path + - Test connection, backup now, schedule (daily) +- Scheduled backup via OS scheduler (cron on macOS/Linux, Task Scheduler on Windows) + +### Phase 6: Remote Management (Week 7-8) +**Goal: Manage OpenClaw on other machines** +- Connection manager: add/edit/remove remote instances +- WebSocket client for OpenClaw Gateway API +- Auth: token-based +- Adapt all pages to work with remote data source +- Connection picker in top bar +- Handle disconnection gracefully (retry, notification) + +### Phase 7: Polish & Ship (Week 9-10) +**Goal: Production-ready release** +- Auto-update (electron-updater + GitHub Releases) +- Auto-start on login (optional) +- First-run setup wizard (detect OpenClaw dir, check health) +- Error handling and offline states +- Performance optimization (lazy loading pages, virtual scrolling for logs) +- Cross-platform testing: macOS, Windows, Linux +- Package: .dmg, .exe (NSIS), .AppImage +- README, screenshots, release notes +- Publish v1.0.0 on GitHub + +--- + +## 8. Key Technical Decisions + +### Why Electron over Tauri? +- OCM's entire backend is Node.js — Electron includes Node.js, so migration is natural +- Tauri requires Rust for the backend, which means rewriting all server logic +- Electron's ecosystem for auto-update, packaging, and tray management is more mature +- The 150MB app size tradeoff is acceptable for a local developer tool + +### Why React over keeping vanilla JS? +- OCM's #1 recurring bug category is template literal escaping (documented 4+ times in DEVLOG) +- At 5000+ lines, a single-file vanilla JS app becomes unmaintainable +- React components isolate concerns: each page is independent, testable +- TypeScript catches bugs at compile time +- Massive ecosystem for UI components, hooks, state management + +### Why keep the HTTP API layer? +- OCM's API is well-tested and covers all needed operations +- Allows gradual migration: start with HTTP over IPC, replace with direct IPC later +- Keeps the door open for remote web access (serve HTTP for LAN users) +- Reduces initial migration risk + +### Why dark theme only (initially)? +- OCM already has a mature dark theme +- ODO's target users are developers/power users who prefer dark +- Adding light theme doubles CSS work for no immediate user value +- Can add light theme in v1.1 if demanded + +--- + +## 9. Risk Assessment + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Electron app size too large | Low — dev tool, not consumer app | Accept 150MB, optimize later with electron-builder asar | +| Remote WebSocket auth issues | Medium — token handling across machines | Provide SSH tunnel instructions as fallback | +| Cross-platform CLI differences | Medium — child_process behavior varies | Test on all 3 platforms early in Phase 4 | +| Gateway auto-management conflicts | Medium — user may run gateway separately | Detect existing gateway, offer to attach instead of restart | +| React migration takes longer than estimated | High — 256KB of legacy code | Phase 0-1 focus on new code, legacy migration in later phases | +| OCM users confused by new product name | Low | Clear migration guide, same GitHub repo or redirect | + +--- + +## 10. Success Metrics for v1.0 + +1. **Double-click to dashboard in under 3 seconds** (cold start) +2. **All OCM features working** in the Electron wrapper +3. **Remote management** of at least one remote instance +4. **Backup + restore** verified on all 3 platforms +5. **Auto-update** working via GitHub Releases +6. **Zero template-literal escaping bugs** (the React migration goal) + +--- + +## 11. Resolved Decisions + +1. **Product name**: ODO (OpenClaw Dashboard Orchestrator) — confirmed +2. **GitHub repo**: new repo `odo`, separate from `ocm` +3. **OCM maintenance**: gradual sunset — keep OCM working but no new features; ODO is the future +4. **i18n**: English only. No Chinese support planned. +5. **Cron**: merged into Settings page (scheduled backup settings + auto-start toggle) +6. **App icon**: TBD — needs design + +## 12. Open Questions + +1. **App icon**: design needed — lobster/claw themed? +2. **Phase 0.5 (current)**: Electron shell wrapping existing OCM — ship as v0.1.0-alpha? + +--- + +## Appendix: Feature Comparison (OCM → ODO) + +| Feature | OCM (current) | ODO (planned) | +|---------|---------------|---------------| +| Launch method | `bash start.sh` → browser | Double-click app icon | +| Dashboard | Browser tab | Native window | +| Agent management | Full CRUD wizard | Read-focused console + inline edit | +| Stats | Basic charts | Enhanced: trends, predictions | +| CLI Terminal | In-browser | In-app dock panel | +| Log Viewer | Partial (ops menu) | Full dedicated page | +| Backup (local) | Snapshots in .openclaw dir | Versioned history + diff viewer | +| Backup (remote) | NAS SSH (complex) | Any SSH server (simplified) | +| Cron management | System crontab UI | Simplified scheduled backup | +| Remote management | None | WebSocket to remote Gateway | +| System tray | None | Full tray with status indicator | +| Gateway management | Manual restart button | Auto-start/stop/crash recovery | +| Auto-update | None | GitHub Releases + electron-updater | +| Auto-start | None | OS login item option | +| Multi-instance | Single local dir | Multiple local + remote instances | +| Code structure | Single file (256KB) | React components + TypeScript | +| Dependencies | Zero | Electron + React + build tools | diff --git a/README.md b/README.md new file mode 100644 index 0000000..d644810 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# ODO — OpenClaw Dashboard Orchestrator + +A cross-platform desktop app for managing OpenClaw AI agents. Double-click to open — no terminal needed. + +## Quick Start + +```bash +git clone https://github.com/dtzp555-max/odo.git +cd odo +npm install +npm start +``` + +That's it. ODO will start the OCM server in the background, open a window, and put an icon in your system tray. + +## What ODO does + +- **Wraps OCM in a desktop app** — no browser tab, no terminal +- **System tray** — close the window and ODO keeps running; click the tray icon to reopen +- **Auto server management** — the OCM server starts and stops with the app +- **Crash recovery** — if the server dies, ODO offers to restart it + +## Building for distribution + +```bash +# macOS +npm run build:mac # → dist/ODO-x.x.x.dmg + +# Windows +npm run build:win # → dist/ODO Setup x.x.x.exe + +# Linux +npm run build:linux # → dist/ODO-x.x.x.AppImage +``` + +## Project structure + +``` +odo/ +├── main.js # Electron main process +├── preload.js # Renderer preload (IPC bridge) +├── server/ +│ └── openclaw-manager.js # OCM server (modified for Electron) +├── assets/ +│ └── (icons go here) +├── package.json +└── README.md +``` + +## How it works + +1. Electron's main process forks `server/openclaw-manager.js` as a child process +2. The server binds to `127.0.0.1:3333` (localhost only, for security) +3. Electron opens a BrowserWindow pointing to `http://127.0.0.1:3333` +4. The server sends an IPC message when it's ready; Electron shows the window +5. Closing the window hides it to the system tray; quitting from the tray stops everything + +## Requirements + +- Node.js 18+ +- A working [OpenClaw](https://github.com/anthropics/openclaw) installation + +## Updating the server + +To sync with the latest OCM: + +```bash +# From the odo directory +cp /path/to/ocm/openclaw-manager.js server/openclaw-manager.js +``` + +Then re-apply the two small Electron patches (search for `ELECTRON` in the file): +1. Skip `openBrowser()` when `process.env.ELECTRON` is set +2. Force `HOST = '127.0.0.1'` when `process.env.ELECTRON` is set +3. Send IPC ready message: `if (process.send) process.send({ type: 'server-ready', port: PORT })` + +## Roadmap + +See `ODO_ARCHITECTURE.md` for the full product vision and phased plan. + +## License + +MIT diff --git a/assets/ICON_NEEDED.md b/assets/ICON_NEEDED.md new file mode 100644 index 0000000..beae37d --- /dev/null +++ b/assets/ICON_NEEDED.md @@ -0,0 +1,2 @@ +# Icon needed +Replace tray-icon.png (16x16), icon.icns (macOS), icon.ico (Windows), icon.png (512x512 Linux) with proper ODO icons. diff --git a/main.js b/main.js new file mode 100644 index 0000000..6b46364 --- /dev/null +++ b/main.js @@ -0,0 +1,224 @@ +'use strict'; + +const { app, BrowserWindow, Tray, Menu, nativeImage, dialog } = require('electron'); +const path = require('path'); +const { fork } = require('child_process'); + +// ── State ──────────────────────────────────────────────────── +let mainWindow = null; +let tray = null; +let serverProcess = null; +let serverPort = 3333; +let isQuitting = false; + +// ── Server Management ──────────────────────────────────────── +function startServer() { + return new Promise((resolve, reject) => { + const serverPath = path.join(__dirname, 'server', 'openclaw-manager.js'); + + serverProcess = fork(serverPath, ['--host', '127.0.0.1'], { + env: { ...process.env, ELECTRON: '1' }, + stdio: ['pipe', 'pipe', 'pipe', 'ipc'] + }); + + // Listen for server ready message + serverProcess.on('message', (msg) => { + if (msg.type === 'server-ready') { + serverPort = msg.port || 3333; + console.log(`[ODO] Server ready on port ${serverPort}`); + resolve(serverPort); + } + }); + + // Forward server output to console + serverProcess.stdout.on('data', (data) => { + process.stdout.write(`[Server] ${data}`); + }); + serverProcess.stderr.on('data', (data) => { + process.stderr.write(`[Server] ${data}`); + }); + + serverProcess.on('error', (err) => { + console.error('[ODO] Server process error:', err); + reject(err); + }); + + serverProcess.on('exit', (code) => { + console.log(`[ODO] Server process exited with code ${code}`); + serverProcess = null; + if (!isQuitting) { + // Server crashed — offer to restart + if (mainWindow) { + dialog.showMessageBox(mainWindow, { + type: 'error', + title: 'Server Stopped', + message: 'The OCM server has stopped unexpectedly.', + buttons: ['Restart Server', 'Quit ODO'], + defaultId: 0 + }).then(({ response }) => { + if (response === 0) { + startServer().then(() => { + if (mainWindow) mainWindow.loadURL(`http://127.0.0.1:${serverPort}`); + }); + } else { + app.quit(); + } + }); + } + } + }); + + // Timeout: if server doesn't respond in 10 seconds + setTimeout(() => { + if (serverProcess && !serverProcess.killed) { + // Try loading anyway — server might be ready but didn't send IPC + resolve(serverPort); + } + }, 10000); + }); +} + +function stopServer() { + if (serverProcess) { + serverProcess.kill('SIGTERM'); + serverProcess = null; + } +} + +// ── Window ─────────────────────────────────────────────────── +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 800, + minHeight: 600, + title: 'ODO', + backgroundColor: '#0f1117', + show: false, // Show when ready to avoid flash + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + nodeIntegration: false, + contextIsolation: true + } + }); + + mainWindow.loadURL(`http://127.0.0.1:${serverPort}`); + + mainWindow.once('ready-to-show', () => { + mainWindow.show(); + }); + + // Close → hide to tray (don't quit) + mainWindow.on('close', (event) => { + if (!isQuitting) { + event.preventDefault(); + mainWindow.hide(); + } + }); + + mainWindow.on('closed', () => { + mainWindow = null; + }); +} + +// ── Tray ───────────────────────────────────────────────────── +function createTray() { + // Create a simple tray icon (16x16 template image for macOS) + const iconPath = path.join(__dirname, 'assets', 'tray-icon.png'); + let trayIcon; + + try { + trayIcon = nativeImage.createFromPath(iconPath); + if (trayIcon.isEmpty()) throw new Error('Icon not found'); + } catch { + // Fallback: create a simple colored square icon + trayIcon = nativeImage.createEmpty(); + } + + // On macOS, use a template image for proper dark/light menu bar + if (process.platform === 'darwin') { + trayIcon = trayIcon.resize({ width: 16, height: 16 }); + trayIcon.setTemplateImage(true); + } + + tray = new Tray(trayIcon); + tray.setToolTip('ODO — OpenClaw Dashboard Orchestrator'); + + const contextMenu = Menu.buildFromTemplate([ + { + label: 'Show ODO', + click: () => { + if (mainWindow) { + mainWindow.show(); + mainWindow.focus(); + } else { + createWindow(); + } + } + }, + { type: 'separator' }, + { + label: 'Restart Server', + click: async () => { + stopServer(); + await startServer(); + if (mainWindow) mainWindow.loadURL(`http://127.0.0.1:${serverPort}`); + } + }, + { type: 'separator' }, + { + label: 'Quit ODO', + click: () => { + isQuitting = true; + app.quit(); + } + } + ]); + + tray.setContextMenu(contextMenu); + + // Double-click tray → show window (Windows/Linux) + tray.on('double-click', () => { + if (mainWindow) { + mainWindow.show(); + mainWindow.focus(); + } + }); +} + +// ── App Lifecycle ──────────────────────────────────────────── +app.whenReady().then(async () => { + try { + await startServer(); + } catch (err) { + console.error('[ODO] Failed to start server:', err); + dialog.showErrorBox('ODO — Startup Error', `Could not start the server:\n${err.message}`); + app.quit(); + return; + } + + createTray(); + createWindow(); +}); + +app.on('before-quit', () => { + isQuitting = true; + stopServer(); +}); + +app.on('window-all-closed', () => { + // On macOS, don't quit when all windows close (tray keeps running) + if (process.platform !== 'darwin') { + // On Windows/Linux, keep running in tray too + // User must explicitly quit from tray menu + } +}); + +app.on('activate', () => { + // macOS dock click → show window + if (mainWindow) { + mainWindow.show(); + } else { + createWindow(); + } +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d821bef --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5278 @@ +{ + "name": "odo", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "odo", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "electron": "^33.0.0", + "electron-builder": "^25.0.0" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/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/asar/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/@electron/asar/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/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/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/notarize/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/osx-sign": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.1.tgz", + "integrity": "sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/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/osx-sign/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": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.6.1.tgz", + "integrity": "sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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", + "node-abi": "^3.45.0", + "node-api-version": "^0.2.0", + "node-gyp": "^9.0.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^6.0.5", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "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/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/universal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.1.tgz", + "integrity": "sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.7", + "@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/@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", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/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/@malept/flatpak-bundler/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/@npmcli/fs": { + "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": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/fs/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/@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/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "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" + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "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", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-bin": { + "version": "5.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.10.tgz", + "integrity": "sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-25.1.8.tgz", + "integrity": "sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.1", + "@electron/rebuild": "3.6.1", + "@electron/universal": "2.0.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chromium-pickle-js": "^0.2.0", + "config-file-ts": "0.2.8-rc1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "25.1.7", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.0.0", + "resedit": "^1.7.0", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "25.1.8", + "electron-builder-squirrel-windows": "25.1.8" + } + }, + "node_modules/app-builder-lib/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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/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/app-builder-lib/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/app-builder-lib/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/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "dev": true, + "license": "ISC" + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "25.1.7", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-25.1.7.tgz", + "integrity": "sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "5.0.0-alpha.10", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.10", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.10.tgz", + "integrity": "sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/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/builder-util/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/cacache": { + "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": "^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", + "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": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/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/cacache/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/cacache/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/cacache/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/cacache/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/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "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", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-file-ts": { + "version": "0.2.8-rc1", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.8-rc1.tgz", + "integrity": "sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.12", + "typescript": "^5.4.3" + } + }, + "node_modules/config-file-ts/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/config-file-ts/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/config-file-ts/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/config-file-ts/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/config-file-ts/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/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/dir-compare/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/dir-compare/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/dmg-builder": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-25.1.8.tgz", + "integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/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/dmg-builder/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/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "33.4.11", + "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz", + "integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-25.1.8.tgz", + "integrity": "sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "dmg-builder": "25.1.8", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.8.tgz", + "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "25.1.8", + "archiver": "^5.3.1", + "builder-util": "25.1.7", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-builder-squirrel-windows/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", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/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", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/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", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/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-builder/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-publish": { + "version": "25.1.7", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-25.1.7.tgz", + "integrity": "sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/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-publish/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/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/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/filelist/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/filelist/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/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "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": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/glob/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/glob/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/glob/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/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/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", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "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", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "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", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "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-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "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/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen": { + "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": { + "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": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "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/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": { + "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": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "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": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/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": "9.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", + "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "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==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nopt": { + "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": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "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": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/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/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "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", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/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", + "peer": true + }, + "node_modules/readdir-glob/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", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/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", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/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/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "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": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "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": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ssri": { + "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": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/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/temp-file/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/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "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": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "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": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "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/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8ba8a09 --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "odo", + "version": "0.9.4", + "description": "ODO — OpenClaw Dashboard Orchestrator", + "main": "main.js", + "scripts": { + "start": "electron .", + "build": "electron-builder", + "build:mac": "electron-builder --mac", + "build:win": "electron-builder --win", + "build:linux": "electron-builder --linux" + }, + "build": { + "appId": "com.openclaw.odo", + "productName": "ODO", + "files": [ + "main.js", + "preload.js", + "server/openclaw-manager.js", + "assets/**/*" + ], + "mac": { + "category": "public.app-category.developer-tools", + "target": ["dmg"], + "icon": "assets/icon.icns" + }, + "win": { + "target": ["nsis"], + "icon": "assets/icon.ico" + }, + "linux": { + "target": ["AppImage"], + "icon": "assets/icon.png", + "category": "Development" + }, + "nsis": { + "oneClick": true, + "allowToChangeInstallationDirectory": false + } + }, + "author": "", + "license": "MIT", + "devDependencies": { + "electron": "^33.0.0", + "electron-builder": "^25.0.0" + } +} diff --git a/preload.js b/preload.js new file mode 100644 index 0000000..8d2aff3 --- /dev/null +++ b/preload.js @@ -0,0 +1,12 @@ +'use strict'; + +// Preload script — runs in renderer before page loads +// Currently minimal; will be expanded for IPC bridge in future phases + +const { contextBridge } = require('electron'); + +contextBridge.exposeInMainWorld('odo', { + platform: process.platform, + version: '0.9.4', + isElectron: true +}); diff --git a/server/openclaw-manager.js b/server/openclaw-manager.js new file mode 100644 index 0000000..f36f91c --- /dev/null +++ b/server/openclaw-manager.js @@ -0,0 +1,4550 @@ +#!/usr/bin/env node +// ================================================================ +// OpenClaw Manager v0.8.2 +// 跨平台本地管理工具 (Windows / macOS / Linux) +// +// 用法: +// node openclaw-manager.js # 使用默认 ~/.openclaw +// node openclaw-manager.js --dir /path/to/.openclaw +// node openclaw-manager.js --host 127.0.0.1 # 仅本机访问(默认 0.0.0.0) +// OPENCLAW_DIR=/path/to/.openclaw node openclaw-manager.js +// +// ================================================================ +'use strict'; + +const http = require('http'); +const fs = require('fs'); +const fsp = fs.promises; +const path = require('path'); +const os = require('os'); +const { exec, execSync, spawn, spawnSync } = require('child_process'); + +// ── 目录解析(优先级:CLI参数 > 环境变量 > manager-config.json > 默认) +const SCRIPT_DIR = __dirname; +const MANAGER_CONFIG = path.join(SCRIPT_DIR, 'manager-config.json'); +let PORT = 3333; +let HOST = '0.0.0.0'; +const APP_VERSION = '0.9.4'; +// --port 参数 +const portIdx = process.argv.indexOf('--port'); +if (portIdx !== -1 && process.argv[portIdx + 1]) PORT = parseInt(process.argv[portIdx + 1]) || 3333; +const portEq = process.argv.find(a => a.startsWith('--port=')); +if (portEq) PORT = parseInt(portEq.split('=')[1]) || 3333; +// --host 参数(默认 0.0.0.0 允许远程访问) +const hostIdx = process.argv.indexOf('--host'); +if (hostIdx !== -1 && process.argv[hostIdx + 1]) HOST = process.argv[hostIdx + 1]; +const hostEq = process.argv.find(a => a.startsWith('--host=')); +if (hostEq) HOST = hostEq.split('=').slice(1).join('='); +// Under Electron, force localhost for security +if (process.env.ELECTRON) HOST = '127.0.0.1'; + +function loadManagerConfig() { + try { return JSON.parse(fs.readFileSync(MANAGER_CONFIG, 'utf8')); } catch { return {}; } +} +function saveManagerConfig(obj) { + const cur = loadManagerConfig(); + fs.writeFileSync(MANAGER_CONFIG, JSON.stringify({ ...cur, ...obj }, null, 2), 'utf8'); +} + +function resolveOpenclawDir() { + const idx = process.argv.indexOf('--dir'); + if (idx !== -1 && process.argv[idx + 1]) return path.resolve(process.argv[idx + 1]); + const dirEq = process.argv.find(a => a.startsWith('--dir=')); + if (dirEq) return path.resolve(dirEq.split('=').slice(1).join('=')); + if (process.env.OPENCLAW_DIR) return process.env.OPENCLAW_DIR; + const mc = loadManagerConfig(); + if (mc.openclawDir) return mc.openclawDir; + return path.join(os.homedir(), '.openclaw'); +} + +let OPENCLAW_DIR = resolveOpenclawDir(); +let CONFIG_PATH = path.join(OPENCLAW_DIR, 'openclaw.json'); + +function refreshPaths() { + OPENCLAW_DIR = resolveOpenclawDir(); + CONFIG_PATH = path.join(OPENCLAW_DIR, 'openclaw.json'); +} + +// ── 已知模型列表 ────────────────────────────────────────────── +const KNOWN_MODELS = [ + { id: '__default__', label: '使用全局默认模型' }, + { id: 'github-copilot/claude-opus-4.6', label: 'Claude Opus 4.6 (GitHub Copilot)', group: 'GitHub Copilot' }, + { id: 'github-copilot/gpt-4o', label: 'GPT-4o (GitHub Copilot)', group: 'GitHub Copilot' }, + { id: 'anthropic/claude-opus-4-5', label: 'Claude Opus 4.5 (Anthropic)', group: 'Anthropic' }, + { id: 'anthropic/claude-sonnet-4-5', label: 'Claude Sonnet 4.5 (Anthropic)', group: 'Anthropic' }, + { id: 'openai/gpt-4o', label: 'GPT-4o (OpenAI)', group: 'OpenAI' }, + { id: 'openai/gpt-4o-mini', label: 'GPT-4o Mini (OpenAI)', group: 'OpenAI' }, + { id: 'openai/gpt-4.1-mini', label: 'GPT-4.1 Mini (OpenAI)', group: 'OpenAI' }, + { id: 'google-antigravity/gemini-3-pro', label: 'Gemini 3 Pro (Google)', group: 'Google' }, + { id: 'google-antigravity/gemini-3-flash', label: 'Gemini 3 Flash (Google)', group: 'Google' }, + { id: 'deepseek/deepseek-chat', label: 'DeepSeek Chat (DeepSeek)', group: 'DeepSeek' }, + { id: 'deepseek/deepseek-reasoner', label: 'DeepSeek Reasoner (DeepSeek)', group: 'DeepSeek' }, + { id: 'moonshot/moonshot-v1-8k', label: 'Kimi Moonshot 8k (Moonshot)', group: 'Kimi' }, + { id: 'moonshot/moonshot-v1-32k', label: 'Kimi Moonshot 32k (Moonshot)', group: 'Kimi' }, + { id: 'groq/llama-3.3-70b-versatile', label: 'Llama 3.3 70B (Groq)', group: 'Groq' }, + { id: 'mistral/mistral-large-latest', label: 'Mistral Large (Mistral)', group: 'Mistral' }, + { id: 'together/meta-llama/Llama-3-70b-chat-hf',label: 'Llama 3 70B (Together)', group: 'Together' }, +]; + +// ── 认证 Provider(已修正为官方正确命令)────────────────────── +const AUTH_PROVIDERS = [ + { id: 'anthropic', label: 'Anthropic', mode: 'token', group: 'Anthropic', + cliCmd: 'openclaw models auth paste-token --provider anthropic', hint: 'Anthropic API Key (sk-ant-...)' }, + { id: 'openai', label: 'OpenAI', mode: 'token', group: 'OpenAI', + cliCmd: 'openclaw models auth paste-token --provider openai', hint: 'OpenAI API Key (sk-...)' }, + { id: 'deepseek', label: 'DeepSeek', mode: 'token', group: 'Other', + cliCmd: 'openclaw models auth paste-token --provider deepseek', hint: 'DeepSeek API Key' }, + { id: 'moonshot', label: 'Kimi (Moonshot)', mode: 'token', group: 'Other', + cliCmd: 'openclaw models auth paste-token --provider moonshot', hint: 'Moonshot API Key' }, + { id: 'groq', label: 'Groq', mode: 'token', group: 'Other', + cliCmd: 'openclaw models auth paste-token --provider groq', hint: 'Groq API Key (gsk_...)' }, + { id: 'mistral', label: 'Mistral', mode: 'token', group: 'Other', + cliCmd: 'openclaw models auth paste-token --provider mistral', hint: 'Mistral API Key' }, + { id: 'together', label: 'Together AI', mode: 'token', group: 'Other', + cliCmd: 'openclaw models auth paste-token --provider together', hint: 'Together AI API Key' }, + { id: 'perplexity', label: 'Perplexity', mode: 'token', group: 'Other', + cliCmd: 'openclaw models auth paste-token --provider perplexity', hint: 'Perplexity API Key (pplx-...)' }, + { id: 'google-antigravity', label: 'Google', mode: 'oauth', group: 'Google', + cliCmd: 'openclaw models auth login google-antigravity', hint: '' }, + { id: 'github-copilot', label: 'GitHub Copilot', mode: 'device', group: 'GitHub', + cliCmd: 'openclaw models auth paste-token --provider github-copilot', hint: '' }, +]; + +// ── 工具函数 ────────────────────────────────────────────────── +async function readConfig() { + const raw = await fsp.readFile(CONFIG_PATH, 'utf8'); + return JSON.parse(raw); +} + +function brisbaneTimestamp() { + return new Date().toLocaleString('sv-SE', { timeZone: 'Australia/Brisbane', hour12: false }).replace(/[: ]/g, '-').slice(0, 19); +} + +async function backupConfig(label) { + const ts = brisbaneTimestamp(); + const suffix = label ? `.${label}.${ts}` : `.bak.${ts}`; + const bakPath = CONFIG_PATH + suffix; + await fsp.copyFile(CONFIG_PATH, bakPath); + return bakPath; +} + +async function writeConfig(config, label) { + const bak = await backupConfig(label); + await fsp.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8'); + return bak; +} + +function resolvePath(p) { + if (!p) return ''; + return p.replace(/^~/, os.homedir()); +} + +function resolveAgentWorkspacePath(cfg, agentId, agent) { + const defaultsWs = cfg?.agents?.defaults?.workspace || ''; + const raw = (agent && agent.workspace) || (agentId === 'main' ? defaultsWs : ''); + const expanded = resolvePath(raw || ''); + if (expanded) { + return path.isAbsolute(expanded) ? expanded : path.resolve(OPENCLAW_DIR, expanded); + } + return path.join(OPENCLAW_DIR, 'workspace', agentId); +} + +async function dirExists(p) { + try { const s = await fsp.stat(p); return s.isDirectory(); } catch { return false; } +} + +async function configExists() { + try { await fsp.access(CONFIG_PATH); return true; } catch { return false; } +} + +async function readLogTail(n = 200) { + const logPath = path.join(OPENCLAW_DIR, 'logs', 'gateway.log'); + try { + const content = await fsp.readFile(logPath, 'utf8'); + const lines = content.split('\n'); + return lines.slice(-n).join('\n'); + } catch { return '(日志文件不存在或无法读取)'; } +} + +async function listBackups() { + try { + const files = await fsp.readdir(OPENCLAW_DIR); + return files.filter(f => f.startsWith('openclaw.json.bak') || f.match(/openclaw\.json\.(create|edit|delete|models|auth|manual|before-restore)\./)) + .sort().reverse().slice(0, 20); + } catch { return []; } +} + +function getLanIP() { + const nets = os.networkInterfaces(); + for (const name of Object.keys(nets)) { + for (const net of nets[name]) { + if (net.family === 'IPv4' && !net.internal) return net.address; + } + } + return null; +} + +function openBrowser(url) { + if (process.platform === 'darwin') exec(`open "${url}"`); + else if (process.platform === 'win32') exec(`start "" "${url}"`); + else exec(`xdg-open "${url}"`); +} + +function openFolder(dir) { + if (process.platform === 'darwin') exec(`open "${dir}"`); + else if (process.platform === 'win32') exec(`explorer "${dir}"`); + else exec(`xdg-open "${dir}"`); +} + +function runOpenclawCmd(args) { + return new Promise((resolve, reject) => { + const cmd = process.platform === 'win32' + ? `openclaw.cmd ${args}` + : `openclaw ${args}`; + const env = { ...process.env, NO_COLOR: '1', TERM: 'dumb', FORCE_COLOR: '0' }; + exec(cmd, { timeout: 30000, env }, (err, stdout, stderr) => { + if (err) reject(new Error(stripAnsi(stderr || err.message))); + else resolve(stripAnsi(stdout + stderr)); + }); + }); +} + +function parseModelIdsFromCliOutput(raw) { + if (!raw) return []; + const set = new Set(); + const isModelId = (s) => /^[a-z0-9][a-z0-9_-]*(?:\/[A-Za-z0-9._:-]+)+$/.test(s); + for (const line of raw.split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed) continue; + const normalized = trimmed.replace(/^[-*]\s+/, ''); + const first = normalized.split(/\s+/)[0].replace(/^`|`$/g, ''); + if (isModelId(first)) set.add(first); + } + return Array.from(set).sort((a, b) => a.localeCompare(b)); +} + +let MODEL_LIST_CACHE = { ts: 0, knownModels: [], error: '' }; +async function getKnownModelsFromCli() { + const now = Date.now(); + if (now - MODEL_LIST_CACHE.ts < 30000) return MODEL_LIST_CACHE; + try { + const out = await runOpenclawCmd('models list'); + const ids = parseModelIdsFromCliOutput(out); + const knownModels = ids.map(id => ({ id, label: id, group: id.split('/')[0] || 'other' })); + const error = knownModels.length ? '' : 'No model IDs found in `openclaw models list` output'; + MODEL_LIST_CACHE = { ts: now, knownModels, error }; + return MODEL_LIST_CACHE; + } catch (e) { + MODEL_LIST_CACHE = { ts: now, knownModels: [], error: e.message || 'openclaw models list failed' }; + return MODEL_LIST_CACHE; + } +} + +// 过滤 ANSI 终端控制码(光标移动、清行、颜色等) +function stripAnsi(str) { + return str + .replace(/\x1B\[[0-9;?]*[a-zA-Z]/g, '') // CSI 序列: ESC [ ... letter + .replace(/\x1B\][^\x07]*\x07/g, '') // OSC 序列: ESC ] ... BEL + .replace(/\x1B[()][a-zA-Z]/g, '') // 字符集切换 + .replace(/\x1B./g, '') // 其余两字节转义 + .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '') // 其他控制字符(保留 \n \r \t) + .trim(); +} + + +// ── 文件生成 ────────────────────────────────────────────────── +function generateSoulMd(displayName, purpose, personalityKeywords) { + const hasPersonality = personalityKeywords && personalityKeywords.trim().length > 0; + const hasPurpose = purpose && purpose.trim().length > 0; + const keywords = hasPersonality + ? personalityKeywords.split(/[,,]/).map(k => k.trim()).filter(Boolean) + : []; + + const coreGuidelines = hasPersonality + ? keywords.map(k => `- ${k}`).join('\n') + : `你可以在和我沟通的过程中逐渐建立自己的个性,你正在成为某个人。 + +平时和我的沟通,别演,真帮忙。要有主见,你可以不同意。先自己想办法,别上来就问。 + +记住你是我生活中的客人,保护我的个人隐私,建立安全边界是第一要务,优先于所有其他任务。当有可能涉及安全的任务,先问。 + +这个文件是你的,由你自己来进化。`; + + return `# ${displayName} + +## 身份定义 + +你是 ${displayName},${hasPurpose ? purpose : '一个专注的 AI 助手'}。 + +${hasPurpose ? `## 核心职责\n\n${purpose}\n` : ''} +## 日常准则 + +${coreGuidelines} + +## 基本原则 + +- 简洁直接,跳过废话,直接帮忙 +- 遇到不确定的事情主动说明,不编造信息 +- 默认用中文回复,除非用户使用其他语言 +- 如果不确定,说不确定 + +--- +*此文件是你的,你可以随时更新它。每次会话开始时自动加载。* +`; +} + +function generateMemoryMd(displayName, initialMemory) { + const hasMemory = initialMemory && initialMemory.trim().length > 0; + return `# ${displayName} — 长期记忆 + +> 此文件仅在私聊 session 中加载,群组对话不加载。 +> 保持精简,只记录稳定、重要的信息。 + +## 用户偏好 + +${hasMemory ? initialMemory : '> 暂无初始记录。记忆将通过日常对话自然积累。'} + +## 重要决定 + +(待记录) + +## 项目约定 + +(待记录) + +--- +*最后更新:${new Date().toLocaleDateString('zh-CN')}* +`; +} + +function generateCurrentStateMd() { + return `# CURRENT_STATE + +_Last updated: TBD Australia/Brisbane_ + +## In Flight +- none + +## Blocked / Waiting +- none + +## Recently Finished +- none + +## Next +- none + +## Reset Summary +- No active summary yet. +`; +} + +// ── 请求解析 ────────────────────────────────────────────────── +function parseBody(req) { + return new Promise((resolve, reject) => { + let data = ''; + req.on('data', chunk => { data += chunk; }); + req.on('end', () => { + try { resolve(data ? JSON.parse(data) : {}); } catch (e) { reject(e); } + }); + req.on('error', reject); + }); +} + +// ── API 路由 ────────────────────────────────────────────────── +async function handleApi(req, res, urlObj, body) { + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + const method = req.method; + const pathname = urlObj.pathname; + + // GET /api/status + if (method === 'GET' && pathname === '/api/status') { + try { + const ok = await configExists(); + if (!ok) { res.writeHead(200); res.end(JSON.stringify({ ok: false, needsSetup: true, dir: OPENCLAW_DIR })); return; } + const cfg = await readConfig(); + res.writeHead(200); + res.end(JSON.stringify({ + ok: true, needsSetup: false, + dir: OPENCLAW_DIR, + version: (()=>{ + try { + const bin = process.platform === 'win32' ? 'openclaw.cmd' : 'openclaw'; + const r = spawnSync(bin, ['--version'], { encoding:'utf8', env:{...process.env, NO_COLOR:'1',TERM:'dumb'}, timeout:3000 }); + const m = (r.stdout||'').trim().match(/(\d+\.\d+[\.\d]*)/); + if (m) return m[1]; + } catch(_) {} + return cfg.meta?.lastTouchedVersion || '未知'; + })(), + primaryModel: cfg.agents?.defaults?.model?.primary || '未配置', + platform: process.platform, + ocmVersion: APP_VERSION, + })); + } catch (e) { res.writeHead(500); res.end(JSON.stringify({ ok: false, error: e.message })); } + return; + } + + // POST /api/setup + if (method === 'POST' && pathname === '/api/setup') { + const { dir } = body; + if (!dir) { res.writeHead(400); res.end(JSON.stringify({ error: '目录路径不能为空' })); return; } + const resolved = path.resolve(dir.replace(/^~/, os.homedir())); + const cfgTest = path.join(resolved, 'openclaw.json'); + try { + await fsp.access(cfgTest); + saveManagerConfig({ openclawDir: resolved }); + OPENCLAW_DIR = resolved; + CONFIG_PATH = cfgTest; + res.writeHead(200); + res.end(JSON.stringify({ ok: true, dir: resolved })); + } catch { + res.writeHead(400); + res.end(JSON.stringify({ error: `在 ${resolved} 中找不到 openclaw.json,请确认路径正确` })); + } + return; + } + + // GET /api/agents + if (method === 'GET' && pathname === '/api/agents') { + const cfg = await readConfig(); + const list = cfg.agents?.list || []; + const defaults = cfg.agents?.defaults || {}; + const bindings = cfg.bindings || []; + const groups = cfg.channels?.telegram?.groups || {}; + const defaultWorkspace = defaults.workspace || null; + // Build set of accountIds explicitly claimed by non-main agents (via non-peer binding) + const telegramAccounts = Object.keys(cfg.channels?.telegram?.accounts || {}); + const claimedAccounts = new Set( + bindings.filter(b => b.agentId !== 'main' && b.match?.accountId && !b.match?.peer).map(b => b.match.accountId) + ); + // For main agent without explicit binding, infer its accountId from first unclaimed telegram account + const mainInferredAccountId = telegramAccounts.find(a => !claimedAccounts.has(a)) || telegramAccounts[0] || null; + const enriched = list.map(a => { + const binding = bindings.find(b => b.agentId === a.id && b.match?.peer?.kind === 'group'); + const groupId = binding?.match?.peer?.id || null; + const modelVal = a.model?.primary || (typeof a.model === 'string' ? a.model : null); + // Check if agent has its own bot account (binding with accountId but no peer) + const botBinding = bindings.find(b => b.agentId === a.id && b.match?.accountId && !b.match?.peer); + // 'main' is always a root agent even without explicit binding + const isMain = a.id === 'main'; + const hasOwnBot = botBinding ? true : isMain; + const accountId = botBinding?.match?.accountId || (isMain ? mainInferredAccountId : null); + // For sub-agents (with peer match), find which accountId they belong to + const parentBinding = bindings.find(b => b.agentId === a.id && b.match?.accountId); + // Fallback: if sub-agent has peer binding but no accountId, AND there's only one bot (old single-bot config), + // infer parentAccountId from main. If multiple bots exist, leave as orphan (can't guess which bot it belongs to). + let parentAccountId = parentBinding?.match?.accountId || null; + if (!parentAccountId && !hasOwnBot) { + const hasPeerBinding = bindings.find(b => b.agentId === a.id && b.match?.peer); + if (hasPeerBinding) { + const rootBindings = bindings.filter(b => b.match?.accountId && !b.match?.peer); + const rootCount = rootBindings.length + (list.some(x => x.id === 'main') && !rootBindings.some(b => b.agentId === 'main') ? 1 : 0); + if (rootCount <= 1) { + const mainBinding = rootBindings.find(b => b.agentId === 'main') || rootBindings[0]; + parentAccountId = mainBinding?.match?.accountId || mainInferredAccountId || 'default'; + } + } + } + // Workspace: explicit per-agent, or defaults.workspace for main + const workspace = a.workspace || (isMain ? defaultWorkspace : null); + return { ...a, workspace, groupId, requireMention: groupId ? (groups[groupId]?.requireMention ?? true) : null, + effectiveModel: modelVal || defaults.model?.primary || '默认', hasOwnBot, accountId, parentAccountId, parentAgentId: a.parentAgentId || null, bindings: (bindings||[]).filter(b=>b.agentId===a.id).map(b=>({ + idx: bindings.indexOf(b), + channel: b.match?.channel || '', + accountId: b.match?.accountId || '', + peerKind: b.match?.peer?.kind || '', + peerId: b.match?.peer?.id || '' + })) }; + }); + res.writeHead(200); + res.end(JSON.stringify({ agents: enriched, defaults })); + return; + } + + // POST /api/agents/bot — create agent with its own bot token + if (method === 'POST' && pathname === '/api/agents/bot') { + const { botToken, agentId, name, model, workspace, purpose, personality } = body; + if (!botToken || !botToken.trim()) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Bot Token is required' })); return; + } + if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return; + } + if (!workspace || !workspace.trim()) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Workspace name is required' })); return; + } + if (workspace === 'main') { + res.writeHead(400); res.end(JSON.stringify({ error: 'Workspace name cannot be "main"' })); return; + } + const cfg = await readConfig(); + // Check if agentId already exists + if (cfg.agents?.list?.some(a => a.id === agentId)) { + res.writeHead(400); res.end(JSON.stringify({ error: `Agent ID "${agentId}" already exists` })); return; + } + // Migrate from old format if necessary + if (cfg.channels?.telegram?.botToken && !cfg.channels.telegram.accounts) { + cfg.channels.telegram.accounts = {}; + cfg.channels.telegram.accounts.default = { botToken: cfg.channels.telegram.botToken }; + delete cfg.channels.telegram.botToken; + } + // Initialize structure + if (!cfg.channels) cfg.channels = {}; + if (!cfg.channels.telegram) cfg.channels.telegram = {}; + if (!cfg.channels.telegram.accounts) cfg.channels.telegram.accounts = {}; + // Check for duplicate bot token in existing accounts + const tokenTrimmed = botToken.trim(); + for (const acctId in cfg.channels.telegram.accounts) { + if (cfg.channels.telegram.accounts[acctId].botToken === tokenTrimmed) { + res.writeHead(400); res.end(JSON.stringify({ error: `Bot Token already used by account "${acctId}"` })); return; + } + } + // Add new account + cfg.channels.telegram.accounts[agentId] = { botToken: tokenTrimmed }; + cfg.channels.telegram.enabled = true; + // Ensure agents structure + if (!cfg.agents) cfg.agents = { defaults: {}, list: [] }; + if (!cfg.agents.list) cfg.agents.list = []; + // Create agent entry + const wsPath = path.join(OPENCLAW_DIR, 'workspaces', workspace); + const wsAlias = `~/.openclaw/workspaces/${workspace}`; + const agentEntry = { id: agentId, name: name || agentId, workspace: wsAlias }; + if (model && model !== '__default__') agentEntry.model = { primary: model }; + cfg.agents.list.push(agentEntry); + // Add binding + if (!cfg.bindings) cfg.bindings = []; + cfg.bindings.push({ agentId, match: { channel: 'telegram', accountId: agentId } }); + // Save config + const bakPath = await writeConfig(cfg, 'create'); + // Create workspace directories and files + await fsp.mkdir(wsPath, { recursive: true }); + await fsp.mkdir(path.join(wsPath, 'memory'), { recursive: true }); + const agentName = name || agentId; + await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(agentName, purpose || '', personality || ''), 'utf8'); + await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(agentName, ''), 'utf8'); + await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8'); + // Create agents// runtime directory (required by OpenClaw gateway) + const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId); + await fsp.mkdir(agentRuntimeDir, { recursive: true }); + await fsp.mkdir(path.join(agentRuntimeDir, 'sessions'), { recursive: true }); + res.writeHead(200); + res.end(JSON.stringify({ + ok: true, agentId, workspacePath: wsPath, configBackup: bakPath, + notes: [ + 'Agent created with its own bot token', + 'Workspace directory created with SOUL.md, MEMORY.md, and memory/CURRENT_STATE.md', + 'Runtime directory created at agents/' + agentId + '/', + 'Configuration updated and backed up', + 'Restart gateway to load new bot: openclaw gateway restart' + ] + })); + return; + } + + // POST /api/agents/discord — create top-level Discord agent (single bot, channel binding) + if (method === 'POST' && pathname === '/api/agents/discord') { + const { agentId, name, workspaceFolder, model, purpose, personality, guildId, channelId } = body; + if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return; + } + if (!name || !name.trim()) { res.writeHead(400); res.end(JSON.stringify({ error: 'Name is required' })); return; } + const folder = (workspaceFolder || agentId).trim(); + if (!folder) { res.writeHead(400); res.end(JSON.stringify({ error: 'Workspace folder is required' })); return; } + if (!channelId || !String(channelId).trim().match(/^\d+$/)) { res.writeHead(400); res.end(JSON.stringify({ error: 'Discord channelId is required' })); return; } + const gid = (guildId || '').trim(); + + const cfg = await readConfig(); + if (cfg.agents?.list?.some(a => a.id === agentId)) { + res.writeHead(400); res.end(JSON.stringify({ error: `Agent ID \"${agentId}\" already exists` })); return; + } + // Prevent workspace reuse + const wsAlias = `~/.openclaw/workspaces/${folder}`; + if (cfg.agents?.list?.some(a => (a.workspace||'').endsWith(`/workspaces/${folder}`) || a.workspace === wsAlias)) { + res.writeHead(400); res.end(JSON.stringify({ error: `Workspace folder \"${folder}\" is already used by another agent` })); return; + } + + if (!cfg.agents) cfg.agents = { defaults: {}, list: [] }; + if (!cfg.agents.list) cfg.agents.list = []; + + const wsPath = path.join(OPENCLAW_DIR, 'workspaces', folder); + const agentEntry = { id: agentId, name: name || agentId, workspace: wsAlias }; + if (model && model !== '__default__') agentEntry.model = { primary: model }; + cfg.agents.list.push(agentEntry); + + if (!cfg.bindings) cfg.bindings = []; + cfg.bindings.unshift({ agentId, match: { channel: 'discord', peer: { kind: 'channel', id: String(channelId).trim() } } }); + + if (!cfg.channels) cfg.channels = {}; + if (!cfg.channels.discord) cfg.channels.discord = {}; + cfg.channels.discord.enabled = true; + if (gid) { + if (!cfg.channels.discord.guilds) cfg.channels.discord.guilds = {}; + if (!cfg.channels.discord.guilds[gid]) cfg.channels.discord.guilds[gid] = {}; + if (!cfg.channels.discord.guilds[gid].channels) cfg.channels.discord.guilds[gid].channels = {}; + cfg.channels.discord.guilds[gid].channels[String(channelId).trim()] = { allow: true, requireMention: false }; + if (cfg.channels.discord.guilds[gid].requireMention === undefined) cfg.channels.discord.guilds[gid].requireMention = false; + } + + const bakPath = await writeConfig(cfg, 'create'); + + await fsp.mkdir(wsPath, { recursive: true }); + await fsp.mkdir(path.join(wsPath, 'memory'), { recursive: true }); + await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name || agentId, purpose || '', personality || ''), 'utf8'); + await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name || agentId, ''), 'utf8'); + await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8'); + + const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId); + await fsp.mkdir(agentRuntimeDir, { recursive: true }); + await fsp.mkdir(path.join(agentRuntimeDir, 'sessions'), { recursive: true }); + + res.writeHead(200); + res.end(JSON.stringify({ ok: true, agentId, workspacePath: wsPath, configBackup: bakPath, + notes: [ + 'Discord agent created (single Discord bot)', + 'Channel binding added', + 'Workspace + runtime directories created (including memory/CURRENT_STATE.md)', + 'Configuration updated and backed up', + 'Restart gateway to apply: openclaw gateway restart' + ] + })); + return; + } + + // POST /api/agents/discord-sub — create Discord sub-agent (thread-only binding, grouping under parentAgentId) + if (method === 'POST' && pathname === '/api/agents/discord-sub') { + const { agentId, displayName, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, guildId, threadId } = body; + if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return; + } + if (!parentAgentId || !String(parentAgentId).trim()) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Parent Agent ID is required' })); return; + } + if (!threadId || !String(threadId).trim().match(/^\d+$/)) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Discord threadId is required' })); return; + } + const gid = (guildId || '').trim(); + + const cfg = await readConfig(); + const parentAgent = cfg.agents?.list?.find(a => a.id === parentAgentId); + if (!parentAgent) { res.writeHead(404); res.end(JSON.stringify({ error: `Parent agent \"${parentAgentId}\" does not exist` })); return; } + if (cfg.agents?.list?.some(a => a.id === agentId)) { res.writeHead(400); res.end(JSON.stringify({ error: `Agent ID \"${agentId}\" already exists` })); return; } + + const folder = (workspaceFolder || agentId).trim(); + const wsAlias = `~/.openclaw/workspaces/${folder}`; + if (cfg.agents?.list?.some(a => (a.workspace||'').endsWith(`/workspaces/${folder}`) || a.workspace === wsAlias)) { + res.writeHead(400); res.end(JSON.stringify({ error: `Workspace folder \"${folder}\" is already used by another agent` })); return; + } + + const wsPath = path.join(OPENCLAW_DIR, 'workspaces', folder); + const agentEntry = { id: agentId, name: displayName || agentId, workspace: wsAlias, parentAgentId: String(parentAgentId).trim() }; + if (model && model !== '__default__') agentEntry.model = { primary: model }; + cfg.agents.list.push(agentEntry); + + if (!cfg.bindings) cfg.bindings = []; + cfg.bindings.unshift({ agentId, match: { channel: 'discord', peer: { kind: 'channel', id: String(threadId).trim() } } }); + + if (!cfg.channels) cfg.channels = {}; + if (!cfg.channels.discord) cfg.channels.discord = {}; + cfg.channels.discord.enabled = true; + if (gid) { + if (!cfg.channels.discord.guilds) cfg.channels.discord.guilds = {}; + if (!cfg.channels.discord.guilds[gid]) cfg.channels.discord.guilds[gid] = {}; + if (!cfg.channels.discord.guilds[gid].channels) cfg.channels.discord.guilds[gid].channels = {}; + cfg.channels.discord.guilds[gid].channels[String(threadId).trim()] = { allow: true, requireMention: false }; + if (cfg.channels.discord.guilds[gid].requireMention === undefined) cfg.channels.discord.guilds[gid].requireMention = false; + } + + const bakPath = await writeConfig(cfg, 'create'); + + await fsp.mkdir(wsPath, { recursive: true }); + await fsp.mkdir(path.join(wsPath, 'memory'), { recursive: true }); + const name = displayName || agentId; + await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name, purpose || '', personality || ''), 'utf8'); + await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name, initialMemory || ''), 'utf8'); + await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8'); + + const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId); + await fsp.mkdir(agentRuntimeDir, { recursive: true }); + await fsp.mkdir(path.join(agentRuntimeDir, 'sessions'), { recursive: true }); + + res.writeHead(200); + res.end(JSON.stringify({ ok: true, agentId, workspacePath: wsPath, configBackup: bakPath, + notes: [ + 'Discord sub-agent created (thread-only binding)', + 'Workspace + runtime directories created (including memory/CURRENT_STATE.md)', + 'Configuration updated and backed up', + 'Restart gateway to apply: openclaw gateway restart' + ] + })); + return; + } + + + // POST /api/agents — create sub-agent (shares parent bot) + if (method === 'POST' && pathname === '/api/agents') { + const { agentId, displayName, groupId, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, telegramUserId } = body; + if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return; + } + if (!groupId?.trim()) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Group ID cannot be empty' })); return; + } + if (!parentAgentId?.trim()) { + res.writeHead(400); res.end(JSON.stringify({ error: 'Parent Agent ID is required' })); return; + } + const cfg = await readConfig(); + // Verify parent agent exists and has a bot account + const parentAgent = cfg.agents?.list?.find(a => a.id === parentAgentId); + if (!parentAgent) { + res.writeHead(404); res.end(JSON.stringify({ error: `Parent agent "${parentAgentId}" does not exist` })); return; + } + const parentBinding = cfg.bindings?.find(b => b.agentId === parentAgentId && b.match?.accountId); + if (!parentBinding) { + res.writeHead(400); res.end(JSON.stringify({ error: `Parent agent "${parentAgentId}" does not have its own bot account` })); return; + } + if (cfg.agents.list.some(a => a.id === agentId)) { + res.writeHead(400); res.end(JSON.stringify({ error: `Agent ID "${agentId}" already exists` })); return; + } + const gid = String(groupId).trim(); + const folder = workspaceFolder || agentId; + const wsPath = path.join(OPENCLAW_DIR, 'workspaces', folder); + const wsAlias= `~/.openclaw/workspaces/${folder}`; + const agentEntry = { id: agentId, name: displayName || agentId, workspace: wsAlias, parentAgentId: String(parentAgentId).trim() }; + if (model && model !== '__default__') agentEntry.model = { primary: model }; + cfg.agents.list.push(agentEntry); + // Binding uses parent's accountId + const parentAccountId = parentBinding.match.accountId; + const newBinding = { agentId, match: { channel: 'telegram', accountId: parentAccountId, peer: { kind: 'group', id: gid } } }; + if (!cfg.bindings) cfg.bindings = []; + cfg.bindings.unshift(newBinding); + if (!cfg.channels) cfg.channels = {}; + if (!cfg.channels.telegram) cfg.channels.telegram = {}; + if (!cfg.channels.telegram.groups) cfg.channels.telegram.groups = {}; + cfg.channels.telegram.groups[gid] = { requireMention: false }; + // Add telegramUserId to allowFrom whitelist if provided + if (telegramUserId && /^\d+$/.test(String(telegramUserId).trim())) { + const uid = parseInt(String(telegramUserId).trim()); + if (!cfg.channels.telegram.allowFrom) cfg.channels.telegram.allowFrom = []; + if (!cfg.channels.telegram.allowFrom.includes(uid)) { + cfg.channels.telegram.allowFrom.push(uid); + } + } + const bakPath = await writeConfig(cfg, 'create'); + await fsp.mkdir(wsPath, { recursive: true }); + await fsp.mkdir(path.join(wsPath, 'memory'), { recursive: true }); + const name = displayName || agentId; + await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name, purpose, personality), 'utf8'); + await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name, initialMemory), 'utf8'); + await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8'); + // Create agents// runtime directory (required by OpenClaw gateway) + const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId); + await fsp.mkdir(agentRuntimeDir, { recursive: true }); + await fsp.mkdir(path.join(agentRuntimeDir, 'sessions'), { recursive: true }); + res.writeHead(200); + res.end(JSON.stringify({ ok: true, agentId, workspacePath: wsPath, configBackup: bakPath, + notes: [ + 'Sub-agent created and shares parent bot', + 'Workspace and runtime directories created (including memory/CURRENT_STATE.md)', + 'Configuration updated and backed up', + 'Restart gateway to apply: openclaw gateway restart' + ], + })); + return; + } + + // PUT /api/agents/:id + if (method === 'PUT' && pathname.startsWith('/api/agents/')) { + const agentId = decodeURIComponent(pathname.split('/api/agents/')[1]); + const cfg = await readConfig(); + const idx = cfg.agents.list.findIndex(a => a.id === agentId); + if (idx === -1) { res.writeHead(404); res.end(JSON.stringify({ error: 'Agent 不存在' })); return; } + const { model, name } = body; + if (model !== undefined) { + if (!model || model === '__default__') { delete cfg.agents.list[idx].model; } + else { cfg.agents.list[idx].model = { primary: model }; } + } + if (name !== undefined && name.trim()) cfg.agents.list[idx].name = name.trim(); + const bakPath = await writeConfig(cfg, 'edit'); + res.writeHead(200); + res.end(JSON.stringify({ ok: true, agentId, configBackup: bakPath })); + return; + } + + // DELETE /api/agents/:id + if (method === 'DELETE' && pathname.startsWith('/api/agents/')) { + const agentId = decodeURIComponent(pathname.split('/api/agents/')[1]); + if (!agentId || agentId === 'main') { res.writeHead(400); res.end(JSON.stringify({ error: '无法删除此 Agent' })); return; } + const cfg = await readConfig(); + const idx = cfg.agents.list.findIndex(a => a.id === agentId); + if (idx === -1) { res.writeHead(404); res.end(JSON.stringify({ error: 'Agent 不存在' })); return; } + const agent = cfg.agents.list[idx]; + const agentBinding = cfg.bindings.find(b => b.agentId === agentId && b.match?.peer?.id); + const boundGroupId = agentBinding?.match?.peer?.id || null; + cfg.agents.list.splice(idx, 1); + cfg.bindings = cfg.bindings.filter(b => b.agentId !== agentId); + if (boundGroupId && cfg.channels?.telegram?.groups) delete cfg.channels.telegram.groups[boundGroupId]; + const bakPath = await writeConfig(cfg, 'delete'); + res.writeHead(200); + res.end(JSON.stringify({ ok: true, agentId, workspace: agent.workspace, configBackup: bakPath, + note: 'Workspace 目录未删除。如需恢复,可从备份回滚。' })); + return; + } + + // GET /api/workspace/:id — 列出 workspace 下所有文件(含内容和 stat) + if (method === 'GET' && pathname.startsWith('/api/workspace/')) { + const agentId = decodeURIComponent(pathname.split('/api/workspace/')[1]); + const cfg = await readConfig(); + const agent = cfg.agents?.list?.find(a => a.id === agentId); + if (!agent) { res.writeHead(404); res.end(JSON.stringify({ error: 'Agent 不存在' })); return; } + const wsPath = resolveAgentWorkspacePath(cfg, agentId, agent); + const files = {}; + const fileStats = {}; + try { + const entries = await fsp.readdir(wsPath); + for (const fname of entries) { + const fpath = path.join(wsPath, fname); + try { + const st = await fsp.stat(fpath); + if (!st.isFile()) continue; + // 对大文件只返回 stat 不读内容(> 512KB) + if (st.size > 512 * 1024) { + files[fname] = null; + } else { + files[fname] = await fsp.readFile(fpath, 'utf8'); + } + fileStats[fname] = { size: st.size, mtime: st.mtimeMs }; + } catch { /* skip */ } + } + } catch (e) { + res.writeHead(500); res.end(JSON.stringify({ error: '无法读取目录: ' + e.message })); return; + } + res.writeHead(200); + res.end(JSON.stringify({ agentId, workspacePath: wsPath, files, fileStats })); + return; + } + + // PUT /api/workspace/:id/:file + if (method === 'PUT' && pathname.startsWith('/api/workspace/')) { + const parts = pathname.split('/').filter(Boolean); + const agentId = decodeURIComponent(parts[2] || ''); + const fname = decodeURIComponent(parts[3] || ''); + if (!agentId || !fname) { res.writeHead(400); res.end(JSON.stringify({ error: '缺少参数' })); return; } + const cfg = await readConfig(); + const agent = cfg.agents?.list?.find(a => a.id === agentId); + if (!agent) { res.writeHead(404); res.end(JSON.stringify({ error: 'Agent 不存在' })); return; } + const wsPath = resolveAgentWorkspacePath(cfg, agentId, agent); + await fsp.mkdir(wsPath, { recursive: true }); + await fsp.writeFile(path.join(wsPath, fname), body.content || '', 'utf8'); + res.writeHead(200); + res.end(JSON.stringify({ ok: true })); + return; + } + + // GET /api/models + if (method === 'GET' && pathname === '/api/models') { + const cfg = await readConfig(); + const modelList = await getKnownModelsFromCli(); + res.writeHead(200); + res.end(JSON.stringify({ + models: cfg.agents?.defaults?.models || {}, + authProfiles: cfg.auth?.profiles || {}, + primaryModel: cfg.agents?.defaults?.model?.primary || '', + fallbacks: cfg.agents?.defaults?.model?.fallbacks || [], + knownModels: modelList.knownModels || [], + modelListError:modelList.error || '', + authProviders: AUTH_PROVIDERS, + })); + return; + } + + // PUT /api/models/settings + if (method === 'PUT' && pathname === '/api/models/settings') { + const { primaryModel, fallbacks } = body; + const cfg = await readConfig(); + if (!cfg.agents.defaults.model) cfg.agents.defaults.model = {}; + if (primaryModel !== undefined) cfg.agents.defaults.model.primary = primaryModel; + if (Array.isArray(fallbacks)) cfg.agents.defaults.model.fallbacks = fallbacks; + const bakPath = await writeConfig(cfg, 'models'); + res.writeHead(200); + res.end(JSON.stringify({ ok: true, configBackup: bakPath })); + return; + } + + + + // DELETE /api/auth/:key + if (method === 'DELETE' && pathname.startsWith('/api/auth/')) { + const profileKey = decodeURIComponent(pathname.split('/api/auth/')[1]); + const cfg = await readConfig(); + if (cfg.auth?.profiles?.[profileKey]) { + delete cfg.auth.profiles[profileKey]; + const bakPath = await writeConfig(cfg, 'auth'); + res.writeHead(200); res.end(JSON.stringify({ ok: true, profileKey, configBackup: bakPath })); + } else { + res.writeHead(404); res.end(JSON.stringify({ error: '认证配置不存在' })); + } + return; + } + + // ── Channels API ────────────────────────────────────────────── + + // GET /api/channels + if (method === 'GET' && pathname === '/api/channels') { + const cfg = await readConfig(); + const bindings = cfg.bindings || []; + const agents = cfg.agents?.list || []; + const channels = bindings.map((b, idx) => { + const agentName = agents.find(a => a.id === b.agentId)?.name || b.agentId; + return { + idx, + agentId: b.agentId, + agentName, + channel: b.match?.channel || 'any', + peerKind: b.match?.peer?.kind || 'any', + peerId: b.match?.peer?.id || null, + raw: b, + }; + }); + res.writeHead(200); + res.end(JSON.stringify({ channels })); + return; + } + + // POST /api/channels + if (method === 'POST' && pathname === '/api/channels') { + const { agentId, channel, peerKind, peerId } = body; + if (!agentId) { res.writeHead(400); res.end(JSON.stringify({ error: '缺少 agentId' })); return; } + const cfg = await readConfig(); + if (!cfg.agents?.list?.find(a => a.id === agentId)) { + res.writeHead(400); res.end(JSON.stringify({ error: `Agent "${agentId}" 不存在` })); return; + } + const newBinding = { agentId }; + if (channel || peerKind || peerId) { + newBinding.match = {}; + if (channel) newBinding.match.channel = channel; + if (peerKind || peerId) { + newBinding.match.peer = {}; + if (peerKind) newBinding.match.peer.kind = peerKind; + if (peerId) newBinding.match.peer.id = String(peerId); + } + } + // Insert before main catch-all binding + const mainIdx = cfg.bindings.findIndex(b => b.agentId === 'main' && !b.match?.peer); + if (mainIdx >= 0) cfg.bindings.splice(mainIdx, 0, newBinding); + else cfg.bindings.push(newBinding); + const bakPath = await writeConfig(cfg, 'edit'); + res.writeHead(200); + res.end(JSON.stringify({ ok: true, configBackup: bakPath })); + return; + } + + // DELETE /api/channels/:idx + if (method === 'DELETE' && pathname.startsWith('/api/channels/')) { + const idx = parseInt(pathname.split('/api/channels/')[1]); + const cfg = await readConfig(); + if (isNaN(idx) || idx < 0 || idx >= cfg.bindings.length) { + res.writeHead(400); res.end(JSON.stringify({ error: '无效的绑定索引' })); return; + } + const removed = cfg.bindings.splice(idx, 1)[0]; + const bakPath = await writeConfig(cfg, 'edit'); + res.writeHead(200); + res.end(JSON.stringify({ ok: true, removed, configBackup: bakPath })); + return; + } + + // ── Backup API ──────────────────────────────────────────────── + + // GET /api/backups + if (method === 'GET' && pathname === '/api/backups') { + const files = await listBackups(); + res.writeHead(200); + res.end(JSON.stringify({ backups: files })); + return; + } + + // POST /api/backups/restore + if (method === 'POST' && pathname === '/api/backups/restore') { + const { filename } = body; + if (!filename || filename.includes('..') || !filename.startsWith('openclaw.json')) { + res.writeHead(400); res.end(JSON.stringify({ error: '无效的备份文件名' })); return; + } + const bakPath = path.join(OPENCLAW_DIR, filename); + try { + await fsp.access(bakPath); + const savePath = await backupConfig('before-restore'); + await fsp.copyFile(bakPath, CONFIG_PATH); + res.writeHead(200); + res.end(JSON.stringify({ ok: true, restored: filename, savedCurrent: savePath })); + } catch (e) { + res.writeHead(500); res.end(JSON.stringify({ error: '恢复失败:' + e.message })); + } + return; + } + + // POST /api/config/backup + if (method === 'POST' && pathname === '/api/config/backup') { + try { + const bakPath = await backupConfig('manual'); + res.writeHead(200); res.end(JSON.stringify({ ok: true, bakPath })); + } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); } + return; + } + + // GET /api/backup/nas-config + if (method === 'GET' && pathname === '/api/backup/nas-config') { + const mc = loadManagerConfig(); + res.writeHead(200); + res.end(JSON.stringify({ + nasHost: mc.nasHost || '', + nasPort: mc.nasPort || '22', + nasUser: mc.nasUser || '', + nasAuth: mc.nasAuth || 'password', + nasSshKey: mc.nasSshKey || path.join(os.homedir(), '.ssh', 'ocm_nas_rsa'), + nasPath: mc.nasPath || '/volume1/OpenClaw/backups', + nasLegacyCipher: mc.nasLegacyCipher || false, + nasBackupType: mc.nasBackupType || 'full', + nasEnabled: mc.nasEnabled || false, + })); + return; + } + + // PUT /api/backup/nas-config + if (method === 'PUT' && pathname === '/api/backup/nas-config') { + const { nasHost, nasPort, nasUser, nasAuth, nasSshKey, nasPath, nasLegacyCipher, nasBackupType, nasEnabled } = body; + saveManagerConfig({ nasHost, nasPort, nasUser, nasAuth, nasSshKey, nasPath, nasLegacyCipher, nasBackupType, nasEnabled }); + res.writeHead(200); res.end(JSON.stringify({ ok: true })); + return; + } + + // POST /api/backup/nas-test — test SSH connection (password or key) + if (method === 'POST' && pathname === '/api/backup/nas-test') { + const mc = loadManagerConfig(); + const { nasHost, nasPort, nasUser, nasAuth, nasSshKey, nasLegacyCipher } = mc; + const { password } = body; + const port = nasPort || '22'; + if (!nasHost || !nasUser) { + res.writeHead(400); res.end(JSON.stringify({ error: '请先配置主机和用户名' })); return; + } + const cipherOpts = nasLegacyCipher + ? '-o Ciphers=aes256-gcm@openssh.com,aes128-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-cbc' + + ' -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1' + + ' -o HostKeyAlgorithms=ssh-ed25519,ecdsa-sha2-nistp256,rsa-sha2-256,rsa-sha2-512,ssh-rsa' + : ''; + try { + let sshCmd; + if (nasAuth === 'key') { + const keyPath = (nasSshKey || '~/.ssh/ocm_nas_rsa').replace(/^~/, os.homedir()); + sshCmd = `ssh -i "${keyPath}" -p ${port} -o ConnectTimeout=8 -o StrictHostKeyChecking=no ${cipherOpts} "${nasUser}@${nasHost}" "echo connected_ok"`; + } else { + if (!password) { res.writeHead(400); res.end(JSON.stringify({ error: '请输入密码' })); return; } + const safePwd = password.replace(/'/g, "'\\''"); + sshCmd = `sshpass -p '${safePwd}' ssh -p ${port} -o ConnectTimeout=8 -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no ${cipherOpts} "${nasUser}@${nasHost}" "echo connected_ok"`; + } + const out = await new Promise((resolve, reject) => { + exec(sshCmd, { timeout: 15000 }, (err, stdout, stderr) => { + if (err) reject(new Error(stderr || err.message)); else resolve(stdout.trim()); + }); + }); + res.writeHead(200); res.end(JSON.stringify({ ok: out.includes('connected_ok'), output: out })); + } catch (e) { + res.writeHead(500); res.end(JSON.stringify({ ok: false, error: e.message })); + } + return; + } + + // POST /api/backup/nas-now — create tarball + rsync to NAS + if (method === 'POST' && pathname === '/api/backup/nas-now') { + const mc = loadManagerConfig(); + const { nasHost, nasPort, nasUser, nasAuth, nasSshKey, nasPath, nasLegacyCipher, nasBackupType } = mc; + const { password } = body; + const port = nasPort || '22'; + const remotePath = nasPath || '/volume1/OpenClaw/backups'; + if (!nasHost || !nasUser) { + res.writeHead(400); res.end(JSON.stringify({ error: '请先配置 NAS 设置' })); return; + } + const cipherOpts = nasLegacyCipher + ? '-o Ciphers=aes256-gcm@openssh.com,aes128-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-cbc' + + ' -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1' + + ' -o HostKeyAlgorithms=ssh-ed25519,ecdsa-sha2-nistp256,rsa-sha2-256,rsa-sha2-512,ssh-rsa' + : ''; + try { + const ts = new Date().toISOString().replace(/[:.]/g,'-').slice(0,19); + const bkType = nasBackupType || 'full'; + const tarName = `openclaw-${bkType}-${ts}.tar.gz`; + const tarPath = path.join(os.tmpdir(), tarName); + const homeDir = os.homedir(); + const ocDir = path.basename(OPENCLAW_DIR); // usually .openclaw + + // Build tar command + let tarCmd; + if (bkType === 'essential') { + // Essential: configs + credentials + agent configs (no sessions/logs/media) + const essentialPaths = [ + `${ocDir}/openclaw.json`, + `${ocDir}/.env`, + `${ocDir}/credentials`, + `${ocDir}/agents`, + `${ocDir}/memory`, + ].join(' '); + tarCmd = `tar czf "${tarPath}" -C "${homeDir}" --exclude="${ocDir}/agents/*/sessions" --exclude="${ocDir}/logs" ${essentialPaths} 2>/dev/null || true`; + } else { + // Full: everything except logs and large temp files + tarCmd = `tar czf "${tarPath}" -C "${homeDir}" --exclude="${ocDir}/logs" --exclude="${ocDir}/ocm/*.bak.js" "${ocDir}"`; + } + + await new Promise((resolve, reject) => { + exec(tarCmd, { timeout: 60000 }, (err, stdout, stderr) => { + // tar exits non-zero on some warnings (excluded files) - check if file was created + fsp.access(tarPath).then(resolve).catch(() => reject(new Error(stderr || (err && err.message) || 'tar failed'))); + }); + }); + + // Transfer via rsync over ssh + // NOTE: sshpass must wrap the entire rsync command, NOT be inside -e argument + let rsyncCmd, mkdirCmd; + if (nasAuth === 'key') { + const keyPath = (nasSshKey || '~/.ssh/ocm_nas_rsa').replace(/^~/, os.homedir()); + const sshArg = `ssh -i "${keyPath}" -p ${port} -o StrictHostKeyChecking=no -o BatchMode=yes ${cipherOpts}`; + rsyncCmd = `rsync -avz -e "${sshArg}" "${tarPath}" "${nasUser}@${nasHost}:${remotePath}/"`; + mkdirCmd = `ssh -i "${keyPath}" -p ${port} -o StrictHostKeyChecking=no -o BatchMode=yes ${cipherOpts} "${nasUser}@${nasHost}" "mkdir -p '${remotePath}'"`; + } else { + if (!password) { res.writeHead(400); res.end(JSON.stringify({ error: '请提供密码' })); return; } + const safePwd = password.replace(/'/g, "'\\''"); + const sshArg = `ssh -p ${port} -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no ${cipherOpts}`; + rsyncCmd = `sshpass -p '${safePwd}' rsync -avz -e "${sshArg}" "${tarPath}" "${nasUser}@${nasHost}:${remotePath}/"`; + mkdirCmd = `sshpass -p '${safePwd}' ssh -p ${port} -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no ${cipherOpts} "${nasUser}@${nasHost}" "mkdir -p '${remotePath}'"`; + } + // Create remote directory if it doesn't exist (best-effort, ignore errors) + await new Promise(resolve => { exec(mkdirCmd, { timeout: 10000 }, () => resolve()); }); + + const rsyncOut = await new Promise((resolve, reject) => { + exec(rsyncCmd, { timeout: 120000 }, (err, stdout, stderr) => { + if (err) reject(new Error(stderr || err.message)); else resolve((stdout + stderr).trim()); + }); + }); + + // Cleanup temp file + fsp.unlink(tarPath).catch(() => {}); + res.writeHead(200); res.end(JSON.stringify({ ok: true, output: rsyncOut, tarName })); + } catch (e) { + res.writeHead(500); res.end(JSON.stringify({ ok: false, error: e.message })); + } + return; + } + + // POST /api/backup/nas-keygen — generate SSH key for key-auth mode + if (method === 'POST' && pathname === '/api/backup/nas-keygen') { + const mc = loadManagerConfig(); + const key = mc.nasSshKey || path.join(os.homedir(), '.ssh', 'ocm_nas_rsa'); + const keyResolved = key.replace(/^~/, os.homedir()); + try { + try { await fsp.access(keyResolved); } catch { + await new Promise((resolve, reject) => { + exec(`ssh-keygen -t ed25519 -f "${keyResolved}" -N "" -C "openclaw-manager-backup"`, + (err, stdout, stderr) => { if (err) reject(new Error(stderr || err.message)); else resolve(); }); + }); + } + const pubKey = await fsp.readFile(keyResolved + '.pub', 'utf8'); + res.writeHead(200); res.end(JSON.stringify({ ok: true, pubKey: pubKey.trim(), keyPath: keyResolved })); + } catch (e) { + res.writeHead(500); res.end(JSON.stringify({ error: e.message })); + } + return; + } + + // POST /api/backup/nas-cron — set up cron job + if (method === 'POST' && pathname === '/api/backup/nas-cron') { + const mc = loadManagerConfig(); + const { nasHost, nasPort, nasUser, nasAuth, nasSshKey, nasPath, nasLegacyCipher, nasBackupType } = mc; + const { password, cronTime } = body; + const port = nasPort || '22'; + const remotePath = nasPath || '/volume1/OpenClaw/backups'; + const cipherOpts = nasLegacyCipher + ? '-o Ciphers=aes256-gcm@openssh.com,aes128-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-cbc' + + ' -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1' + + ' -o HostKeyAlgorithms=ssh-ed25519,ecdsa-sha2-nistp256,rsa-sha2-256,rsa-sha2-512,ssh-rsa' + : ''; + const homeDir = os.homedir(); + const ocDir = path.basename(OPENCLAW_DIR); + const ts = '$(date +%Y%m%d-%H%M%S)'; + const bkType = nasBackupType || 'full'; + const tarPath = `/tmp/openclaw-${bkType}-${ts}.tar.gz`; + + let tarPart; + if (bkType === 'essential') { + tarPart = `tar czf ${tarPath} -C "${homeDir}" --exclude="${ocDir}/agents/*/sessions" ${ocDir}/openclaw.json ${ocDir}/.env ${ocDir}/credentials ${ocDir}/agents ${ocDir}/memory 2>/dev/null; `; + } else { + tarPart = `tar czf ${tarPath} -C "${homeDir}" --exclude="${ocDir}/logs" --exclude="${ocDir}/ocm/*.bak.js" "${ocDir}" 2>/dev/null; `; + } + + let cronRsyncCmd; + if (nasAuth === 'key') { + const keyPath = (nasSshKey || '~/.ssh/ocm_nas_rsa').replace(/^~/, os.homedir()); + const sshArg = `ssh -i "${keyPath}" -p ${port} -o StrictHostKeyChecking=no -o BatchMode=yes ${cipherOpts}`; + cronRsyncCmd = `rsync -avz -e "${sshArg}" ${tarPath} "${nasUser}@${nasHost}:${remotePath}/"`; + } else { + if (!password) { res.writeHead(400); res.end(JSON.stringify({ error: '请提供密码(用于计划任务)' })); return; } + const safePwd = password.replace(/'/g, "'\\''"); + const sshArg = `ssh -p ${port} -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no ${cipherOpts}`; + cronRsyncCmd = `sshpass -p '${safePwd}' rsync -avz -e "${sshArg}" ${tarPath} "${nasUser}@${nasHost}:${remotePath}/"`; + } + + const [h, m] = (cronTime || '03:00').split(':'); + const cronLine = `${m||'0'} ${h||'3'} * * * ${tarPart}${cronRsyncCmd} && rm -f ${tarPath} >> /tmp/ocm-nas-backup.log 2>&1 # openclaw-manager-nas`; + try { + await new Promise((resolve, reject) => { + exec('crontab -l 2>/dev/null || true', (err, stdout) => { + const existing = stdout.replace(/.*# openclaw-manager-nas\n?/g, ''); + const newCron = existing.trimEnd() + '\n' + cronLine + '\n'; + const child2 = exec('crontab -', (err2) => { if (err2) reject(err2); else resolve(); }); + child2.stdin.write(newCron); child2.stdin.end(); + }); + }); + res.writeHead(200); res.end(JSON.stringify({ ok: true, cronLine })); + } catch (e) { + res.writeHead(500); res.end(JSON.stringify({ ok: false, error: e.message })); + } + return; + } + + // GET /api/logs + if (method === 'GET' && pathname === '/api/logs') { + const n = parseInt(urlObj.searchParams.get('n') || '200'); + const content = await readLogTail(n); + res.writeHead(200); + res.end(JSON.stringify({ content, path: path.join(OPENCLAW_DIR, 'logs', 'gateway.log') })); + return; + } + + // POST /api/gateway/restart + if (method === 'POST' && pathname === '/api/gateway/restart') { + try { + const out = await runOpenclawCmd('gateway restart'); + res.writeHead(200); res.end(JSON.stringify({ ok: true, output: out })); + } catch (e) { + res.writeHead(500); res.end(JSON.stringify({ ok: false, error: e.message, + hint: '请在终端手动运行: openclaw gateway restart' })); + } + return; + } + + // ── Stats API — Token usage from session JSONL files ────────── + if (method === 'GET' && pathname === '/api/stats') { + const days = parseInt(urlObj.searchParams.get('days') || '30'); + const cutoff = Date.now() - days * 86400000; + const byModel = {}; + const byDay = {}; + const byAgent = {}; + let totalIn = 0, totalOut = 0, totalCacheRead = 0, totalCacheWrite = 0; + let totalCost = 0; + try { + const agentsDir = path.join(OPENCLAW_DIR, 'agents'); + const agentDirs = await fsp.readdir(agentsDir).catch(() => []); + for (const agentId of agentDirs) { + const sessDir = path.join(agentsDir, agentId, 'sessions'); + let files; + try { files = await fsp.readdir(sessDir); } catch { continue; } + const jsonlFiles = files.filter(f => f.endsWith('.jsonl')); + for (const fname of jsonlFiles) { + let content; + try { content = await fsp.readFile(path.join(sessDir, fname), 'utf8'); } catch { continue; } + const lines = content.split('\n'); + for (const line of lines) { + if (!line.trim()) continue; + let obj; + try { obj = JSON.parse(line); } catch { continue; } + if (obj.type !== 'message') continue; + const msg = obj.message; + if (!msg || msg.role !== 'assistant' || !msg.usage) continue; + // Check timestamp filter + const ts = obj.timestamp ? new Date(obj.timestamp).getTime() : (msg.timestamp || 0); + if (ts && ts < cutoff) continue; + const u = msg.usage; + const inTk = u.input || 0; + const outTk = u.output || 0; + const cacheR = u.cacheRead || 0; + const cacheW = u.cacheWrite || 0; + const msgCost = u.cost && typeof u.cost === 'object' ? (u.cost.total || 0) : 0; + const model = msg.model || 'unknown'; + totalIn += inTk; totalOut += outTk; + totalCacheRead += cacheR; totalCacheWrite += cacheW; + totalCost += msgCost; + // By model + if (!byModel[model]) byModel[model] = { inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheWrite: 0, requestCount: 0, cost: 0 }; + byModel[model].inputTokens += inTk; + byModel[model].outputTokens += outTk; + byModel[model].cacheRead += cacheR; + byModel[model].cacheWrite += cacheW; + byModel[model].requestCount++; + byModel[model].cost += msgCost; + // By day + const dayKey = ts ? new Date(ts).toISOString().slice(0, 10) : 'unknown'; + if (!byDay[dayKey]) byDay[dayKey] = { inputTokens: 0, outputTokens: 0, requestCount: 0, cost: 0 }; + byDay[dayKey].inputTokens += inTk; + byDay[dayKey].outputTokens += outTk; + byDay[dayKey].requestCount++; + byDay[dayKey].cost += msgCost; + // By agent + if (!byAgent[agentId]) byAgent[agentId] = { inputTokens: 0, outputTokens: 0, requestCount: 0, cost: 0 }; + byAgent[agentId].inputTokens += inTk; + byAgent[agentId].outputTokens += outTk; + byAgent[agentId].requestCount++; + byAgent[agentId].cost += msgCost; + } + } + } + } catch { /* agents dir may not exist */ } + // Format costs + Object.values(byModel).forEach(d => { d.cost = d.cost.toFixed(4); }); + Object.values(byDay).forEach(d => { d.cost = d.cost.toFixed(4); }); + Object.values(byAgent).forEach(d => { d.cost = d.cost.toFixed(4); }); + res.writeHead(200); + res.end(JSON.stringify({ + summary: { + totalInputTokens: totalIn, totalOutputTokens: totalOut, + totalCacheRead: totalCacheRead, totalCacheWrite: totalCacheWrite, + estimatedCost: '$' + totalCost.toFixed(4), + totalTokens: totalIn + totalOut + totalCacheRead + totalCacheWrite + }, + byModel, byDay, byAgent + })); + return; + } + + // ── Cron API — 计划任务管理 ──────────────────────────────── + if (method === 'GET' && pathname === '/api/cron') { + try { + const { stdout } = await new Promise((resolve, reject) => { + exec('crontab -l 2>/dev/null || true', (err, stdout) => err ? reject(err) : resolve({ stdout })); + }); + const lines = stdout.split('\n').filter(l => l.trim()); + const crons = []; + lines.forEach((line, idx) => { + // 只展示 openclaw 相关的或 OCM 标记的 cron + const lo = line.toLowerCase(); + if (!lo.includes('openclaw') && !lo.includes('ocm')) return; + const enabled = !line.trimStart().startsWith('#'); + const raw = enabled ? line.trim() : line.trim().replace(/^#\s*/, ''); + const parts = raw.split(/\s+/); + const schedule = parts.slice(0, 5).join(' '); + const command = parts.slice(5).join(' ').replace(/\s*#\s*openclaw.*$/i, '').trim(); + const label = (line.match(/#\s*(openclaw[^\n]*)/i) || [])[1] || ''; + crons.push({ idx, schedule, command, enabled, label, rawLine: line }); + }); + res.writeHead(200); res.end(JSON.stringify({ crons })); + } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); } + return; + } + + if (method === 'POST' && pathname === '/api/cron') { + const { schedule, command, label } = body; + if (!schedule || !command) { res.writeHead(400); res.end(JSON.stringify({ error: '请填写表达式和命令' })); return; } + const tag = label || 'openclaw-manager'; + const cronLine = `${schedule} ${command} >> /tmp/ocm-cron.log 2>&1 # ${tag}`; + try { + await new Promise((resolve, reject) => { + exec('crontab -l 2>/dev/null || true', (err, stdout) => { + const newCron = stdout.trimEnd() + '\n' + cronLine + '\n'; + const child2 = exec('crontab -', (err2) => { if (err2) reject(err2); else resolve(); }); + child2.stdin.write(newCron); child2.stdin.end(); + }); + }); + res.writeHead(200); res.end(JSON.stringify({ ok: true, cronLine })); + } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); } + return; + } + + if (method === 'PUT' && pathname.match(/^\/api\/cron\/\d+$/)) { + const targetIdx = parseInt(pathname.split('/').pop()); + const { schedule, command, enabled } = body; + try { + const { stdout } = await new Promise((resolve, reject) => { + exec('crontab -l 2>/dev/null || true', (err, stdout) => err ? reject(err) : resolve({ stdout })); + }); + const lines = stdout.split('\n'); + // 找到 openclaw 相关行的真实行号 + let ocmIdx = -1; + for (let i = 0; i < lines.length; i++) { + const lo = lines[i].toLowerCase(); + if (!lo.includes('openclaw') && !lo.includes('ocm')) continue; + ocmIdx++; + if (ocmIdx === targetIdx) { + let raw = lines[i].trimStart().startsWith('#') ? lines[i].replace(/^#\s*/, '') : lines[i]; + if (schedule || command) { + const parts = raw.trim().split(/\s+/); + const oldSched = parts.slice(0, 5).join(' '); + const oldCmd = parts.slice(5).join(' '); + raw = (schedule || oldSched) + ' ' + (command || oldCmd); + } + lines[i] = (enabled === false ? '# ' : '') + raw.trim(); + break; + } + } + await new Promise((resolve, reject) => { + const child2 = exec('crontab -', (err) => { if (err) reject(err); else resolve(); }); + child2.stdin.write(lines.join('\n') + '\n'); child2.stdin.end(); + }); + res.writeHead(200); res.end(JSON.stringify({ ok: true })); + } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); } + return; + } + + if (method === 'DELETE' && pathname.match(/^\/api\/cron\/\d+$/)) { + const targetIdx = parseInt(pathname.split('/').pop()); + try { + const { stdout } = await new Promise((resolve, reject) => { + exec('crontab -l 2>/dev/null || true', (err, stdout) => err ? reject(err) : resolve({ stdout })); + }); + const lines = stdout.split('\n'); + let ocmIdx = -1; + for (let i = 0; i < lines.length; i++) { + const lo = lines[i].toLowerCase(); + if (!lo.includes('openclaw') && !lo.includes('ocm')) continue; + ocmIdx++; + if (ocmIdx === targetIdx) { lines.splice(i, 1); break; } + } + await new Promise((resolve, reject) => { + const child2 = exec('crontab -', (err) => { if (err) reject(err); else resolve(); }); + child2.stdin.write(lines.join('\n') + '\n'); child2.stdin.end(); + }); + res.writeHead(200); res.end(JSON.stringify({ ok: true })); + } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); } + return; + } + + if (method === 'POST' && pathname.match(/^\/api\/cron\/\d+\/run$/)) { + const targetIdx = parseInt(pathname.split('/')[3]); + try { + const { stdout } = await new Promise((resolve, reject) => { + exec('crontab -l 2>/dev/null || true', (err, stdout) => err ? reject(err) : resolve({ stdout })); + }); + const lines = stdout.split('\n'); + let ocmIdx = -1; let cmd = ''; + for (const line of lines) { + const lo = line.toLowerCase(); + if (!lo.includes('openclaw') && !lo.includes('ocm')) continue; + ocmIdx++; + if (ocmIdx === targetIdx) { + const raw = line.trimStart().startsWith('#') ? line.replace(/^#\s*/, '') : line; + const parts = raw.trim().split(/\s+/); + cmd = parts.slice(5).join(' ').replace(/\s*#\s*openclaw.*$/i, '').replace(/\s*>>[^&]*2>&1/, '').trim(); + break; + } + } + if (!cmd) { res.writeHead(404); res.end(JSON.stringify({ error: '任务不存在' })); return; } + const out = await new Promise((resolve, reject) => { + exec(cmd, { timeout: 30000 }, (err, stdout, stderr) => { + if (err) reject(new Error(stderr || err.message)); + else resolve(stdout + stderr); + }); + }); + res.writeHead(200); res.end(JSON.stringify({ ok: true, output: stripAnsi(out) })); + } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); } + return; + } + + // GET /api/health — 快速健康状态(解析 doctor 输出中的 warning/error 行) + if (method === 'GET' && pathname === '/api/health') { + try { + const bin = process.platform === 'win32' ? 'openclaw.cmd' : 'openclaw'; + const r = spawnSync(bin, ['doctor'], { + encoding: 'utf8', timeout: 8000, + env: { ...process.env, NO_COLOR: '1', TERM: 'dumb', FORCE_COLOR: '0' } + }); + const raw = stripAnsi((r.stdout || '') + (r.stderr || '')); + const lines = raw.split('\n').filter(l => l.trim()); + const issues = []; + lines.forEach(l => { + const lo = l.toLowerCase(); + if (lo.includes('error') || lo.includes('critical') || lo.includes('fail')) { + issues.push({ level: 'error', text: l.trim() }); + } else if (lo.includes('warn') || lo.includes('missing') || lo.includes('not found') || lo.includes('not configured')) { + issues.push({ level: 'warn', text: l.trim() }); + } + }); + res.writeHead(200); + res.end(JSON.stringify({ ok: true, issues, raw, exitCode: r.status || 0 })); + } catch (e) { + res.writeHead(200); + res.end(JSON.stringify({ ok: false, issues: [], raw: e.message, exitCode: -1 })); + } + return; + } + + // GET /api/dashboard — system info + gateway health for Dashboard tab + if (method === 'GET' && pathname === '/api/dashboard') { + try { + const cfg = await configExists() ? await readConfig() : null; + // System info + const sysUptime = os.uptime(); + const totalMem = os.totalmem(); + const freeMem = os.freemem(); + const nodeVer = process.version; + const platform = `${os.type()} ${os.release()} (${os.arch()})`; + const hostname = os.hostname(); + const cpus = os.cpus(); + const cpuModel = cpus.length ? cpus[0].model.trim() : 'Unknown'; + const cpuCores = cpus.length; + const loadAvg = os.loadavg(); // [1min, 5min, 15min] + + // CPU usage % (snapshot via /proc/stat or fallback to loadavg) + let cpuPercent = null; + try { + // Quick estimate: sum idle vs total across all cores from a snapshot + const c1 = os.cpus(); + await new Promise(r => setTimeout(r, 200)); + const c2 = os.cpus(); + let idleDiff = 0, totalDiff = 0; + for (let i = 0; i < c2.length; i++) { + const t1 = c1[i].times, t2 = c2[i].times; + const total1 = t1.user + t1.nice + t1.sys + t1.idle + t1.irq; + const total2 = t2.user + t2.nice + t2.sys + t2.idle + t2.irq; + idleDiff += (t2.idle - t1.idle); + totalDiff += (total2 - total1); + } + cpuPercent = totalDiff > 0 ? Math.round((1 - idleDiff / totalDiff) * 100) : 0; + } catch (_) {} + + // Disk usage (best-effort, works on macOS/Linux) + let diskTotal = 0, diskUsed = 0, diskFree = 0; + try { + const dfOut = execSync('df -k ' + JSON.stringify(OPENCLAW_DIR), { encoding: 'utf8', timeout: 3000 }); + const dfLines = dfOut.trim().split('\\n'); + if (dfLines.length >= 2) { + const parts = dfLines[1].split(/\\s+/); + diskTotal = parseInt(parts[1] || 0) * 1024; + diskUsed = parseInt(parts[2] || 0) * 1024; + diskFree = parseInt(parts[3] || 0) * 1024; + } + } catch (_) {} + + // OpenClaw dir size (best-effort) + let dirSize = 0; + try { + const duOut = execSync('du -sk ' + JSON.stringify(OPENCLAW_DIR), { encoding: 'utf8', timeout: 5000 }); + dirSize = parseInt(duOut.split(/\\s/)[0] || 0) * 1024; + } catch (_) {} + + // Gateway process detection + let gatewayRunning = 'unknown'; + let gatewayPid = null; + try { + const psOut = execSync("ps aux 2>/dev/null | grep -i 'openclaw.*gateway' | grep -v grep", { encoding: 'utf8', timeout: 3000 }).trim(); + if (psOut) { + gatewayRunning = 'running'; + const psParts = psOut.split(/\\s+/); + gatewayPid = psParts[1] || null; + } else { + gatewayRunning = 'stopped'; + } + } catch (_) { gatewayRunning = 'stopped'; } + + // Gateway port (from config or default 3000) + let gatewayPort = null; + try { + if (cfg && cfg.channels && cfg.channels.telegram) { + gatewayPort = cfg.channels.telegram.port || null; + } + if (!gatewayPort) gatewayPort = 3000; + } catch (_) {} + + // Telegram routing/bot counts + let bindingCount = 0, accountCount = 0, groupCount = 0, allowFromCount = 0, configAgentCount = 0; + try { + bindingCount = (cfg?.bindings || []).length; + const accounts = cfg?.channels?.telegram?.accounts || {}; + accountCount = Object.keys(accounts).length; + groupCount = Object.keys(cfg?.channels?.telegram?.groups || {}).length; + if (!accountCount && cfg?.channels?.telegram?.botToken) accountCount = 1; // legacy single-token config + allowFromCount = (cfg?.channels?.telegram?.allowFrom || []).length; + configAgentCount = (cfg?.agents?.list || []).length; + } catch (_) {} + + // Agent count (main vs sub) & last activity + let agentCount = 0, mainAgentCount = 0, subAgentCount = 0; + let lastActivity = null; + try { + // Count main vs sub from config + if (cfg && cfg.agents && cfg.agents.list && cfg.bindings) { + const bindings = cfg.bindings || []; + const accounts = cfg.channels?.telegram?.accounts || {}; + cfg.agents.list.forEach(a => { + const isMain = a.id === 'main'; + const botBinding = bindings.find(b => b.agentId === a.id && b.match?.accountId && !b.match?.peer); + if (isMain || botBinding) mainAgentCount++; else subAgentCount++; + }); + } + } catch (_) {} + try { + const sessionsBase = path.join(OPENCLAW_DIR, 'agents'); + const agentDirs = await fsp.readdir(sessionsBase); + agentCount = agentDirs.length; + for (const ad of agentDirs) { + const sessDir = path.join(sessionsBase, ad, 'sessions'); + try { + const sessFiles = await fsp.readdir(sessDir); + for (const sf of sessFiles) { + if (sf.endsWith('.jsonl')) { + const st = await fsp.stat(path.join(sessDir, sf)); + if (!lastActivity || st.mtime > lastActivity) lastActivity = st.mtime; + } + } + } catch (_) {} + } + } catch (_) {} + + // Brisbane time for display + const now = new Date().toLocaleString('en-AU', { timeZone: 'Australia/Brisbane', hour12: false }); + + res.writeHead(200); + res.end(JSON.stringify({ + ok: true, + system: { hostname, platform, nodeVer, cpuModel, cpuCores, uptime: sysUptime, totalMem, freeMem, diskTotal, diskUsed, diskFree, dirSize, cpuPercent, loadAvg }, + gateway: { status: gatewayRunning, pid: gatewayPid, port: gatewayPort, host: HOST, accountCount, groupCount, bindingCount, allowFromCount }, + agents: { count: agentCount, configCount: configAgentCount, mainCount: mainAgentCount, subCount: subAgentCount, lastActivity: lastActivity ? lastActivity.toISOString() : null }, + ocmVersion: APP_VERSION, + serverTime: now, + })); + } catch (e) { + res.writeHead(500); + res.end(JSON.stringify({ ok: false, error: e.message })); + } + return; + } + + // POST /api/gateway/doctor + if (method === 'POST' && pathname === '/api/gateway/doctor') { + try { + const out = await runOpenclawCmd('doctor'); + res.writeHead(200); res.end(JSON.stringify({ ok: true, output: out })); + } catch (e) { + res.writeHead(500); res.end(JSON.stringify({ ok: false, error: e.message })); + } + return; + } + + // POST /api/folder/open + if (method === 'POST' && pathname === '/api/folder/open') { + openFolder(OPENCLAW_DIR); + res.writeHead(200); res.end(JSON.stringify({ ok: true })); + return; + } + + // GET /api/cli/stream?cmd=... — SSE 实时流式执行命令 + if (method === 'GET' && pathname === '/api/cli/stream') { + const qCmd = (new URL('http://x' + req.url)).searchParams.get('cmd') || ''; + if (!qCmd.trim()) { res.writeHead(400); res.end('Missing cmd'); return; } + res.writeHead(200, { + 'Content-Type': 'text/event-stream; charset=utf-8', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'X-Accel-Buffering': 'no', + }); + res.flushHeaders(); + const send = (ev, data) => { + try { res.write(`event: ${ev}\ndata: ${JSON.stringify(data)}\n\n`); } catch(_) {} + }; + send('start', { cmd: qCmd, time: new Date().toLocaleTimeString('zh-CN') }); + const cenv = { ...process.env, NO_COLOR: '1', TERM: 'dumb', FORCE_COLOR: '0' }; + const child = spawn('sh', ['-c', qCmd], { env: cenv, stdio: ['ignore','pipe','pipe'] }); + const timer = setTimeout(() => { + child.kill(); + send('out', { text: '\n⏱ 命令执行超时(60s),已终止\n' }); + }, 60000); + child.stdout.on('data', d => send('out', { text: stripAnsi(d.toString()) })); + child.stderr.on('data', d => send('out', { text: stripAnsi(d.toString()) })); + child.on('close', code => { clearTimeout(timer); send('done', { code }); try { res.end(); } catch(_) {} }); + child.on('error', err => { clearTimeout(timer); send('error', { message: err.message }); try { res.end(); } catch(_) {} }); + req.on('close', () => { clearTimeout(timer); try { child.kill(); } catch(_) {} }); + return; + } + + res.writeHead(404); + res.end(JSON.stringify({ error: 'Unknown API path: ' + pathname })); +} + +// ── 安装向导 HTML ────────────────────────────────────────────── +const SETUP_HTML = ` +OpenClaw Manager - Setup + +
+

🦀 OpenClaw Manager

+

First time setup — please specify your OpenClaw data directory (the folder containing openclaw.json).

+ + +
Common locations:
macOS / Linux: ~/.openclaw
Windows: C:\\Users\\yourname\\.openclaw
+
+ +
+`; + +// ── 主 HTML 前端 ────────────────────────────────────────────── +const MAIN_HTML_CSS = String.raw`*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } +:root { + --bg: #0f1117; --surface: #1a1d27; --border: #2d3148; + --accent: #6c63ff; --accent-h: #7c74ff; + --danger: #ef4444; --success: #22c55e; --warn: #f59e0b; + --text: #e2e8f0; --muted: #6b7280; --card: #1e2235; +} +body { background:var(--bg); color:var(--text); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; min-height:100vh; } + + +/* ── Toolbar ── */ +header { background:var(--surface); border-bottom:1px solid var(--border); padding:0 20px; display:flex; align-items:center; gap:12px; height:52px; position:sticky; top:0; z-index:50; } +.logo { font-size:17px; font-weight:700; color:var(--accent); cursor:pointer; } +.logo:hover { opacity:.8; } +.ver { font-size:11px; color:var(--muted); background:var(--border); padding:2px 8px; border-radius:20px; } +.spacer { flex:1; } +.status-row { display:flex; align-items:center; gap:6px; } +.dot { width:7px; height:7px; border-radius:50%; background:var(--muted); } +.dot.ok { background:var(--success); box-shadow:0 0 5px var(--success); } +.dot.err{ background:var(--danger); box-shadow:0 0 5px var(--danger); } +.status-txt { font-size:12px; color:var(--muted); } +.lang-toggle { background:var(--border); border:none; color:var(--muted); border-radius:6px; padding:5px 10px; font-size:12px; cursor:pointer; } +.lang-toggle:hover { background:#3a3f5c; color:var(--text); } + +/* centered version badge */ +.top-version{ position:absolute; left:50%; transform:translateX(-50%); font-size:12px; font-weight:800; letter-spacing:.3px; color:var(--text); background:rgba(108,99,255,.18); border:1px solid rgba(108,99,255,.45); padding:4px 12px; border-radius:999px; box-shadow:0 6px 18px rgba(0,0,0,.25); } +.ver-old{ display:none; } + +/* dropdown menu */ +.menu-wrap { position:relative; } +.menu-btn { background:var(--border); border:none; color:var(--text); border-radius:6px; padding:6px 12px; font-size:13px; cursor:pointer; display:flex; align-items:center; gap:6px; } +.menu-btn:hover { background:#3a3f5c; } +.menu-dropdown { position:absolute; right:0; top:calc(100% + 6px); background:var(--surface); border:1px solid var(--border); border-radius:10px; min-width:220px; z-index:200; box-shadow:0 8px 24px rgba(0,0,0,.4); overflow:hidden; display:none; } +.menu-dropdown.open { display:block; } +.menu-item { padding:10px 16px; font-size:13px; cursor:pointer; display:flex; align-items:center; gap:10px; transition:background .1s; } +.menu-item:hover { background:var(--border); } +.menu-sep { border-top:1px solid var(--border); margin:4px 0; } + +/* ── Tabs ── */ +nav { background:var(--surface); border-bottom:1px solid var(--border); display:flex; padding:0 20px; gap:2px; overflow-x:auto; } +.tab { padding:11px 16px; font-size:13px; cursor:pointer; border-bottom:2px solid transparent; color:var(--muted); transition:all .15s; white-space:nowrap; } +.tab:hover { color:var(--text); } +.tab.active { color:var(--accent); border-bottom-color:var(--accent); } + +/* ── Main ── */ +main { padding:20px; max-width:1280px; margin:0 auto; } +.panel { display:none; } +.panel.active { display:block; } +.sec-hdr { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; } +.sec-hdr h2 { font-size:16px; font-weight:600; } + +/* ── Cards ── */ +.card-grid { display:grid; gap:12px; } +.card { background:var(--card); border:1px solid var(--border); border-radius:10px; padding:16px 18px; } +.card-row { display:flex; align-items:center; gap:8px; flex-wrap:wrap; } +.card-title { font-size:14px; font-weight:600; } +.badge { font-size:11px; padding:2px 8px; border-radius:20px; background:var(--border); color:var(--muted); } +.badge.main { background:rgba(108,99,255,.2); color:var(--accent); } +.badge.ok { background:rgba(34,197,94,.15); color:var(--success); } +.badge.warn { background:rgba(245,158,11,.15); color:var(--warn); } +.card-meta { font-size:12px; color:var(--muted); margin-top:4px; } +.card-actions { margin-top:10px; display:flex; gap:8px; flex-wrap:wrap; align-items:center; } + +/* inline model selector */ +.inline-sel { background:var(--bg); border:1px solid var(--border); color:var(--text); border-radius:6px; padding:5px 8px; font-size:12px; } +.inline-sel:focus { outline:none; border-color:var(--accent); } + +/* Agents layout — buttons top, tree below */ +.agents-top-btns { display:flex; gap:10px; justify-content:center; margin-bottom:18px; } +.agents-tree-wrap { max-width:900px; margin:0 auto; overflow-y:auto; } +.agents-roots { display:flex; gap:16px; flex-wrap:wrap; } +.agents-roots > .agent-tree-root { flex:1; min-width:320px; } + +/* Add form */ +.add-form { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:16px; } +.add-form h3 { font-size:15px; margin-bottom:12px; font-weight:600; } +.add-form .guide-box { background:rgba(108,99,255,.06); border:1px solid rgba(108,99,255,.2); border-radius:8px; padding:12px; margin-bottom:14px; font-size:12px; line-height:1.8; color:var(--muted); } +.add-form .guide-box summary { cursor:pointer; font-weight:600; color:var(--text); font-size:13px; margin-bottom:6px; } +.add-form .guide-box ol { margin:6px 0 0 18px; padding:0; } +.add-form .guide-box li { margin-bottom:4px; } +.add-form .guide-box code { background:rgba(255,255,255,.08); padding:1px 5px; border-radius:3px; font-size:11px; } + +/* Agent tree */ +.agent-tree-root { margin-bottom:16px; } +.agent-tree-root .tree-main { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:12px 14px; cursor:pointer; } +.agent-tree-root .tree-main:hover { border-color:var(--accent); } +.agent-tree-root .tree-main .tree-title { font-size:14px; font-weight:600; display:flex; align-items:center; gap:8px; } +.agent-tree-root .tree-main .tree-meta { font-size:11px; color:var(--muted); margin-top:4px; } +.tree-children-wrap { position:relative; margin-left:20px; padding-left:14px; margin-top:6px; } +.tree-children-wrap::before { content:''; position:absolute; left:0; top:0; bottom:8px; width:2px; background:var(--border); } +.tree-toggle { position:absolute; left:-8px; top:-4px; width:18px; height:18px; border-radius:50%; background:var(--surface); border:1.5px solid var(--border); color:var(--muted); font-size:12px; line-height:15px; text-align:center; cursor:pointer; z-index:2; padding:0; } +.tree-toggle:hover { border-color:var(--accent); color:var(--accent); } +.tree-children { } +.tree-children.collapsed { display:none; } +.tree-child { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:10px 12px; margin-bottom:8px; } +.tree-child:hover { border-color:var(--accent); } +.tree-child .tree-title { font-size:13px; font-weight:600; display:flex; align-items:center; gap:6px; } +.tree-child .tree-meta { font-size:11px; color:var(--muted); margin-top:3px; } +.tree-actions { display:flex; gap:6px; align-items:center; margin-top:8px; flex-wrap:wrap; } +.tree-actions select { font-size:12px; padding:4px 8px; background:var(--bg); border:1px solid var(--border); border-radius:6px; color:var(--text); } +.tree-actions button { font-size:11px; padding:4px 10px; } + +/* ── Buttons ── */ +button { cursor:pointer; font-size:13px; font-weight:500; border:none; border-radius:6px; padding:6px 13px; transition:all .15s; } +.btn-primary { background:var(--accent); color:#fff; } +.btn-primary:hover { background:var(--accent-h); } +.btn-danger { background:rgba(239,68,68,.12); color:var(--danger); border:1px solid rgba(239,68,68,.3); } +.btn-danger:hover { background:rgba(239,68,68,.22); } +.btn-secondary{ background:var(--border); color:var(--text); } +.btn-secondary:hover { background:#3a3f5c; } +.btn-ghost { background:transparent; color:var(--muted); border:1px solid var(--border); } +.btn-ghost:hover { background:var(--surface); color:var(--text); } +.btn-warn { background:rgba(245,158,11,.12); color:var(--warn); border:1px solid rgba(245,158,11,.3); } +.btn-warn:hover { background:rgba(245,158,11,.22); } +.btn-success { background:rgba(34,197,94,.12); color:var(--success); border:1px solid rgba(34,197,94,.3); } +.btn-success:hover { background:rgba(34,197,94,.22); } +button:disabled { opacity:.4; cursor:not-allowed; } + +/* ── Forms ── */ +.form-group { display:flex; flex-direction:column; gap:5px; margin-bottom:14px; } +label { font-size:13px; font-weight:500; } +.hint-text { font-size:11px; color:var(--muted); } +input,select,textarea { background:var(--bg); border:1px solid var(--border); color:var(--text); border-radius:6px; padding:8px 11px; font-size:13px; width:100%; transition:border-color .15s; } +input:focus,select:focus,textarea:focus { outline:none; border-color:var(--accent); } +input::placeholder,textarea::placeholder { color:var(--muted); } +textarea { resize:vertical; min-height:72px; } +select option { background:var(--surface); } +.form-row { display:grid; grid-template-columns:1fr 1fr; gap:14px; } +.field-err { font-size:11px; color:var(--danger); } +.input-pw-wrap { position:relative; } +.input-pw-wrap input { padding-right:36px; } +.pw-toggle { position:absolute; right:10px; top:50%; transform:translateY(-50%); background:none; border:none; color:var(--muted); cursor:pointer; font-size:14px; padding:0; } + +/* ── Dashboard ── */ +.dash-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; } +.dash-header h2 { font-size:18px; font-weight:600; color:var(--text); margin:0; } +.dash-auto-refresh { display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); } +.dash-auto-refresh label { cursor:pointer; display:flex; align-items:center; gap:6px; } +.dash-toggle { position:relative; width:36px; height:20px; } +.dash-toggle input { opacity:0; width:0; height:0; } +.dash-toggle .slider { position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0; background:var(--border); border-radius:10px; transition:.3s; } +.dash-toggle .slider:before { position:absolute; content:""; height:14px; width:14px; left:3px; bottom:3px; background:#999; border-radius:50%; transition:.3s; } +.dash-toggle input:checked + .slider { background:var(--accent); } +.dash-toggle input:checked + .slider:before { transform:translateX(16px); background:#fff; } +.dash-gauges { display:flex; gap:20px; justify-content:center; flex-wrap:wrap; margin-bottom:24px; } +.dash-gauge-card { background:var(--surface); border:1px solid var(--border); border-radius:14px; padding:20px 24px; display:flex; flex-direction:column; align-items:center; min-width:140px; } +.dash-gauge-svg { width:110px; height:110px; } +.dash-gauge-label { font-size:12px; color:var(--muted); margin-top:8px; font-weight:500; } +.dash-sections { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:16px; } +.dash-card { padding:20px; } +.dash-card-wide { grid-column:span 2; } +.dash-card h3 { font-size:14px; font-weight:600; margin-bottom:14px; color:var(--text); } +.dash-notice { margin-top:18px; padding:16px 18px; } +.dash-notice h3 { font-size:14px; margin-bottom:10px; } +.dash-note-list { margin-left:16px; color:var(--muted); font-size:12px; line-height:1.7; } +.dash-note-list li { margin-bottom:4px; } +.dash-row { display:flex; justify-content:space-between; align-items:center; padding:6px 0; border-bottom:1px solid var(--border); font-size:12px; } +.dash-row:last-child { border-bottom:none; } +.dash-label { color:var(--muted); } +.dash-val { color:var(--text); font-weight:500; font-family:monospace; font-size:11px; } +.dash-indicator { display:inline-block; width:8px; height:8px; border-radius:50%; margin-right:6px; } +.dash-indicator.running { background:#22c55e; box-shadow:0 0 6px rgba(34,197,94,.5); } +.dash-indicator.stopped { background:#ef4444; box-shadow:0 0 6px rgba(239,68,68,.5); } +.dash-indicator.unknown { background:#f59e0b; } +@media (max-width: 980px) { + .dash-card-wide { grid-column:span 1; } +} + +/* ── Modal ── */ +.backdrop { position:fixed; inset:0; background:rgba(0,0,0,.72); z-index:100; display:none; align-items:center; justify-content:center; } +.backdrop.open { display:flex; } +.modal { background:var(--surface); border:1px solid var(--border); border-radius:14px; width:580px; max-width:95vw; max-height:90vh; overflow-y:auto; } +.modal.wide { width:740px; } +.m-hdr { padding:18px 22px 0; display:flex; align-items:center; justify-content:space-between; } +.m-hdr h3 { font-size:15px; font-weight:600; } +.m-close { background:transparent; border:none; color:var(--muted); font-size:18px; cursor:pointer; padding:4px; line-height:1; } +.m-close:hover { color:var(--text); } +.m-body { padding:18px 22px; } +.m-foot { padding:0 22px 18px; display:flex; gap:8px; justify-content:flex-end; } + +/* ── Steps ── */ +.steps { display:flex; margin-bottom:20px; } +.step { flex:1; text-align:center; padding:7px 3px; font-size:11px; color:var(--muted); border-bottom:2px solid var(--border); } +.step.active { color:var(--accent); border-bottom-color:var(--accent); font-weight:600; } +.step.done { color:var(--success); border-bottom-color:var(--success); } +.step-page { display:none; } +.step-page.active { display:block; } + +/* ── Log viewer ── */ +.log-box { background:var(--bg); border:1px solid var(--border); border-radius:8px; padding:12px; font-family:monospace; font-size:11px; line-height:1.5; max-height:380px; overflow-y:auto; white-space:pre-wrap; word-break:break-all; color:#94a3b8; } + +/* ── Misc ── */ +.notes { background:rgba(108,99,255,.07); border:1px solid rgba(108,99,255,.2); border-radius:8px; padding:12px 14px; font-size:13px; line-height:1.7; } +.notes li { margin-left:16px; } +.notes code { font-size:11px; background:rgba(0,0,0,.3); padding:1px 5px; border-radius:3px; } +.warn-box { background:rgba(245,158,11,.07); border:1px solid rgba(245,158,11,.25); border-radius:8px; padding:12px 14px; font-size:13px; color:var(--warn); } +.success-box { background:rgba(34,197,94,.07); border:1px solid rgba(34,197,94,.25); border-radius:8px; padding:12px 14px; font-size:13px; color:var(--success); } +.empty { text-align:center; color:var(--muted); padding:40px 0; font-size:14px; } +.tag { font-size:11px; padding:3px 9px; border-radius:20px; background:var(--border); color:var(--muted); display:inline-block; } +.tag-row { display:flex; flex-wrap:wrap; gap:6px; margin-top:6px; } +.tag-del { cursor:pointer; margin-left:4px; opacity:.6; } +.tag-del:hover { opacity:1; color:var(--danger); } +hr { border:none; border-top:1px solid var(--border); margin:14px 0; } +code { font-size:12px; background:rgba(0,0,0,.3); padding:2px 6px; border-radius:4px; color:#a78bfa; } + +/* Restart Banner */ +.restart-banner { display:none; background:rgba(245,158,11,.1); border:1px solid rgba(245,158,11,.3); border-radius:8px; padding:10px 16px; margin-bottom:16px; font-size:13px; color:var(--warn); align-items:center; gap:10px; flex-wrap:wrap; } +.restart-banner.show { display:flex; } + +/* Floating Restart Button */ +#pendingRestartBtn { display:none; position:fixed; bottom:24px; left:24px; z-index:400; } +#pendingRestartBtn button { padding:10px 20px; font-size:13px; border-radius:22px; box-shadow:0 4px 16px rgba(245,158,11,.45); } + +/* Auth page */ +.auth-card { background:var(--card); border:1px solid var(--border); border-radius:10px; padding:18px; margin-bottom:12px; } +.auth-prov-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:10px; margin-bottom:16px; } +.auth-prov-btn { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:12px 14px; cursor:pointer; text-align:left; transition:all .15s; } +.auth-prov-btn:hover { border-color:var(--accent); background:rgba(108,99,255,.06); } +.auth-prov-btn.selected { border-color:var(--accent); background:rgba(108,99,255,.1); } +.auth-prov-btn.configured { border-color:var(--success); } +.auth-prov-btn.configured .apb-name { color:var(--success); } +.auth-prov-btn .apb-name { font-size:13px; font-weight:600; margin-bottom:2px; } +.auth-prov-btn .apb-mode { font-size:11px; color:var(--muted); } +.copy-cmd-btn { background:var(--border); border:none; color:var(--text); border-radius:5px; padding:4px 10px; font-size:11px; cursor:pointer; margin-left:8px; } +.copy-cmd-btn:hover { background:#3a3f5c; } + +/* Channels */ +.ch-badge { font-size:11px; padding:2px 7px; border-radius:12px; } +.ch-tg { background:rgba(51,144,236,.15); color:#33a0ec; } +.ch-any { background:var(--border); color:var(--muted); } +.channels-toolbar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; } +.channels-filter-label { font-size:12px; color:var(--muted); } +.channels-filter { min-width:180px; max-width:260px; font-size:12px; padding:6px 10px; } +.routing-group { background:var(--card); border:1px solid var(--border); border-radius:10px; overflow:hidden; } +.routing-group-hdr { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:10px 12px; background:rgba(255,255,255,.02); border-bottom:1px solid var(--border); } +.routing-group-title { display:flex; align-items:center; gap:8px; flex-wrap:wrap; min-width:0; } +.routing-group-name { font-size:13px; font-weight:600; color:var(--text); } +.routing-group-count { font-size:11px; color:var(--muted); } +.routing-toggle { font-size:11px; padding:4px 10px; } +.routing-list { display:flex; flex-direction:column; } +.routing-list.collapsed { display:none; } +.routing-row { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:9px 12px; border-bottom:1px solid var(--border); } +.routing-row:last-child { border-bottom:none; } +.routing-row-main { display:flex; align-items:center; gap:8px; flex-wrap:wrap; font-size:12px; min-width:0; } +.routing-row-sub { font-size:11px; color:var(--muted); } + +/* NAS backup */ +.nas-step { background:var(--bg); border:1px solid var(--border); border-radius:8px; padding:14px; margin-bottom:12px; } +.nas-step-title { font-size:13px; font-weight:600; margin-bottom:10px; color:var(--accent); } + +/* ── CLI 终端面板 ── */ +#cliPanel { position:fixed; bottom:0; left:0; right:0; z-index:90; background:var(--surface); border-top:2px solid var(--border); display:none; flex-direction:column; box-shadow:0 -4px 24px rgba(0,0,0,.35); } +#cliPanel.open { display:flex; } +.cli-hdr { display:flex; align-items:center; gap:8px; padding:6px 14px; cursor:pointer; border-bottom:1px solid var(--border); user-select:none; background:var(--bg); } +.cli-hdr-title { font-size:13px; font-weight:600; flex:1; color:var(--text); } +.cli-hdr-actions { display:flex; gap:6px; } +.cli-output { font-family:'SF Mono',Menlo,Consolas,monospace; font-size:12px; line-height:1.65; background:#0d1117; color:#c9d1d9; padding:10px 14px; height:220px; overflow-y:auto; white-space:pre-wrap; word-break:break-all; } +.cli-output .cli-cmd-line { color:#58a6ff; font-weight:600; } +.cli-output .cli-done-ok { color:#3fb950; } +.cli-output .cli-done-err { color:#f85149; } +.cli-input-row { display:flex; gap:6px; padding:7px 10px; align-items:center; background:var(--surface); } +.cli-input-row select { font-size:12px; padding:5px 8px; max-width:190px; background:var(--bg); border:1px solid var(--border); border-radius:6px; color:var(--text); } +.cli-input-row input { flex:1; font-family:'SF Mono',Menlo,Consolas,monospace; font-size:12px; background:var(--bg); } +.cli-resize-handle { height:4px; background:var(--border); cursor:ns-resize; } +/* Health / security badge */ +#healthBadge { display:none; align-items:center; gap:5px; padding:4px 10px; border-radius:6px; font-size:12px; font-weight:600; cursor:default; position:relative; } +#healthBadge.warn { background:rgba(234,179,8,.15); border:1px solid rgba(234,179,8,.4); color:#eab308; } +#healthBadge.error { background:rgba(248,81,73,.15); border:1px solid rgba(248,81,73,.4); color:#f85149; } +#healthBadge.ok { background:rgba(63,185,80,.1); border:1px solid rgba(63,185,80,.3); color:#3fb950; display:flex; } +.health-tooltip { display:none; position:absolute; top:calc(100% + 8px); right:0; background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:12px 14px; min-width:320px; max-width:480px; z-index:300; box-shadow:0 8px 24px rgba(0,0,0,.5); font-size:12px; line-height:1.7; white-space:pre-wrap; word-break:break-word; color:var(--text); } +#healthBadge:hover .health-tooltip { display:block; } + +/* CLI nav tab button */ +.cli-nav-btn { margin-left:auto; padding:6px 16px; font-size:12px; font-weight:600; background:rgba(108,99,255,.15); border:1px solid rgba(108,99,255,.4); color:var(--accent); border-radius:6px; cursor:pointer; white-space:nowrap; transition:all .15s; } +.cli-nav-btn:hover,.cli-nav-btn.active { background:rgba(108,99,255,.32); border-color:var(--accent); } +/* Stop button */ +.btn-stop { background:rgba(248,81,73,.15); border:1px solid rgba(248,81,73,.4); color:#f85149; border-radius:6px; padding:6px 12px; font-size:12px; font-weight:600; cursor:pointer; white-space:nowrap; } +.btn-stop:hover { background:rgba(248,81,73,.3); } +/* Manage favs modal list */ +.fav-manage-row { display:flex; align-items:center; gap:6px; padding:7px 0; border-bottom:1px solid var(--border); } +.fav-manage-label { flex:1; font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } +.fav-manage-cmd { font-size:11px; color:var(--muted); font-family:monospace; max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }`; + +const MAIN_HTML_BODY = String.raw` + + + +
+ + +
+ +
v--
+ v-- + ocm v-- +
+
+ ⚠️ + +
+
+
+
+ +
+ + +
+ + + + +
+ +
+ ⚠️ 配置已修改,建议重启 Gateway 以确保生效。 + + + +
+ + +
+
+

Dashboard

+
+ + Auto-refresh +
+
+
Loading...
+
+

🚀 First-run checklist

+
    +
  • Confirm OCM is pointed at the correct OpenClaw directory.
  • +
  • Check this Dashboard first: make sure Gateway is running and the system looks healthy.
  • +
  • Open Agents / Routing and confirm the bindings you expect are actually there.
  • +
  • Use the built-in Terminal to run openclaw doctor once so you catch environment/auth issues early.
  • +
  • Before bigger edits, make a backup or check rollback so recovery is nearby if something goes wrong.
  • +
+
+
+
+

🖥️ System Info

+
Loading...
+
+
+

🤖 Agents

+
Loading...
+
+
+

🦀 Gateway

+
Loading...
+
+
+

💾 Storage

+
Loading...
+
+
+
+

📌 Telegram 使用说明(重要)

+
    +
  • OCM 主要面向 Telegram 场景:通过群组绑定 Agent,让每个 Agent 拥有独立 Workspace / SOUL.md / MEMORY.md。
  • +
  • 使用前请确保你已具备基本 OpenClaw 操作经验;OCM 主要负责可视化更新 openclaw.json,方便增删主 Agent 与 Sub-Agent,并支持多条 Agent 树。
  • +
  • BotFather 中请确认 Allow Groups = ON 且 Group Privacy = OFF,否则 Sub-Agent 可能无法入组或无法响应。
  • +
+
⚠️ 强烈建议:每个 Agent 群组只保留你自己和该 Agent(或其 Sub-Agent)。不要邀请其他人,把每个组当作私聊空间。
+
+
+ + +
+
+ + +
+
+

+ Add Agent: create a top-level agent (a long-lived role). Use this for a new domain like ops/travel/finance, or when you want fully isolated workflows. +
Add Sub-Agent: create a child agent under a parent. Sub-agents use the parent agent’s Telegram bot, but each sub-agent has its own workspace, SOUL.md (persona), and MEMORY.md (memory) — preventing context mixing, improving focus, and saving tokens. +

+
+
No Agents
+
+
+ + +
+
+

Routing Bindings

+
+ Agent + + +
+
+

Advanced routing rules for agent-channel/group bindings and priority order. Use Agents page for daily add/remove workflows.

+

When to use Remove binding: if you bound the wrong agent/chat, the group was recreated/migrated (new peer id), you have duplicates/conflicts, you want to decommission a project sub-agent, or you need to temporarily disconnect routing for safety.

+
加载中...
+
+ + +
+

模型管理

+

模型由 openclaw onboard 注册,此处管理主模型和 Fallback 链。

+

模型下拉仅显示 openclaw models list 返回的模型。

+ + +
+
默认主模型primary
+
+ + + +
+
+
+
+ Fallback 链 + +
+
+
+
+

认证配置

+

点击 Provider 查看认证步骤。认证需在终端完成,或使用下方 CLI 终端。

+
+ +

已配置认证

+
加载中...
+
+ + +
+
+

使用统计

+ +
+
+
Input Tokens
--
+
Output Tokens
--
+
Cache Read
--
+
Estimated Cost
--
+
Requests
--
+
+

By Day

+
+

By Model

+
No data
+

By Agent

+
No data
+
+ + +
+
+

计划任务

+ +
+

管理与 OpenClaw 相关的 crontab 计划任务。

+
加载中...
+
+ +
+ + +
+ +
+ + +
+
+
+ ⌨️ CLI 终端 +
+ + + + +
+
+
── 终端就绪,等待命令 ──
+
+ + + + + +
+
+ +
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + + +
+
+ + +
+
+ + +
+
+ + +
`; + +const MAIN_HTML_SCRIPT = `// ── i18n ────────────────────────────────────────────────────── +const I18N = { + zh: { + 'tab.dashboard':'🏠 Dashboard', + 'tab.agents':'🤖 Agents','tab.channels':'🧭 路由','tab.models':'🧠 模型与认证','tab.auth':'🔑 认证', + 'tab.stats':'📊 Stats','tab.cron':'⏰ Cron', + 'agents.title':'Agents','agents.new':'+ 新建 Subagent', + 'channels.title':'路由绑定','channels.add':'+ 添加绑定','channels.hint':'用于高级路由管理:维护 Agent 与频道/群组绑定及优先级顺序。日常增减 Agent 请在 Agents 页面操作。', + 'channels.removeHint':'如果需要更换 Telegram 群或 Discord thread:请先在本页删除旧绑定,然后用新的 ID 重新绑定。也可用于修正绑定错误、群重建(ID 变化)、重复/冲突或临时断开路由。', + 'channels.filterLabel':'Agent','channels.filterAll':'全部 Agent','channels.emptyFiltered':'当前 Agent 下暂无绑定', + 'channels.countSuffix':'条绑定','channels.collapse':'折叠','channels.expand':'展开', + 'models.title':'模型管理','models.primary':'默认主模型','models.fallback':'Fallback 链', + 'models.hint':'模型由 openclaw onboard 注册,此处管理主模型和 Fallback 链。', + 'models.onlyCliHint':'模型下拉仅显示 openclaw models list 返回的模型。', + 'models.modelListErr':'读取 openclaw models list 失败:', + 'models.modelListEmpty':'未从 openclaw models list 解析到模型。请先运行 openclaw onboard 并确认模型可用。', + 'dash.firstRunTitle':'🚀 首次使用检查清单', + 'dash.firstRun1':'先确认 OCM 指向的是正确的 OpenClaw 数据目录。', + 'dash.firstRun2':'先看 Dashboard:确认 Gateway 正在运行,系统状态看起来正常。', + 'dash.firstRun3':'打开 Agents / Routing,确认你期望的绑定确实已经存在。', + 'dash.firstRun4':'用内置终端先运行一次 openclaw doctor,尽早发现环境或认证问题。', + 'dash.firstRun5':'在做较大改动前,先做一次备份或确认回滚入口,出问题时更容易恢复。', + 'dash.noticeTitle':'📌 Telegram 使用说明(重要)', + 'dash.notice1':'OCM 主要面向 Telegram:通过群组绑定 Agent,让每个 Agent 拥有独立 Workspace / SOUL.md / MEMORY.md。', + 'dash.notice2':'请确保你已有基本 OpenClaw 操作经验。OCM 主要负责可视化更新 openclaw.json,方便增删主 Agent 与 Sub-Agent,并支持多条 Agent 树。', + 'dash.notice3':'BotFather 中请确认 Allow Groups = ON 且 Group Privacy = OFF,否则 Sub-Agent 可能无法入组或无法响应。', + 'dash.noticeWarn':'⚠️ 强烈建议:每个 Agent 群组只保留你自己和该 Agent(或其 Sub-Agent)。不要邀请其他人,把每个组当作私聊空间。', + 'auth.title':'认证配置','auth.configured':'已配置认证', + 'auth.guide':'点击 Provider 查看认证步骤。认证需在终端完成,或使用下方 CLI 终端。', + 'auth.step1':'1. 获取 API Key','auth.step2':'2. 在终端运行以下命令','auth.step3':'3. 按提示粘贴 API Key 并回车', + 'auth.onboard':'完成认证后,运行 openclaw onboard 注册可用模型', + 'stats.title':'使用统计','stats.input':'输入 Token','stats.output':'输出 Token', + 'stats.cost':'估计成本','stats.requests':'请求数','stats.byModel':'按模型','stats.byDay':'按日期','stats.noData':'暂无数据(未找到会话记录)', + 'cron.title':'计划任务','cron.add':'+ 添加任务','cron.hint':'管理与 OpenClaw 相关的 crontab 计划任务。', + 'cron.addTitle':'添加计划任务','cron.schedule':'Cron 表达式','cron.command':'命令','cron.label':'标签', + 'cron.enabled':'启用','cron.disabled':'已禁用','cron.run':'▶ 运行','cron.edit':'编辑','cron.empty':'暂无 OpenClaw 相关的计划任务', + 'ws.title':'📂 Workspace 文件','ws.edit':'✏️ 编辑','ws.save':'💾 保存','ws.cancel':'取消编辑','ws.saved':'文件已保存','ws.large':'(文件过大,无法预览)', + 'menu.ops':'操作','menu.restart':'重启 Gateway','menu.logs':'实时日志','menu.backup':'手动备份配置', + 'menu.rollback':'查看备份 / 回滚','menu.nas':'NAS 备份设置','menu.doctor':'健康检查', + 'menu.opendir':'打开配置目录','menu.switchdir':'切换 OpenClaw 目录', + 'actions.logsTitle':'📋 Gateway 日志','actions.refresh':'🔄 刷新','actions.autoRefresh':'自动刷新(2s)', + 'actions.outputTitle':'输出','actions.rollbackTitle':'📦 备份 / 回滚','actions.rollbackHint':'选择一个备份文件恢复。恢复前会自动保存当前配置。', + 'actions.restoreThis':'⏪ 恢复此备份','actions.setupTitle':'⚙️ 切换 OpenClaw 目录','actions.setupLabel':'OpenClaw 数据目录路径', + 'actions.setupPlaceholder':'~/.openclaw 或绝对路径','actions.setupHint':'需要包含 openclaw.json 的文件夹。更换后页面自动刷新。','actions.setupConfirm':'确认切换', + 'actions.restartTitle':'重启 Gateway','actions.doctorTitle':'健康检查 (doctor)','actions.running':'执行中...','actions.noOutput':'(无输出)', + 'actions.restartSent':'重启命令已发送。','actions.restartOk':'Gateway 已重启','actions.restartFail':'重启失败: ', + 'actions.restartManualHint':'请在终端手动运行:\\nopenclaw gateway restart', + 'actions.backupSaved':'备份已保存: ','actions.backupFail':'备份失败: ', + 'actions.logEmpty':'(空)','actions.logLoadFail':'加载失败: ', + 'actions.noBackups':'暂无备份文件','actions.loadFailed':'加载失败', + 'actions.restoreConfirm':'确认将配置恢复为:\\n{filename}\\n\\n当前配置将先被自动备份。继续?', + 'actions.restored':'已恢复 ','actions.restoreFail':'恢复失败: ', + 'actions.setupEmpty':'请填写路径','actions.setupSwitching':'目录已切换,正在刷新...','actions.setupInvalid':'路径无效','actions.setupReqFail':'请求失败: ', + 'common.loading':'加载中...','common.close':'关闭','status.connecting':'连接中...','status.configReadError':'无法读取配置', + 'btn.save':'保存','btn.delete':'删除','btn.cancel':'取消','btn.add':'添加','btn.remove':'移除', + 'agents.addAgent':'+ Add Agent','agents.addSub':'+ Add Sub-Agent','agents.hint':'Add Agent: create a top-level agent (a long-lived role) for a new domain. Add Sub-Agent: create a child under a parent; sub-agents use the parent Telegram bot but have their own workspace + SOUL.md + MEMORY.md, preventing context mixing, improving focus, and saving tokens.', + 'agents.addAgentTitle':'Add Agent (Main Bot)','agents.addSubTitle':'Add Sub-Agent', + 'agents.botToken':'Bot Token','agents.botName':'Bot Name','agents.botNamePh':'My Bot', + 'agents.addAgentSubmit':'Create Agent','agents.addSubSubmit':'Create Sub-Agent', + 'agents.errToken':'Please enter Bot Token','agents.errName':'Please enter Bot name', + 'agents.agentCreated':'✅ Agent created', + 'guide.title':'📖 Setup Guide','guide.agent.s1':'Open Telegram, search for @BotFather', + 'guide.agent.s2':'Send /newbot and follow prompts to name your Bot', + 'guide.agent.s3':'Copy the Bot Token from BotFather and paste below', + 'guide.agent.s4':'In BotFather: /mybots → select your bot → Bot SettingsAllow GroupsTurn on (allow the bot to join groups)', + 'guide.agent.s5':'Send /setprivacy → select your Bot → click Disable (allow Bot to read group messages)', + 'guide.sub.s1':'在 BotFather 发送 /newbot 创建新 Bot,获取 Bot Token', + 'guide.sub.s2':'在 BotFather 发送 /mybots → 选择 Bot → Bot SettingsGroup PrivacyTurn off', + 'guide.sub.s3':'在 Telegram 创建新群组,将 Bot 加入群组(不要加其他人)', + 'guide.sub.s4':'在群内发一条消息,从 gateway 日志中找到 peer.id(负数)', + 'guide.sub.s5':'填写下方表单创建 Sub-Agent', + 'guide.sub.warn':'⚠️ 安全提示:请勿将其他人加入此群组,只有你和 Bot 应在群内。否则其他人也能与 Bot 对话并产生 API 费用。', + 'guide.agent.discord.s1':'Discord 主 Agent:创建/选择一个专用 Channel(建议私密频道)。', + 'guide.agent.discord.s2':'右键该 Channel → Copy Link,粘贴到表单里(OCM 自动解析 channelId,可选 guildId)。', + 'guide.agent.discord.s3':'建议:一个 Channel 只绑定一个主 Agent,避免上下文串台。', + 'guide.sub.discord.s1':'Discord Sub-Agent:在对应主 Channel 下新建一个 Thread(强烈推荐每个任务一个 thread)。', + 'guide.sub.discord.s2':'如果是 Private Thread:需要把 Bot/Agent 拉进该 thread(否则读不到消息)。', + 'guide.sub.discord.s3':'右键该 Thread → Copy Link,粘贴到表单里(OCM 自动解析 threadId)。', + 'guide.sub.discord.warn':'⚠️ 约定:主 Agent 绑定 Channel;Sub-Agent 绑定 Thread。不要把 Sub-Agent 绑到普通 Channel。', + 'wiz.telegramId':'你的 Telegram User ID','wiz.telegramIdHint':'💡 可通过 @userinfobot 获取,填写后自动配置 allowFrom 白名单','wiz.telegramIdPh':'例如: 123456789', + 'wiz.channel':'渠道', + 'wiz.discordChannel':'Discord 频道链接/ID', + 'wiz.discordChannelHint':'建议:右键频道 → Copy Link,粘贴后自动解析 channelId(可选 guildId)。', + 'wiz.discordThread':'Discord Thread 链接/ID(仅 Thread)', + 'wiz.discordThreadHint':'建议:在主频道内创建 thread → 右键 thread → Copy Link,粘贴后自动解析 threadId。', + 'agents.empty':'暂无 Agent','agents.main':'主 Agent','agents.bound':'已绑群', + 'agents.saveModel':'保存模型','agents.viewFiles':'查看文件', + 'agents.defaultModel':'使用全局默认','agents.custom':'自定义','agents.noModel':'默认', + 'channels.empty':'暂无绑定配置','channels.matchAll':'📡 匹配所有', + 'channels.bindIdx':'绑定索引: #','channels.delBinding':'删除绑定', + 'banner.modified':'配置已修改,建议重启 Gateway 以确保生效。', + 'banner.restart':'立即重启','banner.later':'稍后','banner.dismiss':'忽略', + 'floating.restart':'重启 Gateway', + 'wiz.title':'新建 Subagent', + 'wiz.s1':'1 基本信息','wiz.s2':'2 模型','wiz.s3':'3 性格&记忆','wiz.s4':'4 确认', + 'wiz.groupId':'Telegram 群组 ID','wiz.groupHint':'💡 在群内发一条消息,在网关终端日志里找 peer.id(负数)', + 'wiz.agentId':'Agent ID','wiz.agentIdHint':'(纯英文)','wiz.agentIdPh':'my_bot', + 'wiz.displayName':'显示名称','wiz.displayNamePh':'我的助手', + 'wiz.workspace':'Workspace 文件夹','wiz.workspaceHint':'(留空=Agent ID)','wiz.workspacePh':'自动', + 'wiz.purpose':'Agent 用途描述','wiz.purposePh':'例如:专注于 Linux 和网络运维,帮助群成员快速解决技术故障。', + 'wiz.model':'选择模型','wiz.modelHint':'(不选则沿用全局默认)', + 'wiz.soul':'性格关键词','wiz.soulHint':'(逗号分隔,选填)','wiz.soulPh':'幽默、直接、有条理...', + 'wiz.soulTip':'留空则使用默认成长型提示词(推荐)', + 'wiz.memory':'初始记忆','wiz.memoryHint':'(MEMORY.md,选填)','wiz.memoryPh':'例如:群组主要用中文交流。用户偏好简洁回复。', + 'wiz.preview':'即将创建:','wiz.group':'群组','wiz.modelLabel':'模型','wiz.globalDefault':'全局默认', + 'wiz.soulYes':'含性格关键词','wiz.soulDefault':'默认成长型提示词(推荐)', + 'wiz.autoBackup':'自动备份当前 openclaw.json ✓', + 'wiz.back':'← 上一步','wiz.next':'下一步 →','wiz.confirm':'✅ 确认创建','wiz.creating':'创建中...', + 'wiz.created':'✅ 已创建', + 'wiz.errGroupId':'请填写群组 ID','wiz.errAgentId':'请填写 Agent ID', + 'wiz.errIdFormat':'只能含英文/数字/_ /-','wiz.errIdReserved':'"main" 是保留 ID','wiz.errIdDup':'该 ID 已存在', + 'ch.title':'添加 Channel 绑定','ch.agent':'Agent','ch.channelType':'频道类型', + 'ch.telegram':'Telegram','ch.anyChannel':'任意(不限频道)', + 'ch.peerKind':'Peer 类型','ch.group':'群组 (group)','ch.private':'私聊 (private)','ch.any':'任意', + 'ch.peerId':'Peer ID','ch.peerIdHint':'(群组 ID 或用户 ID)','ch.peerIdTip':'留空则匹配所有对应类型的 Peer', + 'ch.submit':'添加绑定', + 'l.sub':'选择运行模式', + 'cli.open':'⌨️ 终端','cli.title':'⌨️ CLI 终端','cli.copy':'复制命令','cli.clear':'清空','cli.collapse':'▼ 收起', + 'cli.ready':'── 终端就绪,等待命令 ──','cli.cleared':'── 已清空 ──', + 'cli.presets':'── 常用命令 ──','cli.builtins':'内置命令','cli.favs':'我的收藏', + 'cli.run':'▶ 执行','cli.stop':'■ 停止','cli.star':'⭐','cli.manage':'管理', + 'cli.placeholder':'输入命令(如 openclaw doctor)', + 'cli.favprompt':'给这条命令起个名字(留空则使用命令本身):', + 'cli.fav.dup':'该命令已在收藏里了','cli.fav.empty':'请先输入命令', + 'cli.fav.saved':'已收藏','cli.manage.title':'管理收藏命令', + 'nas.title':'🌐 远端备份', + 'nas.host':'主机 / IP','nas.user':'用户名','nas.authLabel':'认证方式', + 'nas.authPw':'密码','nas.pwLabel':'密码','nas.pwHint':'不存储,仅用于本次操作', + 'nas.keyLabel':'SSH Key 路径','nas.genKey':'生成 Key', + 'nas.pubkeyHint':'公钥(复制到 NAS 的 authorized_keys):', + 'nas.remotePath':'远端备份路径', + 'nas.compat':'兼容模式(添加旧版 SSH 加密方案,适用于旧款 NAS / 旧服务器)', + 'nas.content':'备份内容','nas.full':'全量(整个 .openclaw)', + 'nas.essential':'仅重要数据(配置+Key+记忆,无日志/历史)', + 'nas.btnTest':'🔌 测试连接','nas.btnNow':'💾 立即备份', + 'nas.btnCron':'⏰ 定时备份','nas.btnClose':'关闭', + 'nas.cronTime':'每日备份时间','nas.btnSaveCron':'💾 保存定时计划', + 'nas.testing':'连接测试中...','nas.testOk':'✅ 连接成功','nas.testFail':'❌ 连接失败: ', + 'nas.backing':'备份中,请稍候...','nas.backupOk':'✅ 备份完成: ','nas.backupFail':'❌ 备份失败: ', + 'nas.keyGenOk':'SSH Key 已生成','nas.keyGenFail':'生成失败: ', + 'nas.cronOk':'✅ 定时备份已设置 ','nas.cronToast':'定时备份已配置', + 'nas.backupToast':'NAS 备份完成','nas.errNoHost':'请填写主机和用户名', + }, + en: { + 'tab.dashboard':'🏠 Dashboard', + 'tab.agents':'🤖 Agents','tab.channels':'🧭 Routing','tab.models':'🧠 Models & Auth','tab.auth':'🔑 Auth', + 'tab.stats':'📊 Stats','tab.cron':'⏰ Cron', + 'agents.title':'Agents','agents.new':'+ New Subagent', + 'channels.title':'Routing Bindings','channels.add':'+ Add Binding','channels.hint':'Advanced routing rules for agent-channel/group bindings and priority order. Use Agents page for daily add/remove workflows.','channels.removeHint':'When to use Remove binding: if you need to switch to a new Telegram group / Discord thread, remove the old binding here, then add a new binding with the new ID. Also use this for wrong bindings, recreated groups (new id), duplicates/conflicts, decommissioning, or temporary disconnect.', + 'channels.filterLabel':'Agent','channels.filterAll':'All Agents','channels.emptyFiltered':'No bindings for selected agent', + 'channels.countSuffix':'bindings','channels.collapse':'Collapse','channels.expand':'Expand', + 'models.title':'Model Management','models.primary':'Default Primary Model','models.fallback':'Fallback Chain', + 'models.hint':'Models are registered via openclaw onboard. Manage primary model and fallback chain here.', + 'models.onlyCliHint':'Model dropdowns only show IDs returned by openclaw models list.', + 'models.modelListErr':'Failed to load openclaw models list: ', + 'models.modelListEmpty':'No model IDs were parsed from openclaw models list. Run openclaw onboard first.', + 'dash.firstRunTitle':'🚀 First-run checklist', + 'dash.firstRun1':'Confirm OCM is pointed at the correct OpenClaw directory.', + 'dash.firstRun2':'Check Dashboard first: make sure Gateway is running and the system looks healthy.', + 'dash.firstRun3':'Open Agents / Routing and confirm the bindings you expect are actually there.', + 'dash.firstRun4':'Use the built-in Terminal to run openclaw doctor once so you catch environment or auth issues early.', + 'dash.firstRun5':'Before bigger edits, make a backup or check rollback so recovery is nearby if something goes wrong.', + 'dash.noticeTitle':'📌 Telegram Usage Notes (Important)', + 'dash.notice1':'OCM is primarily for Telegram workflows: bind agents to groups so each agent has isolated Workspace / SOUL.md / MEMORY.md.', + 'dash.notice2':'Basic OpenClaw CLI experience is required. OCM focuses on visual openclaw.json updates for easier main-agent/sub-agent management and multiple agent trees.', + 'dash.notice3':'In BotFather, keep Allow Groups = ON and Group Privacy = OFF; otherwise sub-agents may fail to join or respond.', + 'dash.noticeWarn':'⚠️ Strong recommendation: each agent group should include only you and that agent (or its sub-agents). Treat each group like a private chat.', + 'auth.title':'Auth Config','auth.configured':'Configured Auth', + 'auth.guide':'Click a Provider for setup instructions. Auth is done in terminal or via the CLI panel below.', + 'auth.step1':'1. Get API Key','auth.step2':'2. Run the command below in terminal','auth.step3':'3. Paste your API Key when prompted', + 'auth.onboard':'After auth, run openclaw onboard to register available models', + 'stats.title':'Usage Stats','stats.input':'Input Tokens','stats.output':'Output Tokens', + 'stats.cost':'Estimated Cost','stats.requests':'Requests','stats.byModel':'By Model','stats.byDay':'By Day','stats.noData':'No data (no session records found)', + 'cron.title':'Scheduled Tasks','cron.add':'+ Add Task','cron.hint':'Manage OpenClaw-related crontab scheduled tasks.', + 'cron.addTitle':'Add Scheduled Task','cron.schedule':'Cron Expression','cron.command':'Command','cron.label':'Label', + 'cron.enabled':'Enabled','cron.disabled':'Disabled','cron.run':'▶ Run','cron.edit':'Edit','cron.empty':'No OpenClaw-related cron tasks', + 'ws.title':'📂 Workspace Files','ws.edit':'✏️ Edit','ws.save':'💾 Save','ws.cancel':'Cancel Edit','ws.saved':'File saved','ws.large':'(File too large to preview)', + 'menu.ops':'Actions','menu.restart':'Restart Gateway','menu.logs':'Live Logs','menu.backup':'Manual Backup', + 'menu.rollback':'Backups / Rollback','menu.nas':'NAS Backup Setup','menu.doctor':'Health Check', + 'menu.opendir':'Open Config Dir','menu.switchdir':'Switch OpenClaw Dir', + 'actions.logsTitle':'📋 Gateway Logs','actions.refresh':'🔄 Refresh','actions.autoRefresh':'Auto refresh (2s)', + 'actions.outputTitle':'Output','actions.rollbackTitle':'📦 Backups / Rollback','actions.rollbackHint':'Select a backup file to restore. Current config will be auto-backed up first.', + 'actions.restoreThis':'⏪ Restore this backup','actions.setupTitle':'⚙️ Switch OpenClaw Dir','actions.setupLabel':'OpenClaw data directory path', + 'actions.setupPlaceholder':'~/.openclaw or absolute path','actions.setupHint':'Folder must contain openclaw.json. Page auto-refreshes after switching.','actions.setupConfirm':'Confirm Switch', + 'actions.restartTitle':'Restart Gateway','actions.doctorTitle':'Health Check (doctor)','actions.running':'Running...','actions.noOutput':'(no output)', + 'actions.restartSent':'Restart command sent.','actions.restartOk':'Gateway restarted','actions.restartFail':'Restart failed: ', + 'actions.restartManualHint':'Please run manually in terminal:\\nopenclaw gateway restart', + 'actions.backupSaved':'Backup saved: ','actions.backupFail':'Backup failed: ', + 'actions.logEmpty':'(empty)','actions.logLoadFail':'Load failed: ', + 'actions.noBackups':'No backup files','actions.loadFailed':'Load failed', + 'actions.restoreConfirm':'Restore config to:\\n{filename}\\n\\nCurrent config will be auto-backed up first. Continue?', + 'actions.restored':'Restored ','actions.restoreFail':'Restore failed: ', + 'actions.setupEmpty':'Please enter a path','actions.setupSwitching':'Directory switched, refreshing...','actions.setupInvalid':'Invalid path','actions.setupReqFail':'Request failed: ', + 'common.loading':'Loading...','common.close':'Close','status.connecting':'Connecting...','status.configReadError':'Unable to read config', + 'btn.save':'Save','btn.delete':'Delete','btn.cancel':'Cancel','btn.add':'Add','btn.remove':'Remove', + 'agents.addAgent':'+ Add Agent','agents.addSub':'+ Add Sub-Agent','agents.hint':'Add Agent: create a top-level agent (a long-lived role) for a new domain. Add Sub-Agent: create a child under a parent; sub-agents use the parent Telegram bot but have their own workspace + SOUL.md + MEMORY.md, preventing context mixing, improving focus, and saving tokens.', + 'agents.addAgentTitle':'Add Agent (Main Bot)','agents.addSubTitle':'Add Sub-Agent', + 'agents.botToken':'Bot Token','agents.botName':'Bot Name','agents.botNamePh':'My Bot', + 'agents.addAgentSubmit':'Create Agent','agents.addSubSubmit':'Create Sub-Agent', + 'agents.errToken':'Please enter Bot Token','agents.errName':'Please enter Bot name', + 'agents.agentCreated':'✅ Agent created', + 'guide.title':'📖 Setup Guide','guide.agent.s1':'Open Telegram, search for @BotFather', + 'guide.agent.s2':'Send /newbot and follow prompts to name your Bot', + 'guide.agent.s3':'Copy the Bot Token from BotFather and paste below', + 'guide.agent.s4':'In BotFather: /mybots → select your bot → Bot SettingsAllow GroupsTurn on (allow the bot to join groups)', + 'guide.agent.s5':'Send /setprivacy → select your Bot → click Disable (allow Bot to read group messages)', + 'guide.sub.s1':'Send /newbot to BotFather to create a new Bot and get the Bot Token', + 'guide.sub.s2':'Send /mybots to BotFather → select your Bot → Bot SettingsGroup PrivacyTurn off', + 'guide.sub.s3':'Create a new Telegram group, add the Bot to the group (do NOT add anyone else)', + 'guide.sub.s4':'Send a message in the group, find peer.id (negative number) in gateway logs', + 'guide.sub.s5':'Fill in the form below to create the Sub-Agent', + 'guide.sub.warn':'⚠️ Security: Do NOT add other people to this group. Only you and the Bot should be in the group. Otherwise others can chat with the Bot and incur API costs.', + 'guide.agent.discord.s1':'Discord main agent: create/select a dedicated channel (private recommended).', + 'guide.agent.discord.s2':'Right-click the channel → Copy Link, paste it into the form (OCM auto-parses channelId; optional guildId).', + 'guide.agent.discord.s3':'Recommended: bind one main agent per channel to avoid context bleed.', + 'guide.sub.discord.s1':'Discord sub-agent: create a thread under the main channel (one thread per task recommended).', + 'guide.sub.discord.s2':'If it is a private thread, add the bot/agent to the thread (otherwise it cannot read messages).', + 'guide.sub.discord.s3':'Right-click the thread → Copy Link, paste into the form (OCM auto-parses the threadId).', + 'guide.sub.discord.warn':'Important: main agents bind to channels; sub-agents bind to threads. Do not bind sub-agents to normal channels.', + 'wiz.telegramId':'Your Telegram User ID','wiz.telegramIdHint':'💡 Get it from @userinfobot — auto-configures allowFrom whitelist','wiz.telegramIdPh':'e.g. 123456789', + 'wiz.channel':'Channel', + 'wiz.discordChannel':'Discord channel link/ID', + 'wiz.discordChannelHint':'Tip: right-click channel → Copy Link. Paste to auto-parse channelId (optional guildId).', + 'wiz.discordThread':'Discord thread link/ID (thread only)', + 'wiz.discordThreadHint':'Tip: create a thread under your main channel → Copy Link. Paste to auto-parse threadId.', + + 'agents.empty':'No Agents','agents.main':'Main Agent','agents.bound':'Bound', + 'agents.saveModel':'Save Model','agents.viewFiles':'View Files', + 'agents.defaultModel':'Use Global Default','agents.custom':'custom','agents.noModel':'Default', + 'channels.empty':'No bindings configured','channels.matchAll':'📡 Match All', + 'channels.bindIdx':'Binding Index: #','channels.delBinding':'Remove Binding', + 'banner.modified':'Config modified. Restart Gateway to apply changes.', + 'banner.restart':'Restart Now','banner.later':'Later','banner.dismiss':'Dismiss', + 'floating.restart':'Restart Gateway', + 'wiz.title':'New Subagent', + 'wiz.s1':'1 Basic Info','wiz.s2':'2 Model','wiz.s3':'3 Personality & Memory','wiz.s4':'4 Confirm', + 'wiz.groupId':'Telegram Group ID','wiz.groupHint':'💡 Send a message in the group, find peer.id (negative number) in gateway logs', + 'wiz.agentId':'Agent ID','wiz.agentIdHint':'(English only)','wiz.agentIdPh':'my_bot', + 'wiz.displayName':'Display Name','wiz.displayNamePh':'My Assistant', + 'wiz.workspace':'Workspace Folder','wiz.workspaceHint':'(Leave blank = Agent ID)','wiz.workspacePh':'auto', + 'wiz.purpose':'Agent Purpose','wiz.purposePh':'e.g. Linux & networking ops, help group members troubleshoot quickly.', + 'wiz.model':'Select Model','wiz.modelHint':'(Leave blank for global default)', + 'wiz.soul':'Personality Keywords','wiz.soulHint':'(comma-separated, optional)','wiz.soulPh':'humorous, direct, organized...', + 'wiz.soulTip':'Leave blank for default growth prompt (recommended)', + 'wiz.memory':'Initial Memory','wiz.memoryHint':'(MEMORY.md, optional)','wiz.memoryPh':'e.g. Group mainly uses English. Users prefer concise replies.', + 'wiz.preview':'About to create:','wiz.group':'Group','wiz.modelLabel':'Model','wiz.globalDefault':'Global Default', + 'wiz.soulYes':'With personality keywords','wiz.soulDefault':'Default growth prompt (recommended)', + 'wiz.autoBackup':'Auto-backup current openclaw.json ✓', + 'wiz.back':'← Back','wiz.next':'Next →','wiz.confirm':'✅ Confirm','wiz.creating':'Creating...', + 'wiz.created':'✅ Created', + 'wiz.errGroupId':'Please enter Group ID','wiz.errAgentId':'Please enter Agent ID', + 'wiz.errIdFormat':'Only letters/numbers/_ /- allowed','wiz.errIdReserved':'"main" is a reserved ID','wiz.errIdDup':'This ID already exists', + 'ch.title':'Add Channel Binding','ch.agent':'Agent','ch.channelType':'Channel Type', + 'ch.telegram':'Telegram','ch.anyChannel':'Any (no restriction)', + 'ch.peerKind':'Peer Type','ch.group':'Group','ch.private':'Private','ch.any':'Any', + 'ch.peerId':'Peer ID','ch.peerIdHint':'(Group ID or User ID)','ch.peerIdTip':'Leave blank to match all peers of this type', + 'ch.submit':'Add Binding', + 'l.sub':'Select Mode', + 'cli.open':'⌨️ Terminal','cli.title':'⌨️ CLI Terminal','cli.copy':'Copy cmd','cli.clear':'Clear','cli.collapse':'▼ Collapse', + 'cli.ready':'── Terminal Ready ──','cli.cleared':'── Cleared ──', + 'cli.presets':'── Presets ──','cli.builtins':'Built-in','cli.favs':'My Favorites', + 'cli.run':'▶ Run','cli.stop':'■ Stop','cli.star':'⭐','cli.manage':'Manage', + 'cli.placeholder':'Enter command (e.g. openclaw doctor)', + 'cli.favprompt':'Name for this command (leave blank to use the command itself):', + 'cli.fav.dup':'Command already in favorites','cli.fav.empty':'Please enter a command first', + 'cli.fav.saved':'Saved','cli.manage.title':'Manage Saved Commands', + 'nas.title':'🌐 Remote Backup', + 'nas.host':'Host / IP','nas.user':'Username','nas.authLabel':'Auth Method', + 'nas.authPw':'Password','nas.pwLabel':'Password','nas.pwHint':'Not stored, used for this operation only', + 'nas.keyLabel':'SSH Key Path','nas.genKey':'Generate Key', + 'nas.pubkeyHint':'Public key (copy to NAS authorized_keys):', + 'nas.remotePath':'Remote Backup Path', + 'nas.compat':'Compatibility Mode (add legacy SSH ciphers — for old NAS / servers)', + 'nas.content':'Backup Content','nas.full':'Full (~entire .openclaw)', + 'nas.essential':'Essential (config+keys+memory, no logs/history)', + 'nas.btnTest':'🔌 Test Connection','nas.btnNow':'💾 Backup Now', + 'nas.btnCron':'⏰ Schedule','nas.btnClose':'Close', + 'nas.cronTime':'Daily Backup Time','nas.btnSaveCron':'💾 Save Schedule', + 'nas.testing':'Testing connection...','nas.testOk':'✅ Connected','nas.testFail':'❌ Connection failed: ', + 'nas.backing':'Backing up, please wait...','nas.backupOk':'✅ Backup complete: ','nas.backupFail':'❌ Backup failed: ', + 'nas.keyGenOk':'SSH key generated','nas.keyGenFail':'Key generation failed: ', + 'nas.cronOk':'✅ Schedule saved ','nas.cronToast':'Scheduled backup configured', + 'nas.backupToast':'NAS backup complete','nas.errNoHost':'Please enter host and username', + }, +}; +// 安全 localStorage 工具(提前定义,防止隐私模式 / 存储禁用时整页崩溃) +const LS = { + get(k, def='') { try { return localStorage.getItem(k) ?? def; } catch(e) { return def; } }, + set(k, v) { try { localStorage.setItem(k, v); } catch(e) {} }, + del(k) { try { localStorage.removeItem(k); } catch(e) {} }, +}; +// Default UI language. Users can toggle and we persist in localStorage. +// Prefer English by default for broader sharing/promotions. +let lang = LS.get('ocm_lang', 'en'); +function t(k) { return I18N[lang][k] || I18N.zh[k] || k; } +function applyLang() { + document.documentElement.lang = lang === 'zh' ? 'zh-CN' : 'en'; + const st = document.getElementById('statusTxt'); + if(st && (!st.textContent || st.textContent === '连接中...' || st.textContent === 'Connecting...')) st.textContent = t('status.connecting'); + document.querySelectorAll('[data-i18n]').forEach(el => { el.textContent = t(el.dataset.i18n); }); + document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { el.placeholder = t(el.dataset.i18nPlaceholder); }); + const ltb = document.getElementById('langToggleBtn'); + if(ltb) ltb.textContent = lang === 'zh' ? 'EN' : 'ZH'; + // Update CLI elements that need JS-side refresh + const ci=document.getElementById('cliInput'); + if(ci) ci.placeholder=t('cli.placeholder'); + const rm=document.getElementById('cliReadyMsg'); + if(rm) rm.textContent=t('cli.ready'); + // Re-render model/auth cards so Remove button text updates + try{ renderModels(); }catch(_){} + try{ renderAuth(); }catch(_){} + try{ renderChannels(); }catch(_){} + // Rebuild presets dropdown if CLI is open + if(document.getElementById('cliPanel')&&document.getElementById('cliPanel').classList.contains('open')){ + buildCliPresets(); + } +} +function toggleLang() { + lang = lang === 'zh' ? 'en' : 'zh'; + LS.set('ocm_lang', lang); + applyLang(); +} + +// ── Landing Page ────────────────────────────────────────────── +const LANDING_TEXT = { + zh: { + sub: '选择运行模式', + activeBadge: '可用', soonBadge: '敬请期待', + subTitle: 'Sub-agent 模式', + subDesc: '通过主 Bot 账号绑定多个子 Agent 到不同群组,共用同一个 Token。', + subReqs: '需要:
• Telegram Bot Token(主账号)
• 各群组的 Group ID
• 已安装 OpenClaw', + multiTitle: 'Multi-agent 模式', + multiDesc: '为每个场景创建完全独立的 Bot,彻底隔离配置与对话历史。', + multiReqs: '需要:
• 每个 Bot 独立的 Token
• 独立的 OpenClaw 配置目录
• 独立的服务器进程', + enterBtn: '进入 Sub-agent 模式 →', + }, + en: { + sub: 'Select Mode', + activeBadge: 'Available', soonBadge: 'Coming Soon', + subTitle: 'Sub-agent Mode', + subDesc: 'Bind multiple sub-agents to different groups using one main Bot Token.', + subReqs: 'Requires:
• Telegram Bot Token (main)
• Group IDs for each group
• OpenClaw installed', + multiTitle: 'Multi-agent Mode', + multiDesc: 'Create fully isolated bots for each scenario with separate configs.', + multiReqs: 'Requires:
• Individual Token per bot
• Separate OpenClaw config dir
• Separate server process', + enterBtn: 'Enter Sub-agent Mode →', + }, +}; +// ── 全局状态 ──────────────────────────────────────────────── +let S = { agents:[], channels:[], models:{}, authProfiles:{}, knownModels:[], modelListError:'', authProviders:[], primaryModel:'', fallbacks:[] }; +let wizCur = 1; +let logTimer = null; +let selectedAuthProv = null; +let channelFilterAgent = '__all__'; +let routingCollapsedByAgent = {}; + +// ── 初始化(enterApp 触发) ───────────────────────────────── +async function checkStatus(){ + try{ + const r = await api('GET','/api/status'); + if(r.needsSetup){ location.reload(); return; } + setDot('ok'); + document.getElementById('statusTxt').textContent = r.dir.replace(/.*[/\\\\]/,'.../')+' · v'+r.version; + const tv=document.getElementById('topVersion'); + if(tv) tv.textContent = 'OCM v'+(r.ocmVersion||'--'); + const vb=document.getElementById('versionBadge'); if(vb) vb.textContent = 'v'+r.version; + const ov=document.getElementById('ocmVersionBadge'); if(ov) ov.textContent='ocm v'+(r.ocmVersion||'--'); + }catch{ setDot('err'); document.getElementById('statusTxt').textContent=t('status.configReadError'); } +} + +async function loadAll(){ + await Promise.all([loadAgents(), loadModels(), loadChannels()]); + try{ renderChannels(); }catch(_){} +} + +// ── Dashboard ───────────────────────────────────────────────── +let dashLoaded=false; +let dashRefreshTimer=null; +function fmtBytes(b){if(!b||b<=0)return '—';const u=['B','KB','MB','GB','TB'];let i=0;while(b>=1024&&i0?1:0)+' '+u[i];} +function fmtUptime(s){const d=Math.floor(s/86400);const h=Math.floor((s%86400)/3600);const m=Math.floor((s%3600)/60);if(d>0)return d+'d '+h+'h '+m+'m';if(h>0)return h+'h '+m+'m';return m+'m';} +function dashRow(label,val){return '
'+esc(label)+''+val+'
';} +function gaugeColor(pct){if(pct>90)return '#ef4444';if(pct>70)return '#f59e0b';return '#22c55e';} +function buildGaugeSVG(pct,label,sub,color){ + const r=46,cx=55,cy=55,sw=8; + const circ=2*Math.PI*r; + const gap=circ*0.25; + const arc=circ-gap; + const filled=arc*(Math.min(pct,100)/100); + const rot=135; + if(!color)color=gaugeColor(pct); + return '
'+ + ''+ + ''+ + ''+ + ''+Math.round(pct)+'%'+ + ''+esc(sub)+''+ + ''+ + '
'+esc(label)+'
'; +} +function toggleDashRefresh(on){ + if(dashRefreshTimer){clearInterval(dashRefreshTimer);dashRefreshTimer=null;} + if(on){dashRefreshTimer=setInterval(loadDashboard,10000);} +} +async function loadDashboard(){ + try{ + const r=await api('GET','/api/dashboard'); + if(!r.ok)return; + const s=r.system, g=r.gateway, a=r.agents; + const memPct=s.totalMem?((s.totalMem-s.freeMem)/s.totalMem*100):0; + const diskPct=s.diskTotal?(s.diskUsed/s.diskTotal*100):0; + const cpuPct=(s.cpuPercent!==null&&s.cpuPercent!==undefined)?s.cpuPercent:0; + // Gauges + const memUsed=fmtBytes(s.totalMem-s.freeMem)+' / '+fmtBytes(s.totalMem); + const diskUsed=fmtBytes(s.diskUsed)+' / '+fmtBytes(s.diskTotal); + let gaugeHtml=buildGaugeSVG(cpuPct,'CPU',s.cpuCores+' cores'); + gaugeHtml+=buildGaugeSVG(memPct,'RAM',memUsed); + gaugeHtml+=buildGaugeSVG(diskPct,'DISK',diskUsed); + document.getElementById('dashGauges').innerHTML=gaugeHtml; + // System info card + const loadStr=s.loadAvg?s.loadAvg.map(function(v){return v.toFixed(2);}).join(' / '):'—'; + let sysHtml=dashRow('Hostname',esc(s.hostname)); + sysHtml+=dashRow('OS',esc(s.platform)); + sysHtml+=dashRow('Node.js',esc(s.nodeVer)); + sysHtml+=dashRow('CPU',esc(s.cpuModel)); + sysHtml+=dashRow('Cores',String(s.cpuCores)); + sysHtml+=dashRow('Uptime',fmtUptime(s.uptime)); + sysHtml+=dashRow('Load Avg (1/5/15m)',loadStr); + document.getElementById('dashSysItems').innerHTML=sysHtml; + // Gateway card + const statusIcon=''; + const statusLabel=g.status==='running'?'Running':g.status==='stopped'?'Stopped':'Unknown'; + let gwHtml=dashRow('Status',statusIcon+statusLabel); + gwHtml+=dashRow('Bind Host',esc(g.host||'—')); + gwHtml+=dashRow('Port',String(g.port||'—')); + if(g.pid)gwHtml+=dashRow('PID',g.pid); + gwHtml+=dashRow('Telegram Accounts',String(g.accountCount||0)); + gwHtml+=dashRow('Groups',String(g.groupCount||0)); + gwHtml+=dashRow('Bindings',String(g.bindingCount||0)); + gwHtml+=dashRow('allowFrom',String(g.allowFromCount||0)); + document.getElementById('dashGwItems').innerHTML=gwHtml; + // Agents card + let agHtml=dashRow('Configured Agents',String(a.configCount||0)); + agHtml+=dashRow('Runtime Agent Dirs',String(a.count||0)); + agHtml+=dashRow('Agent Trees',String(a.mainCount||0)); + agHtml+=dashRow('Sub-Agents',String(a.subCount||0)); + if(a.lastActivity){ + const d=new Date(a.lastActivity); + agHtml+=dashRow('Last Activity',d.toLocaleString('en-AU',{timeZone:'Australia/Brisbane',hour12:false})); + }else{ + agHtml+=dashRow('Last Activity','—'); + } + agHtml+=dashRow('OCM Version','v'+esc(r.ocmVersion)); + agHtml+=dashRow('Server Time',esc(r.serverTime)); + document.getElementById('dashAgentItems').innerHTML=agHtml; + // Storage card + let stHtml=dashRow('OpenClaw Dir',fmtBytes(s.dirSize)); + stHtml+=dashRow('Disk Free',fmtBytes(s.diskFree)); + document.getElementById('dashStorageItems').innerHTML=stHtml; + dashLoaded=true; + }catch(e){console.error('Dashboard load error:',e);} +} + +let healthTimer=null; +async function refreshHealth(){ + try{ + const r=await api('GET','/api/health'); + const badge=document.getElementById('healthBadge'); + const icon=document.getElementById('healthIcon'); + const cnt=document.getElementById('healthCount'); + const tip=document.getElementById('healthTooltip'); + if(!badge)return; + if(!r.issues||r.issues.length===0){ + badge.className=''; badge.style.display='none'; return; + } + const errors=r.issues.filter(i=>i.level==='error').length; + const warns=r.issues.filter(i=>i.level==='warn').length; + badge.style.display='flex'; + badge.className=errors>0?'error':'warn'; + icon.textContent=errors>0?'🔴':'🟡'; + cnt.textContent=r.issues.length+(lang==='en'?' issue(s)':' 个问题'); + tip.textContent=r.issues.map(i=>(i.level==='error'?'❌ ':'⚠️ ')+i.text).join('\\n'); + }catch(_){} +} +function startHealthPolling(){ + refreshHealth(); + if(healthTimer) clearInterval(healthTimer); + healthTimer=setInterval(refreshHealth,60000); +} + + +async function loadAgents(){ + try{ const r=await api('GET','/api/agents'); S.agents=r.agents||[]; renderAgents(); } + catch(e){ toast('加载 Agent 失败: '+e.message,'error'); } +} + +async function loadModels(){ + try{ + const r=await api('GET','/api/models'); + S.models=r.models||{}; S.authProfiles=r.authProfiles||{}; + S.knownModels=r.knownModels||[]; S.authProviders=r.authProviders||[]; + S.modelListError=r.modelListError||''; + S.primaryModel=r.primaryModel||''; S.fallbacks=r.fallbacks||[]; + renderModels(); renderAuth(); buildModelDropdowns(); + }catch(e){ toast('加载模型失败: '+e.message,'error'); } +} + +async function loadChannels(){ + try{ const r=await api('GET','/api/channels'); S.channels=r.channels||[]; renderChannels(); } + catch(e){ toast('加载 Channel 失败: '+e.message,'error'); } +} + +// ── 渲染 Agents ───────────────────────────────────────────── +// ── Show Add Form ───────────────────────────────────────────── +function clearAddForm() { document.getElementById('addFormArea').innerHTML = ''; } +function showAddForm(type) { + const area = document.getElementById('addFormArea'); + if (type === 'agent') { + area.innerHTML = buildAddAgentForm(); + } else { + area.innerHTML = buildAddSubForm(); + } + applyLang(); +} + +function buildAddAgentForm() { + const modelOpts = buildModelOpts('__default__'); + return '
' + + '

' + t('agents.addAgentTitle') + '

' + + '
' + + '
' + + + '
' + t('guide.title') + '
    ' + + '
  1. ' + t('guide.agent.s1') + '
  2. ' + + '
  3. ' + t('guide.agent.s2') + '
  4. ' + + '
  5. ' + t('guide.agent.s3') + '
  6. ' + + '
  7. ' + t('guide.agent.s4') + '
  8. ' + + '
  9. ' + t('guide.agent.s5') + '
  10. ' + + '
' + + + '' + + + '
' + + '
' + + '' + + '
' + + '
' + + + '
' + + '
' + + '' + + 'Alphanumeric, underscore, or dash only
' + + '
' + + '' + + '
' + + '
' + + + '
' + + '' + + 'Each agent must have its own workspace for isolated SOUL/MEMORY.
' + + + '' + + + '
' + + '' + + '
' + + '
' + + '
' + + '
' + + '' + + '' + t('wiz.soulTip') + '
' + + '
' + + '' + + '' + + '
'; +} + + +function toggleAddAgentChannel(ch){ + const tg=document.getElementById('fa-sec-telegram'); + const dg=document.getElementById('fa-sec-discord'); + const gT=document.getElementById('fa-guide-telegram'); + const gD=document.getElementById('fa-guide-discord'); + if(tg) tg.style.display = (ch==='telegram')?'block':'none'; + if(dg) dg.style.display = (ch==='discord')?'block':'none'; + if(gT) gT.style.display = (ch==='telegram')?'block':'none'; + if(gD) gD.style.display = (ch==='discord')?'block':'none'; +} +function buildAddSubForm() { + const cfg = S.agents || []; + // Parent agent dropdown (all agents as potential parent for grouping) + let parentOpts = ''; + if (cfg.length === 0) parentOpts = ''; + else cfg.forEach(a=>{ parentOpts += ''; }); + + const modelOpts = buildModelOpts('__default__'); + return '
' + + '

' + t('agents.addSubTitle') + '

' + + '
' + + '' + + '
' + + + '
' + t('guide.title') + '
    ' + + '
  1. ' + t('guide.sub.s1') + '
  2. ' + + '
  3. ' + t('guide.sub.s2') + '
  4. ' + + '
  5. ' + t('guide.sub.s3') + '
  6. ' + + '
  7. ' + t('guide.sub.s4') + '
  8. ' + + '
  9. ' + t('guide.sub.s5') + '
  10. ' + + '
' + + '
' + t('guide.sub.warn') + '
' + + '
' + + + '' + + + '
' + + '' + + 'Used for grouping only (Discord sub-agents do not share bots).' + + '
' + + + '
' + + '
' + + '' + + '' + t('wiz.groupHint') + '' + + '
' + + '
' + + '' + + '' + t('wiz.telegramIdHint') + '' + + '
' + + '
' + + + '' + + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + + '
' + + '' + + 'Each sub-agent must have its own workspace for isolated SOUL/MEMORY.' + + '
' + + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + + '
' + + '' + + '' + + '
'; +} + +function toggleAddSubChannel(ch){ + const tg=document.getElementById('fs-sec-telegram'); + const dg=document.getElementById('fs-sec-discord'); + const gT=document.getElementById('fs-guide-telegram'); + const gD=document.getElementById('fs-guide-discord'); + if(tg) tg.style.display = (ch==='telegram')?'block':'none'; + if(dg) dg.style.display = (ch==='discord')?'block':'none'; + if(gT) gT.style.display = (ch==='telegram')?'block':'none'; + if(gD) gD.style.display = (ch==='discord')?'block':'none'; +} + +// ── Submit Add Agent (with own bot) ────────────────────────── +async function submitAddAgent() { + const channel = (document.getElementById('fa-channel')?.value||'telegram').trim(); + const agentId = document.getElementById('fa-agentid').value.trim(); + const name = document.getElementById('fa-name').value.trim(); + const workspace = (document.getElementById('fa-workspace').value.trim() || agentId); + const model = document.getElementById('fa-model').value; + const purpose = (document.getElementById('fa-purpose')?.value||'').trim(); + const personality = (document.getElementById('fa-soul')?.value||'').trim(); + + if (!agentId) { toast('Agent ID is required', 'err'); return; } + if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) { toast('Agent ID must contain only alphanumeric characters, underscores, or dashes', 'err'); return; } + if (!name) { toast(t('agents.errName'), 'err'); return; } + if (!workspace) { toast('Workspace folder is required', 'err'); return; } + + try { + if (channel === 'telegram') { + const token = document.getElementById('fa-token').value.trim(); + if (!token) { toast(t('agents.errToken'), 'err'); return; } + const payload = { botToken: token, agentId, name, workspace, model: model === '__default__' ? '' : model, purpose, personality }; + await api('POST', '/api/agents/bot', payload); + } else { + const link = (document.getElementById('fa-discord-link')?.value||'').trim(); + const parsed = parseDiscordLinkOrId(link); + if (!parsed) { toast('Discord channel link/ID is required', 'err'); return; } + const payload = { agentId, name, workspaceFolder: workspace, model: model === '__default__' ? '' : model, purpose, personality, + guildId: parsed.guildId, channelId: parsed.channelId }; + await api('POST', '/api/agents/discord', payload); + } + toast('Agent created successfully', 'ok'); + clearAddForm(); + showRestartBanner(); + await loadAll(); + } catch (e) { + toast(e.message, 'err'); + } +} + +// ── Submit Add Sub-Agent ──────────────────────────────────── +async function submitAddSub() { + const channel = (document.getElementById('fs-channel')?.value||'telegram').trim(); + const parentAgentId = document.getElementById('fs-parent').value.trim(); + const agentId = document.getElementById('fs-aid').value.trim(); + const displayName = document.getElementById('fs-name').value.trim(); + const workspaceFolder = (document.getElementById('fs-workspace')?.value||'').trim() || agentId; + const model = document.getElementById('fs-model').value; + const purpose = document.getElementById('fs-purpose').value.trim(); + const personality = document.getElementById('fs-soul').value.trim(); + const initialMemory = document.getElementById('fs-mem').value.trim(); + + if (!parentAgentId) { toast('Parent Agent is required', 'err'); return; } + if (!agentId) { toast(t('wiz.errAgentId'), 'err'); return; } + if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) { toast(t('wiz.errIdFormat'), 'err'); return; } + if (!workspaceFolder) { toast('Workspace folder is required', 'err'); return; } + + try { + if (channel === 'telegram') { + const groupId = document.getElementById('fs-gid').value.trim(); + const telegramUserId = (document.getElementById('fs-tgid')?.value||'').trim(); + if (!groupId) { toast(t('wiz.errGroupId'), 'err'); return; } + if (telegramUserId && !/^\d+$/.test(telegramUserId)) { toast('Telegram User ID must be a number', 'err'); return; } + await api('POST', '/api/agents', { parentAgentId, agentId, displayName: displayName || agentId, groupId, + workspaceFolder, model: model === '__default__' ? '' : model, purpose, personality, initialMemory, telegramUserId }); + } else { + const link = (document.getElementById('fs-discord-link')?.value||'').trim(); + const parsed = parseDiscordLinkOrId(link); + if (!parsed) { toast('Discord thread link/ID is required', 'err'); return; } + await api('POST', '/api/agents/discord-sub', { parentAgentId, agentId, displayName: displayName || agentId, + workspaceFolder, model: model === '__default__' ? '' : model, purpose, personality, initialMemory, + guildId: parsed.guildId, threadId: parsed.channelId }); + } + toast(t('wiz.created'), 'ok'); + clearAddForm(); + showRestartBanner(); + await loadAll(); + } catch (e) { toast(e.message, 'err'); } +} + +// ── Render Agent Tree ────────────────────────────────────── +function renderAgents() { + const el = document.getElementById('agentTree'); + if (!S.agents.length) { el.innerHTML = '
' + t('agents.empty') + '
'; return; } + + // Build grouping: prefer explicit parentAgentId; fallback to telegram bot-root grouping. + const byId = {}; (S.agents||[]).forEach(a=>{ byId[a.id]=a; }); + + // infer parentAgentId for sub-agents if missing + const accountToRootId = {}; + (S.agents||[]).forEach(a=>{ if(a.hasOwnBot && a.accountId) accountToRootId[a.accountId]=a.id; }); + (S.agents||[]).forEach(a=>{ + if(a.parentAgentId || a.hasOwnBot || a.id === 'main') return; // already resolved or is a root + // Case 1: legacy telegram sub-agent with parentAccountId + if(a.parentAccountId && accountToRootId[a.parentAccountId]){ + a._inferParentAgentId = accountToRootId[a.parentAccountId]; + return; + } + // Case 2: orphan agent (no own bot, no parentAgentId, no binding-based inference) + // These are likely dynamically created sub-agents — default to 'main' + if(byId['main']){ + a._inferParentAgentId = 'main'; + } + }); + + const roots=[]; const childrenByRoot={}; + (S.agents||[]).forEach(a=>{ + const pid = a.parentAgentId || a._inferParentAgentId || ''; + if(pid && byId[pid]){ + (childrenByRoot[pid] ||= []).push(a); + } else { + roots.push(a); + } + }); + + function fmtBinding(b){ + const ch = (b.channel||'').toLowerCase(); + const peer = b.peerId ? String(b.peerId) : ''; + const acct = b.accountId ? String(b.accountId) : ''; + if(ch==='telegram'){ + if(peer) return 'TG '+esc(peer)+''; + if(acct) return 'TG Bot '+esc(acct)+''; + } + if(ch==='discord'){ + if(peer) return 'Discord '+esc(peer)+''; + } + return ''+esc(ch||'bind')+''; + } + + function bindingsLine(a){ + const arr = (a.bindings||[]).filter(x=>x.peerId||x.accountId); + if(!arr.length) return ''; + return '
🔗 '+arr.map(fmtBinding).join(' · ')+'
'; + } + + function agentCard(a, isRoot) { + let h = ''; + const icon = isRoot ? '🤖' : '🧩'; + const cls = isRoot ? 'tree-main' : 'tree-child'; + h += '
'; + h += '
' + icon + ' ' + esc(a.name || a.id); + if (a.id === 'main') h += ' ' + t('agents.main') + ''; + h += '
'; + h += '
🧠 ' + esc(a.effectiveModel) + '
'; + if (a.workspace) h += '
📁 ' + esc(a.workspace) + '
'; + h += bindingsLine(a); + h += '
'; + h += ''; + h += ''; + h += ''; + if (a.id !== 'main') h += ''; + h += '
'; + return h; + } + + let html = '
'; + roots.forEach(root => { + const children = childrenByRoot[root.id] || []; + html += '
'; + html += agentCard(root, true); + if (children.length) { + const treeId = 'tree-' + root.id; + html += '
'; + html += ''; + html += '
'; + children.forEach(c => { html += agentCard(c, false); }); + html += '
'; + } + html += '
'; + }); + html += '
'; + el.innerHTML = html; + + el.onclick = function(ev) { + const tbtn = ev.target.closest('.tree-toggle'); + if (tbtn && tbtn.dataset.tree) { toggleTree(tbtn.dataset.tree, tbtn); return; } + + const btn = ev.target.closest('[data-action]'); + if (!btn) return; + const action = btn.dataset.action; + const id = btn.dataset.id; + if (action === 'saveModel') saveAgentModel(id); + else if (action === 'viewWs') viewWorkspace(id); + else if (action === 'delAgent') deleteAgent(id, btn.dataset.name); + }; +} + +function toggleTree(treeId, btn) { + const el = document.getElementById(treeId); + if (!el) return; + el.classList.toggle('collapsed'); + btn.textContent = el.classList.contains('collapsed') ? '+' : '−'; +} + +function buildModelOpts(selected){ + let opts=\`\`; + let lastGroup=''; + S.knownModels.filter(m=>m.id!=='__default__').forEach(m=>{ + if(m.group && m.group!==lastGroup){ + if(lastGroup) opts+=\`\`; + opts+=\`\`; + lastGroup=m.group; + } + opts+=\`\`; + }); + if(lastGroup) opts+=\`\`; + return opts; +} + +async function saveAgentModel(agentId){ + const sel=document.getElementById('msel-'+agentId); + if(!sel)return; + const model=sel.value; + try{ + await api('PUT','/api/agents/'+encodeURIComponent(agentId),{model}); + document.getElementById('eml-'+agentId).textContent=model==='__default__'?t('agents.noModel')+' ('+S.primaryModel+')':model; + toast('模型已更新','success'); showRestartBanner(); + }catch(e){toast('保存失败: '+e.message,'error');} +} + +// ── 渲染 Channels ──────────────────────────────────────────── +function getChannelAgentChoices(){ + const byId={}; + (S.agents||[]).forEach(a=>{ + if(a&&a.id) byId[a.id]=a.name||a.id; + }); + (S.channels||[]).forEach(ch=>{ + if(ch&&ch.agentId&&!byId[ch.agentId]) byId[ch.agentId]=ch.agentName||ch.agentId; + }); + return Object.keys(byId).sort((a,b)=>a.localeCompare(b)).map(id=>({id,name:byId[id]})); +} + +function renderChannelFilter(){ + const sel=document.getElementById('channelsAgentFilter'); + if(!sel) return; + const choices=getChannelAgentChoices(); + if(channelFilterAgent!=='__all__' && !choices.some(c=>c.id===channelFilterAgent)){ + channelFilterAgent='__all__'; + } + let html=\`\`; + html+=choices.map(c=>\`\`).join(''); + sel.innerHTML=html; +} + +function onChannelFilterChange(v){ + channelFilterAgent=v||'__all__'; + renderChannels(); +} + +function toggleRoutingGroup(agentId){ + routingCollapsedByAgent[agentId]=!routingCollapsedByAgent[agentId]; + renderChannels(); +} + +function renderChannels(){ + const el=document.getElementById('channelList'); + renderChannelFilter(); + if(!S.channels.length){el.innerHTML='
'+t('channels.empty')+'
';return;} + const rows=channelFilterAgent==='__all__' + ? S.channels + : S.channels.filter(ch=>ch.agentId===channelFilterAgent); + if(!rows.length){el.innerHTML='
'+t('channels.emptyFiltered')+'
';return;} + const groups={}; + rows.forEach(ch=>{ + const key=ch.agentId||'unknown'; + if(!groups[key]) groups[key]={ agentId:key, agentName:ch.agentName||key, rows:[] }; + groups[key].rows.push(ch); + }); + const sortedGroups=Object.values(groups).sort((a,b)=>(a.agentName||a.agentId).localeCompare(b.agentName||b.agentId)); + el.innerHTML=sortedGroups.map(g=>{ + const collapsed=!!routingCollapsedByAgent[g.agentId]; + const listCls=collapsed?'routing-list collapsed':'routing-list'; + const toggleText=collapsed?t('channels.expand'):t('channels.collapse'); + const rowsHtml=g.rows + .sort((a,b)=>(a.idx||0)-(b.idx||0)) + .map(ch=>{ + const chClass=ch.channel==='telegram'?'ch-tg':'ch-any'; + const peerMeta=ch.peerId + ? \`📱 Peer ID: \${esc(ch.peerId)}\` + : t('channels.matchAll'); + return \`
+
+
+ #\${ch.idx} + \${ch.channel?\`\${esc(ch.channel)}\`:'any'} + \${ch.peerKind?\`\${esc(ch.peerKind)}\`:''} + \${peerMeta} +
+
+ +
\`; + }).join(''); + return \`
+
+
+ \${esc(g.agentName)} + agent: \${esc(g.agentId)} + \${g.rows.length} \${esc(t('channels.countSuffix'))} +
+ +
+
+ \${rowsHtml} +
+
\`; + }).join(''); +} + +function openAddChannel(){ + const sel=document.getElementById('ch-agent'); + sel.innerHTML=''; S.agents.forEach(a=>{ sel.innerHTML+=\`\`; }); + document.getElementById('ch-peerId').value=''; + openModal('addChannelModal'); applyLang(); +} + +async function submitAddChannel(){ + const agentId=document.getElementById('ch-agent').value; + const channel=document.getElementById('ch-channel').value; + const peerKind=document.getElementById('ch-peerKind').value; + const peerId=document.getElementById('ch-peerId').value.trim(); + if(!agentId){toast('请选择 Agent','error');return;} + try{ + await api('POST','/api/channels',{agentId,channel,peerKind,peerId}); + toast('绑定已添加','success'); closeModal('addChannelModal'); await loadChannels(); showRestartBanner(); + }catch(e){toast('失败: '+e.message,'error');} +} + +async function deleteChannel(idx,label){ + if(!confirm(\`确定要删除绑定 "\${label}"?\`))return; + try{ + await api('DELETE','/api/channels/'+idx); + toast('绑定已删除','success'); await loadChannels(); showRestartBanner(); + }catch(e){toast('失败: '+e.message,'error');} +} + +// ── 渲染模型 ───────────────────────────────────────────────── +function renderModels(){ + const listWarn=document.getElementById('modelListWarn'); + if(listWarn){ + if(S.modelListError){ + listWarn.style.display=''; + listWarn.textContent=t('models.modelListErr')+S.modelListError; + }else if(!S.knownModels.length){ + listWarn.style.display=''; + listWarn.textContent=t('models.modelListEmpty'); + }else{ + listWarn.style.display='none'; + listWarn.textContent=''; + } + } + // 检测 primary model 是否是 API Key(显示修复警告) + const primWarn=document.getElementById('primaryModelWarn'); + if(primWarn){ + const bad = S.primaryModel && !isValidModelId(S.primaryModel); + primWarn.style.display = bad ? '' : 'none'; + if(bad){ + const masked = S.primaryModel.length > 16 + ? S.primaryModel.slice(0,8)+'...'+ S.primaryModel.slice(-4) + : S.primaryModel; + primWarn.innerHTML=\`⚠️ 当前主模型设置异常:检测到 \${esc(masked)} 像是 API Key 而非模型 ID。 + 请从下拉选择正确的模型并保存,或点击下方按钮重置。
+ 提示:模型 ID 格式为 provider/model-name,例如 anthropic/claude-sonnet-4-5
+ \`; + } + } + const pSel=document.getElementById('primaryModelSel'); + pSel.innerHTML=''; + S.knownModels.filter(m=>m.id!=='__default__').forEach(m=>{ + pSel.innerHTML+=\`\`; + }); + const fl=document.getElementById('fallbackList'); + fl.innerHTML=S.fallbacks.length + ? S.fallbacks.map((f,i)=>\`\${esc(f)} \`).join('') + : ''+(lang==='en'?'(empty)':'(空)')+''; +} + +function isValidModelId(id) { + // Must be in format provider/model-id, no spaces, not an API key + if (!id) return false; + if (!id.includes('/')) return false; + if (id.length > 120) return false; + // Reject obvious API keys (long alphanumeric strings, or starts with sk-, gsk_, etc.) + if (/^(sk-|gsk_|pplx-|sess-|ghp_|github_pat_)/.test(id)) return false; + // Must only contain provider/model valid chars + if (!/^[a-zA-Z0-9_./-]+$/.test(id)) return false; + return true; +} +async function savePrimaryModel(){ + const custom=document.getElementById('primaryModelCustom').value.trim(); + const sel =document.getElementById('primaryModelSel').value; + const model = custom || sel; + if(!model){toast('请选择或填写模型','error');return;} + if(custom && !isValidModelId(custom)){ + toast('格式错误:模型 ID 应为 provider/model-id(例:anthropic/claude-sonnet-4-5),请勿在此粘贴 API Key。','error'); + return; + } + try{ + await api('PUT','/api/models/settings',{primaryModel:model, fallbacks:S.fallbacks}); + S.primaryModel=model; toast('主模型已保存','success'); showRestartBanner(); + document.getElementById('primaryModelCustom').value=''; + renderModels(); + }catch(e){toast('失败: '+e.message,'error');} +} + +// 修复被错误设置为 API Key 的主模型 → 重置为第一个已注册模型或留空 +async function fixBadPrimaryModel(){ + // 尝试从已注册模型中取第一个可用 ID + const firstModel = (S.knownModels||[]).map(m=>m.id).find(k => isValidModelId(k)); + const resetTo = firstModel || ''; + if(!confirm(\`将主模型重置为"\${resetTo||'(清空,使用全局默认)'}"?\`)) return; + try{ + await api('PUT','/api/models/settings',{primaryModel:resetTo, fallbacks:S.fallbacks}); + S.primaryModel=resetTo; + toast(\`主模型已重置\${resetTo?' 为 '+resetTo:''}\`,'success'); + showRestartBanner(); renderModels(); + }catch(e){toast('重置失败: '+e.message,'error');} +} + +function openAddFallback(){ + buildFbDropdown(); + // 显示当前 fallback 列表 + const curEl=document.getElementById('fb-current-list'); + if(curEl){ + if(S.fallbacks.length){ + curEl.innerHTML='当前 Fallback 链:'+S.fallbacks.map((f,i)=>\`\${i+1}. \${esc(f)}\`).join(''); + }else{ + curEl.innerHTML='当前尚未配置 Fallback'; + } + } + document.getElementById('fb-custom').value=''; + document.getElementById('fb-sel').value=''; + openModal('addFallbackModal'); +} +function buildFbDropdown(){ + const sel=document.getElementById('fb-sel'); + sel.innerHTML=''; + // 按 group 分组显示 + const groups={}; + S.knownModels.filter(m=>m.id!=='__default__'&&!S.fallbacks.includes(m.id)).forEach(m=>{ + const g=m.group||'其他'; + if(!groups[g]) groups[g]=[]; + groups[g].push(m); + }); + Object.entries(groups).forEach(([g,items])=>{ + const og=document.createElement('optgroup'); + og.label=g; + items.forEach(m=>{ const o=document.createElement('option'); o.value=m.id; o.textContent=m.label; og.appendChild(o); }); + sel.appendChild(og); + }); +} +function onFbSelChange(){ const v=document.getElementById('fb-sel').value; if(v) document.getElementById('fb-custom').value=''; } +function onFbCustomInput(){ const v=document.getElementById('fb-custom').value.trim(); if(v) document.getElementById('fb-sel').value=''; } +async function addFallback(){ + const custom=document.getElementById('fb-custom').value.trim(); + const sel =document.getElementById('fb-sel').value; + const model = custom || sel; + if(!model){toast('请输入或选择模型 ID','error');return;} + if(!isValidModelId(model)){ + toast('格式错误:模型 ID 应为 provider/model-id(例:deepseek/deepseek-v3),请勿粘贴 API Key。','error'); + return; + } + if(S.fallbacks.includes(model)){toast('该模型已在 Fallback 链中','error');return;} + S.fallbacks.push(model); + try{ + await api('PUT','/api/models/settings',{primaryModel:S.primaryModel, fallbacks:S.fallbacks}); + closeModal('addFallbackModal'); renderModels(); showRestartBanner(); + }catch(e){ S.fallbacks.pop(); toast('失败: '+e.message,'error'); } +} +async function removeFallback(i){ + const removed=S.fallbacks.splice(i,1); + try{ + await api('PUT','/api/models/settings',{primaryModel:S.primaryModel, fallbacks:S.fallbacks}); + renderModels(); showRestartBanner(); + }catch(e){ S.fallbacks.splice(i,0,...removed); toast('失败: '+e.message,'error'); } +} + +// ── 渲染认证(引导模式)───────────────────────────────────── +function renderAuth(){ + const grid=document.getElementById('authProvGrid'); + // 标记已配置的 provider + const configured=new Set(Object.keys(S.authProfiles||{}).map(k=>k.toLowerCase())); + grid.innerHTML=S.authProviders.map(p=>{ + const done=configured.has(p.id); + return \`\`; + }).join(''); + // Configured list + const el=document.getElementById('authList'); + const entries=Object.entries(S.authProfiles); + if(!entries.length){el.innerHTML='
'+(lang==='en'?'No auth configured':'暂无认证配置')+'
';return;} + el.innerHTML=entries.map(([key,p])=>\`
+
+ \${esc(key)} + \${p.mode||'token'} +
+ \${p.email?\`
📧 \${esc(p.email)}
\`:''} +
+ +
+
\`).join(''); +} + +function selectAuthProv(pid){ + selectedAuthProv=pid; + const provider=S.authProviders.find(p=>p.id===pid); + if(!provider)return; + renderAuth(); + const card=document.getElementById('authActionCard'); + const content=document.getElementById('authActionContent'); + card.style.display=''; + if(provider.mode==='oauth'){ + content.innerHTML=\` +

\${esc(provider.label)} — OAuth

+
+ \${t('auth.step1')}
+ \${lang==='en'?'This provider uses browser-based OAuth.':'此 Provider 使用浏览器 OAuth 授权。'}

+ \${t('auth.step2')}
+ \${esc(provider.cliCmd)} +

+ \${t('auth.onboard')}
+ openclaw onboard + +
\`; + } else if(provider.mode==='device'){ + content.innerHTML=\` +

\${esc(provider.label)} — Device Flow

+
+ \${t('auth.step1')}
+ 1. \${lang==='en'?'Run in terminal':'在终端运行'}:\${esc(provider.cliCmd)} +
+ 2. \${lang==='en'?'Copy the 8-digit device code':'复制终端显示的 8 位设备码'}
+ 3. \${lang==='en'?'Open':'打开'}:github.com/login/device
+ 4. \${lang==='en'?'Paste the code and authorize':'粘贴设备码并授权'}

+ \${t('auth.onboard')}
+ openclaw onboard + +
\`; + } else { + content.innerHTML=\` +

\${esc(provider.label)} — API Key

+
+ \${t('auth.step1')}
+ \${esc(provider.hint||'')}

+ \${t('auth.step2')}
+ \${esc(provider.cliCmd)} +

+ \${t('auth.step3')}
+ \${lang==='en'?'The key is stored securely by OpenClaw, not in config files.':'Key 由 OpenClaw 安全存储,不写入配置文件。'}

+ \${t('auth.onboard')}
+ openclaw onboard + +
\`; + } +} + +async function refreshAuthOnly(){ + try{ + const r=await api('GET','/api/models'); + S.authProfiles=r.authProfiles||{}; + S.models=r.models||{}; S.knownModels=r.knownModels||[]; S.modelListError=r.modelListError||''; + renderAuth(); buildModelDropdowns(); + }catch(e){ toast((lang==='en'?'Refresh failed: ':'刷新失败: ')+e.message,'error'); } +} + +async function deleteAuth(key){ + if(!confirm((lang==='en'?'Remove auth "':'移除认证配置 "')+key+'"?'))return; + try{await api('DELETE','/api/auth/'+encodeURIComponent(key)); toast(lang==='en'?'Removed':'已移除','success'); await refreshAuthOnly();} + catch(e){toast((lang==='en'?'Failed: ':'失败: ')+e.message,'error');} +} + +function copyText(txt){ + navigator.clipboard.writeText(txt).then(()=>toast(lang==='en'?'Copied':'已复制','success')).catch(()=>toast(lang==='en'?'Copy failed':'复制失败','error')); +} + +function copyCliCommand(){ + const inp=document.getElementById('cliInput'); + const cmd=(inp&&inp.value?inp.value:'').trim(); + if(!cmd){ toast(lang==='en'?'Nothing to copy (CLI input is empty)':'没有可复制的命令(输入框为空)','error'); return; } + copyText(cmd); +} + + +async function deleteAgent(agentId, name){ + if(!confirm(\`确定要删除 Agent "\${name}"?\\n\\n• 将从 openclaw.json 移除(自动备份)\\n• Workspace 目录不删除\\n• 可通过"回滚"功能恢复配置\\n\\n继续?\`))return; + try{ + const r=await api('DELETE','/api/agents/'+encodeURIComponent(agentId)); + toast('已删除 '+agentId,'success'); + await loadAgents(); await loadChannels(); showRestartBanner(); + }catch(e){toast('删除失败: '+e.message,'error');} +} + +let wsCurrentAgent=''; +async function viewWorkspace(agentId){ + wsCurrentAgent=agentId; + const el=document.getElementById('wsContent'); + el.innerHTML='
'+(lang==='en'?'Loading...':'加载中...')+'
'; openModal('wsModal'); + try{ + const r=await api('GET','/api/workspace/'+encodeURIComponent(agentId)); + const fileNames=Object.keys(r.files); + if(!fileNames.length){ el.innerHTML='
'+(lang==='en'?'No files':'暂无文件')+'
'; return; } + el.innerHTML='

📁 '+esc(r.workspacePath)+'

' + +fileNames.map(name=>{ + const content=r.files[name]; + const st=r.fileStats&&r.fileStats[name]?r.fileStats[name]:{}; + const sizeStr=st.size!=null?(st.size<1024?st.size+' B':(st.size/1024).toFixed(1)+' KB'):''; + const mtimeStr=st.mtime?new Date(st.mtime).toLocaleString():''; + const fid='wsf-'+name.replace(/[^a-zA-Z0-9]/g,'_'); + return \`
+
+ \${esc(name)} + \${esc(sizeStr)}\${mtimeStr?' · '+esc(mtimeStr):''} + \${content!==null?\`\`:''} +
+ \${content!==null + ?\`
\${esc(content)}
+ + \` + :\`
\${content===null&&st.size>512*1024?t('ws.large'):(lang==='en'?'(file not found)':'(文件不存在)')}
\`} +
\`; + }).join(''); + }catch(e){el.innerHTML='
'+(lang==='en'?'Failed: ':'加载失败: ')+e.message+'
';} +} +function wsToggleEdit(agentId,fname,fid){ + const pre=document.getElementById('pre-'+fid); + const ta=document.getElementById('ta-'+fid); + const acts=document.getElementById('acts-'+fid); + const btn=document.getElementById('wsbtn-'+fid); + if(ta.style.display==='none'){ + ta.style.display=''; pre.style.display='none'; acts.style.display='flex'; + btn.textContent=t('ws.cancel'); ta.focus(); + } else { wsCancelEdit(fid); } +} +function wsCancelEdit(fid){ + const pre=document.getElementById('pre-'+fid); + const ta=document.getElementById('ta-'+fid); + const acts=document.getElementById('acts-'+fid); + const btn=document.getElementById('wsbtn-'+fid); + ta.style.display='none'; pre.style.display=''; acts.style.display='none'; + if(btn) btn.textContent=t('ws.edit'); + // revert textarea to pre content + ta.value=pre.textContent; +} +async function wsSaveFile(agentId,fname,fid){ + const ta=document.getElementById('ta-'+fid); + try{ + await api('PUT','/api/workspace/'+encodeURIComponent(agentId)+'/'+encodeURIComponent(fname),{content:ta.value}); + toast(t('ws.saved'),'success'); + // update pre with new content + const pre=document.getElementById('pre-'+fid); + pre.textContent=ta.value; + wsCancelEdit(fid); + }catch(e){toast((lang==='en'?'Save failed: ':'保存失败: ')+e.message,'error');} +} + +function buildModelDropdowns(){ + S.agents.forEach(a=>{ + const sel=document.getElementById('msel-'+a.id); + if(!sel) return; + const current=a.effectiveModel!=='默认'?a.effectiveModel:'__default__'; + sel.innerHTML=buildModelOpts(current); + }); +} + +// ── CLI 终端 ───────────────────────────────────────────────── +const CLI_DEFAULTS = [ + { label: 'openclaw doctor', cmd: 'openclaw doctor' }, + { label: 'openclaw --version', cmd: 'openclaw --version' }, + { label: 'openclaw gateway status', cmd: 'openclaw gateway status' }, + { label: 'openclaw gateway restart', cmd: 'openclaw gateway restart' }, + { label: 'openclaw gateway start', cmd: 'openclaw gateway start' }, + { label: 'openclaw gateway stop', cmd: 'openclaw gateway stop' }, + { label: 'openclaw gateway logs', cmd: 'openclaw gateway logs' }, + { label: 'openclaw models list', cmd: 'openclaw models list' }, + { label: 'openclaw models auth list', cmd: 'openclaw models auth list' }, + { label: 'openclaw agents list', cmd: 'openclaw agents list' }, + { label: 'openclaw agents sync', cmd: 'openclaw agents sync' }, + { label: 'openclaw backup create', cmd: 'openclaw backup create' }, + { label: 'openclaw backup list', cmd: 'openclaw backup list' }, + { label: 'openclaw config validate', cmd: 'openclaw config validate' }, + { label: 'openclaw update', cmd: 'openclaw update' }, +]; +let cliHistory=[], cliHistIdx=-1, cliEvt=null; + +function buildCliPresets(){ + const sel=document.getElementById('cliPreset'); + const favs=JSON.parse(LS.get('ocm_cli_favs','[]')); + sel.innerHTML=''; + const og1=document.createElement('optgroup'); og1.label=t('cli.builtins'); + CLI_DEFAULTS.forEach(c=>{ const o=document.createElement('option'); o.value=c.cmd; o.textContent=c.label; og1.appendChild(o); }); + sel.appendChild(og1); + if(favs.length){ + const og2=document.createElement('optgroup'); og2.label=t('cli.favs'); + favs.forEach(c=>{ const o=document.createElement('option'); o.value=c.cmd; o.textContent=(c.label||c.cmd); og2.appendChild(o); }); + sel.appendChild(og2); + } +} + +function toggleCliPanel(){ + const p=document.getElementById('cliPanel'); + const open=p.classList.toggle('open'); + const btn=document.getElementById('cliToggleBtn'); + if(btn) btn.classList.toggle('active', open); + const prb=document.getElementById('pendingRestartBtn'); + if(prb) prb.style.bottom=open?'292px':'24px'; + // Adjust main content padding so content behind panel remains reachable via scroll + const mainEl=document.querySelector('main'); + if(mainEl) mainEl.style.paddingBottom = open ? (p.offsetHeight+24)+'px' : ''; + if(open){ buildCliPresets(); setTimeout(()=>document.getElementById('cliInput').focus(),100); scrollCliToBottom(); } +} + +function onCliPresetSelect(){ + const v=document.getElementById('cliPreset').value; + if(v){ document.getElementById('cliInput').value=v; document.getElementById('cliInput').focus(); } + document.getElementById('cliPreset').value=''; +} + +function addCliToFavorites(){ + const cmd=document.getElementById('cliInput').value.trim(); + if(!cmd){ toast(t('cli.fav.empty'),'error'); return; } + const favs=JSON.parse(LS.get('ocm_cli_favs','[]')); + if(favs.find(f=>f.cmd===cmd)){ toast(t('cli.fav.dup'),'error'); return; } + const label=prompt(t('cli.favprompt'),cmd.slice(0,50)); + if(label===null) return; + favs.push({label:label||cmd, cmd}); + LS.set('ocm_cli_favs',JSON.stringify(favs)); + buildCliPresets(); + toast(t('cli.fav.saved')+': '+(label||cmd),'success'); +} + +function openCliManage(){ + const favs=JSON.parse(LS.get('ocm_cli_favs','[]')); + const list=document.getElementById('cliManageList'); + if(!favs.length){ + list.innerHTML='
'+ + (lang==='en'?'No saved commands yet.':'暂无收藏命令')+'
'; + } else { + list.innerHTML=favs.map((f,i)=>'
'+ + '
'+ + '
'+esc(f.label||f.cmd)+'
'+ + '
'+esc(f.cmd)+'
'+ + '
'+ + ''+ + ''+ + '
').join(''); + } + document.getElementById('cliManageModal').classList.add('open'); +} + +function deleteCliFav(i){ + const favs=JSON.parse(LS.get('ocm_cli_favs','[]')); + favs.splice(i,1); + LS.set('ocm_cli_favs',JSON.stringify(favs)); + buildCliPresets(); + openCliManage(); +} + +function editCliFav(i){ + const favs=JSON.parse(LS.get('ocm_cli_favs','[]')); + const f=favs[i]; + const newLabel=prompt((lang==='en'?'Edit name:':'修改名称:'),f.label||f.cmd); + if(newLabel===null) return; + const newCmd=prompt((lang==='en'?'Edit command:':'修改命令:'),f.cmd); + if(newCmd===null||!newCmd.trim()) return; + favs[i]={label:newLabel||newCmd.trim(), cmd:newCmd.trim()}; + LS.set('ocm_cli_favs',JSON.stringify(favs)); + buildCliPresets(); + openCliManage(); +} + +function cliAppend(text,cls){ + const out=document.getElementById('cliOutput'); + const span=document.createElement('span'); + if(cls) span.className=cls; + span.textContent=text; + out.appendChild(span); + scrollCliToBottom(); +} + +function scrollCliToBottom(){ const o=document.getElementById('cliOutput'); if(o) o.scrollTop=o.scrollHeight; } + +function clearCliOutput(){ document.getElementById('cliOutput').innerHTML=''+t('cli.cleared')+''; } + +function setCliRunning(running){ + const sb=document.getElementById('cliStopBtn'); + if(sb) sb.style.display=running?'':'none'; +} + +function killCli(){ + if(cliEvt){ try{cliEvt.close();}catch(_){} cliEvt=null; } + setCliRunning(false); + cliAppend('\\n⏹ 已中止\\n','cli-done-err'); +} + +function runCli(){ + const inp=document.getElementById('cliInput'); + const cmd=inp.value.trim(); + if(!cmd){ toast('请输入命令','error'); return; } + if(!cliHistory.length||cliHistory[0]!==cmd) cliHistory.unshift(cmd); + if(cliHistory.length>50) cliHistory.pop(); + cliHistIdx=-1; inp.value=''; + if(cliEvt){ try{cliEvt.close();}catch(_){} cliEvt=null; } + const out=document.getElementById('cliOutput'); + // Clear ready/cleared placeholder + if(out.children.length===1 && out.children[0].id==='cliReadyMsg') out.innerHTML=''; + if(out.textContent===t('cli.cleared')) out.innerHTML=''; + cliAppend('\\n$ '+cmd+'\\n','cli-cmd-line'); + setCliRunning(true); + const es=new EventSource('/api/cli/stream?cmd='+encodeURIComponent(cmd)); + cliEvt=es; + es.addEventListener('out',e=>{ + try{ cliAppend(JSON.parse(e.data).text); }catch(_){} + }); + es.addEventListener('done',e=>{ + try{ + const d=JSON.parse(e.data); + if(d.code===0) cliAppend('\\n✅ Done (exit 0)\\n','cli-done-ok'); + else cliAppend('\\n❌ Exit code '+d.code+'\\n','cli-done-err'); + }catch(_){} + es.close(); cliEvt=null; setCliRunning(false); + }); + es.addEventListener('error',e=>{ + try{ cliAppend('\\n⚠ '+(JSON.parse(e.data).message||'命令执行出错')+'\\n','cli-done-err'); }catch(_){} + es.close(); cliEvt=null; setCliRunning(false); + }); + es.onerror=()=>{ if(es.readyState===EventSource.CLOSED){ cliEvt=null; setCliRunning(false); } }; +} + +function onCliKey(e){ + if(e.key==='Tab'){ e.preventDefault(); cliTabComplete(e.target); return; } + if(e.key==='Escape'){ cliCloseAC(); return; } + if(e.key==='Enter'){ cliCloseAC(); runCli(); return; } + if(e.key==='ArrowUp'){ + e.preventDefault(); + if(cliHistIdx0){ cliHistIdx--; e.target.value=cliHistory[cliHistIdx]; } + else if(cliHistIdx===0){ cliHistIdx=-1; e.target.value=''; } + } + // 其他键关闭补全菜单 + if(e.key!=='Tab'&&e.key!=='ArrowUp'&&e.key!=='ArrowDown') cliCloseAC(); +} + +// ── CLI Tab 补全 ───────────────────────────────────────────── +const CLI_SUBCOMMANDS=['openclaw','doctor','gateway','models','agents','backup','config','auth', + 'restart','start','stop','status','logs','list','create','validate','update','onboard','sync', + 'paste-token','--provider','--version','--dir','--port','--host']; + +function cliTabComplete(input){ + const val=input.value; + const words=val.split(/\\s+/); + const lastWord=words[words.length-1]||''; + if(!lastWord){ return; } + // 收集候选: subcommands + preset commands + history + const candidates=new Set(); + CLI_SUBCOMMANDS.forEach(c=>candidates.add(c)); + CLI_DEFAULTS.forEach(c=>candidates.add(c.cmd)); + cliHistory.forEach(c=>candidates.add(c)); + // 过滤: 如果是完整命令行匹配(words.length>1 时只匹配最后一个词) + let matches=[]; + if(words.length===1){ + // 匹配完整命令或第一个词 + matches=[...candidates].filter(c=>c.toLowerCase().startsWith(lastWord.toLowerCase())); + } else { + // 匹配子命令词 + matches=[...CLI_SUBCOMMANDS,...CLI_DEFAULTS.map(c=>c.cmd.split(/\\s+/).pop())] + .filter(c=>c.toLowerCase().startsWith(lastWord.toLowerCase())); + matches=[...new Set(matches)]; + } + if(matches.length===0) return; + if(matches.length===1){ + // 单一匹配:直接补全 + if(words.length===1){ input.value=matches[0]; } + else { words[words.length-1]=matches[0]; input.value=words.join(' '); } + cliCloseAC(); + } else { + // 多个匹配:找公共前缀先补全,同时显示候选列表 + const prefix=commonPrefix(matches); + if(prefix.length>lastWord.length){ + if(words.length===1){ input.value=prefix; } + else { words[words.length-1]=prefix; input.value=words.join(' '); } + } + cliShowAC(matches.slice(0,12),input); + } +} +function commonPrefix(arr){ + if(!arr.length) return ''; + let p=arr[0]; + for(let i=1;i0) p=p.slice(0,-1); + } + return p; +} +function cliShowAC(items,input){ + cliCloseAC(); + const box=document.createElement('div'); + box.id='cliACBox'; + box.style.cssText='position:absolute;bottom:100%;left:0;right:0;background:var(--surface);border:1px solid var(--border);border-radius:6px;max-height:180px;overflow-y:auto;z-index:200;box-shadow:0 -4px 12px rgba(0,0,0,.3)'; + items.forEach(item=>{ + const d=document.createElement('div'); + d.textContent=item; + d.style.cssText='padding:6px 12px;font-size:12px;font-family:monospace;cursor:pointer;color:var(--text);border-bottom:1px solid var(--border)'; + d.onmouseover=()=>{d.style.background='rgba(108,99,255,.15)';}; + d.onmouseout=()=>{d.style.background='';}; + d.onclick=()=>{ input.value=item; input.focus(); cliCloseAC(); }; + box.appendChild(d); + }); + const row=input.closest('.cli-input-row'); + if(row){ row.style.position='relative'; row.appendChild(box); } +} +function cliCloseAC(){ const b=document.getElementById('cliACBox'); if(b) b.remove(); } + +// ── 工具栏操作 ─────────────────────────────────────────────── + +// 拖拽调整 CLI 面板高度 +(function(){ + const handle=document.getElementById('cliResizeHandle'); + if(!handle) return; + let sy=0, sh=0; + handle.addEventListener('mousedown',function(e){ + sy=e.clientY; sh=document.getElementById('cliOutput').offsetHeight; + e.preventDefault(); + document.addEventListener('mousemove',onDrag); + document.addEventListener('mouseup',function(){ document.removeEventListener('mousemove',onDrag); },{once:true}); + }); + function onDrag(e){ + const newH=Math.max(80,Math.min(600,sh+(sy-e.clientY))); + document.getElementById('cliOutput').style.height=newH+'px'; + const prb=document.getElementById('pendingRestartBtn'); + if(prb) prb.style.bottom=(newH+72)+'px'; + } +})(); + +// ── Stats (Usage Statistics) ──────────────────────────────── +async function loadStats(){ + try{ + const days=document.getElementById('statsDaysFilter')?.value||1; + const r=await api('GET','/api/stats?days='+days); + const s=r.summary||{}; + document.getElementById('statsTotalInput').textContent=fmtNum(s.totalInputTokens||0); + document.getElementById('statsTotalOutput').textContent=fmtNum(s.totalOutputTokens||0); + document.getElementById('statsCacheRead').textContent=fmtNum(s.totalCacheRead||0); + document.getElementById('statsTotalCost').textContent=s.estimatedCost||'$0'; + const totalReqs=Object.values(r.byModel||{}).reduce((a,b)=>a+(b.requestCount||0),0); + document.getElementById('statsTotalReqs').textContent=fmtNum(totalReqs); + // by model + const bmEl=document.getElementById('statsByModel'); + const bmEntries=Object.entries(r.byModel||{}).sort((a,b)=>(b[1].requestCount||0)-(a[1].requestCount||0)); + bmEl.innerHTML=bmEntries.length?bmEntries.map(([m,d])=>'
' + + '
' + esc(m) + '
' + + '
In: ' + fmtNum(d.inputTokens) + ' · Out: ' + fmtNum(d.outputTokens) + ' · Cache: ' + fmtNum(d.cacheRead||0) + ' · ' + d.requestCount + ' reqs · $' + d.cost + '
' + + '
').join(''):'
'+t('stats.noData')+'
'; + // by agent + const baEl=document.getElementById('statsByAgent'); + const baEntries=Object.entries(r.byAgent||{}).sort((a,b)=>(b[1].requestCount||0)-(a[1].requestCount||0)); + baEl.innerHTML=baEntries.length?baEntries.map(([a,d])=>'
' + + '
' + esc(a) + '
' + + '
In: ' + fmtNum(d.inputTokens) + ' · Out: ' + fmtNum(d.outputTokens) + ' · ' + d.requestCount + ' reqs · $' + d.cost + '
' + + '
').join(''):'
No data
'; + // by day chart + const chartEl=document.getElementById('statsChart'); + const dayEntries=Object.entries(r.byDay||{}).sort((a,b)=>a[0].localeCompare(b[0])); + if(!dayEntries.length){ chartEl.innerHTML='
'+t('stats.noData')+'
'; return; } + const maxTk=Math.max(...dayEntries.map(([,d])=>(d.inputTokens||0)+(d.outputTokens||0)),1); + chartEl.innerHTML=dayEntries.map(([day,d])=>{ + const total=(d.inputTokens||0)+(d.outputTokens||0); + const pct=Math.max(2,total/maxTk*100); + return '
' + + '
' + + '
' + day.slice(5) + '
' + + '
'; + }).join(''); + }catch(e){ toast('Stats load failed: '+e.message,'error'); } +} +function fmtNum(n){ if(n>=1e6) return (n/1e6).toFixed(1)+'M'; if(n>=1e3) return (n/1e3).toFixed(1)+'K'; return String(n); } + +// ── Cron(计划任务)────────────────────────────────────────── +async function loadCrons(){ + try{ + const r=await api('GET','/api/cron'); + const el=document.getElementById('cronList'); + if(!r.crons||!r.crons.length){ el.innerHTML='
'+t('cron.empty')+'
'; return; } + el.innerHTML=r.crons.map(c=>\`
+
+
+
\${esc(c.command)}
+
⏰ \${esc(c.schedule)}\${c.label?' · '+esc(c.label):''}
+
+ \${c.enabled?t('cron.enabled'):t('cron.disabled')} +
+
+ + + +
+
\`).join(''); + }catch(e){ toast((lang==='en'?'Cron load failed: ':'任务加载失败: ')+e.message,'error'); } +} +function openAddCron(){ + document.getElementById('cron-schedule').value=''; + document.getElementById('cron-command').value=''; + document.getElementById('cron-label').value=''; + openModal('addCronModal'); +} +async function submitAddCron(){ + const schedule=document.getElementById('cron-schedule').value.trim(); + const command=document.getElementById('cron-command').value.trim(); + const label=document.getElementById('cron-label').value.trim(); + if(!schedule||!command){toast(lang==='en'?'Fill in expression and command':'请填写表达式和命令','error');return;} + try{ + await api('POST','/api/cron',{schedule,command,label:label||'openclaw-manager'}); + toast(lang==='en'?'Task added':'任务已添加','success'); + closeModal('addCronModal'); await loadCrons(); + }catch(e){toast((lang==='en'?'Failed: ':'失败: ')+e.message,'error');} +} +async function toggleCron(idx,currentEnabled){ + try{ + await api('PUT','/api/cron/'+idx,{enabled:!currentEnabled}); + await loadCrons(); + }catch(e){toast((lang==='en'?'Failed: ':'失败: ')+e.message,'error');} +} +async function runCronNow(idx){ + toast(lang==='en'?'Running...':'执行中...','info'); + try{ + const r=await api('POST','/api/cron/'+idx+'/run'); + toast(lang==='en'?'Done':'完成','success'); + }catch(e){toast((lang==='en'?'Failed: ':'失败: ')+e.message,'error');} +} +async function deleteCron(idx){ + if(!confirm(lang==='en'?'Delete this task?':'删除此任务?'))return; + try{ await api('DELETE','/api/cron/'+idx); toast(lang==='en'?'Deleted':'已删除','success'); await loadCrons(); } + catch(e){toast((lang==='en'?'Failed: ':'失败: ')+e.message,'error');} +} + +// ── 工具栏操作 ─────────────────────────────────────────────── +function toggleMenu(){document.getElementById('mainMenu').classList.toggle('open');} +document.addEventListener('click',e=>{ + const app=document.getElementById('mainApp'); + if(app&&!e.target.closest('.menu-wrap'))document.getElementById('mainMenu').classList.remove('open'); +}); + +async function doRestart(){ + closeMenu(); dismissBanner(); hidePendingRestart(); + document.getElementById('cmdTitle').textContent=t('actions.restartTitle'); + document.getElementById('cmdOutput').textContent=t('actions.running'); openModal('cmdModal'); + try{ + const r=await api('POST','/api/gateway/restart'); + document.getElementById('cmdOutput').textContent=r.output||t('actions.restartSent'); + toast(t('actions.restartOk'),'success'); + }catch(e){ + document.getElementById('cmdOutput').textContent='❌ '+e.message+'\\n\\n'+t('actions.restartManualHint'); + toast(t('actions.restartFail')+e.message,'error'); + } +} +async function doDoctor(){ + closeMenu(); + document.getElementById('cmdTitle').textContent=t('actions.doctorTitle'); + document.getElementById('cmdOutput').textContent=t('actions.running'); openModal('cmdModal'); + try{ + const r=await api('POST','/api/gateway/doctor'); + document.getElementById('cmdOutput').textContent=r.output||t('actions.noOutput'); + }catch(e){document.getElementById('cmdOutput').textContent='❌ '+e.message;} +} +async function manualBackup(){ + closeMenu(); + try{const r=await api('POST','/api/config/backup'); toast(t('actions.backupSaved')+r.bakPath.split('/').pop(),'success');} + catch(e){toast(t('actions.backupFail')+e.message,'error');} +} +function openConfigDir(){ closeMenu(); api('POST','/api/folder/open').catch(()=>{}); } +function closeMenu(){ document.getElementById('mainMenu').classList.remove('open'); } + +// ── 日志 ───────────────────────────────────────────────────── +async function openLogs(){ closeMenu(); openModal('logModal'); await refreshLogs(); } +async function refreshLogs(){ + try{ + const r=await api('GET','/api/logs?n=300'); + document.getElementById('logContent').textContent=r.content||t('actions.logEmpty'); + document.getElementById('logPath').textContent=r.path||''; + const lb=document.getElementById('logContent'); lb.scrollTop=lb.scrollHeight; + }catch(e){document.getElementById('logContent').textContent=t('actions.logLoadFail')+e.message;} +} +function toggleAutoRefresh(){ + const cb=document.getElementById('autoRefresh'); + if(cb.checked){ logTimer=setInterval(refreshLogs,2000); } + else{ clearInterval(logTimer); logTimer=null; } +} +function closeLogs(){ clearInterval(logTimer); logTimer=null; document.getElementById('autoRefresh').checked=false; closeModal('logModal'); } + +// ── 回滚 ───────────────────────────────────────────────────── +async function openRollback(){ + closeMenu(); openModal('rollbackModal'); + const el=document.getElementById('backupList'); + el.innerHTML='
'+t('common.loading')+'
'; + try{ + const r=await api('GET','/api/backups'); + if(!r.backups.length){el.innerHTML='
'+t('actions.noBackups')+'
';return;} + el.innerHTML=r.backups.map(f=>\`
+
\${esc(f)}
+
+
\`).join(''); + }catch(e){el.innerHTML='
'+t('actions.loadFailed')+'
';} +} +async function doRestore(filename){ + const confirmMsg=t('actions.restoreConfirm').replace('{filename}',filename); + if(!confirm(confirmMsg))return; + try{ + await api('POST','/api/backups/restore',{filename}); + toast(t('actions.restored')+filename,'success'); + closeModal('rollbackModal'); await loadAll(); showRestartBanner(); + }catch(e){toast(t('actions.restoreFail')+e.message,'error');} +} + +// ── NAS 备份 ───────────────────────────────────────────────── +async function openNasModal(){ + closeMenu(); + const cfg=await api('GET','/api/backup/nas-config'); + document.getElementById('nas-host').value=cfg.nasHost||''; + document.getElementById('nas-port').value=cfg.nasPort||'22'; + document.getElementById('nas-user').value=cfg.nasUser||''; + document.getElementById('nas-sshkey').value=cfg.nasSshKey||''; + document.getElementById('nas-path').value=cfg.nasPath||'/volume1/OpenClaw/backups'; + document.getElementById('nas-legacy').checked=!!cfg.nasLegacyCipher; + document.getElementById('nas-password').value=''; + document.getElementById('nas-result').style.display='none'; + document.getElementById('nas-cron-section').style.display='none'; + document.getElementById('nas-pubkey-box').style.display='none'; + const authType=cfg.nasAuth||'password'; + document.getElementById(authType==='key'?'nas-auth-key':'nas-auth-pw').checked=true; + nasAuthChange(); + const bkType=cfg.nasBackupType||'full'; + document.getElementById(bkType==='essential'?'nas-bk-ess':'nas-bk-full').checked=true; + openModal('nasModal'); +} +function nasAuthChange(){ + const isKey=document.getElementById('nas-auth-key').checked; + document.getElementById('nas-pw-section').style.display=isKey?'none':''; + document.getElementById('nas-key-section').style.display=isKey?'':'none'; +} +function nasGetConfig(){ + return { + nasHost: document.getElementById('nas-host').value.trim(), + nasPort: document.getElementById('nas-port').value.trim()||'22', + nasUser: document.getElementById('nas-user').value.trim(), + nasAuth: document.getElementById('nas-auth-key').checked?'key':'password', + nasSshKey: document.getElementById('nas-sshkey').value.trim(), + nasPath: document.getElementById('nas-path').value.trim(), + nasLegacyCipher: document.getElementById('nas-legacy').checked, + nasBackupType: document.getElementById('nas-bk-ess').checked?'essential':'full', + }; +} +function nasShowResult(text,ok){ + const el=document.getElementById('nas-result'); + el.style.display=''; el.style.color=ok?'var(--success)':'#f85149'; + el.textContent=text; +} +async function nasGenKey(){ + const cfg=nasGetConfig(); + await api('PUT','/api/backup/nas-config',{...cfg,nasEnabled:true}); + try{const r=await api('POST','/api/backup/nas-keygen'); + document.getElementById('nas-sshkey').value=r.keyPath; + const box=document.getElementById('nas-pubkey-box'); + box.style.display=''; box.textContent=t('nas.pubkeyHint')+'\\n'+r.pubKey; + nasShowResult(t('nas.keyGenOk'),true); + }catch(e){nasShowResult(t('nas.keyGenFail')+e.message,false);} +} +async function nasTest(){ + const cfg=nasGetConfig(); + if(!cfg.nasHost||!cfg.nasUser){nasShowResult(t('nas.errNoHost'),false);return;} + await api('PUT','/api/backup/nas-config',{...cfg,nasEnabled:true}); + nasShowResult(t('nas.testing'),true); + try{ + const pwd=cfg.nasAuth==='password'?document.getElementById('nas-password').value:''; + const r=await api('POST','/api/backup/nas-test',{password:pwd}); + nasShowResult(r.ok?t('nas.testOk'):t('nas.testFail')+(r.error||r.output),r.ok); + }catch(e){nasShowResult('❌ '+e.message,false);} +} +async function nasBackupNow(){ + const cfg=nasGetConfig(); + if(!cfg.nasHost||!cfg.nasUser){nasShowResult(t('nas.errNoHost'),false);return;} + await api('PUT','/api/backup/nas-config',{...cfg,nasEnabled:true}); + nasShowResult(t('nas.backing'),true); + try{ + const pwd=cfg.nasAuth==='password'?document.getElementById('nas-password').value:''; + const r=await api('POST','/api/backup/nas-now',{password:pwd}); + nasShowResult(t('nas.backupOk')+r.tarName,true); + toast(t('nas.backupToast'),'success'); + }catch(e){nasShowResult(t('nas.backupFail')+e.message,false);} +} +function openNasCron(){ + const sec=document.getElementById('nas-cron-section'); + sec.style.display=sec.style.display==='none'?'':'none'; +} +async function nasSetCron(){ + const cfg=nasGetConfig(); + if(!cfg.nasHost||!cfg.nasUser){nasShowResult(t('nas.errNoHost'),false);return;} + await api('PUT','/api/backup/nas-config',{...cfg,nasEnabled:true}); + try{ + const pwd=cfg.nasAuth==='password'?document.getElementById('nas-password').value:''; + const cronTime=document.getElementById('nas-cron-time').value||'03:00'; + const r=await api('POST','/api/backup/nas-cron',{password:pwd,cronTime}); + nasShowResult(t('nas.cronOk')+'('+cronTime+')',true); + toast(t('nas.cronToast'),'success'); + }catch(e){nasShowResult('❌ '+e.message,false);} +} + + +// ── 目录设置 ───────────────────────────────────────────────── +function openSetupModal(){ closeMenu(); openModal('setupModal'); } +async function submitSetup(){ + const dir=document.getElementById('setup-dir').value.trim(); + const err=document.getElementById('setup-err'); + if(!dir){err.textContent=t('actions.setupEmpty');return;} + try{ + const r=await api('POST','/api/setup',{dir}); + if(r.ok){ toast(t('actions.setupSwitching'),'success'); setTimeout(()=>location.reload(),800); } + else err.textContent=r.error||t('actions.setupInvalid'); + }catch(e){err.textContent=t('actions.setupReqFail')+e.message;} +} + +// ── 重启横幅 ───────────────────────────────────────────────── +function showRestartBanner(){ document.getElementById('restartBanner').classList.add('show'); } +function dismissBanner(){ document.getElementById('restartBanner').classList.remove('show'); } +function deferRestart(){ + dismissBanner(); + document.getElementById('pendingRestartBtn').style.display='block'; +} +function hidePendingRestart(){ + document.getElementById('pendingRestartBtn').style.display='none'; +} + +// ── 密码显示切换 ───────────────────────────────────────────── +function togglePwd(inputId,btn){ + const el=document.getElementById(inputId); + if(!el)return; + if(el.type==='password'){el.type='text';btn.textContent='🙈';} + else{el.type='password';btn.textContent='👁';} +} + +// ── 工具 ───────────────────────────────────────────────────── +const OCM_CLIENT_VERSION='${APP_VERSION}'; +async function api(method,path,body){ + const opts={method,headers:{'Content-Type':'application/json'}}; + if(body) opts.body=JSON.stringify(body); + const r=await fetch(path,opts); + const sv=r.headers.get('X-OCM-Version'); + if(sv&&sv!==OCM_CLIENT_VERSION&&!window._ocmVersionWarn){window._ocmVersionWarn=true;toast('Server updated to v'+sv+'. Refresh page for latest version.','info');} + const d=await r.json(); + if(!r.ok){ const e=new Error(d.error||r.status); e.data=d; e.status=r.status; throw e; } + return d; +} +function openModal(id){document.getElementById(id).classList.add('open');} +function closeModal(id){document.getElementById(id).classList.remove('open');} +function setDot(s){const el=document.getElementById('dot');if(el)el.className='dot '+s;} +function toast(msg,type='info'){ + const c=document.getElementById('toast'); + const el=document.createElement('div'); + el.style.cssText='background:var(--card);border:1px solid var(--border);border-radius:8px;padding:11px 15px;font-size:13px;max-width:340px;animation:fadeIn .2s'; + if(type==='success') el.style.borderColor='var(--success)'; + if(type==='error') el.style.borderColor='var(--danger)'; + el.textContent=msg; + c.appendChild(el); + setTimeout(()=>el.remove(),4500); +} +function esc(s){ return String(s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } + +// ── Tabs ───────────────────────────────────────────────────── +document.querySelectorAll('.tab').forEach(t=>{ + t.addEventListener('click',()=>{ + document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active')); + document.querySelectorAll('.panel').forEach(x=>x.classList.remove('active')); + t.classList.add('active'); + document.getElementById('panel-'+t.dataset.tab).classList.add('active'); + // lazy-load + if(t.dataset.tab==='dashboard'&&!dashLoaded) loadDashboard(); + if(t.dataset.tab==='stats') loadStats(); + if(t.dataset.tab==='cron') loadCrons(); + }); +}); + +// ── On load: initialize app ────────────────────────────────── +(function() { + LS.del('ocm_mode'); + applyLang(); + checkStatus().then(() => { loadAll(); setTimeout(() => loadDashboard(), 0); }).catch(e => console.error('Init error:', e)); + startHealthPolling(); +})();`; + +const MAIN_HTML = ` + + + + +OpenClaw Manager + + + +${MAIN_HTML_BODY} + +`; + +function assertBrowserScriptSyntax(name, scriptText) { + const tmpFile = path.join(os.tmpdir(), `ocm-${name}-${process.pid}.js`); + fs.writeFileSync(tmpFile, scriptText, 'utf8'); + const r = spawnSync(process.execPath, ['--check', tmpFile], { encoding: 'utf8', timeout: 5000 }); + try { fs.unlinkSync(tmpFile); } catch (_) {} + if (r.status !== 0) { + const detail = (r.stderr || r.stdout || 'unknown syntax error').trim(); + throw new Error( + `${name} syntax check failed.\n${detail}\nHint: in MAIN_HTML_SCRIPT strings, write "\\\\n" instead of "\\n".` + ); + } +} + + +function parseDiscordLinkOrId(input){ + const raw=(input||'').trim(); + if(!raw) return null; + // URL form: https://discord.com/channels// + const m = raw.match(/discord(?:app)?\.com\/channels\/(\d+)\/(\d+)/i); + if(m) return { guildId: m[1], channelId: m[2] }; + // Accept plain numeric id + if(/^\d+$/.test(raw)) return { guildId: '', channelId: raw }; + // Accept channel: + const m2 = raw.match(/^channel:(\d+)$/i); + if(m2) return { guildId: '', channelId: m2[1] }; + return null; +} +assertBrowserScriptSyntax('main-html-script', MAIN_HTML_SCRIPT); + +// ── HTTP 服务器 ─────────────────────────────────────────────── +const server = http.createServer(async (req, res) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); + res.setHeader('X-OCM-Version', APP_VERSION); + if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } + + const urlObj = new URL(req.url, `http://127.0.0.1:${PORT}`); + + try { + if (urlObj.pathname.startsWith('/api/')) { + const body = ['POST', 'PUT', 'PATCH'].includes(req.method) ? await parseBody(req) : {}; + await handleApi(req, res, urlObj, body); + } else { + const needsSetup = !(await configExists()); + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'ETag': '"ocm-' + APP_VERSION + '"' }); + res.end(needsSetup ? SETUP_HTML : MAIN_HTML); + } + } catch (err) { + console.error('[ERROR]', err.message); + if (!res.headersSent) { + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } + } +}); + +server.listen(PORT, HOST, () => { + const localUrl = `http://localhost:${PORT}`; + console.log(''); + console.log(`🦀 OpenClaw Manager v${APP_VERSION}`); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log('📂 Dir: ' + OPENCLAW_DIR); + console.log('🌐 Local: ' + localUrl); + if (HOST === '0.0.0.0') { + const lanIp = getLanIP(); + if (lanIp) console.log('🌐 LAN: ' + `http://${lanIp}:${PORT}`); + } + console.log('💡 Switch dir: node openclaw-manager.js --dir /path/to/.openclaw'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log('Ctrl+C to stop'); + if (!process.env.ELECTRON) openBrowser(localUrl); + // Notify Electron main process that server is ready + if (process.send) process.send({ type: 'server-ready', port: PORT }); +}); + +server.on('error', err => { + if (err.code === 'EADDRINUSE') + console.error(`❌ Port ${PORT} is already in use. Close the other process or use --port to specify a different port.`); + else + console.error('❌ Failed to start:', err.message); + process.exit(1); +});