mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat(skills+terminal): make bundled skill scripts runnable out of the box (#13384)
* feat(skills): inject absolute skill dir and expand ${HERMES_SKILL_DIR} templates
When a skill loads, the activation message now exposes the absolute
skill directory and substitutes ${HERMES_SKILL_DIR} /
${HERMES_SESSION_ID} tokens in the SKILL.md body, so skills with
bundled scripts can instruct the agent to run them by absolute path
without an extra skill_view round-trip.
Also adds opt-in inline-shell expansion: !`cmd` snippets in SKILL.md
are pre-executed (with the skill directory as CWD) and their stdout is
inlined into the message before the agent reads it. Off by default —
enable via skills.inline_shell in config.yaml — because any snippet
runs on the host without approval.
Changes:
- agent/skill_commands.py: template substitution, inline-shell
expansion, absolute skill-dir header, supporting-files list now
shows both relative and absolute forms.
- hermes_cli/config.py: new skills.template_vars,
skills.inline_shell, skills.inline_shell_timeout knobs.
- tests/agent/test_skill_commands.py: coverage for header, both
template tokens (present and missing session id), template_vars
disable, inline-shell default-off, enabled, CWD, and timeout.
- website/docs/developer-guide/creating-skills.md: documents the
template tokens, the absolute-path header, and the opt-in inline
shell with its security caveat.
Validation: tests/agent/ 1591 passed (includes 9 new tests).
E2E: loaded a real skill in an isolated HERMES_HOME; confirmed
${HERMES_SKILL_DIR} resolves to the absolute path, ${HERMES_SESSION_ID}
resolves to the passed task_id, !`date` runs when opt-in is set, and
stays literal when it isn't.
* feat(terminal): source ~/.bashrc (and user-listed init files) into session snapshot
bash login shells don't source ~/.bashrc, so tools that install themselves
there — nvm, asdf, pyenv, cargo, custom PATH exports — stay invisible to
the environment snapshot Hermes builds once per session. Under systemd
or any context with a minimal parent env, that surfaces as
'node: command not found' in the terminal tool even though the binary
is reachable from every interactive shell on the machine.
Changes:
- tools/environments/local.py: before the login-shell snapshot bootstrap
runs, prepend guarded 'source <file>' lines for each resolved init
file. Missing files are skipped, each source is wrapped with a
'[ -r ... ] && . ... || true' guard so a broken rc can't abort the
bootstrap.
- hermes_cli/config.py: new terminal.shell_init_files (explicit list,
supports ~ and ${VAR}) and terminal.auto_source_bashrc (default on)
knobs. When shell_init_files is set it takes precedence; when it's
empty and auto_source_bashrc is on, ~/.bashrc gets auto-sourced.
- tests/tools/test_local_shell_init.py: 10 tests covering the resolver
(auto-bashrc, missing file, explicit override, ~/${VAR} expansion,
opt-out) and the prelude builder (quoting, guarded sourcing), plus
a real-LocalEnvironment snapshot test that confirms exports in the
init file land in subsequent commands' environment.
- website/docs/reference/faq.md: documents the fix in Troubleshooting,
including the zsh-user pattern of sourcing ~/.zshrc or nvm.sh
directly via shell_init_files.
Validation: 10/10 new tests pass; tests/tools/test_local_*.py 40/40
pass; tests/agent/ 1591/1591 pass; tests/hermes_cli/test_config.py
50/50 pass. E2E in an isolated HERMES_HOME: confirmed that a fake
~/.bashrc setting a marker var and PATH addition shows up in a real
LocalEnvironment().execute() call, that auto_source_bashrc=false
suppresses it, that an explicit shell_init_files entry wins over the
auto default, and that a missing bashrc is silently skipped.
This commit is contained in:
parent
b48ea41d27
commit
328223576b
7 changed files with 665 additions and 3 deletions
|
|
@ -272,6 +272,45 @@ Put the most common workflow first. Edge cases and advanced usage go at the bott
|
|||
|
||||
For XML/JSON parsing or complex logic, include helper scripts in `scripts/` — don't expect the LLM to write parsers inline every time.
|
||||
|
||||
#### Referencing bundled scripts from SKILL.md
|
||||
|
||||
When a skill is loaded, the activation message exposes the absolute skill directory as `[Skill directory: /abs/path]` and also substitutes two template tokens anywhere in the SKILL.md body:
|
||||
|
||||
| Token | Replaced with |
|
||||
|---|---|
|
||||
| `${HERMES_SKILL_DIR}` | Absolute path to the skill's directory |
|
||||
| `${HERMES_SESSION_ID}` | The active session id (left in place if there is no session) |
|
||||
|
||||
So a SKILL.md can tell the agent to run a bundled script directly with:
|
||||
|
||||
```markdown
|
||||
To analyse the input, run:
|
||||
|
||||
node ${HERMES_SKILL_DIR}/scripts/analyse.js <input>
|
||||
```
|
||||
|
||||
The agent sees the substituted absolute path and invokes the `terminal` tool with a ready-to-run command — no path math, no extra `skill_view` round-trip. Disable substitution globally with `skills.template_vars: false` in `config.yaml`.
|
||||
|
||||
#### Inline shell snippets (opt-in)
|
||||
|
||||
Skills can also embed inline shell snippets written as `` !`cmd` `` in the SKILL.md body. When enabled, each snippet's stdout is inlined into the message before the agent reads it, so skills can inject dynamic context:
|
||||
|
||||
```markdown
|
||||
Current date: !`date -u +%Y-%m-%d`
|
||||
Git branch: !`git -C ${HERMES_SKILL_DIR} rev-parse --abbrev-ref HEAD`
|
||||
```
|
||||
|
||||
This is **off by default** — any snippet in a SKILL.md runs on the host without approval, so only enable it for skill sources you trust:
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
skills:
|
||||
inline_shell: true
|
||||
inline_shell_timeout: 10 # seconds per snippet
|
||||
```
|
||||
|
||||
Snippets run with the skill directory as their working directory, and output is capped at 4000 characters. Failures (timeouts, non-zero exits) show up as a short `[inline-shell error: ...]` marker instead of breaking the whole skill.
|
||||
|
||||
### Test It
|
||||
|
||||
Run the skill and verify the agent follows the instructions correctly:
|
||||
|
|
|
|||
|
|
@ -160,6 +160,33 @@ brew install python@3.12 # macOS
|
|||
|
||||
The installer handles this automatically — if you see this error during manual installation, upgrade Python first.
|
||||
|
||||
#### Terminal commands say `node: command not found` (or `nvm`, `pyenv`, `asdf`, …)
|
||||
|
||||
**Cause:** Hermes builds a per-session environment snapshot by running `bash -l` once at startup. A bash login shell reads `/etc/profile`, `~/.bash_profile`, and `~/.profile`, but **does not source `~/.bashrc`** — so tools that install themselves there (`nvm`, `asdf`, `pyenv`, `cargo`, custom `PATH` exports) stay invisible to the snapshot. This most commonly happens when Hermes runs under systemd or in a minimal shell where nothing has pre-loaded the interactive shell profile.
|
||||
|
||||
**Solution:** Hermes auto-sources `~/.bashrc` by default. If that's not enough — e.g. you're a zsh user whose PATH lives in `~/.zshrc`, or you init `nvm` from a standalone file — list the extra files to source in `~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
terminal:
|
||||
shell_init_files:
|
||||
- ~/.zshrc # zsh users: pulls zsh-managed PATH into the bash snapshot
|
||||
- ~/.nvm/nvm.sh # direct nvm init (works regardless of shell)
|
||||
- /etc/profile.d/cargo.sh # system-wide rc files
|
||||
# When this list is set, the default ~/.bashrc auto-source is NOT added —
|
||||
# include it explicitly if you want both:
|
||||
# - ~/.bashrc
|
||||
# - ~/.zshrc
|
||||
```
|
||||
|
||||
Missing files are skipped silently. Sourcing happens in bash, so files that rely on zsh-only syntax may error — if that's a concern, source just the PATH-setting portion (e.g. nvm's `nvm.sh` directly) rather than the whole rc file.
|
||||
|
||||
To disable the auto-source behaviour (strict login-shell semantics only):
|
||||
|
||||
```yaml
|
||||
terminal:
|
||||
auto_source_bashrc: false
|
||||
```
|
||||
|
||||
#### `uv: command not found`
|
||||
|
||||
**Cause:** The `uv` package manager isn't installed or not in PATH.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue