fix(dev): bind hermes to the active worktree after activation
Activation defined PATH and PYTHONPATH, but `hermes` still resolved to a global command or an MSIX alias. The shell now defines `hermes` as a function for this worktree. It runs this checkout's CLI only while the shell is inside the tree, and it refuses outside it. The prompt gains a branch prefix and drops it outside the tree. `deactivate` removes the function and the prefix.
This commit is contained in:
@@ -79,14 +79,14 @@ En Bash, desde la raíz del repositorio:
|
||||
|
||||
```bash
|
||||
source ./activate
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
En PowerShell:
|
||||
|
||||
```powershell
|
||||
. .\activate.ps1
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
### Entorno independiente de pruebas
|
||||
|
||||
@@ -125,20 +125,22 @@ Bash:
|
||||
|
||||
```bash
|
||||
source ./activate
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
. .\activate.ps1
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
Run `python hermes` for this checkout, not a global `hermes` alias. PM activation
|
||||
Run `hermes` for this checkout. Activation defines it as a function for this
|
||||
worktree, so it hides a global `hermes` command or MSIX alias and refuses
|
||||
outside the worktree. PM activation
|
||||
syncs tools and Python dependencies before adding them to the shell. It does not
|
||||
install JS workspaces or rewrite launchers and shell configuration. `deactivate`
|
||||
restores the prior shell environment.
|
||||
restores the prior shell environment and removes the function.
|
||||
|
||||
### Manual development and test environment
|
||||
|
||||
@@ -176,7 +178,7 @@ This test environment does not replace PM's application selection or tool
|
||||
store. Do not point a bundled app at it or install into an MSIX payload.
|
||||
|
||||
For an isolated development instance, select a disposable `HERMES_HOME` before
|
||||
starting the source command. Use `python hermes setup` to configure it rather
|
||||
starting the source command. Use `hermes setup` to configure it rather
|
||||
than copying production credentials into the checkout.
|
||||
|
||||
### JavaScript workspaces and website
|
||||
|
||||
83
activate
83
activate
@@ -8,6 +8,10 @@
|
||||
# Emits the composed pm env (PATH + tool vars) into the CURRENT shell, with
|
||||
# save/restore: `deactivate` undoes exactly what activation changed.
|
||||
#
|
||||
# `hermes` becomes a shell function for this checkout. It runs only while the
|
||||
# shell is inside this worktree and refuses outside it, so a sibling worktree
|
||||
# keeps its own command. A prompt prefix names the worktree.
|
||||
#
|
||||
# Sync through setup before selecting the environment. PM owns freshness;
|
||||
# activation does not maintain a second dependency stamp.
|
||||
# ============================================================================
|
||||
@@ -82,6 +86,65 @@ for k, v in json.load(sys.stdin).items():
|
||||
continue
|
||||
print("export %s=%s" % (k, shlex.quote(str(v))))')"
|
||||
|
||||
# This checkout, not whichever `hermes` PATH finds. A function beats PATH, an
|
||||
# alias, and a hashed command, including the MSIX execution alias.
|
||||
__HERMES_WORKTREE="$_hermes_repo"
|
||||
# The branch names the worktree. A checkout cannot share a branch with another.
|
||||
__HERMES_WORKTREE_NAME="$(git -C "$_hermes_repo" rev-parse --abbrev-ref HEAD 2>/dev/null)"
|
||||
if [ -z "$__HERMES_WORKTREE_NAME" ] || [ "$__HERMES_WORKTREE_NAME" = HEAD ]; then
|
||||
__HERMES_WORKTREE_NAME="$(basename "$_hermes_repo")"
|
||||
fi
|
||||
__HERMES_SAVED_PS1="${PS1-__HERMES_UNSET__}"
|
||||
__HERMES_SAVED_PROMPT_COMMAND="${PROMPT_COMMAND-__HERMES_UNSET__}"
|
||||
|
||||
_hermes_worktree_here() {
|
||||
_hermes_top="$(git rev-parse --show-toplevel 2>/dev/null)" || return 1
|
||||
[ "$(cd "$_hermes_top" && pwd -P)" = "$(cd "$__HERMES_WORKTREE" && pwd -P)" ]
|
||||
}
|
||||
|
||||
hermes() {
|
||||
_hermes_worktree_here || {
|
||||
printf '%s\n' "hermes: $(pwd) is outside $__HERMES_WORKTREE; refusing (the installed command is hidden while this checkout is active)" >&2
|
||||
return 1
|
||||
}
|
||||
(
|
||||
cd "$__HERMES_WORKTREE" || exit 1
|
||||
if [ -n "${PYTHON-}" ]; then
|
||||
exec "$PYTHON" hermes "$@"
|
||||
elif [ -x .venv/Scripts/python.exe ]; then
|
||||
exec .venv/Scripts/python.exe hermes "$@"
|
||||
elif [ -x .venv/bin/python ]; then
|
||||
exec .venv/bin/python hermes "$@"
|
||||
elif [ -x venv/Scripts/python.exe ]; then
|
||||
exec venv/Scripts/python.exe hermes "$@"
|
||||
elif [ -x venv/bin/python ]; then
|
||||
exec venv/bin/python hermes "$@"
|
||||
else
|
||||
exec python hermes "$@"
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
_hermes_prompt() {
|
||||
if [ "$__HERMES_SAVED_PS1" = "__HERMES_UNSET__" ]; then
|
||||
_hermes_base_ps1=""
|
||||
else
|
||||
_hermes_base_ps1="$__HERMES_SAVED_PS1"
|
||||
fi
|
||||
if _hermes_worktree_here; then
|
||||
PS1="($__HERMES_WORKTREE_NAME) $_hermes_base_ps1"
|
||||
else
|
||||
PS1="$_hermes_base_ps1"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -n "${PROMPT_COMMAND-}" ]; then
|
||||
PROMPT_COMMAND="_hermes_prompt; ${PROMPT_COMMAND}"
|
||||
else
|
||||
PROMPT_COMMAND=_hermes_prompt
|
||||
fi
|
||||
_hermes_prompt
|
||||
|
||||
deactivate() {
|
||||
export PATH="$__HERMES_SAVED_PATH"
|
||||
for _hermes_k in $__HERMES_KEY_LIST; do
|
||||
@@ -94,9 +157,21 @@ deactivate() {
|
||||
fi
|
||||
unset "__HERMES_SAVED_$_hermes_k" "__HERMES_PRIOR_$_hermes_k"
|
||||
done
|
||||
unset __HERMES_SAVED_PATH __HERMES_KEY_LIST __HERMES_ACTIVATED
|
||||
unset -f deactivate
|
||||
unset _hermes_k _hermes_was _hermes_old
|
||||
if [ "$__HERMES_SAVED_PS1" = "__HERMES_UNSET__" ]; then
|
||||
unset PS1
|
||||
else
|
||||
PS1="$__HERMES_SAVED_PS1"
|
||||
fi
|
||||
if [ "$__HERMES_SAVED_PROMPT_COMMAND" = "__HERMES_UNSET__" ]; then
|
||||
unset PROMPT_COMMAND
|
||||
else
|
||||
PROMPT_COMMAND="$__HERMES_SAVED_PROMPT_COMMAND"
|
||||
fi
|
||||
unset __HERMES_SAVED_PATH __HERMES_KEY_LIST __HERMES_ACTIVATED \
|
||||
__HERMES_WORKTREE __HERMES_WORKTREE_NAME \
|
||||
__HERMES_SAVED_PS1 __HERMES_SAVED_PROMPT_COMMAND
|
||||
unset -f deactivate hermes _hermes_worktree_here _hermes_prompt
|
||||
unset _hermes_k _hermes_was _hermes_old _hermes_top
|
||||
}
|
||||
|
||||
# remember the key list for deactivate (kept out of the loop vars above)
|
||||
@@ -104,4 +179,4 @@ __HERMES_KEY_LIST="$_hermes_keys"
|
||||
|
||||
unset _hermes_repo _hermes_os _hermes_arch _hermes_target _hermes_store \
|
||||
_hermes_py _hermes_json _hermes_keys _hermes_k _hermes_entry _hermes_venvpy \
|
||||
_hermes_winarch
|
||||
_hermes_winarch _hermes_top
|
||||
|
||||
76
activate.ps1
76
activate.ps1
@@ -65,13 +65,87 @@ foreach ($key in $global:_hermesKeys) {
|
||||
foreach ($property in $composed.PSObject.Properties) {
|
||||
Set-Item -Path "env:$($property.Name)" -Value ([string]$property.Value)
|
||||
}
|
||||
# This checkout, not whichever `hermes` PATH finds. A function beats PATH,
|
||||
# an alias, and the MSIX execution alias. It runs only while the shell is
|
||||
# inside this worktree.
|
||||
$global:_hermesWorktree = $repo
|
||||
# The branch names the worktree. A checkout cannot share a branch with another.
|
||||
# git's "not a repository" is not an activation failure: the directory name is
|
||||
# the label, and the command still refuses outside this tree.
|
||||
$branch = $null
|
||||
try { $branch = & git -C $repo rev-parse --abbrev-ref HEAD 2>$null } catch { $branch = $null }
|
||||
if ($branch -and $branch -ne 'HEAD') {
|
||||
$global:_hermesWorktreeName = $branch
|
||||
} else {
|
||||
$global:_hermesWorktreeName = Split-Path -Leaf $repo
|
||||
}
|
||||
if (Test-Path function:prompt) {
|
||||
$global:_hermesSavedPrompt = (Get-Item function:prompt).ScriptBlock
|
||||
} else {
|
||||
$global:_hermesSavedPrompt = $null
|
||||
}
|
||||
|
||||
function global:_hermesWorktreeHere {
|
||||
# Prompt calls this after every command. Keep the user's exit code.
|
||||
$saved = $global:LASTEXITCODE
|
||||
$top = $null
|
||||
try { $top = & git rev-parse --show-toplevel 2>$null } catch { $top = $null }
|
||||
$global:LASTEXITCODE = $saved
|
||||
if (-not $top) { return $false }
|
||||
$here = [System.IO.Path]::GetFullPath($top).TrimEnd('\')
|
||||
$root = [System.IO.Path]::GetFullPath($global:_hermesWorktree).TrimEnd('\')
|
||||
return $here.Equals($root, [System.StringComparison]::OrdinalIgnoreCase)
|
||||
}
|
||||
|
||||
function global:hermes {
|
||||
if (-not (_hermesWorktreeHere)) {
|
||||
$here = (Get-Location).Path
|
||||
Write-Error "hermes: $here is outside $($global:_hermesWorktree); refusing (the installed command is hidden while this checkout is active)" -ErrorAction Continue
|
||||
$global:LASTEXITCODE = 1
|
||||
return
|
||||
}
|
||||
Push-Location -LiteralPath $global:_hermesWorktree
|
||||
try {
|
||||
$py = $env:PYTHON
|
||||
if (-not $py) {
|
||||
foreach ($candidate in @(
|
||||
'.venv\Scripts\python.exe', 'venv\Scripts\python.exe',
|
||||
'.venv\bin\python', 'venv\bin\python'
|
||||
)) {
|
||||
if (Test-Path -LiteralPath $candidate) { $py = $candidate; break }
|
||||
}
|
||||
}
|
||||
if (-not $py) { $py = 'python' }
|
||||
& $py hermes @args
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function global:prompt {
|
||||
$prefix = ''
|
||||
if (_hermesWorktreeHere) { $prefix = "($($global:_hermesWorktreeName)) " }
|
||||
if ($global:_hermesSavedPrompt) {
|
||||
return $prefix + (& $global:_hermesSavedPrompt)
|
||||
}
|
||||
return "$prefix$($(Get-Location).Path)> "
|
||||
}
|
||||
|
||||
function global:deactivate {
|
||||
foreach ($key in $global:_hermesKeys) {
|
||||
$saved = $global:_hermesSaved[$key]
|
||||
if ($saved.WasSet) { Set-Item -Path "env:$key" -Value $saved.Value }
|
||||
else { Remove-Item -Path "env:$key" -ErrorAction SilentlyContinue }
|
||||
}
|
||||
if ($global:_hermesSavedPrompt) {
|
||||
Set-Item -Path function:prompt -Value $global:_hermesSavedPrompt
|
||||
} else {
|
||||
Remove-Item function:prompt -ErrorAction SilentlyContinue
|
||||
}
|
||||
$global:_hermesKeys = $null
|
||||
$global:_hermesSaved = $null
|
||||
Remove-Item function:deactivate
|
||||
$global:_hermesWorktree = $null
|
||||
$global:_hermesWorktreeName = $null
|
||||
$global:_hermesSavedPrompt = $null
|
||||
Remove-Item function:deactivate, function:hermes, function:_hermesWorktreeHere -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
@@ -50,19 +50,20 @@ Bash:
|
||||
|
||||
```bash
|
||||
source ./activate
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
. .\activate.ps1
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
Run `python hermes` for this checkout, not a global `hermes` alias. PM activation
|
||||
syncs tools and Python dependencies before adding them to the shell. It does
|
||||
not install JS workspaces or rewrite launchers and shell configuration. `deactivate` restores the prior shell environment.
|
||||
Run `hermes` for this checkout. Activation defines it as a function for this
|
||||
worktree, so it hides a global `hermes` alias and refuses outside the worktree.
|
||||
PM activation syncs tools and Python dependencies before adding them to the shell. It does
|
||||
not install JS workspaces or rewrite launchers and shell configuration. `deactivate` restores the prior shell environment and removes the function.
|
||||
|
||||
### Manual development and test environment {#manual-development-and-test-environment}
|
||||
|
||||
@@ -100,7 +101,7 @@ This test environment does not replace PM's application selection or tool
|
||||
store. Do not point a bundled app at it or install into an MSIX payload.
|
||||
|
||||
For an isolated development instance, select a disposable `HERMES_HOME` before
|
||||
starting the source command. Use `python hermes setup` to configure it rather
|
||||
starting the source command. Use `hermes setup` to configure it rather
|
||||
than copying production credentials into the checkout.
|
||||
|
||||
### JavaScript workspaces and website
|
||||
|
||||
@@ -325,10 +325,16 @@ rebuild. A setup failure returns an error before changing the activated shell
|
||||
environment, including when re-sourcing an already active environment.
|
||||
|
||||
After sync, activation prepends installed PM tools to `PATH` and sets
|
||||
`PYTHONPATH` to this checkout and its selected dependency tree. It does not
|
||||
change an OS-wide PATH or activate a conventional venv prompt.
|
||||
`PYTHONPATH` to this checkout and its selected dependency tree. It also
|
||||
defines `hermes` as a shell function for this worktree. The function runs
|
||||
this checkout's CLI and hides the installed command, including an MSIX alias.
|
||||
It runs only while the shell is inside this worktree and refuses outside it,
|
||||
so a sibling worktree does not inherit the command. The prompt gains a prefix
|
||||
naming the branch, and drops it outside the tree. It does not
|
||||
change an OS-wide PATH or install a conventional venv prompt.
|
||||
Start in a clean shell rather than nesting this inside another venv.
|
||||
`deactivate` restores the environment values captured by the activation script.
|
||||
`deactivate` restores the environment values captured by the activation script,
|
||||
and removes the function and the prompt prefix.
|
||||
It does not uninstall packages or stop processes that you started.
|
||||
|
||||
Verify the interpreter and source before doing work:
|
||||
@@ -338,7 +344,7 @@ python -c "import sys, pm; print(sys.executable); print(pm.__file__)"
|
||||
python -c "import httpx; print(httpx.__file__)"
|
||||
node --version
|
||||
npm --version
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
`python` must resolve to the PM store interpreter. `pm.__file__` must point
|
||||
@@ -348,13 +354,14 @@ attention, even if `source ./activate` itself returned successfully.
|
||||
|
||||
### Work on this source tree
|
||||
|
||||
Use checkout-qualified commands so a global `hermes` command or MSIX alias
|
||||
cannot run a different installation:
|
||||
`hermes` is this worktree's CLI while the shell is inside it. Outside the
|
||||
worktree the function refuses, so it cannot run another checkout's tree or
|
||||
fall through to an installed command:
|
||||
|
||||
```bash
|
||||
python hermes setup
|
||||
python hermes
|
||||
python hermes --tui
|
||||
hermes setup
|
||||
hermes
|
||||
hermes --tui
|
||||
python -m pm.cli status
|
||||
```
|
||||
|
||||
|
||||
@@ -56,29 +56,29 @@ Do not clone into a signed app package or overwrite the packaged runtime.
|
||||
|
||||
Read the [developer workflow](../reference/package-management.md#developer-workflow)
|
||||
for native build prerequisites and current bootstrap limitations. Select your
|
||||
intended `HERMES_HOME` before preparation, then use the checkout's PM bootstrap:
|
||||
intended `HERMES_HOME` before preparation, then activate. Activation runs the
|
||||
bootstrap itself:
|
||||
|
||||
```bash
|
||||
bash setup-hermes.sh
|
||||
source ./activate
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
On native Windows, use PowerShell:
|
||||
|
||||
```powershell
|
||||
.\setup-hermes.ps1
|
||||
. .\activate.ps1
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
The bootstrap reads tool pins from `pm/lock.json` and delegates installation
|
||||
to PM. Current first-party code requires Python 3.14 (`>=3.14,<3.15`).
|
||||
The source default is the `all` extra, not the desktop bundle's `--all-extras`.
|
||||
|
||||
Activation composes the installed tool environment. `python hermes` explicitly
|
||||
runs this checkout and avoids an older `hermes` command or MSIX alias on PATH.
|
||||
`deactivate` restores the shell environment when you finish.
|
||||
Activation composes the installed tool environment and defines `hermes` as this
|
||||
worktree's CLI. The function hides an older `hermes` command or MSIX alias and
|
||||
refuses outside the worktree.
|
||||
`deactivate` restores the shell environment and removes the function when you finish.
|
||||
|
||||
For test dependencies and manual environments, use the
|
||||
[development setup](../developer-guide/contributing.md).
|
||||
@@ -95,23 +95,23 @@ POSIX example:
|
||||
|
||||
```bash
|
||||
export HERMES_HOME="$HOME/hermes-source-data"
|
||||
python hermes setup
|
||||
python hermes
|
||||
hermes setup
|
||||
hermes
|
||||
```
|
||||
|
||||
PowerShell example:
|
||||
|
||||
```powershell
|
||||
$env:HERMES_HOME = Join-Path $HOME 'hermes-source-data'
|
||||
python hermes setup
|
||||
python hermes
|
||||
hermes setup
|
||||
hermes
|
||||
```
|
||||
|
||||
If you change the home after preparing PM state, run the bootstrap for that
|
||||
home before relying on its selected dependencies. Do not assume that changing
|
||||
the environment variable moves data or copies runtime state.
|
||||
|
||||
To build a source desktop, run `python hermes desktop` from the prepared
|
||||
To build a source desktop, run `hermes desktop` from the prepared
|
||||
checkout. Opening the old packaged app still starts its packaged backend.
|
||||
|
||||
## Docker users
|
||||
@@ -158,11 +158,11 @@ diagnostics and garbage collection rather than deleting the shared data root.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Wrong version:** inspect command resolution, then use `python hermes --version`
|
||||
- **Wrong version:** inspect command resolution, then use `hermes --version`
|
||||
from the activated checkout.
|
||||
- **Missing dependencies:** run `python -m pm.cli install` from the intended
|
||||
source environment, then restart the affected Hermes process.
|
||||
- **Gateway already running:** inspect `python hermes gateway status` for the
|
||||
- **Gateway already running:** inspect `hermes gateway status` for the
|
||||
selected profile. Stop the identified owner; do not kill unrelated processes.
|
||||
- **Different skills after first run:** newer code can sync bundled skills into
|
||||
the data home. A source checkout is not a read-only view of that home.
|
||||
|
||||
@@ -45,19 +45,20 @@ Bash:
|
||||
|
||||
```bash
|
||||
source ./activate
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
. .\activate.ps1
|
||||
python hermes --version
|
||||
hermes --version
|
||||
```
|
||||
|
||||
PowerShell 开头的点和空格用于 dot-source,不能省略。
|
||||
激活通过 PM 准备工具并同步依赖,但不创建 JS workspaces,也不设置常规 venv 提示符。
|
||||
使用 `python hermes` 明确运行当前 checkout,避免命中全局命令或 MSIX 别名。
|
||||
激活把 `hermes` 定义成当前 worktree 的函数,因此会盖住全局命令和 MSIX 别名,
|
||||
并在离开该 worktree 时拒绝运行。
|
||||
`deactivate` 恢复激活前的环境,不卸载依赖或停止已启动的进程。
|
||||
|
||||
### 独立开发和测试环境 {#manual-development-and-test-environment}
|
||||
@@ -79,7 +80,7 @@ PM 不会自动删除已有目录。不要通过原始 pip 或 uv 命令修改 H
|
||||
Windows 上通过 Bash 运行 `scripts/run_tests.sh`,并预先准备本机 C++ 编译环境。
|
||||
|
||||
独立测试环境不替代 PM 工具存储或应用的依赖选择。不要修改签名应用的载荷。
|
||||
运行开发实例前,选择临时的 `HERMES_HOME`,再使用 `python hermes setup` 配置它。
|
||||
运行开发实例前,选择临时的 `HERMES_HOME`,再使用 `hermes setup` 配置它。
|
||||
不要把生产凭据复制到 checkout。
|
||||
|
||||
从仓库根目录运行 `npm ci` 安装 JS workspaces。网站单独使用:
|
||||
|
||||
Reference in New Issue
Block a user