@logicseek/hello-cc 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/README.md +20 -7
- package/README.zh-CN.md +15 -6
- package/bin/hcc.mjs +383 -62
- package/docs/commands.md +16 -7
- package/docs/commands.zh-CN.md +14 -7
- package/docs/guide.md +24 -7
- package/docs/guide.zh-CN.md +20 -6
- package/lib/core/coordination/timeline.mjs +1 -0
- package/lib/core/sessions/launch.mjs +3 -0
- package/lib/integrations/shims/script.mjs +66 -24
- package/lib/integrations/shims.mjs +45 -6
- package/lib/runtime/client.mjs +1 -1
- package/lib/runtime/state.mjs +6 -3
- package/lib/setup.mjs +1 -0
- package/lib/shell-path.mjs +31 -16
- package/lib/ui/help.mjs +36 -1
- package/lib/web/http.mjs +8 -1
- package/package.json +1 -1
- package/scripts/regression.mjs +651 -48
package/docs/commands.md
CHANGED
|
@@ -12,7 +12,7 @@ hcc uninstall [--purge --yes]
|
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
`hcc update` updates the global npm install. `hcc uninstall` removes local
|
|
15
|
-
hooks and
|
|
15
|
+
hooks, shims, and the shell PATH entry; add `--purge --yes` only when you also want to remove the
|
|
16
16
|
current project's `.hello-cc` data and guidance blocks.
|
|
17
17
|
|
|
18
18
|
## Start And Stop
|
|
@@ -29,7 +29,9 @@ shims, starts or reuses the Web console, and returns the terminal to you. Bare
|
|
|
29
29
|
first use. Use `--local` to bind only `127.0.0.1`, `--token` or `HCC_WEB_TOKEN`
|
|
30
30
|
to replace the saved token, and
|
|
31
31
|
`--no-token` only in trusted local/test environments. Use `hcc up` only when you
|
|
32
|
-
want coordination without the Web console or shims.
|
|
32
|
+
want coordination without the Web console or shims. Provider shims only join
|
|
33
|
+
projects with a local `.hello-cc/runtime.json` from `hcc web`; they do not use a
|
|
34
|
+
global runtime to manage arbitrary directories.
|
|
33
35
|
|
|
34
36
|
## Peers And Status
|
|
35
37
|
|
|
@@ -93,6 +95,8 @@ sender by default. Use `msg thread` to inspect the full thread for one message.
|
|
|
93
95
|
|
|
94
96
|
```text
|
|
95
97
|
hcc task create --title TEXT [--body TEXT] [--from ID] [--to ID] [--priority N]
|
|
98
|
+
hcc task dispatch --to ID --title TEXT [--body TEXT] [--from ID] [--message TEXT] [--no-inject] [--force]
|
|
99
|
+
hcc task dispatch --to ID --id N [--from ID] [--message TEXT] [--no-inject] [--force]
|
|
96
100
|
hcc task list [--status S] [--peer ID] [--all]
|
|
97
101
|
hcc task claim [--peer ID] --id N[,N] [--id N] [--ids N,N] [--force]
|
|
98
102
|
hcc task takeover [--peer ID] --id N --reason TEXT [--policy any|blocked|stale|blocked-or-stale] [--stale-after SECONDS]
|
|
@@ -107,11 +111,16 @@ Tasks are shared project facts. They remain visible until marked `done` or
|
|
|
107
111
|
task before claiming a new pending task; use `--force` only when intentionally
|
|
108
112
|
taking additional pending tasks, and combine it with `--count N` for explicit
|
|
109
113
|
batch claims. `task claim` also accepts repeated `--id` values and comma-separated
|
|
110
|
-
`--id` or `--ids` lists.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
`--id` or `--ids` lists. `task dispatch` is the explicit one-step form for
|
|
115
|
+
assigning a new or existing task to one peer, sending the durable task message,
|
|
116
|
+
and injecting the startup prompt only when that peer has a running managed
|
|
117
|
+
Claude/Codex terminal. Use `--no-inject` for message-only dispatch, and use
|
|
118
|
+
`--force` only when intentionally injecting while the target already owns another
|
|
119
|
+
active task. Use `task takeover` when explicitly taking a non-complete task from
|
|
120
|
+
another owner; it records the previous owner, requires a reason, and notifies
|
|
121
|
+
them. Add `--policy blocked`, `stale`, or `blocked-or-stale` when takeover
|
|
122
|
+
should only proceed under that auditable precondition. The default policy is
|
|
123
|
+
`any` for compatibility.
|
|
115
124
|
|
|
116
125
|
## Teams
|
|
117
126
|
|
package/docs/commands.zh-CN.md
CHANGED
|
@@ -10,7 +10,7 @@ hcc update [--tag TAG] [--registry URL] [--dry-run]
|
|
|
10
10
|
hcc uninstall [--purge --yes]
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
`hcc update` 更新全局 npm 安装。`hcc uninstall` 移除本机 hooks 和
|
|
13
|
+
`hcc update` 更新全局 npm 安装。`hcc uninstall` 移除本机 hooks、shims 和 shell PATH 配置;
|
|
14
14
|
只有在确定也要删除当前项目的 `.hello-cc` 数据和指导块时,才加
|
|
15
15
|
`--purge --yes`。
|
|
16
16
|
|
|
@@ -26,7 +26,9 @@ hcc up [--no-discover] [--no-guidance]
|
|
|
26
26
|
Web 控制台,然后把终端还给你。裸 `hcc web` 会监听 `0.0.0.0` 并使用固定
|
|
27
27
|
URL token;首次使用时自动生成并保存。用 `--local` 可只绑定 `127.0.0.1`,
|
|
28
28
|
用 `--token` 或 `HCC_WEB_TOKEN` 可替换保存的 token,只有在可信本地/测试环境才使用 `--no-token`。
|
|
29
|
-
只想使用本地协作、不需要 Web 或 shims 时,再使用 `hcc up`。
|
|
29
|
+
只想使用本地协作、不需要 Web 或 shims 时,再使用 `hcc up`。provider shim 只会加入
|
|
30
|
+
已经由 `hcc web` 生成本地 `.hello-cc/runtime.json` 的项目,不会使用全局 runtime
|
|
31
|
+
去管理任意目录。
|
|
30
32
|
|
|
31
33
|
## Peers 和状态
|
|
32
34
|
|
|
@@ -86,6 +88,8 @@ hcc broadcast MESSAGE [--from ID] [--task N] [--inject]
|
|
|
86
88
|
|
|
87
89
|
```text
|
|
88
90
|
hcc task create --title TEXT [--body TEXT] [--from ID] [--to ID] [--priority N]
|
|
91
|
+
hcc task dispatch --to ID --title TEXT [--body TEXT] [--from ID] [--message TEXT] [--no-inject] [--force]
|
|
92
|
+
hcc task dispatch --to ID --id N [--from ID] [--message TEXT] [--no-inject] [--force]
|
|
89
93
|
hcc task list [--status S] [--peer ID] [--all]
|
|
90
94
|
hcc task claim [--peer ID] --id N[,N] [--id N] [--ids N,N] [--force]
|
|
91
95
|
hcc task takeover [--peer ID] --id N --reason TEXT [--policy any|blocked|stale|blocked-or-stale] [--stale-after SECONDS]
|
|
@@ -98,11 +102,14 @@ hcc task done [--peer ID] --id N --summary TEXT
|
|
|
98
102
|
任务是项目共享事实。任务会一直可见,直到被标记为 `done` 或 `abandoned`。
|
|
99
103
|
`task next` 会优先返回当前 peer 已经认领、运行、审查或阻塞中的任务;只有明确
|
|
100
104
|
要再接 pending 任务时才使用 `--force`,并可结合 `--count N` 做显式批量认领。
|
|
101
|
-
`task claim` 也支持重复 `--id`、逗号分隔的 `--id` 或 `--ids`
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
`task claim` 也支持重复 `--id`、逗号分隔的 `--id` 或 `--ids` 列表。`task
|
|
106
|
+
dispatch` 是显式的一步分发:创建或指定已有任务并分配给目标 peer、发送持久任务
|
|
107
|
+
消息,并且只在目标 peer 有正在运行的托管 Claude/Codex 终端时注入启动提示。只想
|
|
108
|
+
发消息时使用 `--no-inject`;目标已经拥有其他活动任务但仍要注入时,才使用
|
|
109
|
+
`--force`。明确要从其他 owner 手里接管未完成任务时,使用 `task takeover`;它要求
|
|
110
|
+
填写 reason,会记录原 owner 并通知对方。需要更保守时,可加 `--policy blocked`、
|
|
111
|
+
`stale` 或 `blocked-or-stale`,要求任务符合可审计的阻塞/陈旧条件后才允许接管。
|
|
112
|
+
默认 policy 仍是 `any`,以保持兼容。
|
|
106
113
|
|
|
107
114
|
## 团队
|
|
108
115
|
|
package/docs/guide.md
CHANGED
|
@@ -21,7 +21,7 @@ hcc update
|
|
|
21
21
|
`hcc update --help` to see the available update options. `hcc task update`
|
|
22
22
|
updates a coordination task, not the installed package.
|
|
23
23
|
|
|
24
|
-
Remove hooks and
|
|
24
|
+
Remove hooks, shims, and the shell PATH entry from this machine:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
hcc uninstall
|
|
@@ -83,11 +83,12 @@ with the same web console. The frontend Project selector switches between
|
|
|
83
83
|
registered roots. Each project still keeps its own `.hello-cc/mesh.db`, so
|
|
84
84
|
tasks, messages, locks, and peers stay isolated.
|
|
85
85
|
|
|
86
|
-
After the first shim install, open a new terminal or
|
|
86
|
+
After the first shim install, open a new terminal or reload the rc file for
|
|
87
|
+
your shell:
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
source ~/.
|
|
90
|
-
|
|
89
|
+
- bash: `source ~/.bashrc`
|
|
90
|
+
- zsh: `source ~/.zshrc`
|
|
91
|
+
- fish: `source ~/.config/fish/config.fish`
|
|
91
92
|
|
|
92
93
|
Then direct project-local commands are wrapped automatically:
|
|
93
94
|
|
|
@@ -103,9 +104,17 @@ The wrappers create or reuse local persistent tmux terminals:
|
|
|
103
104
|
- The local terminal attaches to the same tmux session and remains usable.
|
|
104
105
|
- The web console controls the same tmux pane, not a temporary browser-only
|
|
105
106
|
terminal.
|
|
107
|
+
- Shims only use the current project's `.hello-cc/runtime.json`. A global Web
|
|
108
|
+
runtime does not make arbitrary directories managed; if the current project
|
|
109
|
+
has not run `hcc web`, `claude` and `codex` fall back to the real provider CLI
|
|
110
|
+
without creating `.hello-cc/mesh.db`.
|
|
106
111
|
- `hcc down` stops only the web runtime. It does not kill tmux sessions.
|
|
107
112
|
- Running `hcc web` again reattaches live recorded tmux panes.
|
|
108
113
|
|
|
114
|
+
Linux also has richer process auto-discovery through `/proc`. On macOS, prefer
|
|
115
|
+
shim-launched or `hcc peer start` sessions when you want reliable terminal
|
|
116
|
+
control and stable peer identity.
|
|
117
|
+
|
|
109
118
|
For local coordination without Web or shims, use:
|
|
110
119
|
|
|
111
120
|
```bash
|
|
@@ -113,11 +122,14 @@ hcc up
|
|
|
113
122
|
```
|
|
114
123
|
|
|
115
124
|
Most users should start with `hcc web`.
|
|
125
|
+
`hcc web --local` is still Web mode; it binds the Web runtime only to
|
|
126
|
+
`127.0.0.1`.
|
|
116
127
|
|
|
117
128
|
## Project Boundary
|
|
118
129
|
|
|
119
|
-
By default, the project boundary is the current working directory.
|
|
120
|
-
|
|
130
|
+
By default, the project boundary is the current working directory. Explicit
|
|
131
|
+
`hcc` commands such as `hcc web`, `hcc up`, `hcc task`, or `hcc peer start`
|
|
132
|
+
create the project database on first use:
|
|
121
133
|
|
|
122
134
|
```text
|
|
123
135
|
/repo-a/.hello-cc/mesh.db
|
|
@@ -127,6 +139,11 @@ current directory has no `.hello-cc/mesh.db`, hello-cc creates one on first use:
|
|
|
127
139
|
Those are two different projects by default. Sessions communicate naturally
|
|
128
140
|
when they start from the same project path.
|
|
129
141
|
|
|
142
|
+
Provider shims are stricter: direct `claude` and `codex` commands only join
|
|
143
|
+
hello-cc when the current project already has a local `.hello-cc/runtime.json`
|
|
144
|
+
from `hcc web`. They do not use `~/.hello-cc/runtime.json` to auto-register or
|
|
145
|
+
create databases for unrelated directories.
|
|
146
|
+
|
|
130
147
|
To share one bus across paths or worktrees, opt in explicitly:
|
|
131
148
|
|
|
132
149
|
```bash
|
package/docs/guide.zh-CN.md
CHANGED
|
@@ -20,7 +20,7 @@ hcc update
|
|
|
20
20
|
`hcc update` 会执行 `npm install -g @logicseek/hello-cc@latest`。使用
|
|
21
21
|
`hcc update --help` 可以查看更新选项。`hcc task update` 更新的是协作任务状态,不是安装包版本。
|
|
22
22
|
|
|
23
|
-
移除本机 hooks 和
|
|
23
|
+
移除本机 hooks、shims 和 shell PATH 配置:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
hcc uninstall
|
|
@@ -76,11 +76,11 @@ stop: hcc down
|
|
|
76
76
|
|
|
77
77
|
`hcc web` 使用单个全局 Web runtime。你在另一个项目目录再次运行 `hcc web` 时,不会再开一个端口,而是把那个项目注册到同一个 Web 控制台。前端 Project 下拉框可以在不同项目 root 之间切换;每个项目仍然使用自己的 `.hello-cc/mesh.db`,任务、消息、锁和 peers 不会混在一起。
|
|
78
78
|
|
|
79
|
-
第一次安装 shim
|
|
79
|
+
第一次安装 shim 后,打开新终端,或根据当前 shell 重新加载配置:
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
source ~/.
|
|
83
|
-
|
|
81
|
+
- bash: `source ~/.bashrc`
|
|
82
|
+
- zsh: `source ~/.zshrc`
|
|
83
|
+
- fish: `source ~/.config/fish/config.fish`
|
|
84
84
|
|
|
85
85
|
之后在这个项目目录中直接输入:
|
|
86
86
|
|
|
@@ -95,9 +95,15 @@ codex resume <session-id>
|
|
|
95
95
|
|
|
96
96
|
- 本地终端会 attach 到同一个 tmux 会话,仍然可以像普通 Claude/Codex 一样交互。
|
|
97
97
|
- Web 控制台看到并操作的是同一个 tmux pane,不是浏览器里临时创建的假终端。
|
|
98
|
+
- shim 只使用当前项目的 `.hello-cc/runtime.json`。全局 Web runtime 不会让任意
|
|
99
|
+
目录自动进入管理;如果当前项目没有运行过 `hcc web`,`claude` 和 `codex`
|
|
100
|
+
会回退到真实 provider CLI,不会创建 `.hello-cc/mesh.db`。
|
|
98
101
|
- `hcc down` 只停止 Web runtime,不会杀掉这些 tmux 会话。
|
|
99
102
|
- 重新运行 `hcc web` 后,只要 tmux pane 还活着,Web 会重新接管。
|
|
100
103
|
|
|
104
|
+
Linux 还可以通过 `/proc` 做更完整的进程自动发现。macOS 上如果需要可靠的终端
|
|
105
|
+
控制和稳定 peer 身份,建议使用 shim 启动的会话或 `hcc peer start`。
|
|
106
|
+
|
|
101
107
|
如果只想初始化本地通信、不启动 Web、不安装 shim,可使用:
|
|
102
108
|
|
|
103
109
|
```bash
|
|
@@ -105,10 +111,14 @@ hcc up
|
|
|
105
111
|
```
|
|
106
112
|
|
|
107
113
|
普通用户优先使用 `hcc web`。
|
|
114
|
+
`hcc web --local` 仍然是 Web 模式,只是把 Web runtime 限制为监听
|
|
115
|
+
`127.0.0.1`。
|
|
108
116
|
|
|
109
117
|
## 项目边界
|
|
110
118
|
|
|
111
|
-
|
|
119
|
+
默认项目边界就是当前工作目录。显式运行 `hcc web`、`hcc up`、`hcc task` 或
|
|
120
|
+
`hcc peer start` 这类 `hcc` 命令时,如果当前目录没有 `.hello-cc/mesh.db`,
|
|
121
|
+
hello-cc 会在首次使用时创建:
|
|
112
122
|
|
|
113
123
|
```text
|
|
114
124
|
/repo-a/.hello-cc/mesh.db
|
|
@@ -117,6 +127,10 @@ hcc up
|
|
|
117
127
|
|
|
118
128
|
这两个目录默认是两个不同项目,不会互相看到任务和消息。想让多个终端自然通信,就在同一个项目目录启动它们。
|
|
119
129
|
|
|
130
|
+
provider shim 更严格:直接运行 `claude` 和 `codex` 时,只有当前项目已经通过
|
|
131
|
+
`hcc web` 生成本地 `.hello-cc/runtime.json` 才会加入 hello-cc。它们不会使用
|
|
132
|
+
`~/.hello-cc/runtime.json` 去自动注册无关目录,也不会为这些目录创建数据库。
|
|
133
|
+
|
|
120
134
|
如果确实要让多个路径或 worktree 共用一个总线,需要显式指定:
|
|
121
135
|
|
|
122
136
|
```bash
|
|
@@ -14,6 +14,8 @@ const LAUNCH_ENV_IGNORED_KEYS = new Set([
|
|
|
14
14
|
'HCC_NO_AUTO_INSTALL_TMUX',
|
|
15
15
|
'HCC_PEER',
|
|
16
16
|
'HCC_ROOT',
|
|
17
|
+
'HCC_RUNTIME_LOCAL_ONLY',
|
|
18
|
+
'HCC_SKIP_SHIM_INSTALL',
|
|
17
19
|
PROVIDER_STATE_ENV,
|
|
18
20
|
'HCC_SHIM_ENSURED',
|
|
19
21
|
'HCC_SHIM_NO_ATTACH',
|
|
@@ -37,6 +39,7 @@ const LAUNCH_ENV_IGNORED_KEYS = new Set([
|
|
|
37
39
|
export function childSessionEnv(extra = {}, baseEnv = process.env) {
|
|
38
40
|
const env = { ...(baseEnv || {}), ...extra };
|
|
39
41
|
delete env[WEB_CHILD_ENV];
|
|
42
|
+
delete env.HCC_RUNTIME_LOCAL_ONLY;
|
|
40
43
|
return env;
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { shellQuoteArg } from '../../format.mjs';
|
|
2
|
+
|
|
1
3
|
export function generateShim(hccBin, realBin, tool) {
|
|
4
|
+
const hccBinLiteral = shellQuoteArg(hccBin);
|
|
5
|
+
const realBinLiteral = shellQuoteArg(realBin);
|
|
2
6
|
if (tool.resumeFlag) {
|
|
3
7
|
// Claude: --resume/--continue/--session-id/--fork-session aware wrapper.
|
|
4
8
|
return `#!/usr/bin/env bash
|
|
@@ -7,19 +11,22 @@ export function generateShim(hccBin, realBin, tool) {
|
|
|
7
11
|
# Real binary: ${realBin}
|
|
8
12
|
|
|
9
13
|
set -e
|
|
10
|
-
REAL_BIN
|
|
11
|
-
HCC_BIN
|
|
14
|
+
REAL_BIN=${realBinLiteral}
|
|
15
|
+
HCC_BIN=${hccBinLiteral}
|
|
16
|
+
HCC_TMUX_BIN="\${HCC_TMUX_BIN:-$(command -v tmux 2>/dev/null || printf 'tmux')}"
|
|
17
|
+
export HCC_TMUX_BIN
|
|
12
18
|
|
|
13
19
|
if [ "\${HCC_SHIM_ENSURED:-}" != "1" ]; then
|
|
14
20
|
set +e
|
|
15
|
-
"$HCC_BIN" shim ensure "${tool.name}" "$0" >/dev/null 2>&1
|
|
21
|
+
"$HCC_BIN" shim ensure "${tool.name}" "$0" "$REAL_BIN" >/dev/null 2>&1
|
|
16
22
|
ENSURE_STATUS=$?
|
|
17
23
|
set -e
|
|
18
|
-
if [ "$ENSURE_STATUS" = "75" ]; then
|
|
24
|
+
if [ "$ENSURE_STATUS" = "0" ] || [ "$ENSURE_STATUS" = "75" ]; then
|
|
19
25
|
export HCC_SHIM_ENSURED=1
|
|
20
26
|
exec "$0" "$@"
|
|
21
27
|
fi
|
|
22
28
|
fi
|
|
29
|
+
unset HCC_SHIM_ENSURED
|
|
23
30
|
|
|
24
31
|
should_passthrough() {
|
|
25
32
|
for arg in "$@"; do
|
|
@@ -112,7 +119,7 @@ current_tmux_pane() {
|
|
|
112
119
|
if [ -z "$TTY_PATH" ] || [ "$TTY_PATH" = "not a tty" ]; then
|
|
113
120
|
return 1
|
|
114
121
|
fi
|
|
115
|
-
|
|
122
|
+
"$HCC_TMUX_BIN" list-panes -a -F '#{pane_id} #{pane_tty}' 2>/dev/null | awk -v tty="$TTY_PATH" '$2 == tty { print $1; found = 1; exit } END { exit found ? 0 : 1 }'
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
CURRENT_HCC_PANE=""
|
|
@@ -123,7 +130,7 @@ detect_current_hcc_pane() {
|
|
|
123
130
|
if [ -z "$CURRENT_HCC_PANE" ]; then
|
|
124
131
|
return 1
|
|
125
132
|
fi
|
|
126
|
-
CURRENT_HCC_SESSION=$(
|
|
133
|
+
CURRENT_HCC_SESSION=$(command "$HCC_TMUX_BIN" display-message -p -t "$CURRENT_HCC_PANE" '#S' 2>/dev/null || true)
|
|
127
134
|
case "$CURRENT_HCC_SESSION" in
|
|
128
135
|
hcc-*-*) return 0 ;;
|
|
129
136
|
esac
|
|
@@ -132,23 +139,39 @@ detect_current_hcc_pane() {
|
|
|
132
139
|
return 1
|
|
133
140
|
}
|
|
134
141
|
|
|
142
|
+
hcc_runtime_unavailable_start_failure() {
|
|
143
|
+
case "$1" in
|
|
144
|
+
*"No running web runtime found"*|*"No running hello-cc web runtime found"*|*"Runtime is not reachable"*|*"RUNTIME_NOT_RUNNING"*|*"RUNTIME_UNREACHABLE"*|*"RUNTIME_START_FAILED"*|*"RUNTIME_START_TIMEOUT"*)
|
|
145
|
+
return 0
|
|
146
|
+
;;
|
|
147
|
+
esac
|
|
148
|
+
return 1
|
|
149
|
+
}
|
|
150
|
+
|
|
135
151
|
if detect_current_hcc_pane; then
|
|
136
152
|
SESSION_PEER_ID=$(printf '%s' "$CURRENT_HCC_SESSION" | sed 's/^hcc-[^-]*-//')
|
|
137
153
|
if [ -n "$SESSION_PEER_ID" ] && [ "$SESSION_PEER_ID" != "$CURRENT_HCC_SESSION" ]; then
|
|
138
154
|
PEER_ID="$SESSION_PEER_ID"
|
|
139
155
|
fi
|
|
140
156
|
export HCC_PEER="$PEER_ID"
|
|
141
|
-
"$HCC_BIN"
|
|
142
|
-
"$HCC_BIN" peer attach "$PEER_ID" --kind "${tool.kind}" --pane "$CURRENT_HCC_PANE" --cwd "$PWD" --force >/dev/null 2>&1 || true
|
|
157
|
+
HCC_RUNTIME_LOCAL_ONLY=1 "$HCC_BIN" peer attach "$PEER_ID" --kind "${tool.kind}" --pane "$CURRENT_HCC_PANE" --cwd "$PWD" --force >/dev/null 2>&1 || true
|
|
143
158
|
exec "$REAL_BIN" "$@"
|
|
144
159
|
fi
|
|
145
160
|
|
|
161
|
+
HCC_PROVIDER_LAUNCH_SCRIPT='REAL_BIN="$1"; shift; HCC_TMUX_BIN="\${HCC_TMUX_BIN:-tmux}"; command "$HCC_TMUX_BIN" set-environment HCC_PROVIDER_STATE running >/dev/null 2>&1 || true; "$REAL_BIN" "$@"; command "$HCC_TMUX_BIN" set-environment HCC_PROVIDER_STATE exited >/dev/null 2>&1 || true; exec bash --noprofile --norc'
|
|
162
|
+
|
|
146
163
|
set +e
|
|
147
164
|
# Wrap in shell so pane_pid stays as bash; Ctrl+C drops back to bash
|
|
148
|
-
|
|
165
|
+
START_OUT_FILE=$(mktemp "\${TMPDIR:-/tmp}/hcc-shim-start.XXXXXX")
|
|
166
|
+
(HCC_RUNTIME_LOCAL_ONLY=1 "$HCC_BIN" peer start "$PEER_ID" --kind "${tool.kind}" --restart-env -- bash -lc "$HCC_PROVIDER_LAUNCH_SCRIPT" _ "$REAL_BIN" "$@") >"$START_OUT_FILE" 2>&1
|
|
149
167
|
START_STATUS=$?
|
|
168
|
+
START_OUT=$(cat "$START_OUT_FILE" 2>/dev/null || true)
|
|
169
|
+
rm -f "$START_OUT_FILE"
|
|
150
170
|
set -e
|
|
151
171
|
if [ "$START_STATUS" -ne 0 ]; then
|
|
172
|
+
if hcc_runtime_unavailable_start_failure "$START_OUT"; then
|
|
173
|
+
exec "$REAL_BIN" "$@"
|
|
174
|
+
fi
|
|
152
175
|
printf '%s\\n' "$START_OUT" >&2
|
|
153
176
|
exit "$START_STATUS"
|
|
154
177
|
fi
|
|
@@ -164,17 +187,17 @@ if [ "$HCC_SHIM_NO_ATTACH" = "1" ] || [ ! -t 0 ]; then
|
|
|
164
187
|
exit 0
|
|
165
188
|
fi
|
|
166
189
|
|
|
167
|
-
SESSION=$(
|
|
190
|
+
SESSION=$(command "$HCC_TMUX_BIN" display-message -p -t "$PANE" '#S' 2>/dev/null || true)
|
|
168
191
|
if [ -z "$SESSION" ]; then
|
|
169
192
|
printf '%s\\n' "$START_OUT"
|
|
170
193
|
exit 0
|
|
171
194
|
fi
|
|
172
195
|
|
|
173
196
|
if [ -n "$TMUX" ]; then
|
|
174
|
-
exec
|
|
197
|
+
exec "$HCC_TMUX_BIN" switch-client -t "$SESSION"
|
|
175
198
|
fi
|
|
176
199
|
|
|
177
|
-
exec
|
|
200
|
+
exec "$HCC_TMUX_BIN" attach-session -t "$SESSION"
|
|
178
201
|
`;
|
|
179
202
|
}
|
|
180
203
|
|
|
@@ -185,19 +208,22 @@ exec tmux attach-session -t "$SESSION"
|
|
|
185
208
|
# Real binary: ${realBin}
|
|
186
209
|
|
|
187
210
|
set -e
|
|
188
|
-
REAL_BIN
|
|
189
|
-
HCC_BIN
|
|
211
|
+
REAL_BIN=${realBinLiteral}
|
|
212
|
+
HCC_BIN=${hccBinLiteral}
|
|
213
|
+
HCC_TMUX_BIN="\${HCC_TMUX_BIN:-$(command -v tmux 2>/dev/null || printf 'tmux')}"
|
|
214
|
+
export HCC_TMUX_BIN
|
|
190
215
|
|
|
191
216
|
if [ "\${HCC_SHIM_ENSURED:-}" != "1" ]; then
|
|
192
217
|
set +e
|
|
193
|
-
"$HCC_BIN" shim ensure "${tool.name}" "$0" >/dev/null 2>&1
|
|
218
|
+
"$HCC_BIN" shim ensure "${tool.name}" "$0" "$REAL_BIN" >/dev/null 2>&1
|
|
194
219
|
ENSURE_STATUS=$?
|
|
195
220
|
set -e
|
|
196
|
-
if [ "$ENSURE_STATUS" = "75" ]; then
|
|
221
|
+
if [ "$ENSURE_STATUS" = "0" ] || [ "$ENSURE_STATUS" = "75" ]; then
|
|
197
222
|
export HCC_SHIM_ENSURED=1
|
|
198
223
|
exec "$0" "$@"
|
|
199
224
|
fi
|
|
200
225
|
fi
|
|
226
|
+
unset HCC_SHIM_ENSURED
|
|
201
227
|
|
|
202
228
|
first_non_option() {
|
|
203
229
|
EXPECT_VALUE=0
|
|
@@ -317,7 +343,7 @@ current_tmux_pane() {
|
|
|
317
343
|
if [ -z "$TTY_PATH" ] || [ "$TTY_PATH" = "not a tty" ]; then
|
|
318
344
|
return 1
|
|
319
345
|
fi
|
|
320
|
-
|
|
346
|
+
"$HCC_TMUX_BIN" list-panes -a -F '#{pane_id} #{pane_tty}' 2>/dev/null | awk -v tty="$TTY_PATH" '$2 == tty { print $1; found = 1; exit } END { exit found ? 0 : 1 }'
|
|
321
347
|
}
|
|
322
348
|
|
|
323
349
|
CURRENT_HCC_PANE=""
|
|
@@ -328,7 +354,7 @@ detect_current_hcc_pane() {
|
|
|
328
354
|
if [ -z "$CURRENT_HCC_PANE" ]; then
|
|
329
355
|
return 1
|
|
330
356
|
fi
|
|
331
|
-
CURRENT_HCC_SESSION=$(
|
|
357
|
+
CURRENT_HCC_SESSION=$(command "$HCC_TMUX_BIN" display-message -p -t "$CURRENT_HCC_PANE" '#S' 2>/dev/null || true)
|
|
332
358
|
case "$CURRENT_HCC_SESSION" in
|
|
333
359
|
hcc-*-*) return 0 ;;
|
|
334
360
|
esac
|
|
@@ -337,23 +363,39 @@ detect_current_hcc_pane() {
|
|
|
337
363
|
return 1
|
|
338
364
|
}
|
|
339
365
|
|
|
366
|
+
hcc_runtime_unavailable_start_failure() {
|
|
367
|
+
case "$1" in
|
|
368
|
+
*"No running web runtime found"*|*"No running hello-cc web runtime found"*|*"Runtime is not reachable"*|*"RUNTIME_NOT_RUNNING"*|*"RUNTIME_UNREACHABLE"*|*"RUNTIME_START_FAILED"*|*"RUNTIME_START_TIMEOUT"*)
|
|
369
|
+
return 0
|
|
370
|
+
;;
|
|
371
|
+
esac
|
|
372
|
+
return 1
|
|
373
|
+
}
|
|
374
|
+
|
|
340
375
|
if detect_current_hcc_pane; then
|
|
341
376
|
SESSION_PEER_ID=$(printf '%s' "$CURRENT_HCC_SESSION" | sed 's/^hcc-[^-]*-//')
|
|
342
377
|
if [ -n "$SESSION_PEER_ID" ] && [ "$SESSION_PEER_ID" != "$CURRENT_HCC_SESSION" ]; then
|
|
343
378
|
PEER_ID="$SESSION_PEER_ID"
|
|
344
379
|
fi
|
|
345
380
|
export HCC_PEER="$PEER_ID"
|
|
346
|
-
"$HCC_BIN"
|
|
347
|
-
"$HCC_BIN" peer attach "$PEER_ID" --kind "${tool.kind}" --pane "$CURRENT_HCC_PANE" --cwd "$PWD" --force >/dev/null 2>&1 || true
|
|
381
|
+
HCC_RUNTIME_LOCAL_ONLY=1 "$HCC_BIN" peer attach "$PEER_ID" --kind "${tool.kind}" --pane "$CURRENT_HCC_PANE" --cwd "$PWD" --force >/dev/null 2>&1 || true
|
|
348
382
|
exec "$REAL_BIN" "$@"
|
|
349
383
|
fi
|
|
350
384
|
|
|
385
|
+
HCC_PROVIDER_LAUNCH_SCRIPT='REAL_BIN="$1"; shift; HCC_TMUX_BIN="\${HCC_TMUX_BIN:-tmux}"; command "$HCC_TMUX_BIN" set-environment HCC_PROVIDER_STATE running >/dev/null 2>&1 || true; "$REAL_BIN" "$@"; command "$HCC_TMUX_BIN" set-environment HCC_PROVIDER_STATE exited >/dev/null 2>&1 || true; exec bash --noprofile --norc'
|
|
386
|
+
|
|
351
387
|
set +e
|
|
352
388
|
# Wrap in shell so pane_pid stays as bash; Ctrl+C drops back to bash
|
|
353
|
-
|
|
389
|
+
START_OUT_FILE=$(mktemp "\${TMPDIR:-/tmp}/hcc-shim-start.XXXXXX")
|
|
390
|
+
(HCC_RUNTIME_LOCAL_ONLY=1 "$HCC_BIN" peer start "$PEER_ID" --kind "${tool.kind}" --restart-env -- bash -lc "$HCC_PROVIDER_LAUNCH_SCRIPT" _ "$REAL_BIN" "$@") >"$START_OUT_FILE" 2>&1
|
|
354
391
|
START_STATUS=$?
|
|
392
|
+
START_OUT=$(cat "$START_OUT_FILE" 2>/dev/null || true)
|
|
393
|
+
rm -f "$START_OUT_FILE"
|
|
355
394
|
set -e
|
|
356
395
|
if [ "$START_STATUS" -ne 0 ]; then
|
|
396
|
+
if hcc_runtime_unavailable_start_failure "$START_OUT"; then
|
|
397
|
+
exec "$REAL_BIN" "$@"
|
|
398
|
+
fi
|
|
357
399
|
printf '%s\\n' "$START_OUT" >&2
|
|
358
400
|
exit "$START_STATUS"
|
|
359
401
|
fi
|
|
@@ -369,16 +411,16 @@ if [ "$HCC_SHIM_NO_ATTACH" = "1" ] || [ ! -t 0 ]; then
|
|
|
369
411
|
exit 0
|
|
370
412
|
fi
|
|
371
413
|
|
|
372
|
-
SESSION=$(
|
|
414
|
+
SESSION=$(command "$HCC_TMUX_BIN" display-message -p -t "$PANE" '#S' 2>/dev/null || true)
|
|
373
415
|
if [ -z "$SESSION" ]; then
|
|
374
416
|
printf '%s\\n' "$START_OUT"
|
|
375
417
|
exit 0
|
|
376
418
|
fi
|
|
377
419
|
|
|
378
420
|
if [ -n "$TMUX" ]; then
|
|
379
|
-
exec
|
|
421
|
+
exec "$HCC_TMUX_BIN" switch-client -t "$SESSION"
|
|
380
422
|
fi
|
|
381
423
|
|
|
382
|
-
exec
|
|
424
|
+
exec "$HCC_TMUX_BIN" attach-session -t "$SESSION"
|
|
383
425
|
`;
|
|
384
426
|
}
|
|
@@ -10,8 +10,9 @@ export const SHIM_DIR = path.join(os.homedir(), '.hcc-shims');
|
|
|
10
10
|
* Install shim scripts for `claude` and `codex` into SHIM_DIR.
|
|
11
11
|
* Returns { shimDir, installed: [paths], changed: [paths], skipped: [names] }.
|
|
12
12
|
*/
|
|
13
|
-
export function installShims(hccBin) {
|
|
13
|
+
export function installShims(hccBin, opts = {}) {
|
|
14
14
|
fs.mkdirSync(SHIM_DIR, { recursive: true });
|
|
15
|
+
const realBins = opts.realBins && typeof opts.realBins === 'object' ? opts.realBins : {};
|
|
15
16
|
|
|
16
17
|
const tools = [
|
|
17
18
|
{ name: 'claude', kind: 'claude', resumeFlag: '--resume' },
|
|
@@ -23,17 +24,23 @@ export function installShims(hccBin) {
|
|
|
23
24
|
const skipped = [];
|
|
24
25
|
|
|
25
26
|
for (const tool of tools) {
|
|
26
|
-
const
|
|
27
|
+
const shimPath = path.join(SHIM_DIR, tool.name);
|
|
28
|
+
let previous = null;
|
|
29
|
+
try { previous = fs.readFileSync(shimPath, 'utf8'); } catch {}
|
|
30
|
+
const requestedRealBin = realBins[tool.name] || null;
|
|
31
|
+
const existingRealBin = previous ? shimRealBinFromContent(previous) : null;
|
|
32
|
+
const realBin = existingRealBin && fsExists(existingRealBin)
|
|
33
|
+
? existingRealBin
|
|
34
|
+
: requestedRealBin && fsExists(requestedRealBin)
|
|
35
|
+
? requestedRealBin
|
|
36
|
+
: findRealBinary(tool.name);
|
|
27
37
|
if (!realBin) {
|
|
28
38
|
skipped.push(`${tool.name} (not found on PATH)`);
|
|
29
39
|
continue;
|
|
30
40
|
}
|
|
31
|
-
const shimPath = path.join(SHIM_DIR, tool.name);
|
|
32
41
|
const content = generateShim(hccBin, realBin, tool);
|
|
33
|
-
let previous = null;
|
|
34
|
-
try { previous = fs.readFileSync(shimPath, 'utf8'); } catch {}
|
|
35
42
|
if (previous !== content) {
|
|
36
|
-
|
|
43
|
+
writeExecutableAtomic(shimPath, content);
|
|
37
44
|
changed.push(shimPath);
|
|
38
45
|
} else {
|
|
39
46
|
try { fs.chmodSync(shimPath, 0o755); } catch {}
|
|
@@ -66,6 +73,20 @@ export function verifyShims() {
|
|
|
66
73
|
);
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
export function shimStatus() {
|
|
77
|
+
const tools = {};
|
|
78
|
+
for (const name of ['claude', 'codex']) {
|
|
79
|
+
const shimPath = path.join(SHIM_DIR, name);
|
|
80
|
+
tools[name] = { installed: fsExists(shimPath), path: shimPath };
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
shimDir: SHIM_DIR,
|
|
84
|
+
tools,
|
|
85
|
+
installed: Object.values(tools).some((tool) => tool.installed),
|
|
86
|
+
complete: Object.values(tools).every((tool) => tool.installed)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
/**
|
|
70
91
|
* Find the real binary for `name`, skipping the SHIM_DIR.
|
|
71
92
|
* Checks PATH, then npm global bin, then common install locations.
|
|
@@ -104,3 +125,21 @@ export function findRealBinary(name) {
|
|
|
104
125
|
function fsExists(p) {
|
|
105
126
|
try { fs.accessSync(p); return true; } catch { return false; }
|
|
106
127
|
}
|
|
128
|
+
|
|
129
|
+
function shimRealBinFromContent(content) {
|
|
130
|
+
const match = String(content || '').match(/^# Real binary: ([^\r\n]+)$/m);
|
|
131
|
+
return match ? match[1] : null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function writeExecutableAtomic(file, content) {
|
|
135
|
+
const dir = path.dirname(file);
|
|
136
|
+
const base = path.basename(file);
|
|
137
|
+
const tmp = path.join(dir, `.${base}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
138
|
+
try {
|
|
139
|
+
fs.writeFileSync(tmp, content, { mode: 0o755 });
|
|
140
|
+
fs.renameSync(tmp, file);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
try { fs.rmSync(tmp, { force: true }); } catch {}
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
}
|
package/lib/runtime/client.mjs
CHANGED
|
@@ -6,7 +6,7 @@ const DEFAULT_CLI_NAME = 'hcc';
|
|
|
6
6
|
|
|
7
7
|
export async function runtimeRequest(ctx, method, route, body = null, runtime = null, opts = {}) {
|
|
8
8
|
const cliName = opts.cliName || DEFAULT_CLI_NAME;
|
|
9
|
-
const rt = runtime || readRuntime(ctx, { cliName });
|
|
9
|
+
const rt = runtime || readRuntime(ctx, { cliName, localOnly: Boolean(opts.localOnly) });
|
|
10
10
|
const url = runtimeApiUrl(rt, route);
|
|
11
11
|
const headers = { 'Content-Type': 'application/json' };
|
|
12
12
|
headers['X-HCC-Root'] = ctx.root;
|
package/lib/runtime/state.mjs
CHANGED
|
@@ -39,6 +39,7 @@ export function writeRuntime(ctx, runtime) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function readRuntime(ctx, opts = {}) {
|
|
42
|
+
const localOnly = Boolean(opts.localOnly || process.env.HCC_RUNTIME_LOCAL_ONLY === '1');
|
|
42
43
|
if (process.env.HCC_RUNTIME_URL) {
|
|
43
44
|
return {
|
|
44
45
|
base_url: process.env.HCC_RUNTIME_URL,
|
|
@@ -56,9 +57,11 @@ export function readRuntime(ctx, opts = {}) {
|
|
|
56
57
|
try { fs.rmSync(file, { force: true }); } catch {}
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
if (!localOnly) {
|
|
61
|
+
const global = readGlobalRuntimeFile();
|
|
62
|
+
if (global?.base_url) {
|
|
63
|
+
return { ...global, source: globalRuntimePath(), global: true };
|
|
64
|
+
}
|
|
62
65
|
}
|
|
63
66
|
const productName = opts.productName || DEFAULT_PRODUCT_NAME;
|
|
64
67
|
const cliName = opts.cliName || DEFAULT_CLI_NAME;
|