From 81b903243e49e40acd04825cfb6f61e2897f2fd3 Mon Sep 17 00:00:00 2001 From: ethernet Date: Mon, 20 Jul 2026 20:24:24 -0400 Subject: [PATCH] feat(desktop): bundle and display third-party licenses Generate JavaScript and Python dependency license inventories during desktop builds, ship them with Electron and Nix packages, and surface them in Settings. Use the locked Nix Python closure for Python license generation and preserve reproducible timestamps through SOURCE_DATE_EPOCH. --- LICENSE_AUDIT.md | 282 +++++++++++ apps/desktop/electron/main.ts | 41 ++ apps/desktop/electron/preload.ts | 1 + apps/desktop/package.json | 16 +- apps/desktop/scripts/generate-js-licenses.mjs | 182 ++++++++ .../scripts/generate-python-licenses.mjs | 181 +++++++ apps/desktop/src/app/settings/index.tsx | 14 +- .../src/app/settings/licenses-settings.tsx | 145 ++++++ apps/desktop/src/app/settings/types.ts | 1 + apps/desktop/src/global.d.ts | 1 + apps/desktop/src/i18n/en.ts | 3 +- apps/desktop/src/i18n/ja.ts | 3 +- apps/desktop/src/i18n/types.ts | 1 + apps/desktop/src/i18n/zh-hant.ts | 3 +- apps/desktop/src/i18n/zh.ts | 3 +- nix/desktop.nix | 11 +- nix/hermes-agent.nix | 8 +- package-lock.json | 442 +++++++++++++++++- pyproject.toml | 2 +- uv.lock | 63 +++ 20 files changed, 1390 insertions(+), 13 deletions(-) create mode 100644 LICENSE_AUDIT.md create mode 100644 apps/desktop/scripts/generate-js-licenses.mjs create mode 100644 apps/desktop/scripts/generate-python-licenses.mjs create mode 100644 apps/desktop/src/app/settings/licenses-settings.tsx diff --git a/LICENSE_AUDIT.md b/LICENSE_AUDIT.md new file mode 100644 index 00000000000..8e4a63c81c9 --- /dev/null +++ b/LICENSE_AUDIT.md @@ -0,0 +1,282 @@ +# Hermes Agent โ€” Dependency License Audit + +**Date:** 2026-07-20 +**Project license:** MIT (Copyright 2025 Nous Research) +**Audited by:** ethie (automated โ€” PyPI/npm/GitHub registry queries + lockfile parsing) + +## Scope + +Every dependency that ships in a Hermes Agent distribution, across all three +ecosystems and all four npm lockfiles: + +| Ecosystem | Lockfile | Packages | +|-----------|----------|----------| +| Python | `uv.lock` | 233 | +| npm (root workspaces) | `package-lock.json` | 1,387 | +| npm (website / Docusaurus) | `website/package-lock.json` | 1,443 | +| npm (whatsapp-bridge) | `scripts/whatsapp-bridge/package-lock.json` | 167 | +| npm (photon sidecar) | `plugins/platforms/photon/sidecar/package-lock.json` | 144 | +| npm (deduped across all 4) | โ€” | 2,401 unique | +| Rust | `apps/bootstrap-installer/src-tauri/Cargo.toml` | 19 direct (no `Cargo.lock`) | + +**Total: 2,653 dependency versions audited.** + +## Executive summary + +The overwhelming majority of dependencies (2,564 / 2,653 = 96.6%) are under +permissive licenses (MIT, ISC, Apache-2.0, BSD, PSF) that are fully +compatible with the project's MIT license. No action is needed for those. + +**9 packages require attention** โ€” they fall into copyleft, attribution-required, +proprietary, or unlicensed categories that create real compliance obligations. +The most serious is **`libsignal` (GPL-3.0)** in the whatsapp-bridge, which has +strong copyleft implications if the bridge is distributed as part of Hermes. + +**There is currently zero license-compliance scanning in CI.** The existing +`osv-scanner.yml` covers CVEs only; `supply-chain-audit.yml` covers malware +patterns only. Neither checks license compatibility. There is no `NOTICE` or +`THIRD_PARTY_LICENSES` file at the repo root (the only `NOTICE` is inside +`plugins/security-guidance/` for forked Anthropic Apache-2.0 code). + +--- + +## Current compliance posture + +| What exists | What it covers | Gap | +|-------------|----------------|-----| +| `osv-scanner.yml` | Known CVEs in pinned deps (vuln DB) | Does not check licenses | +| `supply-chain-audit.yml` | Malware patterns in PR diffs (.pth, base64+exec, etc.) | Does not check licenses | +| `tests/docker/test_license_file_present.py` | Our own LICENSE ships in the Docker image | Only checks our LICENSE, not deps | +| `plugins/security-guidance/NOTICE` | Forked Anthropic code attribution | Local to that plugin only | +| **Missing** | **License compatibility scanning** | **No CI check prevents adding a GPL/AGPL/unlicensed dep** | +| **Missing** | **NOTICE / THIRD_PARTY_LICENSES file** | **No bundled attribution for permissive licenses that require it** | + +--- + +## Findings by severity + +### ๐Ÿ”ด Critical โ€” GPL-3.0 (strong copyleft) + +#### `libsignal@6.0.0` (npm, whatsapp-bridge) + +- **License:** GPL-3.0 +- **Source:** `https://github.com/WhiskeySockets/libsignal-node` +- **Where:** `scripts/whatsapp-bridge/package.json` โ†’ `@whiskeysockets/baileys` depends on it +- **Direct dep?** No โ€” transitive via `@whiskeysockets/baileys` +- **Compliance issue:** GPL-3.0 is strong copyleft. If the whatsapp-bridge is + distributed as part of Hermes Agent (which is MIT), the GPL-3.0 license + would require the *entire combined work* to be GPL-3.0. This is a + license conflict โ€” MIT and GPL-3.0 are compatible (MIT code can be included + in a GPL-3.0 project), but only if the combined work is distributed under + GPL-3.0, which contradicts Hermes's MIT license. +- **Mitigation:** The whatsapp-bridge is a standalone sidecar script + (`scripts/whatsapp-bridge/bridge.js`), not imported by the Python core. + If it ships as a separate process invoked via subprocess (not linked + into the Hermes binary), the "aggregate, not derivative" argument may + apply. **This needs a legal review** to confirm the boundary. + +### ๐ŸŸ  High โ€” LGPL-3.0 (weak copyleft) + +LGPL allows linking from non-LGPL code, but modifications to the LGPL +library itself must be released under LGPL, and the license + source +must be offered to recipients. + +#### Python + +| Package | Version | Extra | Notes | +|---------|---------|-------|-------| +| `edge-tts` | 7.2.7 | `[edge-tts]` | Default TTS provider. LICENSE file confirms: one file (srt_composer.py) is MIT, rest is LGPLv3. | +| `python-telegram-bot` | 22.6 | `[messaging]`, `[termux]` | PEP 639 `license_expression: LGPL-3.0-only`. LICENSE file ships GPL-3 text (LGPL is a permitted additional permission). | + +#### npm (sharp / libvips native binaries) + +| Package | Version | License | Notes | +|---------|---------|---------|-------| +| `@img/sharp-libvips-*` (10 platform variants) | 1.3.2 | LGPL-3.0-or-later | libvips native binaries. Pulled by `sharp` (image processing). | +| `@img/sharp-win32-*` (3 variants) | 0.35.3 | Apache-2.0 AND LGPL-3.0-or-later | sharp Windows binaries. | +| `@img/sharp-wasm32` | 0.35.3 | Apache-2.0 AND LGPL-3.0-or-later AND MIT | sharp wasm binary. | + +- **Where:** Root `package-lock.json`, transitive via `sharp` +- **Direct dep?** No โ€” `sharp` is not declared in any workspace `package.json`; + it's pulled transitively by an upstream package. +- **Compliance issue:** LGPL-3.0-or-later allows use in non-LGPL projects, + but requires: (1) providing the LGPL license text, (2) providing a way to + relink against a modified version of the library. For statically-linked + native binaries, this means documenting how to swap the .node/.so/.dll. +- **Mitigation:** Include LGPL license text in NOTICE file. The "provide a + way to relink" obligation is satisfied by the npm package boundary (users + can `npm install` a different `sharp` build). + +### ๐ŸŸก Medium โ€” MPL-2.0 / MPL-1.1 (file-level weak copyleft) + +MPL is file-level copyleft: modifications to MPL-licensed *files* must stay +MPL, but it does not infect the rest of the project. Compatible with MIT. + +| Package | Version | Ecosystem | Notes | +|---------|---------|-----------|-------| +| `lightningcss` (+ 11 platform variants) | 1.32.0 | npm | CSS transformer, pulled by `@tailwindcss` + `vite`. | +| `lunr-languages` | 1.14.0 | npm (website) | Language packs for Docusaurus search. Old MPL-1.1. | +| `certifi` | 2026.5.20 | python | CA bundle. | +| `mautrix` | 0.21.0 | python | Matrix SDK (`[matrix]` extra). | +| `pathspec` | 1.1.1 | python | .gitignore matching. Core dep. | + +- **Compliance:** Include MPL license text in NOTICE. No source disclosure + obligation. If we modify any MPL file, that file must stay MPL. + +### ๐ŸŸก Medium โ€” CC-BY-4.0 (attribution required) + +CC-BY-4.0 requires attribution in a way that is "reasonable to the medium." +For software distribution, this means the NOTICE file. + +| Package | Version | Ecosystem | Notes | +|---------|---------|-----------|-------| +| `@vscode/codicons` | 0.0.45 | npm | Icon font, direct dep in `apps/desktop` + `apps/bootstrap-installer`. | +| `caniuse-lite` | 1.0.30001799 | npm | Browser support DB, transitive via `browserslist`. | + +- **Compliance:** Add attribution lines to NOTICE file. + +### ๐ŸŸก Medium โ€” Non-standard / proprietary license + +#### `gsap@3.15.0` (npm) + +- **License:** "Standard 'no charge' license: https://gsap.com/standard-license" +- **Where:** `web/package.json` (direct dep), also pulled by `@nous-research/ui` +- **Compliance issue:** This is not an OSI-approved license. GSAP's own terms + apply. Per gsap.com: "GSAP is now free for everyone, thanks to Webflow's + support!" โ€” but the license has specific terms (no resale as a standalone + library, etc.) that differ from MIT. +- **Compliance:** Reproduce GSAP's license terms in the NOTICE file with a + link to the full text. + +### ๐ŸŸข Low โ€” No license (all-rights-reserved) + +| Package | Version | Ecosystem | Notes | +|---------|---------|-----------|-------| +| `hindsight-client` | 0.6.1 | python | Optional `[hindsight]` extra. No LICENSE in wheel, no repo link. | +| `format` | 0.2.2 | npm (website) | sprintf library. No LICENSE anywhere. Transitive via Docusaurus. | + +- **Compliance issue:** Without a license, the default is "all rights reserved" + โ€” the copyright holder has not granted permission to use, modify, or + distribute. Technically, using these at all is a copyright infringement. +- **Mitigation:** + - `hindsight-client`: Contact the "Hindsight Team" for a license + clarification, or remove the extra and document it as user-installed. + - `format`: It's a tiny, ancient (2012-era) sprintf utility pulled + transitively by Docusaurus. Contact the author (samsonjs) or replace + with a licensed alternative. Alternatively, Docusaurus may have already + addressed this upstream. + +### โœ… Resolved (were "missing" in metadata, verified permissive) + +These packages had no license in their npm/PyPI metadata but were verified +permissive by fetching their LICENSE files from GitHub or inspecting tarballs: + +| Package | Resolved license | How verified | +|---------|-----------------|--------------| +| `khroma` | MIT | GitHub `fabiospampinato/khroma` LICENSE file | +| `eval` | MIT | GitHub `pierrec/node-eval` LICENSE file | +| `require-like` | MIT | GitHub `felixge/node-require-like` package.json | +| `qrcode-terminal` | MIT | GitHub `gtanner/qrcode-terminal` LICENSE file | +| `@photon-ai/slack` | MIT | Tarball `package/proto/LICENSE`: "Copyright (c) 2025 Photon AI" | +| `@photon-ai/whatsapp-business` | Likely MIT | Same Photon AI scope; no LICENSE in tarball (unverified) | +| `agent-client-protocol` | Apache-2.0 | GitHub `agentclientprotocol/python-sdk` LICENSE | +| `azure-core` | MIT | GitHub `Azure/azure-sdk-for-python` LICENSE | +| `azure-identity` | MIT | GitHub `Azure/azure-sdk-for-python` LICENSE | +| `fal-client` | Apache-2.0 | GitHub `fal-ai/fal` LICENSE | +| `mistralai` | Apache-2.0 | GitHub `mistralai/client-python` LICENSE | +| `microsoft-teams-*` (4 pkgs) | MIT | GitHub `microsoft/teams.py` LICENSE | +| `honcho-ai` | Apache-2.0 | PyPI `license_expression` field | + +### โœ… No issue (compound permissive licenses) + +| Package | License | Why it's fine | +|---------|---------|---------------| +| `@bufbuild/protobuf` | Apache-2.0 AND BSD-3-Clause | Both permissive | +| `dompurify` | MPL-2.0 OR Apache-2.0 | Can elect Apache-2.0 | + +--- + +## Rust (Cargo) + +No `Cargo.lock` exists in the repo, so a transitive audit is deferred. +All 19 direct dependencies in `apps/bootstrap-installer/src-tauri/Cargo.toml` +are permissive (MIT or Apache-2.0 or both). Run `cargo generate-lockfile` +before the installer is first built/distributed to enable the transitive check. + +--- + +## Compliance plan + +### 1. Create a root `NOTICE` file + +The biggest gap. Many permissive licenses (MIT, Apache-2.0, BSD) require +retaining the copyright + license notice when distributed. Currently we ship +our own `LICENSE` but don't bundle attribution for dependencies. + +**Action:** Generate `NOTICE` (or `THIRD_PARTY_LICENSES.md`) at repo root, +containing: +- The project's own MIT license + copyright +- For each non-MIT dependency: the license name, copyright holder, and + license text (or link) +- Attribution blocks for CC-BY-4.0 packages (codicons, caniuse-lite) +- Reproduced terms for GSAP's non-standard license +- LGPL/MPL license texts for the copyleft deps + +This can be auto-generated from the lockfiles using a tool like +`pip-licenses` (Python) + `license-checker` (npm) in CI. + +### 2. Add license-scanning CI + +**Action:** Add a `license-audit.yml` workflow that: +- Runs `pip-licenses` (or `uv pip licenses`) against `uv.lock` +- Runs `license-checker` (or `licensee`) against all 4 `package-lock.json` files +- Fails on: GPL, AGPL, unlisted, or "unknown" licenses +- Warns on: LGPL, MPL, CC-BY, non-OSI licenses (advisory, not blocking) +- Posts findings as a collapsible `
` PR comment + +This closes the gap where a PR could add a GPL-3.0 dependency and CI would +never catch it. + +### 3. Resolve the `libsignal` GPL-3.0 question + +**Action:** Legal review to confirm whether `scripts/whatsapp-bridge/` is a +separate aggregate (not a derivative of Hermes) when distributed. If the +bridge ships as a standalone subprocess invoked via `node bridge.js`, the +GPL-3.0 obligation may not extend to Hermes itself. If there's any doubt, +document the boundary clearly or move the bridge to a separate repo. + +### 4. Resolve unlicensed packages + +**Action:** +- `hindsight-client`: Contact the Hindsight Team for a license, or move the + `[hindsight]` extra to lazy-install only (it already is, partially). +- `format`: Check if Docusaurus has dropped or replaced it upstream. If not, + file an issue with samsonjs/format to add a license, or patch it out. + +### 5. Generate `Cargo.lock` for the bootstrap installer + +**Action:** Run `cargo generate-lockfile` in +`apps/bootstrap-installer/src-tauri/` and commit it, so the transitive Rust +dependency tree is auditable before the installer is distributed. + +### 6. Document the MPL/LGPL boundary + +**Action:** Add a section to `CONTRIBUTING.md` documenting that: +- MPL-2.0 deps are acceptable (file-level copyleft, MIT-compatible) but + modifications to MPL files must stay MPL +- LGPL-3.0 deps are acceptable (linking is fine) but the NOTICE file must + carry the LGPL text and document how to relink +- GPL/AGPL deps are NOT acceptable in the core or any workspace that ships + with Hermes โ€” they must be isolated in standalone sidecar processes + +--- + +## Appendix: tools used + +This audit was performed with: +- `uv.lock` parsing (233 Python packages, PEP 639 `license_expression` field) +- `package-lock.json` parsing (4 lockfiles, 2,401 unique npm packages) +- PyPI JSON API (`/pypi/{name}/{version}/json`) for Python license metadata +- npm registry API (`registry.npmjs.org`) for npm license metadata +- GitHub raw (`raw.githubusercontent.com`) for LICENSE file verification +- npm tarball inspection for packages with no metadata diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index b67e8b69f3a..bd3a802c0e6 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -8521,6 +8521,47 @@ ipcMain.handle('hermes:readFileText', async (_event, filePath) => { } }) +// Read a bundled license file from the app's extraResources directory. +// The desktop build generates dependencies.txt (JS deps) and +// dependencies-python.txt (Python deps) via the generate-licenses scripts, +// and electron-builder ships them via extraResources so they land at +// process.resourcesPath/ in the packaged app. In dev (running +// from a checkout), they live in apps/desktop/dist/. +// +// Returns the file contents as a string, or null if the file is missing. +// The Settings โ†’ Licenses page uses this to display third-party license +// attributions required by MIT/Apache/BSD licenses. +ipcMain.handle('hermes:readBundledLicenseFile', async (_event, filename: string) => { + if (!filename || typeof filename !== 'string') { + return null + } + + // Only allow plain filenames โ€” no path traversal. + const safe = path.basename(filename) + if (safe !== filename) { + return null + } + + // electron-builder extraResources place files directly in resources/. + // The Nix package preserves the renderer's dist/ directory instead, so + // support both layouts plus the local dev build location. + const candidates = [ + process.resourcesPath ? path.join(process.resourcesPath, safe) : null, + process.resourcesPath ? path.join(process.resourcesPath, 'dist', safe) : null, + path.join(APP_ROOT, 'dist', safe) + ].filter(Boolean) + + for (const p of candidates) { + try { + return await fs.promises.readFile(p, 'utf8') + } catch { + // ENOENT โ€” try next candidate + } + } + + return null +}) + ipcMain.handle('hermes:selectPaths', async (_event, options: any = {}) => { const properties = options?.directories ? ['openDirectory'] : ['openFile'] diff --git a/apps/desktop/electron/preload.ts b/apps/desktop/electron/preload.ts index 732d13a5366..1bfc43391e8 100644 --- a/apps/desktop/electron/preload.ts +++ b/apps/desktop/electron/preload.ts @@ -61,6 +61,7 @@ contextBridge.exposeInMainWorld('hermesDesktop', { requestMicrophoneAccess: () => ipcRenderer.invoke('hermes:requestMicrophoneAccess'), readFileDataUrl: filePath => ipcRenderer.invoke('hermes:readFileDataUrl', filePath), readFileText: filePath => ipcRenderer.invoke('hermes:readFileText', filePath), + readBundledLicenseFile: filename => ipcRenderer.invoke('hermes:readBundledLicenseFile', filename), selectPaths: options => ipcRenderer.invoke('hermes:selectPaths', options), writeClipboard: text => ipcRenderer.invoke('hermes:writeClipboard', text), saveImageFromUrl: url => ipcRenderer.invoke('hermes:saveImageFromUrl', url), diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 743269c1047..a7549f9db31 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -20,7 +20,7 @@ "profile:main:cpu": "wait-on http://127.0.0.1:5174 && node scripts/bundle-electron-main.mjs --dev && cross-env XCURSOR_SIZE=24 NODE_OPTIONS=--cpu-prof HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron .", "start": "npm run build && electron .", "prebuild": "tsc -b . --clean", - "build": "node scripts/assert-root-install.mjs && node scripts/write-build-stamp.mjs && vite build && node scripts/bundle-electron-main.mjs && node scripts/stage-native-deps.mjs", + "build": "node scripts/assert-root-install.mjs && node scripts/write-build-stamp.mjs && vite build && npm run generate-licenses && node scripts/bundle-electron-main.mjs && node scripts/stage-native-deps.mjs", "postbuild": "node scripts/assert-dist-built.mjs", "prebuilder": "node scripts/patch-electron-builder-mac-binary.mjs", "builder": "cross-env NODE_OPTIONS=--max-old-space-size=16384 node scripts/run-electron-builder.mjs", @@ -53,7 +53,10 @@ "check": "npm run typecheck && npm run test && npm run test:desktop:all", "test:e2e": "playwright test e2e/", "test:e2e:visual": "WLR_BACKENDS=headless WLR_NO_HARDWARE_CURSORS=1 cage -- npx playwright test e2e/ --reporter=list", - "test:e2e:update-snapshots": "WLR_BACKENDS=headless WLR_NO_HARDWARE_CURSORS=1 cage -- npx playwright test e2e/ --reporter=list --update-snapshots" + "test:e2e:update-snapshots": "WLR_BACKENDS=headless WLR_NO_HARDWARE_CURSORS=1 cage -- npx playwright test e2e/ --reporter=list --update-snapshots", + "generate-licenses": "node scripts/generate-js-licenses.mjs && node scripts/generate-python-licenses.mjs", + "generate-licenses:js": "node scripts/generate-js-licenses.mjs", + "generate-licenses:python": "node scripts/generate-python-licenses.mjs" }, "dependencies": { "@assistant-ui/react": "^0.14.23", @@ -151,6 +154,7 @@ "eslint-plugin-unused-imports": "^4.4.1", "globals": "^16.5.0", "jsdom": "^29.1.1", + "license-checker": "^25.0.1", "prettier": "^3.8.3", "rcedit": "^5.0.2", "tsx": "^4.22.4", @@ -194,6 +198,14 @@ { "from": "assets/icon.ico", "to": "icon.ico" + }, + { + "from": "dist/dependencies.txt", + "to": "dependencies.txt" + }, + { + "from": "dist/dependencies-python.txt", + "to": "dependencies-python.txt" } ], "asar": true, diff --git a/apps/desktop/scripts/generate-js-licenses.mjs b/apps/desktop/scripts/generate-js-licenses.mjs new file mode 100644 index 00000000000..f8b916fcaeb --- /dev/null +++ b/apps/desktop/scripts/generate-js-licenses.mjs @@ -0,0 +1,182 @@ +// Generates a bundled third-party license inventory for the desktop app's +// JavaScript dependencies. +// +// Scans the installed node_modules tree with license-checker, then emits +// dist/dependencies.txt โ€” a plain-text file containing each package's +// name, version, license, repository URL, and full license text when +// available. This file is shipped in the electron-builder app bundle via +// extraResources and surfaced in the Settings โ†’ Licenses page. +// +// Why license-checker (standalone CLI) instead of rollup-plugin-license: +// Vite 8 uses rolldown under the hood, and rollup-plugin-license is a +// rollup plugin โ€” compatibility is uncertain and the plugin approach +// only covers deps that end up in the bundle. license-checker scans the +// full node_modules tree, so the inventory is complete even for deps +// that are never imported at runtime (native modules, etc.). +import { execFileSync } from 'node:child_process' +import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +// __dirname = apps/desktop/scripts/ +// repoRoot = apps/desktop/scripts/../../.. = repo root +const repoRoot = path.resolve(__dirname, '..', '..', '..') +const distDir = path.resolve(__dirname, '..', 'dist') + +// We scan from the repo root, not the desktop workspace, because npm +// workspaces hoist all dependencies to the root node_modules/. Scanning +// from apps/desktop only finds the handful of deps that have their own +// node_modules (non-hoisted native modules etc.), while the repo root +// captures the full production dependency tree (77+ packages). +const SCAN_ROOT = repoRoot + +function runLicenseChecker() { + const bin = path.join(repoRoot, 'node_modules', '.bin', 'license-checker') + // --production: exclude devDependencies โ€” the shipped app only carries + // production deps in its bundle, so the license inventory matches. + // --json: structured output for easy post-processing. + // --relativeLicensePath: emit paths to LICENSE files so we can read them. + const args = ['--production', '--json', '--relativeLicensePath'] + const raw = execFileSync(bin, args, { + cwd: SCAN_ROOT, + encoding: 'utf8', + maxBuffer: 50 * 1024 * 1024, + stdio: ['pipe', 'pipe', 'pipe'], + }) + return JSON.parse(raw) +} + +function readLicenseFile(relPath) { + if (!relPath) { + return null + } + + // license-checker returns paths relative to the scan root (cwd). + // In a workspace, these look like "node_modules/foo/LICENSE" and + // resolve correctly from the workspace root. + const full = path.resolve(SCAN_ROOT, relPath) + if (!existsSync(full)) { + return null + } + + try { + return readFileSync(full, 'utf8').trim() + } catch { + return null + } +} + +// Format the inventory as a human-readable plain-text file. Each package +// gets a delimited block with its metadata + full license text, so the +// result is a self-contained NOTICE-equivalent. +function generatedAt() { + // Nix sets SOURCE_DATE_EPOCH for reproducible builds. Local builds retain a + // useful wall-clock timestamp when that conventional variable is absent. + const epoch = Number(process.env.SOURCE_DATE_EPOCH) + return new Date(Number.isFinite(epoch) && epoch > 0 ? epoch * 1000 : Date.now()).toISOString() +} + +function formatInventory(entries) { + const lines = [] + + lines.push('Third-Party Software Licenses') + lines.push('=============================') + lines.push('') + lines.push('This file lists the open-source licenses of the npm dependencies') + lines.push('bundled with the Hermes desktop application.') + lines.push('') + lines.push(`Generated: ${generatedAt()}`) + lines.push(`Total packages: ${entries.length}`) + lines.push('') + + for (const entry of entries) { + lines.push('โ”€'.repeat(70)) + lines.push(`Package: ${entry.name}@${entry.version}`) + lines.push(`License: ${entry.license}`) + if (entry.repository) { + lines.push(`Repository: ${entry.repository}`) + } + if (entry.publisher) { + lines.push(`Publisher: ${entry.publisher}`) + } + if (entry.email) { + lines.push(`Email: ${entry.email}`) + } + lines.push('') + if (entry.licenseText) { + lines.push(entry.licenseText) + } else { + lines.push('(No license text file found โ€” see the package repository for details.)') + } + lines.push('') + } + + return lines.join('\n') +} + +function main() { + mkdirSync(distDir, { recursive: true }) + + let data + try { + data = runLicenseChecker() + } catch (e) { + // license-checker not installed or failed โ€” write a stub so the build + // doesn't fail and the Settings page can show a graceful message. + const stub = [ + 'Third-Party Software Licenses', + '=============================', + '', + 'WARNING: license-checker was not found or failed to run.', + 'The JavaScript dependency license inventory could not be generated.', + '', + `Error: ${e.message}`, + '', + ].join('\n') + writeFileSync(path.join(distDir, 'dependencies.txt'), stub, 'utf8') + console.warn(`[generate-js-licenses] license-checker failed: ${e.message}`) + return + } + + // Dedupe by name@version โ€” the same package can appear once. + const seen = new Map() + + for (const [pkgKey, info] of Object.entries(data)) { + // pkgKey is "name@version" + const atIdx = pkgKey.lastIndexOf('@') + const name = atIdx > 0 ? pkgKey.slice(0, atIdx) : pkgKey + const version = atIdx > 0 ? pkgKey.slice(atIdx + 1) : '' + + const dedupeKey = `${name}@${version}` + if (seen.has(dedupeKey)) { + continue + } + + const licenseText = info.licenseFile + ? readLicenseFile(info.licenseFile) + : null + + seen.set(dedupeKey, { + name, + version, + license: info.licenses || info.license || 'UNKNOWN', + repository: info.repository || null, + publisher: info.publisher || null, + email: info.email || null, + licenseText, + }) + } + + const entries = [...seen.values()].sort((a, b) => + a.name.toLowerCase().localeCompare(b.name.toLowerCase()), + ) + + const output = formatInventory(entries) + const outPath = path.join(distDir, 'dependencies.txt') + writeFileSync(outPath, output, 'utf8') + + console.log(`[generate-js-licenses] Wrote ${entries.length} packages to ${outPath}`) +} + +main() diff --git a/apps/desktop/scripts/generate-python-licenses.mjs b/apps/desktop/scripts/generate-python-licenses.mjs new file mode 100644 index 00000000000..5a6bb9ec87d --- /dev/null +++ b/apps/desktop/scripts/generate-python-licenses.mjs @@ -0,0 +1,181 @@ +// Generates a bundled third-party license inventory for the Hermes Agent +// Python dependencies. +// +// Shells out to `pip-licenses` against the active Python environment, +// emitting dist/dependencies-python.txt โ€” a plain-text file containing +// each package's name, version, license, and full license text (when +// available in the installed dist-info). +// +// pip-licenses reads from the installed environment, so the inventory +// reflects whatever extras are actually installed on the build machine. +// --from=mixed is critical: it reads both the legacy License classifier +// AND the PEP 639 license_expression field, which is where modern +// packages (cryptography, pydantic, fastapi, etc.) declare their SPDX +// license. Without --from=mixed, ~30% of packages show as UNKNOWN. +import { execFileSync } from 'node:child_process' +import { mkdirSync, writeFileSync, existsSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +// __dirname = apps/desktop/scripts/; three parents is the repository root. +const repoRoot = path.resolve(__dirname, '..', '..', '..') +const distDir = path.resolve(__dirname, '..', 'dist') + +// Locate the Python environment to scan. Resolution order: +// 1. HERMES_PYTHON (Nix dev shell / Nix desktop build) +// 2. VIRTUAL_ENV (explicit activated venv) +// 3. repo-root .venv or venv (local dev worktree) +// 4. HERMES_HOME/hermes-agent/venv (managed install, matches main.ts) +// 5. pip-licenses on PATH (fallback) +function findPipLicensesBin() { + // Nix provides the exact Python interpreter through HERMES_PYTHON. It is + // a store path rather than a traditional venv directory, so looking for + // bin/pip-licenses next to it does not work; invoke the installed module. + if (process.env.HERMES_PYTHON && existsSync(process.env.HERMES_PYTHON)) { + return { type: 'module', path: process.env.HERMES_PYTHON, venv: null } + } + + // pip-licenses is a console script installed into the venv's bin/Scripts + // dir. We look for it relative to candidate venv roots. + const binName = process.platform === 'win32' ? 'pip-licenses.exe' : 'pip-licenses' + const scriptName = process.platform === 'win32' ? 'pip-licenses-script.py' : 'pip-licenses' + + const candidates = [] + + if (process.env.VIRTUAL_ENV) { + candidates.push(process.env.VIRTUAL_ENV) + } + + candidates.push(path.join(repoRoot, '.venv')) + candidates.push(path.join(repoRoot, 'venv')) + + if (process.env.HERMES_HOME) { + candidates.push(path.join(process.env.HERMES_HOME, 'hermes-agent', 'venv')) + } + + // Check $HOME/.hermes/hermes-agent/venv (the managed-install default) + candidates.push(path.join(process.env.HOME || '', '.hermes', 'hermes-agent', 'venv')) + + for (const venv of candidates) { + const binDir = process.platform === 'win32' + ? path.join(venv, 'Scripts') + : path.join(venv, 'bin') + const binPath = path.join(binDir, binName) + const scriptPath = path.join(binDir, scriptName) + + if (existsSync(binPath)) { + return { type: 'bin', path: binPath, venv } + } + if (existsSync(scriptPath)) { + // Need to run it with the venv's python + const pythonBin = process.platform === 'win32' + ? path.join(venv, 'Scripts', 'python.exe') + : path.join(venv, 'bin', 'python') + if (existsSync(pythonBin)) { + return { type: 'module', path: pythonBin, venv } + } + } + } + + // Fallback: assume pip-licenses is on PATH (CI/dev shells) + return { type: 'path', path: 'pip-licenses', venv: null } +} + +function runPipLicenses() { + const { type, path: binPath } = findPipLicensesBin() + + // Flags: + // --from=mixed โ€” read PEP 639 license_expression AND legacy classifier + // --with-license-file โ€” include the full license text from the dist-info + // --with-notice-file โ€” include NOTICE files (Apache-2.0 ยง4(d) requirement) + // --no-license-path โ€” don't emit the on-disk path (we're bundling, not linking) + // --format=plain-vertical โ€” one package per block, readable as a NOTICE file + // + // Note: --with-license-file prints a warning about long fields with + // plain-vertical format, but the output is still correct โ€” the warning + // is cosmetic. We suppress stderr to keep the build log clean. + const args = [ + '--from=mixed', + '--with-license-file', + '--with-notice-file', + '--no-license-path', + '--format=plain-vertical', + ] + + let result + if (type === 'module') { + // python -m piplicenses + result = execFileSync(binPath, ['-m', 'piplicenses', ...args], { + encoding: 'utf8', + maxBuffer: 50 * 1024 * 1024, + stdio: ['pipe', 'pipe', 'pipe'], + env: { ...process.env, PYTHONUNBUFFERED: '1' }, + }) + } else { + // Direct binary or PATH lookup + result = execFileSync(binPath, args, { + encoding: 'utf8', + maxBuffer: 50 * 1024 * 1024, + stdio: ['pipe', 'pipe', 'pipe'], + env: { ...process.env, PYTHONUNBUFFERED: '1' }, + }) + } + + return result +} + +function generatedAt() { + // Nix sets SOURCE_DATE_EPOCH for reproducible builds. Local builds retain a + // useful wall-clock timestamp when that conventional variable is absent. + const epoch = Number(process.env.SOURCE_DATE_EPOCH) + return new Date(Number.isFinite(epoch) && epoch > 0 ? epoch * 1000 : Date.now()).toISOString() +} + +function main() { + mkdirSync(distDir, { recursive: true }) + + let output + try { + output = runPipLicenses() + } catch (e) { + // pip-licenses not installed โ€” write a stub so the build doesn't fail + // and the Settings page can show a graceful "not available" message. + output = [ + 'Third-Party Software Licenses (Python)', + '=======================================', + '', + 'WARNING: pip-licenses was not found in the Python environment.', + 'The Python dependency license inventory could not be generated.', + '', + 'To generate this file, install pip-licenses in the hermes venv:', + ' pip install pip-licenses', + '', + `Error: ${e.message}`, + '', + ].join('\n') + console.warn(`[generate-python-licenses] pip-licenses not available: ${e.message}`) + } + + // Prepend a header so the file is self-documenting + const header = [ + 'Third-Party Software Licenses (Python)', + '=======================================', + '', + 'This file lists the open-source licenses of the Python dependencies', + 'of the Hermes Agent runtime bundled with the Hermes desktop app.', + '', + `Generated: ${generatedAt()}`, + '', + ].join('\n') + + const outPath = path.join(distDir, 'dependencies-python.txt') + writeFileSync(outPath, header + output, 'utf8') + + // Count packages: in plain-vertical format, each block is name\nversion\nlicense\ntext + // We approximate by counting version-like lines (X.Y.Z or X.Y patterns). + const pkgCount = (output.match(/^\d+\.\d+/gm) || []).length + console.log(`[generate-python-licenses] Wrote ~${pkgCount} packages to ${outPath}`) +} + +main() diff --git a/apps/desktop/src/app/settings/index.tsx b/apps/desktop/src/app/settings/index.tsx index f51cca30f94..a3666a91615 100644 --- a/apps/desktop/src/app/settings/index.tsx +++ b/apps/desktop/src/app/settings/index.tsx @@ -11,6 +11,7 @@ import { BarChart3, Bell, Download, + FileText, Globe, Info, Keyboard, @@ -38,6 +39,7 @@ import { SECTIONS } from './constants' import { GatewaySettings } from './gateway-settings' import { KeybindSettings } from './keybind-settings' import { KEYS_VIEWS, KeysSettings, type KeysView } from './keys-settings' +import { LicensesSettings } from './licenses-settings' import { NotificationsSettings } from './notifications-settings' import { PluginsSettings } from './plugins-settings' import { PROVIDER_VIEWS, ProvidersSettings, type ProviderView } from './providers-settings' @@ -54,7 +56,8 @@ const SETTINGS_VIEWS: readonly SettingsViewId[] = [ 'billing', 'plugins', 'sessions', - 'about' + 'about', + 'licenses' ] export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: SettingsPageProps) { @@ -249,6 +252,13 @@ export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: Set id: 'about', label: t.settings.nav.about, onSelect: () => setActiveView('about') + }, + { + active: activeView === 'licenses', + icon: FileText, + id: 'licenses', + label: t.settings.nav.licenses, + onSelect: () => setActiveView('licenses') } ] @@ -293,6 +303,8 @@ export function SettingsView({ onClose, onConfigSaved, onMainModelChanged }: Set ) : activeView === 'about' ? ( + ) : activeView === 'licenses' ? ( + ) : activeView === 'gateway' ? ( ) : activeView === 'keybinds' ? ( diff --git a/apps/desktop/src/app/settings/licenses-settings.tsx b/apps/desktop/src/app/settings/licenses-settings.tsx new file mode 100644 index 00000000000..183ce41f9e0 --- /dev/null +++ b/apps/desktop/src/app/settings/licenses-settings.tsx @@ -0,0 +1,145 @@ +import { useEffect, useState } from 'react' + +import { Button } from '@/components/ui/button' +import { Codicon } from '@/components/ui/codicon' +import { useI18n } from '@/i18n' +import { FileText, Loader2, AlertCircle } from '@/lib/icons' + +import { ListRow, SectionHeading, SettingsContent } from './primitives' + +type LicenseSource = { + key: string + filename: string + label: string + description: string +} + +const LICENSE_SOURCES: readonly LicenseSource[] = [ + { + key: 'js', + filename: 'dependencies.txt', + label: 'JavaScript Dependencies', + description: 'npm packages bundled with the desktop app renderer and Electron main process.' + }, + { + key: 'python', + filename: 'dependencies-python.txt', + label: 'Python Dependencies', + description: 'Python packages from the Hermes Agent runtime environment.' + } +] + +async function loadLicenseFile(filename: string): Promise { + const bridge = window.hermesDesktop + if (!bridge?.readBundledLicenseFile) { + return null + } + + return bridge.readBundledLicenseFile(filename) +} + +function LicenseFileBlock({ source }: { source: LicenseSource }) { + const [content, setContent] = useState(undefined) + + useEffect(() => { + let cancelled = false + void loadLicenseFile(source.filename).then(result => { + if (!cancelled) { + setContent(result) + } + }) + return () => { + cancelled = true + } + }, [source.filename]) + + const isLoading = content === undefined + const isMissing = content === null + const isEmpty = content !== null && content !== undefined && content.trim() === '' + + return ( +
+ + +
+ {isLoading ? ( +
+ + Loadingโ€ฆ +
+ ) : isMissing ? ( +
+ +
+

License file not found

+

+ Run npm run generate-licenses in{' '} + apps/desktop to generate it, or it was + not bundled with this build. +

+
+
+ ) : isEmpty ? ( +
+ License file is empty. +
+ ) : ( + <> +
+ + {source.description} + + +
+
+              {content}
+            
+ + )} +
+
+ ) +} + +export function LicensesSettings() { + return ( + +
+ + + + + {LICENSE_SOURCES.map(source => ( + + ))} + + +
+
+ ) +} diff --git a/apps/desktop/src/app/settings/types.ts b/apps/desktop/src/app/settings/types.ts index 2828609ef63..dc250277065 100644 --- a/apps/desktop/src/app/settings/types.ts +++ b/apps/desktop/src/app/settings/types.ts @@ -10,6 +10,7 @@ export type SettingsView = | 'gateway' | 'keybinds' | 'keys' + | 'licenses' | 'notifications' | 'plugins' | 'providers' diff --git a/apps/desktop/src/global.d.ts b/apps/desktop/src/global.d.ts index 9202c28e7fc..e6809af3263 100644 --- a/apps/desktop/src/global.d.ts +++ b/apps/desktop/src/global.d.ts @@ -76,6 +76,7 @@ declare global { requestMicrophoneAccess: () => Promise readFileDataUrl: (filePath: string) => Promise readFileText: (filePath: string) => Promise + readBundledLicenseFile: (filename: string) => Promise selectPaths: (options?: HermesSelectPathsOptions) => Promise writeClipboard: (text: string) => Promise saveImageFromUrl: (url: string) => Promise diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts index a17ecbb3bd6..25dcd5806b5 100644 --- a/apps/desktop/src/i18n/en.ts +++ b/apps/desktop/src/i18n/en.ts @@ -325,7 +325,8 @@ export const en: Translations = { about: 'About', billing: 'Billing', notifications: 'Notifications', - plugins: 'Plugins' + plugins: 'Plugins', + licenses: 'Licenses' }, plugins: { title: 'Desktop plugins', diff --git a/apps/desktop/src/i18n/ja.ts b/apps/desktop/src/i18n/ja.ts index 3ade882c6ab..a9319c89fc3 100644 --- a/apps/desktop/src/i18n/ja.ts +++ b/apps/desktop/src/i18n/ja.ts @@ -225,7 +225,8 @@ export const ja = defineLocale({ archivedChats: 'ใ‚ขใƒผใ‚ซใ‚คใƒ–ๆธˆใฟใƒใƒฃใƒƒใƒˆ', about: 'ๆƒ…ๅ ฑ', billing: '่ซ‹ๆฑ‚', - notifications: '้€š็Ÿฅ' + notifications: '้€š็Ÿฅ', + licenses: 'ใƒฉใ‚คใ‚ปใƒณใ‚น' }, notifications: { title: '้€š็Ÿฅ', diff --git a/apps/desktop/src/i18n/types.ts b/apps/desktop/src/i18n/types.ts index 25a9f67e219..cbe3f06ed59 100644 --- a/apps/desktop/src/i18n/types.ts +++ b/apps/desktop/src/i18n/types.ts @@ -284,6 +284,7 @@ export interface Translations { billing: string notifications: string plugins: string + licenses: string } plugins: { title: string diff --git a/apps/desktop/src/i18n/zh-hant.ts b/apps/desktop/src/i18n/zh-hant.ts index d061a6d57a7..8f0b55d70c6 100644 --- a/apps/desktop/src/i18n/zh-hant.ts +++ b/apps/desktop/src/i18n/zh-hant.ts @@ -219,7 +219,8 @@ export const zhHant = defineLocale({ archivedChats: 'ๅทฒๅฐๅญ˜่Šๅคฉ', about: '้—œๆ–ผ', billing: 'ๅธณๅ–ฎ', - notifications: '้€š็Ÿฅ' + notifications: '้€š็Ÿฅ', + licenses: 'ๆŽˆๆฌŠๆขๆฌพ' }, notifications: { title: '้€š็Ÿฅ', diff --git a/apps/desktop/src/i18n/zh.ts b/apps/desktop/src/i18n/zh.ts index 61d6ff602aa..65a360d02f5 100644 --- a/apps/desktop/src/i18n/zh.ts +++ b/apps/desktop/src/i18n/zh.ts @@ -316,7 +316,8 @@ export const zh: Translations = { about: 'ๅ…ณไบŽ', billing: '่ดฆๅ•', notifications: '้€š็Ÿฅ', - plugins: 'ๆ’ไปถ' + plugins: 'ๆ’ไปถ', + licenses: '่ฎธๅฏ่ฏ' }, plugins: { title: 'ๆกŒ้ขๆ’ไปถ', diff --git a/nix/desktop.nix b/nix/desktop.nix index cb18a4f0491..a62345c988d 100644 --- a/nix/desktop.nix +++ b/nix/desktop.nix @@ -14,6 +14,7 @@ hermesNpmLib, electron, hermesAgent, + licensePython, ... }: let @@ -72,11 +73,15 @@ let # typecheck :3 npm exec tsc -b - # build the renderer bundle - # vite's emptyOutDir wipes dist/ on every run - # so it has to be first + # Build the renderer bundle first: Vite's emptyOutDir wipes dist/. npm exec vite build + # Generate attribution files AFTER Vite, otherwise emptyOutDir would + # delete them. HERMES_PYTHON points at the sealed nix venv that has + # hermes's locked runtime closure plus pip-licenses from [dev]. + HERMES_PYTHON=${licensePython}/bin/python3 node scripts/generate-js-licenses.mjs + HERMES_PYTHON=${licensePython}/bin/python3 node scripts/generate-python-licenses.mjs + # build the electron bundle node scripts/bundle-electron-main.mjs diff --git a/nix/hermes-agent.nix b/nix/hermes-agent.nix index 043d1e94202..57f4ee4f719 100644 --- a/nix/hermes-agent.nix +++ b/nix/hermes-agent.nix @@ -225,6 +225,11 @@ stdenv.mkDerivation (finalAttrs: { passthru = let + # A sealed, non-editable venv containing the normal [all] runtime + # groups plus [dev] tooling. Desktop's Nix renderer build uses this + # only for pip-licenses, so the generated attribution inventory matches + # the locked Python closure without making license tooling a runtime dep. + licensePython = (mkHermesVenv (extraDependencyGroups ++ [ "dev" ])).venv; devPython = (mkHermesVenv (extraDependencyGroups ++ [ "dev" ])).editableVenv; in { @@ -233,6 +238,7 @@ stdenv.mkDerivation (finalAttrs: { hermesWeb hermesNpmLib hermesVenv + licensePython ; # `hermesDesktop` references `finalAttrs.finalPackage` (this whole @@ -243,7 +249,7 @@ stdenv.mkDerivation (finalAttrs: { # runtime PATH (ripgrep/git/ffmpeg/etc). No re-implementation # of the agent resolution in the desktop wrapper. hermesDesktop = callPackage ./desktop.nix { - inherit hermesNpmLib electron; + inherit hermesNpmLib electron licensePython; hermesAgent = finalAttrs.finalPackage; }; diff --git a/package-lock.json b/package-lock.json index 3236f2e7310..b9fd225d6a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -178,6 +178,7 @@ "eslint-plugin-unused-imports": "^4.4.1", "globals": "^16.5.0", "jsdom": "^29.1.1", + "license-checker": "^25.0.1", "prettier": "^3.8.3", "rcedit": "^5.0.2", "tsx": "^4.22.4", @@ -7368,6 +7369,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-includes": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", @@ -7489,6 +7500,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, "node_modules/asn1js": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", @@ -9202,6 +9220,17 @@ } } }, + "node_modules/debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -9386,6 +9415,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/dijkstrajs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", @@ -13153,6 +13193,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -13321,6 +13368,147 @@ "node": ">= 0.8.0" } }, + "node_modules/license-checker": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", + "integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "^2.4.1", + "debug": "^3.1.0", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "read-installed": "~4.0.3", + "semver": "^5.5.0", + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-satisfies": "^4.0.0", + "treeify": "^1.1.0" + }, + "bin": { + "license-checker": "bin/license-checker" + } + }, + "node_modules/license-checker/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/license-checker/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/license-checker/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/license-checker/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/license-checker/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/license-checker/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/license-checker/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/license-checker/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -14803,7 +14991,6 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -15060,6 +15247,58 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/normalize-package-data/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -15073,6 +15312,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true, + "license": "ISC" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -15254,6 +15500,38 @@ "node": ">= 0.8.0" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -16438,6 +16716,49 @@ "read-binary-file-arch": "cli.js" } }, + "node_modules/read-installed": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", + "integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.2" + } + }, + "node_modules/read-installed/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-package-json": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", + "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -16461,6 +16782,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, "node_modules/redux": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", @@ -17412,6 +17747,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "*" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -17452,6 +17797,73 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spdx-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", + "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-find-index": "^1.0.2", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdx-ranges": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", + "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", + "dev": true, + "license": "(MIT AND CC-BY-3.0)" + }, + "node_modules/spdx-satisfies": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz", + "integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-compare": "^1.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -18152,6 +18564,16 @@ "tree-kill": "cli.js" } }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -18803,6 +19225,13 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==", + "dev": true, + "license": "MIT" + }, "node_modules/uuid": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", @@ -18822,6 +19251,17 @@ "integrity": "sha512-LdabyT4OffkyXFCe9UT+uMkxNBs5rcTVuZClvxQr08D5TUgo1OFKkoT65qYRCsiKBl/usHjpXvP4hHMzzDRj3A==", "license": "MIT" }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", diff --git a/pyproject.toml b/pyproject.toml index c630b3cf7bb..23296f37931 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,7 +157,7 @@ edge-tts = ["edge-tts==7.2.7"] modal = ["modal==1.3.4"] daytona = ["daytona==0.155.0"] hindsight = ["hindsight-client==0.6.1"] -dev = ["debugpy==1.8.20", "pytest==9.0.2", "pytest-asyncio==1.3.0", "mcp==1.26.0", "starlette==1.0.1", "ty==0.0.21", "ruff==0.15.10", "setuptools==81.0.0"] # starlette: CVE-2026-48710; setuptools: latest <82 (torch >=2.11 caps setuptools<82) +dev = ["debugpy==1.8.20", "pytest==9.0.2", "pytest-asyncio==1.3.0", "mcp==1.26.0", "starlette==1.0.1", "ty==0.0.21", "ruff==0.15.10", "setuptools==81.0.0", "pip-licenses==5.0.0"] # starlette: CVE-2026-48710; setuptools: latest <82 (torch >=2.11 caps setuptools<82); pip-licenses: generate THIRD_PARTY_LICENSES at desktop build time messaging = ["python-telegram-bot[webhooks]==22.6", "discord.py[voice]==2.7.1", "aiohttp==3.14.1", "brotlicffi==1.2.0.1", "slack-bolt==1.29.0", "slack-sdk==3.43.0", "qrcode==7.4.2"] # aiohttp 3.14.1: CVE-2026-34513/34518/34519/34520/34525 + 34993(RCE)/47265 cron = [] # croniter is now a core dependency; this extra kept for back-compat slack = ["slack-bolt==1.29.0", "slack-sdk==3.43.0", "aiohttp==3.14.1"] diff --git a/uv.lock b/uv.lock index 257f7d69645..5bdfeed1deb 100644 --- a/uv.lock +++ b/uv.lock @@ -1591,6 +1591,7 @@ daytona = [ dev = [ { name = "debugpy" }, { name = "mcp" }, + { name = "pip-licenses" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "ruff" }, @@ -1811,6 +1812,7 @@ requires-dist = [ { name = "parallel-web", marker = "extra == 'parallel-web'", specifier = "==0.4.2" }, { name = "pathspec", specifier = "==1.1.1" }, { name = "pillow", specifier = "==12.2.0" }, + { name = "pip-licenses", marker = "extra == 'dev'", specifier = "==5.0.0" }, { name = "prompt-toolkit", specifier = "==3.0.52" }, { name = "psutil", specifier = "==7.2.2" }, { name = "ptyprocess", marker = "sys_platform != 'win32'", specifier = ">=0.7.0,<1" }, @@ -3013,6 +3015,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, ] +[[package]] +name = "pip-licenses" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prettytable" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/49/d36a3ddb73d22970a35afa3e9fd53c8318150f8122e4257ca9875f1d4e38/pip_licenses-5.0.0.tar.gz", hash = "sha256:0633a1f9aab58e5a6216931b0e1d5cdded8bcc2709ff563674eb0e2ff9e77e8e", size = 41542, upload-time = "2024-07-23T10:48:29.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/0a/bfaf1479d09d19f503a669d9c8e433ac59ae687fb8da1d8207eb85c5a9f4/pip_licenses-5.0.0-py3-none-any.whl", hash = "sha256:82c83666753efb86d1af1c405c8ab273413eb10d6689c218df2f09acf40e477d", size = 20497, upload-time = "2024-07-23T10:48:27.59Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -3049,6 +3064,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/07/ca761fd0e3b23e9bfeaaeeffa5df4c235fd0c5086d89934553583e6902f3/posthog-7.21.0-py3-none-any.whl", hash = "sha256:11dca1e9772bedcb6721751fb0945fa8dcddbf9a4e72e2430483b53cdb8d1ec0", size = 370932, upload-time = "2026-06-26T15:56:59.844Z" }, ] +[[package]] +name = "prettytable" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/74/ba08d81e668ccfe8658d7520a307e63c19862c08eb4ccb26f356c5239a7a/prettytable-3.18.0.tar.gz", hash = "sha256:439217116152244369caf3d9f1caf2f9fe29b03bd79e88d2928c8e718c95d680", size = 76373, upload-time = "2026-06-22T16:07:50.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl", hash = "sha256:b3346e0e6f79180833aebaac088ae926340586cf6d7d991b9eb125b65f72313a", size = 37357, upload-time = "2026-06-22T16:07:48.595Z" }, +] + [[package]] name = "prompt-toolkit" version = "3.0.52" @@ -4105,6 +4132,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + [[package]] name = "tornado" version = "6.5.5"