| name | create-project-hapi |
| description | 为指定项目创建完全隔离的 hapi(Claude Code On the Go)实例,包括独立数据目录、LaunchAgent 持久化、zsh wrapper 和 app.hapi.run 直连 URL。与全局 ~/.hapi 互不干扰。 |
| disable-model-invocation | false |
| argument-hints | <project-dir> [hub-port=3007] |
create-project-hapi
为项目单独开一个 hapi 实例,与全局 ~/.hapi 完全隔离。适合:
- 一台机器上维护多个项目的独立 hapi 配置、token、relay
- 项目级 hapi 随用户登录自动启动,不依赖当前终端
- 团队复用同一套项目级 hapi 配置
前置条件
- macOS
- hapi 已通过 Homebrew 安装:
/opt/homebrew/bin/hapi
- 使用 zsh
- 已知目标项目根目录绝对路径
参数
<project-dir>:项目根目录绝对路径
[hub-port]:可选,默认 3007,避免与全局 hapi hub 3006 冲突
执行步骤
1. 设置变量
PROJECT_DIR="/path/to/project"
PROJECT_NAME=$(basename "$PROJECT_DIR")
HAPI_PORT="${2:-3007}"
HAPI_HOME="$PROJECT_DIR/.hapi"
PLIST_DIR="$HOME/Library/LaunchAgents"
2. 创建独立 HAPI_HOME 并生成 token
mkdir -p "$HAPI_HOME"
HAPI_HOME="$HAPI_HOME" HAPI_LISTEN_PORT="$HAPI_PORT" hapi hub --relay
首次启动会生成新的 cliApiToken 并保存到 $HAPI_HOME/settings.json。终端会打印 relay URL,例如:
https://xxxxxx.relay.hapi.run
记下 token 和 relay URL。token 可通过以下命令再次读取:
python3 -c "import json; print(json.load(open('$HAPI_HOME/settings.json'))['cliApiToken'])"
3. 创建 LaunchAgent plist
Hub(带 relay)
文件:$PLIST_DIR/com.hapi.$PROJECT_NAME.hub.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.hapi.PROJECT_NAME.hub</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/hapi</string>
<string>hub</string>
<string>--relay</string>
</array>
<key>WorkingDirectory</key>
<string>PROJECT_DIR</string>
<key>EnvironmentVariables</key>
<dict>
<key>HAPI_HOME</key>
HAPI_HOME
HAPI_LISTEN_PORT
HAPI_PORT
PATH
/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
RunAtLoad
KeepAlive
ThrottleInterval
10
StandardOutPath
HOME/Library/Logs/hapi/PROJECT_NAME-hub.log
StandardErrorPath
HOME/Library/Logs/hapi/PROJECT_NAME-hub-error.log
Runner
文件:$PLIST_DIR/com.hapi.$PROJECT_NAME.runner.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.hapi.PROJECT_NAME.runner</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/hapi</string>
<string>runner</string>
<string>start</string>
</array>
<key>WorkingDirectory</key>
<string>PROJECT_DIR</string>
<key>EnvironmentVariables</key>
<dict>
<key>HAPI_HOME</key>
HAPI_HOME
HAPI_API_URL
http://localhost:HAPI_PORT
HAPI_CLAUDE_PATH
HOME/.hapi/claude-wrappers/hapi-claude
PATH
/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
RunAtLoad
KeepAlive
ThrottleInterval
10
StandardOutPath
HOME/Library/Logs/hapi/PROJECT_NAME-runner.log
StandardErrorPath
HOME/Library/Logs/hapi/PROJECT_NAME-runner-error.log
替换占位符:PROJECT_NAME、PROJECT_DIR、HAPI_HOME、HAPI_PORT、HOME。
4. 加载 LaunchAgent
mkdir -p "$HOME/Library/Logs/hapi"
launchctl load -w "$PLIST_DIR/com.hapi.$PROJECT_NAME.hub.plist"
launchctl load -w "$PLIST_DIR/com.hapi.$PROJECT_NAME.runner.plist"
5. 创建 Claude 模式 wrapper 脚本
hapi 默认调用系统 claude 命令。若希望 hapi 使用 ckh/cg 等自定义 Claude 启动方式(对应 ~/.cp/*.json 的 settings),需要创建一个 wrapper 脚本,并通过 HAPI_CLAUDE_PATH 环境变量让 hapi 使用它。
当 hapi 启动 direct-connect / runner 会话时,会透传一个自己的 --settings(session-hook,用于注入 hapi 的 SessionStart hook)。这个 session-hook 如果直接覆盖 mode settings,会导致 ANTHROPIC_BASE_URL 等 provider env 丢失。因此 wrapper 需要把 mode settings 与 hapi 透传的 session-hook 合并后再传给 Claude。
创建文件:$HOME/.hapi/claude-wrappers/hapi-claude
#!/bin/bash
set -euo pipefail
mode="${HAPI_CLAUDE_MODE:-}"
if [[ -z "$mode" ]]; then
if [[ -n "${HAPI_HOME:-}" && -f "$HAPI_HOME/claude-mode" ]]; then
mode=$(<"$HAPI_HOME/claude-mode")
mode="${mode//[[:space:]]/}"
fi
fi
if [[ -z "$mode" ]]; then
echo "HAPI_CLAUDE_MODE is not set, falling back to system claude" >&2
exec claude "$@"
fi
case "$mode" in
cg) code=700000; binary="$HOME/.cc-expand/bin/claude-70w"; settings="$HOME/.cp/glm.json" ;;
ck) code=270000; binary="$HOME/.cc-expand/bin/claude-27w"; settings="$HOME/.cp/kimi.json" ;;
ckh) code=270000; binary="$HOME/.cc-expand/bin/claude-27w"; settings= ;;
ckz) code=270000; binary=; settings= ;;
ckzh) code=270000; binary=; settings= ;;
cks) code=270000; binary=; settings= ;;
cksh) code=270000; binary=; settings= ;;
ckg) code=270000; binary=; settings= ;;
ckgh) code=270000; binary=; settings= ;;
cv) code=700000; binary=; settings= ;;
cvg) code=700000; binary=; settings= ;;
cds) code=700000; binary=; settings= ;;
cdsf) code=700000; binary=; settings= ;;
cmm) code=700000; binary=; settings= ;;
cmmf) code=201000; binary=; settings= ;;
cog) code=700000; binary=; settings= ;;
coq) code=270000; binary=; settings= ;;
cods) code=700000; binary=; settings= ;;
*)
>&2
1
;;
[[ ! -x ]];
>&2
1
[[ ! -f ]];
>&2
1
hapi_settings=
remaining_args=()
i=0
[[ -lt ]];
arg=
[[ == ]];
[[ $((i+)) -lt ]];
hapi_settings=
i=$((i+))
[[ == --settings=* ]];
hapi_settings=
i=$((i+))
remaining_args+=()
i=$((i+))
tmpdir=$( -d)
700
tmp_settings=
[[ -n && -f ]];
jq --arg v -s >
jq --arg v >
600
() {
-rf
}
cleanup EXIT
--settings --dangerously-skip-permissions
然后赋权:
chmod +x "$HOME/.hapi/claude-wrappers/hapi-claude"
6. 设置项目默认 Claude 模式
runner 在后台为远程会话启动 Claude 时,没有 shell wrapper 来传 ckh/cg 等模式。因此需要把项目默认模式写到 $HAPI_HOME/claude-mode,wrapper 在 HAPI_CLAUDE_MODE 为空时会读取它。
mkdir -p "$HAPI_HOME"
echo "ckh" > "$HAPI_HOME/claude-mode"
之后每次在项目目录内用 hapi ckh ... / hapi cg ... 等启动,zsh wrapper 都会自动更新这个文件,所以远程会话会跟最近一次本地使用的模式保持一致。
7. 配置 zsh wrapper
在 ~/.zshrc 中添加一个函数,使进入该项目目录后运行 hapi 命令自动使用项目级实例,并在 $HAPI_HOME/url.txt 写入 app.hapi.run 直连 URL。同时支持:
- 首参数指定 Claude 启动模式(如
hapi ckh hub --relay)
- 把 Claude 参数写在 hapi 子命令前(如
hapi ckh --continue hub --relay),wrapper 会自动把 Claude 参数挪到子命令后面透传
hapi() {
local project_dir="PROJECT_DIR"
local hapi_subcommands=(hub runner auth codex gemini opencode mcp connect notify doctor server)
local mode=""
case "${1:-}" in
cg|ck|ckh|ckz|ckzh|cks|cksh|ckg|ckgh|cv|cvg|cds|cdsf|cmm|cmmf|cog|coq|cods)
mode="$1"
shift
;;
esac
local claude_args=()
local hapi_args=()
local found_subcmd=0
while [[ $# -gt 0 ]]; do
local is_subcmd=0
for sub in "${hapi_subcommands[@]}"; do
if [[ "$1" == "$sub" ]]; then
is_subcmd=1
break
fi
done
if [[ $is_subcmd -eq 1 ]]; then
found_subcmd=1
fi
if [[ $found_subcmd -eq 1 ]]; then
hapi_args+=("$1")
else
claude_args+=()
all_args=( )
hapi_home=
url_file=
[[ -n && == * ]];
>
[[ == * ]];
token=
[[ -f ]];
token=$(python3 -c 2>/dev/null)
relay_url=
hub_log=
[[ -f ]];
relay_url=$( grep -oE | -1)
[[ -n && -n ]];
encoded_hub=$(python3 -c )
>
[[ -n ]];
HAPI_CLAUDE_PATH= \
HAPI_CLAUDE_MODE= \
HAPI_HOME= \
HAPI_LISTEN_PORT= \
HAPI_API_URL= \
hapi
HAPI_HOME= \
HAPI_LISTEN_PORT= \
HAPI_API_URL= \
hapi
[[ -n ]];
HAPI_CLAUDE_PATH= \
HAPI_CLAUDE_MODE= \
hapi
hapi
}
替换占位符:PROJECT_DIR、PROJECT_NAME、HAPI_PORT。
注意:这里使用 command grep 绕过可能存在的 grep → rg shadow,避免正则解析错误。
注意:hapi CLI 要求 Claude 参数出现在子命令之后,wrapper 会自动重排,因此 hapi ckh --continue hub --relay 等价于 hapi ckh hub --relay --continue。
7. 忽略 .hapi 目录
在项目 .gitignore 中加入:
# hapi local instance data (tokens, logs, runtime)
.hapi/
8. 验证
cd "$PROJECT_DIR"
hapi runner status
cat .hapi/url.txt
hapi ckh hub --relay
连接 app.hapi.run
打开 .hapi/url.txt 中的 URL,或在浏览器中手动操作:
- 打开 https://app.hapi.run
- 点右上角 Hub (自定义)
- 服务器地址填入 relay URL(如
https://xxxxxx.relay.hapi.run)
- 保存
- 访问令牌填入
cliApiToken
- 登录
如果提示 Invalid access token,检查浏览器 localStorage 中的 hapi_hub_url 是否与当前项目的 relay URL 一致,不一致则清除 localStorage 或手动切换 Hub。
Claude 启动模式
zsh wrapper 支持把 cg/ckh/ck 等作为 hapi 的首参数,hapi 会通过 $HOME/.hapi/claude-wrappers/hapi-claude 启动对应 settings 的 Claude:
hapi ckh
hapi ckh hub --relay
hapi ckh --continue hub --relay
hapi cg runner status
由于 hapi CLI 要求 Claude 参数出现在子命令之后,wrapper 会自动把 --continue 这类 Claude 参数挪到子命令后面(例如 hapi ckh --continue hub --relay 实际调用 hapi hub --relay --continue)。
远程会话的模式
runner 在后台为远程(app.hapi.run)会话启动 Claude 时,没有 shell 可以传 ckh/cg 参数,因此它会读取 $HAPI_HOME/claude-mode 文件作为默认模式。zsh wrapper 每次在项目目录内使用带 mode 的命令时,都会自动更新这个文件,保证远程会话与最近一次本地使用的模式一致。
如果想手动切换远程默认模式:
echo "cg" > /path/to/project/.hapi/claude-mode
不带模式参数时,hapi 保持默认行为(调用系统 claude)。
支持的 mode 与 ~/.zshrc 中的 Claude 启动函数一一对应:
| mode | code | settings 文件 |
|---|
cg | 700000 | ~/.cp/glm.json |
ck | 270000 | ~/.cp/kimi.json |
ckh | 270000 | ~/.cp/kimi-highspeed.json |
ckz | 270000 | ~/.cp/kimi-zhongge.json |
ckzh | 270000 | ~/.cp/kimi-zhongge-highspeed.json |
cks | 270000 | ~/.cp/kimi-shipeng.json |
cksh | 270000 | ~/.cp/kimi-shipeng-highspeed.json |
ckg | 270000 | ~/.cp/kimi-gaohui.json |
ckgh | 270000 | ~/.cp/kimi-gaohui-highspeed.json |
cv | 700000 | ~/.cp/volc.json |
cvg | 700000 | ~/.cp/volc-glm.json |
cds | 700000 | ~/.cp/ds.json |
cdsf | 700000 | ~/.cp/ds-flash.json |
cmm | 700000 | ~/.cp/minimax.json |
cmmf | 201000 | ~/.cp/minimax-flash.json |
cog | 700000 | ~/.cp/opencode-glm.json |
coq | 270000 | ~/.cp/opencode-qwen.json |
cods | 700000 | ~/.cp/opencode-ds.json |
卸载
PROJECT_DIR="/path/to/project"
PROJECT_NAME=$(basename "$PROJECT_DIR")
launchctl unload -w "$HOME/Library/LaunchAgents/com.hapi.$PROJECT_NAME.hub.plist"
launchctl unload -w "$HOME/Library/LaunchAgents/com.hapi.$PROJECT_NAME.runner.plist"
rm -f "$HOME/Library/LaunchAgents/com.hapi.$PROJECT_NAME."*.plist
rm -rf "$PROJECT_DIR/.hapi"
注意事项
- 多个 hapi relay 实例可以共存,tunwg 会自动分配不同子域名。
- token 保存在
$HAPI_HOME/settings.json 中,不要提交到 git。
.hapi/ 必须加入 .gitignore。
- 每个项目实例使用独立端口,避免与全局 hub
3006 冲突。