Files
hermes-agent/apps/desktop/package.json
Jack Lau 15e74fa625 fix(desktop): guard the whole build-critical dep set, before clean
Refs #86443

assert-root-install.mjs exists to turn an incomplete root install into one
actionable line instead of a failure deep inside the build. It only ever
checked that vite resolved, so an install covering part of the workspace
graph passed the guard and died later on something else. That is the shape
reported in #86443: the updater's npm install brought in 521 of the 769
packages a full install gives, root node_modules had vite but not katex, and
the build failed on an unresolved katex/dist/katex.min.css with nothing
pointing at the install as the cause. apps/desktop/src/styles.css imports
that stylesheet, so katex is as load-bearing for the renderer bundle as vite
is, and electron / electron-builder are the same for packaging.

Check all four and name every missing one, so a partial install is reported
once and completely rather than one package per build attempt.

Resolution walks node_modules upward the way Node's own lookup does, rather
than going through require.resolve: a package whose exports map does not
expose ./package.json is not resolvable by path even when correctly
installed, and that must not read as missing. It also keeps a dependency
that landed in the app workspace instead of the hoisted root passing.

The guard now runs from prebuild, ahead of npm run clean, so a tree that
cannot build is rejected before the build deletes its own outputs. On this
checkout clean removes build/electron-types and the tsbuildinfo files, not
release/, so this ordering is not by itself what saves a packaged app; it is
the narrow correctness point that a doomed build should not destroy anything
first. build keeps its own call for anyone invoking the build steps directly,
and the check is pure filesystem lookups, so running it twice costs nothing.

The check is extracted as a pure checkRootInstall() returning {ok, error},
matching assert-dist-built.mjs, so it is unit testable without spawning a
process.
2026-09-02 00:07:58 -07:00

307 lines
12 KiB
JSON

{
"name": "hermes",
"productName": "Hermes",
"private": true,
"version": "0.17.0",
"description": "Native desktop shell for Hermes Agent.",
"author": "Nous Research",
"repository": {
"type": "git",
"url": "git+https://github.com/NousResearch/hermes-agent.git"
},
"type": "module",
"main": "dist/electron-main.mjs",
"engines": {
"node": "^22.22.0 || ^24.11.0 || >=26.0.0"
},
"scripts": {
"clean": "npm run clean:e2e && npm run clean:renderer && npm run clean:electron",
"clean:e2e": "tsc --build tsconfig.e2e.json --clean",
"clean:renderer": "tsc --build tsconfig.json --clean ",
"clean:electron": "tsc --build tsconfig.electron.json --clean",
"dev": "concurrently -k \"npm:dev:renderer\" \"npm:dev:electron\"",
"dev:fake-boot": "cross-env HERMES_DESKTOP_BOOT_FAKE=1 HERMES_DESKTOP_BOOT_FAKE_STEP_MS=650 npm run dev",
"dev:mock": "node scripts/dev-mock.mjs",
"dev:renderer": "node scripts/assert-root-install.mjs && npm run clean:renderer && vite --host 127.0.0.1 --port 5174",
"dev:electron": "tsc --build tsconfig.electron.json && wait-on http://127.0.0.1:5174 && node scripts/bundle-electron-main.mjs --dev && cross-env XCURSOR_SIZE=24 HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron .",
"profile:main": "tsc --build tsconfig.electron.json && wait-on http://127.0.0.1:5174 && node scripts/bundle-electron-main.mjs --dev && cross-env XCURSOR_SIZE=24 HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron --inspect=9229 .",
"profile:main:cpu": "tsc --build tsconfig.electron.json && 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": "node scripts/assert-root-install.mjs && npm run 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",
"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",
"pack": "npm run build && npm run builder -- --dir --publish never",
"dist": "npm run build && npm run builder",
"dist:mac": "npm run build && npm run builder -- --mac",
"dist:mac:dmg": "npm run build && npm run builder -- --mac dmg",
"dist:mac:zip": "npm run build && npm run builder -- --mac zip",
"dist:win": "npm run build && npm run builder -- --win",
"dist:win:msi": "npm run build && npm run builder -- --win msi",
"dist:win:nsis": "npm run build && npm run builder -- --win nsis",
"dist:linux": "npm run build && npm run builder -- --linux AppImage deb rpm",
"perf": "node scripts/perf/run.mjs",
"update:shim": "bash ../../scripts/desktop-update/repro.sh shim",
"update:shim:fail": "bash ../../scripts/desktop-update/repro.sh shim-fail",
"update:repro:fresh": "bash ../../scripts/desktop-update/repro.sh fresh",
"update:repro:behind": "bash ../../scripts/desktop-update/repro.sh behind",
"update:repro:error": "bash ../../scripts/desktop-update/repro.sh error",
"update:repro:gate": "bash ../../scripts/desktop-update/repro.sh gate",
"update:repro:launch": "bash ../../scripts/desktop-update/repro.sh launch",
"perf:serve": "node scripts/perf/serve.mjs",
"test:desktop": "node scripts/test-desktop.mjs",
"test:desktop:all": "node scripts/test-desktop.mjs all",
"test:desktop:dmg": "node scripts/test-desktop.mjs dmg",
"test:desktop:nsis": "node scripts/test-desktop.mjs nsis",
"test:desktop:existing": "node scripts/test-desktop.mjs existing",
"test:desktop:fresh": "node scripts/test-desktop.mjs fresh",
"typecheck": "tsc -p . --noEmit && tsc -p tsconfig.electron.json --noEmit && tsc -p tsconfig.e2e.json --noEmit",
"lint": "eslint src/ electron/",
"lint:fix": "eslint src/ electron/ --fix",
"fmt": "prettier --write 'src/**/*.{ts,tsx}' 'electron/**/*.ts' 'vite.config.ts'",
"fix": "npm run lint:fix && npm run fmt",
"test:ui": "vitest run --project ui",
"test:desktop:platforms": "vitest run --project electron",
"test:find-in-page-native": "electron electron/find-in-page-native-fixture",
"test": "vitest run",
"preview": "node scripts/assert-root-install.mjs && vite preview --host 127.0.0.1 --port 4174",
"check:test:ui": "npm run test:ui",
"check:test:desktop:platforms": "npm run test:desktop:platforms",
"check:test:desktop:all": "npm run test:desktop:all",
"check:lint": "npm run typecheck && npm run lint",
"check": "npm run check:lint && npm run test:ui && npm run test:desktop:platforms && npm run test:desktop:all",
"test:e2e": "npm run build && playwright test e2e/",
"test:e2e:visual": "npm run build && WLR_BACKENDS=headless WLR_NO_HARDWARE_CURSORS=1 cage -- npx playwright test e2e/ --reporter=list",
"test:e2e:update-snapshots": "npm run build && WLR_BACKENDS=headless WLR_NO_HARDWARE_CURSORS=1 cage -- npx playwright test e2e/ --reporter=list --update-snapshots",
"repro:short-session-hang": "node scripts/run-short-session-hang-repro.mjs",
"repro:short-session-hang:test": "node --test scripts/run-short-session-hang-repro.test.mjs"
},
"dependencies": {
"@assistant-ui/core": "0.2.23",
"@assistant-ui/react": "0.14.24",
"@assistant-ui/react-streamdown": "0.3.5",
"@audiowave/react": "0.6.2",
"@chenglou/pretext": "0.0.6",
"@codemirror/commands": "6.10.4",
"@codemirror/language": "6.12.4",
"@codemirror/language-data": "6.5.2",
"@codemirror/state": "6.7.1",
"@codemirror/view": "6.43.6",
"@dnd-kit/core": "6.3.1",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
"@hermes/shared": "file:../shared",
"@icons-pack/react-simple-icons": "13.11.1",
"@lezer/highlight": "1.2.3",
"@nanostores/react": "1.1.0",
"@nous-research/ui": "0.18.2",
"@streamdown/code": "1.1.1",
"@streamdown/math": "1.0.2",
"@tabler/icons-react": "3.44.0",
"@tailwindcss/typography": "0.5.20",
"@tailwindcss/vite": "4.3.3",
"@tanstack/react-query": "5.101.2",
"@tanstack/react-virtual": "3.14.6",
"@vscode/codicons": "0.0.45",
"@xterm/addon-fit": "0.11.0",
"@xterm/addon-serialize": "0.14.0",
"@xterm/addon-unicode11": "0.9.0",
"@xterm/addon-web-links": "0.12.0",
"@xterm/addon-webgl": "0.19.0",
"@xterm/xterm": "6.0.0",
"blobatar": "2.0.0",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"d3-force": "3.0.0",
"dnd-core": "14.0.1",
"dompurify": "3.4.13",
"driver.js": "1.8.0",
"emojibase-data": "16.0.3",
"fflate": "0.8.3",
"frimousse": "0.3.0",
"hast-util-from-html-isomorphic": "2.0.0",
"hast-util-to-text": "4.0.2",
"ignore": "7.0.6",
"katex": "0.16.47",
"mermaid": "11.16.1",
"motion": "12.42.2",
"nanostores": "1.4.2",
"node-pty": "1.1.0",
"radix-ui": "1.6.7",
"react": "19.2.7",
"react-arborist": "3.13.2",
"react-dnd-html5-backend": "14.1.0",
"react-dom": "19.2.7",
"react-router": "8.3.0",
"react-shiki": "0.9.3",
"remark-math": "6.0.0",
"remend": "1.3.0",
"shiki": "4.3.1",
"simple-git": "3.36.0",
"streamdown": "2.5.0",
"tailwind-merge": "3.6.0",
"tailwindcss": "4.3.3",
"tw-shimmer": "0.4.12",
"unicode-animations": "1.0.3",
"unified": "11.0.5",
"unist-util-visit-parents": "6.0.2",
"use-stick-to-bottom": "1.1.6",
"vfile": "6.0.3",
"web-haptics": "0.0.6"
},
"optionalDependencies": {
"get-windows": "9.3.0"
},
"devDependencies": {
"@babel/core": "8.0.1",
"@electron/rebuild": "4.2.0",
"@playwright/test": "1.62.1",
"@rolldown/plugin-babel": "0.2.3",
"@testing-library/dom": "10.4.1",
"@testing-library/react": "16.3.2",
"@types/babel__core": "7.20.5",
"@types/d3-force": "3.0.10",
"@types/hast": "3.0.5",
"@types/node": "22.20.1",
"@types/react": "19.2.17",
"@types/react-dom": "19.2.3",
"@vitejs/plugin-react": "6.0.3",
"babel-plugin-react-compiler": "1.0.0",
"bippy": "0.5.43",
"concurrently": "10.0.4",
"cross-env": "10.1.0",
"electron": "40.10.2",
"electron-builder": "26.15.3",
"esbuild": "0.28.1",
"jsdom": "29.1.1",
"prettier": "3.9.5",
"rcedit": "5.0.2",
"tsx": "4.23.1",
"typescript": "6.0.3",
"vite": "8.2.0",
"vitest": "4.1.10",
"wait-on": "9.0.10"
},
"build": {
"electronVersion": "40.10.2",
"appId": "com.nousresearch.hermes",
"productName": "Hermes",
"executableName": "Hermes",
"protocols": [
{
"name": "Hermes Protocol",
"schemes": [
"hermes"
]
}
],
"artifactName": "Hermes-${version}-${os}-${arch}.${ext}",
"icon": "assets/icon",
"directories": {
"output": "release"
},
"files": [
"dist/**",
"assets/**",
"public/**",
"package.json"
],
"beforeBuild": "scripts/before-build.mjs",
"beforePack": "scripts/before-pack.mjs",
"afterPack": "scripts/after-pack.mjs",
"extraResources": [
{
"from": "build/install-stamp.json",
"to": "install-stamp.json"
},
{
"from": "assets/icon.ico",
"to": "icon.ico"
}
],
"asar": true,
"afterSign": "scripts/notarize.mjs",
"asarUnpack": [
"**/*.node",
"**/prebuilds/**",
"dist/**"
],
"mac": {
"category": "public.app-category.developer-tools",
"entitlements": "electron/entitlements.mac.plist",
"entitlementsInherit": "electron/entitlements.mac.inherit.plist",
"extendInfo": {
"CFBundleDisplayName": "Hermes",
"CFBundleExecutable": "Hermes",
"CFBundleName": "Hermes",
"NSAudioCaptureUsageDescription": "Hermes uses audio capture for voice conversations.",
"NSCameraUsageDescription": "Hermes uses the camera when a plugin or feature you enable requests it.",
"NSMicrophoneUsageDescription": "Hermes uses the microphone for voice input and voice conversations.",
"NSCalendarsUsageDescription": "Hermes needs access to Calendar to provide requested meeting and scheduling support.",
"NSCalendarsFullAccessUsageDescription": "Hermes needs full access to Calendar to read and manage events when explicitly requested.",
"NSRemindersUsageDescription": "Hermes needs access to Reminders to provide requested personal-assistant and scheduling support.",
"NSRemindersFullAccessUsageDescription": "Hermes needs full access to Reminders to read and manage reminders when explicitly requested.",
"NSScreenCaptureUsageDescription": "Hermes captures the screen when you ask the agent to screenshot or record it.",
"NSLocalNetworkUsageDescription": "Hermes connects to devices on your local network when a plugin or feature you enable requests it.",
"NSAppleMusicUsageDescription": "Hermes accesses your music library when a plugin or feature you enable requests it."
},
"gatekeeperAssess": false,
"hardenedRuntime": true,
"target": [
"dmg",
"zip"
]
},
"dmg": {
"title": "Install Hermes",
"backgroundColor": "#f5f5f7",
"iconSize": 96,
"window": {
"width": 560,
"height": 360
},
"contents": [
{
"x": 160,
"y": 170,
"type": "file"
},
{
"x": 400,
"y": 170,
"type": "link",
"path": "/Applications"
}
]
},
"win": {
"legalTrademarks": "Hermes",
"target": [
"nsis",
"msi"
],
"signAndEditExecutable": false
},
"linux": {
"category": "Development",
"maintainer": "Nous Research <support@nousresearch.com>",
"synopsis": "Native desktop shell for Hermes Agent.",
"target": [
"AppImage",
"deb",
"rpm"
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": false,
"shortcutName": "Hermes",
"uninstallDisplayName": "Hermes",
"warningsAsErrors": false
}
}
}