mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
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.
This commit is contained in:
parent
67e73ae958
commit
81b903243e
20 changed files with 1390 additions and 13 deletions
282
LICENSE_AUDIT.md
Normal file
282
LICENSE_AUDIT.md
Normal file
|
|
@ -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 `<details>` 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
|
||||
|
|
@ -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/<filename> in the packaged app. In dev (running
|
||||
// from a checkout), they live in apps/desktop/dist/<filename>.
|
||||
//
|
||||
// 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']
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
182
apps/desktop/scripts/generate-js-licenses.mjs
Normal file
182
apps/desktop/scripts/generate-js-licenses.mjs
Normal file
|
|
@ -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()
|
||||
181
apps/desktop/scripts/generate-python-licenses.mjs
Normal file
181
apps/desktop/scripts/generate-python-licenses.mjs
Normal file
|
|
@ -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 <args>
|
||||
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()
|
||||
|
|
@ -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
|
|||
<AppearanceSettings />
|
||||
) : activeView === 'about' ? (
|
||||
<AboutSettings />
|
||||
) : activeView === 'licenses' ? (
|
||||
<LicensesSettings />
|
||||
) : activeView === 'gateway' ? (
|
||||
<GatewaySettings />
|
||||
) : activeView === 'keybinds' ? (
|
||||
|
|
|
|||
145
apps/desktop/src/app/settings/licenses-settings.tsx
Normal file
145
apps/desktop/src/app/settings/licenses-settings.tsx
Normal file
|
|
@ -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<string | null> {
|
||||
const bridge = window.hermesDesktop
|
||||
if (!bridge?.readBundledLicenseFile) {
|
||||
return null
|
||||
}
|
||||
|
||||
return bridge.readBundledLicenseFile(filename)
|
||||
}
|
||||
|
||||
function LicenseFileBlock({ source }: { source: LicenseSource }) {
|
||||
const [content, setContent] = useState<string | null | undefined>(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 (
|
||||
<div className="mt-4">
|
||||
<SectionHeading icon={FileText} title={source.label} />
|
||||
|
||||
<div className="overflow-hidden rounded-xl border border-border/70 bg-muted/20">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center gap-2 px-4 py-3 text-sm text-muted-foreground">
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
Loading…
|
||||
</div>
|
||||
) : isMissing ? (
|
||||
<div className="flex items-start gap-2 px-4 py-3 text-sm text-muted-foreground">
|
||||
<AlertCircle className="mt-0.5 size-3.5 shrink-0 text-amber-500" />
|
||||
<div>
|
||||
<p className="font-medium text-foreground">License file not found</p>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
Run <code className="rounded bg-muted px-1 py-0.5 text-xs">npm run generate-licenses</code> in{' '}
|
||||
<code className="rounded bg-muted px-1 py-0.5 text-xs">apps/desktop</code> to generate it, or it was
|
||||
not bundled with this build.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : isEmpty ? (
|
||||
<div className="px-4 py-3 text-sm text-muted-foreground">
|
||||
License file is empty.
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center justify-between border-b border-border/50 px-4 py-2">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{source.description}
|
||||
</span>
|
||||
<Button
|
||||
onClick={() => void navigator.clipboard.writeText(content || '')}
|
||||
size="sm"
|
||||
variant="text"
|
||||
>
|
||||
<Codicon className="size-3" name="copy" />
|
||||
Copy
|
||||
</Button>
|
||||
</div>
|
||||
<pre className="max-h-[400px] overflow-auto px-4 py-3 text-xs leading-relaxed text-foreground/80 whitespace-pre-wrap break-words font-mono">
|
||||
{content}
|
||||
</pre>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function LicensesSettings() {
|
||||
return (
|
||||
<SettingsContent>
|
||||
<div className="mx-auto w-full max-w-4xl">
|
||||
<SectionHeading icon={FileText} title="Third-Party Licenses" />
|
||||
|
||||
<ListRow
|
||||
description="License attributions for open-source dependencies bundled with Hermes. These files are generated at build time and shipped with the application."
|
||||
title="Open-Source License Attribution"
|
||||
/>
|
||||
|
||||
{LICENSE_SOURCES.map(source => (
|
||||
<LicenseFileBlock key={source.key} source={source} />
|
||||
))}
|
||||
|
||||
<div className="mt-6 px-1 pb-4 text-xs text-muted-foreground">
|
||||
Hermes Agent is licensed under the MIT License. For the full text, see the LICENSE file in the{' '}
|
||||
<a
|
||||
href="https://github.com/NousResearch/hermes-agent"
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
void window.hermesDesktop?.openExternal?.('https://github.com/NousResearch/hermes-agent')
|
||||
}}
|
||||
className="text-primary hover:underline"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
source repository
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</SettingsContent>
|
||||
)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ export type SettingsView =
|
|||
| 'gateway'
|
||||
| 'keybinds'
|
||||
| 'keys'
|
||||
| 'licenses'
|
||||
| 'notifications'
|
||||
| 'plugins'
|
||||
| 'providers'
|
||||
|
|
|
|||
1
apps/desktop/src/global.d.ts
vendored
1
apps/desktop/src/global.d.ts
vendored
|
|
@ -76,6 +76,7 @@ declare global {
|
|||
requestMicrophoneAccess: () => Promise<boolean>
|
||||
readFileDataUrl: (filePath: string) => Promise<string>
|
||||
readFileText: (filePath: string) => Promise<HermesReadFileTextResult>
|
||||
readBundledLicenseFile: (filename: string) => Promise<string | null>
|
||||
selectPaths: (options?: HermesSelectPathsOptions) => Promise<string[]>
|
||||
writeClipboard: (text: string) => Promise<boolean>
|
||||
saveImageFromUrl: (url: string) => Promise<boolean>
|
||||
|
|
|
|||
|
|
@ -325,7 +325,8 @@ export const en: Translations = {
|
|||
about: 'About',
|
||||
billing: 'Billing',
|
||||
notifications: 'Notifications',
|
||||
plugins: 'Plugins'
|
||||
plugins: 'Plugins',
|
||||
licenses: 'Licenses'
|
||||
},
|
||||
plugins: {
|
||||
title: 'Desktop plugins',
|
||||
|
|
|
|||
|
|
@ -225,7 +225,8 @@ export const ja = defineLocale({
|
|||
archivedChats: 'アーカイブ済みチャット',
|
||||
about: '情報',
|
||||
billing: '請求',
|
||||
notifications: '通知'
|
||||
notifications: '通知',
|
||||
licenses: 'ライセンス'
|
||||
},
|
||||
notifications: {
|
||||
title: '通知',
|
||||
|
|
|
|||
|
|
@ -284,6 +284,7 @@ export interface Translations {
|
|||
billing: string
|
||||
notifications: string
|
||||
plugins: string
|
||||
licenses: string
|
||||
}
|
||||
plugins: {
|
||||
title: string
|
||||
|
|
|
|||
|
|
@ -219,7 +219,8 @@ export const zhHant = defineLocale({
|
|||
archivedChats: '已封存聊天',
|
||||
about: '關於',
|
||||
billing: '帳單',
|
||||
notifications: '通知'
|
||||
notifications: '通知',
|
||||
licenses: '授權條款'
|
||||
},
|
||||
notifications: {
|
||||
title: '通知',
|
||||
|
|
|
|||
|
|
@ -316,7 +316,8 @@ export const zh: Translations = {
|
|||
about: '关于',
|
||||
billing: '账单',
|
||||
notifications: '通知',
|
||||
plugins: '插件'
|
||||
plugins: '插件',
|
||||
licenses: '许可证'
|
||||
},
|
||||
plugins: {
|
||||
title: '桌面插件',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
442
package-lock.json
generated
442
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
63
uv.lock
generated
63
uv.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue