Files
hermes-agent/scripts/build/README.md

17 KiB
Raw Blame History

Shared product builders

These modules compile frontends and assemble a runnable agent from prepared inputs. They do not replace npm, uv, PM, or Nix. Shared bundle builds describes the providers and distribution adapters.

This reference describes the current interfaces, not completed artifact acceptance. A successful parser or helper test does not prove a distribution build or a target-native runtime.

Ownership

Layer Owns Examples
Dependency provider Tools, locked dependencies, native libraries and build environments node-deps.mjs, PM Python operations, Nix importNpmLock/uv2nix, Termux wheelhouse
Product builder Compilation or application assembly from prepared inputs tui.mjs, web.mjs, desktop.mjs, agent.py, ../generate_icons.py
Distribution adapter Product selection, target preparation, package layout, signing and publication ../bundles/, ../../Dockerfile, ../../nix/, ../termux/

node-deps.mjs and pm.build_environment prepare dependencies. Unlike the product builders, they can access package registries. There is no universal installer, all-products dispatcher, or cross-platform Python environment.

Prepared JavaScript workspace

Run commands from the repository root. Replace the absolute example paths with build-owned paths.

node scripts/build/node-deps.mjs --source /work/source \
  --workspace ui-tui --workspace web

--source and at least one --workspace are required. A workspace can be its locked path or package name. The provider deduplicates the selection and runs one root npm ci operation. It includes root dependencies, development dependencies, and optional dependencies. It checks Node/npm against the root engines declarations. npm lifecycle scripts remain enabled.

The prepared source needs these inputs:

  • Root package.json and package-lock.json.
  • Selected workspace manifests and their required file: dependency sources.
  • Source files and configuration for the selected product.
  • Resolved dependencies in workspace-local or root node_modules.

Request the complete workspace union once. A later, narrower npm ci can remove dependencies that another product needs. This provider modifies the prepared workspace. It does not build a frontend or publish a completion stamp. Nix supplies dependencies through importNpmLock instead of this command.

Frontend products

node scripts/build/tui.mjs --source /work/source --out /work/products/tui
node scripts/build/web.mjs --source /work/source \
  --icons /work/products/icons --out /work/products/web
node scripts/build/desktop.mjs --source /work/source \
  --icons /work/products/icons --stamp /work/install-stamp.json \
  --native-deps /work/native-deps --out /work/products/desktop
Builder Required CLI arguments Additional CLI arguments Product contents
tui.mjs --source, --out None dist/entry.js and package.json with type: module
web.mjs --source, --out, --icons None index.html, Vite assets, and public assets
desktop.mjs --source, --out, --icons, --stamp, --native-deps --typecheck, --platform Renderer assets, electron-main.mjs, electron-preload.js, and native node_modules

Each output is the product directory itself. The desktop output is a dist directory, not an application package. The exported functions are buildTui, buildWeb, and buildDesktop. They return output paths, not a new provenance manifest.

The compilers resolve modules from the supplied workspace. They do not run npm, uv, PM installation, or icon preparation. TypeScript/Vite scratch files stay outside source inputs. Compilation uses a private output directory next to the destination. A successful compile replaces the destination. A failed compile leaves the previous product in place and reports failure. Its presence alone does not prove that the latest build succeeded.

Existing arbitrary output directories require the builder's .hermes-product marker. Files, symlinks, and source directories are rejected. The exact npm destinations (ui-tui/dist, hermes_cli/web_dist, apps/desktop/dist, and apps/desktop/build/native-deps) remain rebuildable without a prior marker. Other in-tree products live beneath .build/ or apps/desktop/build/products/.

Icons and native inputs

--icons names the generator's output root, not a directory of loose icons. The web builder reads web/public/ beneath it and requires favicon.ico. The desktop builder reads apps/desktop/public/ beneath it and requires apple-touch-icon.png.

Run the generator with a prepared icon-build Python environment:

python scripts/generate_icons.py --source /work/source --out /work/products/icons
python scripts/generate_icons.py --source /work/source --out /work/products/icons --check

The generator reads artwork from SOURCE/assets and writes its declared paths beneath OUT. These paths also include desktop packaging, website, and bootstrap-installer assets. --check regenerates targets in memory and checks output image properties. It does not compare output bytes with regenerated bytes. The generator writes targets directly, not through the frontend publication helper.

The convenience wrapper prepares the locked environment and can access the network:

node scripts/generate-icons.mjs --source /work/source --out /work/products/icons

It uses the isolated icon-build dependency group and SOURCE/.cache/icon-build. Both icon commands accept --check. Without explicit paths, they use the source checkout as the output root.

The desktop native tree contains prepared packages, including node-pty with its compiled binding. macOS also requires get-windows/main. The provider owns the architecture and Electron ABI match. --platform defaults to the running Node platform and controls native-file checks. It does not cross-compile a binding. --typecheck enables the desktop renderer TypeScript check and defaults to false. The web builder always runs its TypeScript project check.

The supplied install stamp controls the desktop main/preload build identity. The compiler does not create an install stamp, build the dashboard, package Electron, or sign native files.

Source-development entrypoints

Existing npm commands use these recipes:

npm run build --workspace ui-tui
npm run build --workspace web
npm run build --workspace apps/desktop
Command Output Preparation outside the product compiler
TUI build ui-tui/dist/entry.js Existing installed workspace dependencies
Web build hermes_cli/web_dist/ npm prebuild prepares icons
Desktop build apps/desktop/dist/ Icons, root-install assertion, install stamp, and native-dependency staging

TUI and web scripts support a no-argument development mode. Explicit product mode requires the arguments in the earlier table. The desktop product script has no no-argument mode. The Node product parsers expose no --help flag.

Python dependency provider

python -m pm.build_env --source /work/source \
  --python /work/tools/python --out /work/venv --sealed \
  --extra all --extra messaging
Argument Contract
--source, --out Required prepared source and fresh environment destination
--python Optional build interpreter; otherwise PM selects its pinned Python
--cache Optional build cache directory; otherwise PM selects its cache
--group Repeatable build/test dependency-group selection
--sealed Prune build-time editable and virtualenv marker .pth files
--extra Repeatable extra selection
--all-extras Select all extras instead of --extra
--no-install-project Exclude the root application install, but retain workspace-member installation
--offline Prohibit uv network access. Required artifacts must already be available

OUT must not exist. Failure removes this invocation's environment, not a pre-existing environment. Success prints its Python executable. The CLI is an explicit build request. The Python function pm.build_environment accepts the same semantic inputs and an optional explicit build env mapping. It returns the same executable as a Path.

PM owns pinned installer acquisition, environment creation, frozen workspace sync, dependency checks, and failure cleanup. Callers never resolve or pass a uv executable. It preserves project policy, uses the supplied interpreter, and disables interpreter downloads. It does not discover user plugins or publish a live PM selection. Nix retains its declarative dependency provider. Termux builds native wheels separately, then uses PM's requirements-environment operation with an explicit bionic interpreter and an offline wheelhouse. The build cache remains available after this call.

Other build adapters use the same command with --requirements FILE (or repeated --requirement SPEC) for a caller-owned dependency list, --manager-runtime for the independent PM graph, --check-lock for non-mutating CI lock validation, and --export-requirements FILE for marker-preserving frozen export. Cache teardown uses python -m pm.build_env --prune-cache --cache PATH; add --ci only when the cache will not be packaged for offline installation.

Native bundle staging keeps its HOME and PM state temporary, but not its uv cache. scripts.bundles.stage --cache PATH (or hermes pm bundle --cache PATH) selects the persistent cache explicitly. Direct staging also accepts the provider's UV_CACHE_DIR; otherwise it uses the output parent's .uv-cache. The PM runtime and application dependency builds receive this same cache. CI's setup-pm restores it and save-pm-cache prunes/saves it after the build, including a failed build. Cache keys use only the v2 namespace, without legacy fallback. The packaged uv-cache/ is a copy, not the writable build cache.

Windows ARM64 build prerequisites

scripts/windows-build-deps.ps1 owns Visual Studio ARM64, Clang, Rust, and static OpenSSL preparation. Source setup calls its initializer. Native build adapters use scripts/build/windows-deps.ps1 through windows_deps.py, before isolating HOME or compiling Node/Python dependencies. CI uses the same script through setup-windows-build-deps, with an OpenSSL cache outside the product. The product compilers and assembler do not install these prerequisites.

The PowerShell entrypoint accepts -StateRoot for persistent build-tool state and -EnvironmentFile for its prepared environment. The Python adapter passes that environment only to build children. Rust's original toolchain homes stay explicit, so temporary HOME isolation cannot hide an initialized toolchain. CI exports the same compiler, SDK, Rust, and OpenSSL environment to later steps. Warm OpenSSL reuse validates both static libraries and its development header.

Runnable agent assembly

python -m scripts.build.agent --inputs /work/agent-inputs.json --out /work/agent

The only builder arguments are --inputs and --out. Python entrypoints also accept argparse's -h/--help. The library interface is assemble(AgentInputs(...), out). The input JSON rejects unknown fields.

Input fields

All supplied filesystem input paths must be absolute and exist. repo and bin_dir are output-relative names, not filesystem inputs.

Field Required Meaning
project Yes pyproject.toml with static project metadata and [project.scripts]
code Yes Prepared source tree, or installed code root for reference placement
repo Yes Code/resource location beneath OUT, such as hermes-agent, ., or share/hermes-agent
placement Yes contained, fixed, or references
target Yes linux-x64, linux-arm64, darwin-x64, darwin-arm64, win32-x64, win32-arm64, or linux-arm64-bionic
python Yes Prepared target interpreter file
site_packages Yes Prepared application dependency directory
environment Yes Prepared application environment root
pm_runtime Yes Independent PM runtime directory with pm-runtime.json
bin_dir No One output-relative directory name. Default: bin
tools No Prepared runtime tool directory. The manifest defaults to tools if omitted
command_dir For references Directory of prepared commands declared by the project
resources No Resource-name to directory mapping
frontends No tui and/or web product paths
ref No Source identity copied to the completion manifest
stamp No Prepared install stamp copied to OUT/repo/install-stamp.json
features No Prepared feature inventory copied to OUT/enabled-features.json
env No Additional environment values in the reference-placement command map

Recognized resource names are skills, optional-skills, plugins, locales, and optional-mcps. Distribution adapters supply the required resources. The generic assembler does not infer a missing resource mapping. TUI inputs require dist/entry.js and package.json. Web inputs require index.html.

The PM marker supplies python and sitePackages paths relative to its runtime directory, or absolute store references. These paths must resolve to real inputs. If a supplied stamp declares nix or docker, its pmRuntime must match the supplied PM runtime. The assembler does not create that environment.

Placement and output

  • contained: The provider prepares the interpreter, dependencies, PM runtime, and tools inside OUT. Assembly copies source/resources, plants frontends, and calls the portable link helper.
  • fixed: The provider owns final-prefix preparation. Assembly copies or reuses source/resources and generates launchers without portable relocation. Docker and Termux use this placement.
  • references: Assembly references installed code and commands without copying Python code or replacing wheel metadata. It links explicit resources and frontends, then emits command-map.json. Nix creates its native wrappers from this map.

Source-layout placement writes project distribution metadata without building a Hermes wheel. It also writes site_packages/hermes-agent.pth with a relative code path. Source-layout placement therefore requires an output-owned dependency directory, including in fixed mode. Reference placement does not write this file.

Commands derive from [project.scripts], not a second command list. POSIX launchers use the supplied runtime paths. Windows launcher minting runs the target interpreter and therefore needs a runnable native environment. A target label alone does not prove ABI compatibility.

For copied frontends, the assembler places TUI files at OUT/repo/hermes_cli/tui_dist/ and web files at OUT/repo/hermes_cli/web_dist/. Reference placement links TUI at OUT/ui-tui and web at OUT/repo/web_dist and records their environment bindings.

After structural assembly, manifest.json records schema, target, repo, venv, store, launchers, and runtime, plus ref when supplied. Its runtime contains repoDir, toolsDir, storePython, sitePackages, and commands. Reference placement also emits command sources, destinations, entrypoints, and environment values in command-map.json.

Agent assembly modifies its output in place. It removes old completion records before work and writes the manifest last. This is not the frontend builder's atomic-directory publication contract. A failed assembly can leave partial files. The manifest is not evidence of a target-native launch or signed-package acceptance.

Implementation references

These locations define the interfaces described here:

Contract Source
Frontend arguments and publication frontend-common.mjs:31–79
TUI product and development output tui.mjs:25–103
Web inputs and TypeScript check web.mjs:8–71
Desktop inputs and native checks desktop.mjs:11–69
Locked workspace union node-deps.mjs:31–68
Python provider ../../pm/operations.py, ../../pm/environment.py
Agent input fields and checks inputs.py:30–115
Agent assembly and outputs agent.py:76–168
Launcher implementation launchers.py, launcher_wrapper.py, mint_launchers.py

Verification still required

Parser checks and source inspection establish the documented call shapes. They do not establish offline compilation, cache reuse, or runtime success. Artifact acceptance still needs these checks:

  • Real frontend builds from immutable prepared inputs without network access.
  • Standalone TUI interaction and dashboard assets/backend behavior.
  • Native Electron bindings under the packaged Electron version.
  • Agent CLI, ACP, plugins, and catalogs from an unrelated working directory.
  • Native payload relocation and offline mutable-environment reconstruction.
  • Docker runtime probes as its non-root user and layer-content inspection.
  • Actual Nix builds and commands through store-reference wrappers.
  • Fresh network-disabled bionic installation and Android device acceptance.

Docker/Nix build execution belongs to the distribution verification work, not this documentation pass. No build-pass claim follows from this reference.