@logicseek/hello-cc 0.1.5 → 0.1.7
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 +130 -0
- package/README.md +21 -3
- package/README.zh-CN.md +21 -3
- package/assets/screenshots/web-console.png +0 -0
- package/bin/hcc.mjs +1943 -3519
- package/docs/README.md +2 -0
- package/docs/README.zh-CN.md +3 -1
- package/docs/architecture-split-review.md +217 -0
- package/docs/architecture.md +385 -0
- package/docs/architecture.zh-CN.md +364 -0
- package/docs/layout-split-stack-audit.md +298 -0
- package/lib/automation.mjs +8 -0
- package/lib/cli-args.mjs +1 -1
- package/lib/cli-runtime.mjs +45 -0
- package/lib/coordination-state.mjs +218 -0
- package/lib/core/coordination/automation.mjs +222 -0
- package/lib/core/coordination/locks.mjs +46 -0
- package/lib/core/coordination/messages.mjs +116 -0
- package/lib/core/coordination/tasks.mjs +240 -0
- package/lib/core/coordination/teams.mjs +73 -0
- package/lib/core/coordination/timeline.mjs +196 -0
- package/lib/core/peers/bindings.mjs +83 -0
- package/lib/core/peers/format.mjs +13 -0
- package/lib/core/peers/identity.mjs +61 -0
- package/lib/core/peers/liveness.mjs +90 -0
- package/lib/core/peers/reconcile.mjs +238 -0
- package/lib/core/peers/session.mjs +14 -0
- package/lib/core/sessions/launch.mjs +89 -0
- package/lib/db/schema.mjs +324 -0
- package/lib/db/stores/peers.mjs +231 -0
- package/lib/db-schema.mjs +7 -324
- package/lib/errors.mjs +1 -7
- package/lib/handoff.mjs +26 -0
- package/lib/help.mjs +3 -0
- package/lib/integrations/hooks.mjs +171 -0
- package/lib/integrations/peers/identity.mjs +208 -0
- package/lib/integrations/peers/processes.mjs +151 -0
- package/lib/integrations/providers.mjs +282 -0
- package/lib/integrations/shims/script.mjs +384 -0
- package/lib/integrations/shims.mjs +106 -0
- package/lib/json-file.mjs +1 -14
- package/lib/locks.mjs +10 -0
- package/lib/messages.mjs +3 -0
- package/lib/package-meta.mjs +6 -24
- package/lib/peer-bindings.mjs +14 -0
- package/lib/peer-format.mjs +4 -0
- package/lib/peer-identity.mjs +15 -0
- package/lib/project-context.mjs +6 -0
- package/lib/project-registry.mjs +7 -0
- package/lib/provider-commands.mjs +13 -0
- package/lib/release/package-meta.mjs +31 -0
- package/lib/release/release-notes.mjs +40 -0
- package/lib/release-notes.mjs +6 -40
- package/lib/runtime/client.mjs +42 -0
- package/lib/runtime/paths.mjs +45 -0
- package/lib/runtime/project-context.mjs +32 -0
- package/lib/runtime/projects.mjs +76 -0
- package/lib/runtime/state.mjs +128 -0
- package/lib/runtime-client.mjs +3 -0
- package/lib/runtime-paths.mjs +11 -45
- package/lib/runtime-state.mjs +11 -0
- package/lib/session-launch.mjs +17 -0
- package/lib/setup.mjs +17 -293
- package/lib/shared/errors.mjs +7 -0
- package/lib/shared/json-file.mjs +14 -0
- package/lib/shim-script.mjs +1 -384
- package/lib/state-render.mjs +5 -0
- package/lib/task-cli.mjs +45 -0
- package/lib/task-liveness.mjs +8 -0
- package/lib/task-store.mjs +3 -0
- package/lib/team-planning.mjs +7 -0
- package/lib/terminal/tmux.mjs +372 -0
- package/lib/timeline.mjs +12 -0
- package/lib/tmux.mjs +18 -0
- package/lib/ui/help.mjs +419 -0
- package/lib/ui/state-render.mjs +61 -0
- package/lib/web/http.mjs +55 -0
- package/lib/web/peer-actions.mjs +306 -0
- package/lib/web/runtime.mjs +154 -0
- package/lib/web/ui-template.mjs +2057 -0
- package/lib/web-http.mjs +7 -55
- package/lib/web-peer-actions.mjs +3 -0
- package/lib/web-runtime.mjs +17 -108
- package/lib/web-ui-template.mjs +3 -1909
- package/package.json +1 -1
- package/scripts/github-release.mjs +2 -2
- package/scripts/regression.mjs +2901 -79
- package/scripts/release-notes.mjs +2 -2
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
export function generateShim(hccBin, realBin, tool) {
|
|
2
|
+
if (tool.resumeFlag) {
|
|
3
|
+
// Claude: --resume/--continue/--session-id/--fork-session aware wrapper.
|
|
4
|
+
return `#!/usr/bin/env bash
|
|
5
|
+
# hello-cc shim for ${tool.name} (auto-generated; do not edit manually)
|
|
6
|
+
# Wraps ${tool.name} with hello-cc peer mesh integration.
|
|
7
|
+
# Real binary: ${realBin}
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
REAL_BIN="${realBin}"
|
|
11
|
+
HCC_BIN="${hccBin}"
|
|
12
|
+
|
|
13
|
+
if [ "\${HCC_SHIM_ENSURED:-}" != "1" ]; then
|
|
14
|
+
set +e
|
|
15
|
+
"$HCC_BIN" shim ensure "${tool.name}" "$0" >/dev/null 2>&1
|
|
16
|
+
ENSURE_STATUS=$?
|
|
17
|
+
set -e
|
|
18
|
+
if [ "$ENSURE_STATUS" = "75" ]; then
|
|
19
|
+
export HCC_SHIM_ENSURED=1
|
|
20
|
+
exec "$0" "$@"
|
|
21
|
+
fi
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
should_passthrough() {
|
|
25
|
+
for arg in "$@"; do
|
|
26
|
+
case "$arg" in
|
|
27
|
+
-h|--help|-v|--version|-p|--print|--bare|--safe-mode)
|
|
28
|
+
return 0
|
|
29
|
+
;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
case "\${1:-}" in
|
|
34
|
+
agents|auth|auto-mode|doctor|install|mcp|plugin|plugins|project|setup-token|ultrareview|update|upgrade)
|
|
35
|
+
return 0
|
|
36
|
+
;;
|
|
37
|
+
esac
|
|
38
|
+
|
|
39
|
+
return 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if should_passthrough "$@"; then
|
|
43
|
+
exec "$REAL_BIN" "$@"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Use the current directory as the hello-cc project root unless HCC_ROOT is set.
|
|
47
|
+
HCC_ROOT=$("$HCC_BIN" find-root 2>/dev/null || true)
|
|
48
|
+
|
|
49
|
+
if [ -z "$HCC_ROOT" ]; then
|
|
50
|
+
exec "$REAL_BIN" "$@"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
sanitize_peer_part() {
|
|
54
|
+
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9._-]/-/g; s/^-*//; s/-*$//' | cut -c 1-32
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
tty_peer_part() {
|
|
58
|
+
TTY_NUM=$(tty 2>/dev/null | tr -dc '0-9' | tail -c 4)
|
|
59
|
+
PARENT_PID="\${PPID:-$$}"
|
|
60
|
+
if [ -n "$TTY_NUM" ]; then
|
|
61
|
+
printf '%s-%s' "$TTY_NUM" "$PARENT_PID"
|
|
62
|
+
else
|
|
63
|
+
printf '%s' "$PARENT_PID"
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
RESUME_ID=""
|
|
68
|
+
SESSION_ID=""
|
|
69
|
+
SESSION_NAME=""
|
|
70
|
+
IS_CONTINUE=0
|
|
71
|
+
IS_FORK=0
|
|
72
|
+
PREV_ARG=""
|
|
73
|
+
for arg in "$@"; do
|
|
74
|
+
case "$PREV_ARG" in
|
|
75
|
+
--resume|-r) RESUME_ID="$arg" ;;
|
|
76
|
+
--session-id) SESSION_ID="$arg" ;;
|
|
77
|
+
--name|-n) SESSION_NAME="$arg" ;;
|
|
78
|
+
esac
|
|
79
|
+
case "$arg" in
|
|
80
|
+
--resume=*) RESUME_ID="\${arg#--resume=}" ;;
|
|
81
|
+
--session-id=*) SESSION_ID="\${arg#--session-id=}" ;;
|
|
82
|
+
--name=*) SESSION_NAME="\${arg#--name=}" ;;
|
|
83
|
+
--continue|-c) IS_CONTINUE=1 ;;
|
|
84
|
+
--fork-session) IS_FORK=1 ;;
|
|
85
|
+
esac
|
|
86
|
+
PREV_ARG="$arg"
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
if [ -n "$HCC_PEER" ]; then
|
|
90
|
+
PEER_ID="$HCC_PEER"
|
|
91
|
+
elif [ -n "$SESSION_ID" ]; then
|
|
92
|
+
PEER_ID="${tool.kind}-\$(sanitize_peer_part "\${SESSION_ID:0:8}")"
|
|
93
|
+
elif [ -n "$RESUME_ID" ] && [ "$IS_FORK" != "1" ]; then
|
|
94
|
+
PEER_ID="${tool.kind}-\$(sanitize_peer_part "\${RESUME_ID:0:8}")"
|
|
95
|
+
elif [ -n "$CLAUDE_CODE_SESSION_ID" ]; then
|
|
96
|
+
PEER_ID="${tool.kind}-\$(sanitize_peer_part "\${CLAUDE_CODE_SESSION_ID:0:8}")"
|
|
97
|
+
elif [ -n "$SESSION_NAME" ] && [ "$IS_FORK" != "1" ] && [ "$IS_CONTINUE" != "1" ]; then
|
|
98
|
+
PEER_ID="${tool.kind}-\$(sanitize_peer_part "$SESSION_NAME")"
|
|
99
|
+
else
|
|
100
|
+
PEER_ID="${tool.kind}-\$(tty_peer_part)"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
export HCC_PEER="$PEER_ID"
|
|
104
|
+
export HCC_ROOT
|
|
105
|
+
|
|
106
|
+
current_tmux_pane() {
|
|
107
|
+
if [ -n "\${TMUX_PANE:-}" ]; then
|
|
108
|
+
printf '%s\\n' "$TMUX_PANE"
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
TTY_PATH=$(tty 2>/dev/null || true)
|
|
112
|
+
if [ -z "$TTY_PATH" ] || [ "$TTY_PATH" = "not a tty" ]; then
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
tmux 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
|
+
}
|
|
117
|
+
|
|
118
|
+
CURRENT_HCC_PANE=""
|
|
119
|
+
CURRENT_HCC_SESSION=""
|
|
120
|
+
detect_current_hcc_pane() {
|
|
121
|
+
CURRENT_HCC_SESSION=""
|
|
122
|
+
CURRENT_HCC_PANE=$(current_tmux_pane || true)
|
|
123
|
+
if [ -z "$CURRENT_HCC_PANE" ]; then
|
|
124
|
+
return 1
|
|
125
|
+
fi
|
|
126
|
+
CURRENT_HCC_SESSION=$(tmux display-message -p -t "$CURRENT_HCC_PANE" '#S' 2>/dev/null || true)
|
|
127
|
+
case "$CURRENT_HCC_SESSION" in
|
|
128
|
+
hcc-*-*) return 0 ;;
|
|
129
|
+
esac
|
|
130
|
+
CURRENT_HCC_PANE=""
|
|
131
|
+
CURRENT_HCC_SESSION=""
|
|
132
|
+
return 1
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if detect_current_hcc_pane; then
|
|
136
|
+
SESSION_PEER_ID=$(printf '%s' "$CURRENT_HCC_SESSION" | sed 's/^hcc-[^-]*-//')
|
|
137
|
+
if [ -n "$SESSION_PEER_ID" ] && [ "$SESSION_PEER_ID" != "$CURRENT_HCC_SESSION" ]; then
|
|
138
|
+
PEER_ID="$SESSION_PEER_ID"
|
|
139
|
+
fi
|
|
140
|
+
export HCC_PEER="$PEER_ID"
|
|
141
|
+
"$HCC_BIN" web >/dev/null 2>&1 || true
|
|
142
|
+
"$HCC_BIN" peer attach "$PEER_ID" --kind "${tool.kind}" --pane "$CURRENT_HCC_PANE" --cwd "$PWD" --force >/dev/null 2>&1 || true
|
|
143
|
+
exec "$REAL_BIN" "$@"
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
set +e
|
|
147
|
+
# Wrap in shell so pane_pid stays as bash; Ctrl+C drops back to bash
|
|
148
|
+
START_OUT=$("$HCC_BIN" web >/dev/null 2>&1 || true; "$HCC_BIN" peer start "$PEER_ID" --kind "${tool.kind}" --restart-env -- bash -lc 'REAL_BIN="$1"; shift; hcc_provider_state() { HCC_TMUX_SESSION=$(tmux display-message -p "#S" 2>/dev/null || true); if [ -n "$HCC_TMUX_SESSION" ]; then tmux set-environment -t "$HCC_TMUX_SESSION" HCC_PROVIDER_STATE "$1" >/dev/null 2>&1 || true; fi; }; hcc_provider_state running; "$REAL_BIN" "$@"; hcc_provider_state exited; exec bash' _ "$REAL_BIN" "$@" 2>&1)
|
|
149
|
+
START_STATUS=$?
|
|
150
|
+
set -e
|
|
151
|
+
if [ "$START_STATUS" -ne 0 ]; then
|
|
152
|
+
printf '%s\\n' "$START_OUT" >&2
|
|
153
|
+
exit "$START_STATUS"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
PANE=$(printf '%s\\n' "$START_OUT" | sed -n 's/.*pane=\\(%[0-9][0-9]*\\).*/\\1/p' | tail -n 1)
|
|
157
|
+
if [ -z "$PANE" ]; then
|
|
158
|
+
printf '%s\\n' "$START_OUT"
|
|
159
|
+
exit 0
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
if [ "$HCC_SHIM_NO_ATTACH" = "1" ] || [ ! -t 0 ]; then
|
|
163
|
+
printf '%s\\n' "$START_OUT"
|
|
164
|
+
exit 0
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
SESSION=$(tmux display-message -p -t "$PANE" '#S' 2>/dev/null || true)
|
|
168
|
+
if [ -z "$SESSION" ]; then
|
|
169
|
+
printf '%s\\n' "$START_OUT"
|
|
170
|
+
exit 0
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
if [ -n "$TMUX" ]; then
|
|
174
|
+
exec tmux switch-client -t "$SESSION"
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
exec tmux attach-session -t "$SESSION"
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Codex: resume/fork subcommand aware wrapper.
|
|
182
|
+
return `#!/usr/bin/env bash
|
|
183
|
+
# hello-cc shim for ${tool.name} (auto-generated; do not edit manually)
|
|
184
|
+
# Wraps ${tool.name} with hello-cc peer mesh integration.
|
|
185
|
+
# Real binary: ${realBin}
|
|
186
|
+
|
|
187
|
+
set -e
|
|
188
|
+
REAL_BIN="${realBin}"
|
|
189
|
+
HCC_BIN="${hccBin}"
|
|
190
|
+
|
|
191
|
+
if [ "\${HCC_SHIM_ENSURED:-}" != "1" ]; then
|
|
192
|
+
set +e
|
|
193
|
+
"$HCC_BIN" shim ensure "${tool.name}" "$0" >/dev/null 2>&1
|
|
194
|
+
ENSURE_STATUS=$?
|
|
195
|
+
set -e
|
|
196
|
+
if [ "$ENSURE_STATUS" = "75" ]; then
|
|
197
|
+
export HCC_SHIM_ENSURED=1
|
|
198
|
+
exec "$0" "$@"
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
first_non_option() {
|
|
203
|
+
EXPECT_VALUE=0
|
|
204
|
+
for arg in "$@"; do
|
|
205
|
+
if [ "$EXPECT_VALUE" = "1" ]; then
|
|
206
|
+
EXPECT_VALUE=0
|
|
207
|
+
continue
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
case "$arg" in
|
|
211
|
+
--)
|
|
212
|
+
EXPECT_VALUE=0
|
|
213
|
+
continue
|
|
214
|
+
;;
|
|
215
|
+
-c|--config|-i|--image|-m|--model|-p|--profile|-s|--sandbox|-C|--cd|--add-dir|-a|--ask-for-approval|--remote|--remote-auth-token-env)
|
|
216
|
+
EXPECT_VALUE=1
|
|
217
|
+
continue
|
|
218
|
+
;;
|
|
219
|
+
--config=*|--image=*|--model=*|--profile=*|--sandbox=*|--cd=*|--add-dir=*|--ask-for-approval=*|--remote=*|--remote-auth-token-env=*)
|
|
220
|
+
continue
|
|
221
|
+
;;
|
|
222
|
+
--*)
|
|
223
|
+
continue
|
|
224
|
+
;;
|
|
225
|
+
-*)
|
|
226
|
+
continue
|
|
227
|
+
;;
|
|
228
|
+
*)
|
|
229
|
+
printf '%s' "$arg"
|
|
230
|
+
return 0
|
|
231
|
+
;;
|
|
232
|
+
esac
|
|
233
|
+
done
|
|
234
|
+
return 1
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
should_passthrough() {
|
|
238
|
+
for arg in "$@"; do
|
|
239
|
+
case "$arg" in
|
|
240
|
+
-h|--help|-V|--version)
|
|
241
|
+
return 0
|
|
242
|
+
;;
|
|
243
|
+
esac
|
|
244
|
+
done
|
|
245
|
+
|
|
246
|
+
SUBCOMMAND=$(first_non_option "$@" || true)
|
|
247
|
+
case "$SUBCOMMAND" in
|
|
248
|
+
help|exec|e|review|login|logout|mcp|plugin|mcp-server|app-server|remote-control|completion|update|doctor|sandbox|debug|apply|a|archive|unarchive|cloud|exec-server|features)
|
|
249
|
+
return 0
|
|
250
|
+
;;
|
|
251
|
+
esac
|
|
252
|
+
|
|
253
|
+
return 1
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if should_passthrough "$@"; then
|
|
257
|
+
exec "$REAL_BIN" "$@"
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
HCC_ROOT=$("$HCC_BIN" find-root 2>/dev/null || true)
|
|
261
|
+
|
|
262
|
+
if [ -z "$HCC_ROOT" ]; then
|
|
263
|
+
exec "$REAL_BIN" "$@"
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
sanitize_peer_part() {
|
|
267
|
+
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9._-]/-/g; s/^-*//; s/-*$//' | cut -c 1-32
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
tty_peer_part() {
|
|
271
|
+
TTY_NUM=$(tty 2>/dev/null | tr -dc '0-9' | tail -c 4)
|
|
272
|
+
PARENT_PID="\${PPID:-$$}"
|
|
273
|
+
if [ -n "$TTY_NUM" ]; then
|
|
274
|
+
printf '%s-%s' "$TTY_NUM" "$PARENT_PID"
|
|
275
|
+
else
|
|
276
|
+
printf '%s' "$PARENT_PID"
|
|
277
|
+
fi
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
SUBCMD=""
|
|
281
|
+
RESUME_ID=""
|
|
282
|
+
EXPECT_SESSION=0
|
|
283
|
+
for arg in "$@"; do
|
|
284
|
+
if [ -z "$SUBCMD" ]; then
|
|
285
|
+
if [ "$arg" = "resume" ] || [ "$arg" = "fork" ]; then
|
|
286
|
+
SUBCMD="$arg"
|
|
287
|
+
EXPECT_SESSION=1
|
|
288
|
+
fi
|
|
289
|
+
continue
|
|
290
|
+
fi
|
|
291
|
+
if [ "$EXPECT_SESSION" = "1" ]; then
|
|
292
|
+
case "$arg" in
|
|
293
|
+
--last) EXPECT_SESSION=0 ;;
|
|
294
|
+
-*) ;;
|
|
295
|
+
*) RESUME_ID="$arg"; EXPECT_SESSION=0 ;;
|
|
296
|
+
esac
|
|
297
|
+
fi
|
|
298
|
+
done
|
|
299
|
+
|
|
300
|
+
if [ -n "$HCC_PEER" ]; then
|
|
301
|
+
PEER_ID="$HCC_PEER"
|
|
302
|
+
elif [ "$SUBCMD" = "resume" ] && [ -n "$RESUME_ID" ]; then
|
|
303
|
+
PEER_ID="${tool.kind}-\$(sanitize_peer_part "\${RESUME_ID:0:8}")"
|
|
304
|
+
else
|
|
305
|
+
PEER_ID="${tool.kind}-\$(tty_peer_part)"
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
export HCC_PEER="$PEER_ID"
|
|
309
|
+
export HCC_ROOT
|
|
310
|
+
|
|
311
|
+
current_tmux_pane() {
|
|
312
|
+
if [ -n "\${TMUX_PANE:-}" ]; then
|
|
313
|
+
printf '%s\\n' "$TMUX_PANE"
|
|
314
|
+
return 0
|
|
315
|
+
fi
|
|
316
|
+
TTY_PATH=$(tty 2>/dev/null || true)
|
|
317
|
+
if [ -z "$TTY_PATH" ] || [ "$TTY_PATH" = "not a tty" ]; then
|
|
318
|
+
return 1
|
|
319
|
+
fi
|
|
320
|
+
tmux 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
|
+
}
|
|
322
|
+
|
|
323
|
+
CURRENT_HCC_PANE=""
|
|
324
|
+
CURRENT_HCC_SESSION=""
|
|
325
|
+
detect_current_hcc_pane() {
|
|
326
|
+
CURRENT_HCC_SESSION=""
|
|
327
|
+
CURRENT_HCC_PANE=$(current_tmux_pane || true)
|
|
328
|
+
if [ -z "$CURRENT_HCC_PANE" ]; then
|
|
329
|
+
return 1
|
|
330
|
+
fi
|
|
331
|
+
CURRENT_HCC_SESSION=$(tmux display-message -p -t "$CURRENT_HCC_PANE" '#S' 2>/dev/null || true)
|
|
332
|
+
case "$CURRENT_HCC_SESSION" in
|
|
333
|
+
hcc-*-*) return 0 ;;
|
|
334
|
+
esac
|
|
335
|
+
CURRENT_HCC_PANE=""
|
|
336
|
+
CURRENT_HCC_SESSION=""
|
|
337
|
+
return 1
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if detect_current_hcc_pane; then
|
|
341
|
+
SESSION_PEER_ID=$(printf '%s' "$CURRENT_HCC_SESSION" | sed 's/^hcc-[^-]*-//')
|
|
342
|
+
if [ -n "$SESSION_PEER_ID" ] && [ "$SESSION_PEER_ID" != "$CURRENT_HCC_SESSION" ]; then
|
|
343
|
+
PEER_ID="$SESSION_PEER_ID"
|
|
344
|
+
fi
|
|
345
|
+
export HCC_PEER="$PEER_ID"
|
|
346
|
+
"$HCC_BIN" web >/dev/null 2>&1 || true
|
|
347
|
+
"$HCC_BIN" peer attach "$PEER_ID" --kind "${tool.kind}" --pane "$CURRENT_HCC_PANE" --cwd "$PWD" --force >/dev/null 2>&1 || true
|
|
348
|
+
exec "$REAL_BIN" "$@"
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
set +e
|
|
352
|
+
# Wrap in shell so pane_pid stays as bash; Ctrl+C drops back to bash
|
|
353
|
+
START_OUT=$("$HCC_BIN" web >/dev/null 2>&1 || true; "$HCC_BIN" peer start "$PEER_ID" --kind "${tool.kind}" --restart-env -- bash -lc 'REAL_BIN="$1"; shift; hcc_provider_state() { HCC_TMUX_SESSION=$(tmux display-message -p "#S" 2>/dev/null || true); if [ -n "$HCC_TMUX_SESSION" ]; then tmux set-environment -t "$HCC_TMUX_SESSION" HCC_PROVIDER_STATE "$1" >/dev/null 2>&1 || true; fi; }; hcc_provider_state running; "$REAL_BIN" "$@"; hcc_provider_state exited; exec bash' _ "$REAL_BIN" "$@" 2>&1)
|
|
354
|
+
START_STATUS=$?
|
|
355
|
+
set -e
|
|
356
|
+
if [ "$START_STATUS" -ne 0 ]; then
|
|
357
|
+
printf '%s\\n' "$START_OUT" >&2
|
|
358
|
+
exit "$START_STATUS"
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
PANE=$(printf '%s\\n' "$START_OUT" | sed -n 's/.*pane=\\(%[0-9][0-9]*\\).*/\\1/p' | tail -n 1)
|
|
362
|
+
if [ -z "$PANE" ]; then
|
|
363
|
+
printf '%s\\n' "$START_OUT"
|
|
364
|
+
exit 0
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
if [ "$HCC_SHIM_NO_ATTACH" = "1" ] || [ ! -t 0 ]; then
|
|
368
|
+
printf '%s\\n' "$START_OUT"
|
|
369
|
+
exit 0
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
SESSION=$(tmux display-message -p -t "$PANE" '#S' 2>/dev/null || true)
|
|
373
|
+
if [ -z "$SESSION" ]; then
|
|
374
|
+
printf '%s\\n' "$START_OUT"
|
|
375
|
+
exit 0
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
if [ -n "$TMUX" ]; then
|
|
379
|
+
exec tmux switch-client -t "$SESSION"
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
exec tmux attach-session -t "$SESSION"
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { generateShim } from './shims/script.mjs';
|
|
6
|
+
|
|
7
|
+
export const SHIM_DIR = path.join(os.homedir(), '.hcc-shims');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Install shim scripts for `claude` and `codex` into SHIM_DIR.
|
|
11
|
+
* Returns { shimDir, installed: [paths], changed: [paths], skipped: [names] }.
|
|
12
|
+
*/
|
|
13
|
+
export function installShims(hccBin) {
|
|
14
|
+
fs.mkdirSync(SHIM_DIR, { recursive: true });
|
|
15
|
+
|
|
16
|
+
const tools = [
|
|
17
|
+
{ name: 'claude', kind: 'claude', resumeFlag: '--resume' },
|
|
18
|
+
{ name: 'codex', kind: 'codex', resumeSubcmd: 'resume' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const installed = [];
|
|
22
|
+
const changed = [];
|
|
23
|
+
const skipped = [];
|
|
24
|
+
|
|
25
|
+
for (const tool of tools) {
|
|
26
|
+
const realBin = findRealBinary(tool.name);
|
|
27
|
+
if (!realBin) {
|
|
28
|
+
skipped.push(`${tool.name} (not found on PATH)`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const shimPath = path.join(SHIM_DIR, tool.name);
|
|
32
|
+
const content = generateShim(hccBin, realBin, tool);
|
|
33
|
+
let previous = null;
|
|
34
|
+
try { previous = fs.readFileSync(shimPath, 'utf8'); } catch {}
|
|
35
|
+
if (previous !== content) {
|
|
36
|
+
fs.writeFileSync(shimPath, content, { mode: 0o755 });
|
|
37
|
+
changed.push(shimPath);
|
|
38
|
+
} else {
|
|
39
|
+
try { fs.chmodSync(shimPath, 0o755); } catch {}
|
|
40
|
+
}
|
|
41
|
+
installed.push(shimPath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { shimDir: SHIM_DIR, installed, changed, skipped };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Remove shim scripts.
|
|
49
|
+
*/
|
|
50
|
+
export function uninstallShims() {
|
|
51
|
+
const removed = [];
|
|
52
|
+
for (const name of ['claude', 'codex']) {
|
|
53
|
+
const p = path.join(SHIM_DIR, name);
|
|
54
|
+
try { fs.unlinkSync(p); removed.push(p); } catch {}
|
|
55
|
+
}
|
|
56
|
+
try { fs.rmdirSync(SHIM_DIR); } catch {}
|
|
57
|
+
return removed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns true if at least one shim is installed and in an HCC shim dir.
|
|
62
|
+
*/
|
|
63
|
+
export function verifyShims() {
|
|
64
|
+
return ['claude', 'codex'].some(name =>
|
|
65
|
+
fsExists(path.join(SHIM_DIR, name))
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Find the real binary for `name`, skipping the SHIM_DIR.
|
|
71
|
+
* Checks PATH, then npm global bin, then common install locations.
|
|
72
|
+
*/
|
|
73
|
+
export function findRealBinary(name) {
|
|
74
|
+
// which -a lists all matches; pick first one outside SHIM_DIR
|
|
75
|
+
const r = spawnSync('which', ['-a', name], { encoding: 'utf8', timeout: 3000 });
|
|
76
|
+
if (r.status === 0) {
|
|
77
|
+
for (const p of r.stdout.trim().split('\n')) {
|
|
78
|
+
const resolved = p.trim();
|
|
79
|
+
if (resolved && !resolved.startsWith(SHIM_DIR)) return resolved;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Try npm global bin
|
|
84
|
+
const npmR = spawnSync('npm', ['root', '-g'], { encoding: 'utf8', timeout: 3000 });
|
|
85
|
+
if (npmR.status === 0) {
|
|
86
|
+
const npmBin = path.join(path.dirname(npmR.stdout.trim()), 'bin', name);
|
|
87
|
+
if (fsExists(npmBin)) return npmBin;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Common locations
|
|
91
|
+
for (const dir of [
|
|
92
|
+
'/usr/local/bin',
|
|
93
|
+
'/usr/bin',
|
|
94
|
+
path.join(os.homedir(), '.local', 'bin'),
|
|
95
|
+
path.join(os.homedir(), '.nvm', 'current', 'bin'),
|
|
96
|
+
]) {
|
|
97
|
+
const p = path.join(dir, name);
|
|
98
|
+
if (fsExists(p)) return p;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function fsExists(p) {
|
|
105
|
+
try { fs.accessSync(p); return true; } catch { return false; }
|
|
106
|
+
}
|
package/lib/json-file.mjs
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
export function readJsonSafe(filePath) {
|
|
5
|
-
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
6
|
-
catch { return null; }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function writeJsonSafe(filePath, data) {
|
|
10
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
11
|
-
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
12
|
-
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n');
|
|
13
|
-
fs.renameSync(tmpPath, filePath);
|
|
14
|
-
}
|
|
1
|
+
export { readJsonSafe, writeJsonSafe } from './shared/json-file.mjs';
|
package/lib/locks.mjs
ADDED
package/lib/messages.mjs
ADDED
package/lib/package-meta.mjs
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function readJson(file) {
|
|
10
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function readPackageJson(root) {
|
|
14
|
-
return readJson(path.join(root, 'package.json'));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function readPackageMeta(root) {
|
|
18
|
-
const pkg = readPackageJson(root);
|
|
19
|
-
return {
|
|
20
|
-
name: pkg.name,
|
|
21
|
-
version: pkg.version,
|
|
22
|
-
packageJson: pkg
|
|
23
|
-
};
|
|
24
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
packageRoot,
|
|
3
|
+
readJson,
|
|
4
|
+
readPackageJson,
|
|
5
|
+
readPackageMeta
|
|
6
|
+
} from './release/package-meta.mjs';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
bindingFromDetected,
|
|
3
|
+
bindingHasProviderSession,
|
|
4
|
+
bindingHasRuntime,
|
|
5
|
+
bindingProviderSessionValue,
|
|
6
|
+
comparePeerBindings,
|
|
7
|
+
mergePeerBinding,
|
|
8
|
+
mergeRuntimeBinding,
|
|
9
|
+
peerBindingRuntimeRank
|
|
10
|
+
} from './core/peers/bindings.mjs';
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
createPeerBindingStore
|
|
14
|
+
} from './db/stores/peers.mjs';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
autoPeerBasis,
|
|
3
|
+
autoPeerId,
|
|
4
|
+
autoPeerKind,
|
|
5
|
+
autoPeerProviderSession,
|
|
6
|
+
autoPeerResumeId,
|
|
7
|
+
autoPeerSessionId,
|
|
8
|
+
currentPeer,
|
|
9
|
+
currentTtyName,
|
|
10
|
+
readAncestorCliInfo,
|
|
11
|
+
resolveCurrentPeer,
|
|
12
|
+
resumeIdFromArgs,
|
|
13
|
+
sanitizePeerPart,
|
|
14
|
+
shortHash
|
|
15
|
+
} from './integrations/peers/identity.mjs';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
bindingFromRun,
|
|
3
|
+
buildClaudeCommand,
|
|
4
|
+
buildCodexCommand,
|
|
5
|
+
buildPeerCommand,
|
|
6
|
+
defaultSessionCommand,
|
|
7
|
+
hasResumeOpts,
|
|
8
|
+
inferPeerKind,
|
|
9
|
+
parseClaudeCommandArgs,
|
|
10
|
+
parseCodexCommandArgs,
|
|
11
|
+
providerSessionParts,
|
|
12
|
+
providerSessionPeerId
|
|
13
|
+
} from './integrations/providers.mjs';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
export function packageRoot(fromUrl = import.meta.url) {
|
|
6
|
+
let dir = path.dirname(fileURLToPath(fromUrl));
|
|
7
|
+
for (;;) {
|
|
8
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
|
9
|
+
const parent = path.dirname(dir);
|
|
10
|
+
if (parent === dir) break;
|
|
11
|
+
dir = parent;
|
|
12
|
+
}
|
|
13
|
+
return path.resolve(path.dirname(fileURLToPath(fromUrl)), '..', '..');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readJson(file) {
|
|
17
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readPackageJson(root) {
|
|
21
|
+
return readJson(path.join(root, 'package.json'));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readPackageMeta(root) {
|
|
25
|
+
const pkg = readPackageJson(root);
|
|
26
|
+
return {
|
|
27
|
+
name: pkg.name,
|
|
28
|
+
version: pkg.version,
|
|
29
|
+
packageJson: pkg
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function normalizeVersion(version) {
|
|
2
|
+
return String(version || '').replace(/^v/, '');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function releaseSection(markdown, version) {
|
|
6
|
+
const lines = markdown.split(/\r?\n/);
|
|
7
|
+
const heading = `## ${version}`;
|
|
8
|
+
const start = lines.findIndex((line) => line.trim() === heading);
|
|
9
|
+
if (start < 0) return null;
|
|
10
|
+
|
|
11
|
+
let end = lines.length;
|
|
12
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
13
|
+
if (/^##\s+/.test(lines[i])) {
|
|
14
|
+
end = i;
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return lines.slice(start, end).join('\n').trimEnd();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateReleaseSection(section, version) {
|
|
22
|
+
const problems = [];
|
|
23
|
+
if (!section) {
|
|
24
|
+
problems.push(`CHANGELOG.md is missing section ## ${version}`);
|
|
25
|
+
return problems;
|
|
26
|
+
}
|
|
27
|
+
if (!section.includes('### Summary')) problems.push(`## ${version} is missing ### Summary`);
|
|
28
|
+
if (!section.includes('### Highlights')) problems.push(`## ${version} is missing ### Highlights`);
|
|
29
|
+
if (section.includes('TODO') || section.includes('TBD')) problems.push(`## ${version} still contains TODO/TBD text`);
|
|
30
|
+
const bulletCount = (section.match(/^- /gm) || []).length;
|
|
31
|
+
if (bulletCount < 2) problems.push(`## ${version} should include at least two release-note bullets`);
|
|
32
|
+
return problems;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function repoFromPackage(pkg) {
|
|
36
|
+
const repo = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;
|
|
37
|
+
if (!repo) return null;
|
|
38
|
+
const match = repo.match(/github\.com[:/](.+?\/.+?)(?:\.git)?$/);
|
|
39
|
+
return match ? match[1] : null;
|
|
40
|
+
}
|