@logicseek/hello-cc 0.1.3 → 0.1.5
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 +110 -0
- package/bin/hcc.mjs +865 -2430
- package/docs/commands.md +21 -9
- package/docs/commands.zh-CN.md +19 -9
- package/lib/cli-args.mjs +89 -0
- package/lib/db-schema.mjs +324 -0
- package/lib/errors.mjs +7 -0
- package/lib/format.mjs +39 -0
- package/lib/guidance.mjs +123 -0
- package/lib/json-file.mjs +14 -0
- package/lib/package-meta.mjs +24 -0
- package/lib/release-notes.mjs +40 -0
- package/lib/runtime-paths.mjs +45 -0
- package/lib/setup.mjs +3 -474
- package/lib/shell-path.mjs +69 -0
- package/lib/shim-script.mjs +384 -0
- package/lib/web-http.mjs +55 -0
- package/lib/web-runtime.mjs +108 -0
- package/lib/web-ui-template.mjs +1909 -0
- package/package.json +1 -1
- package/scripts/github-release.mjs +11 -49
- package/scripts/regression.mjs +443 -18
- package/scripts/release-notes.mjs +6 -38
package/lib/setup.mjs
CHANGED
|
@@ -10,6 +10,9 @@ import fs from 'node:fs';
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import { readJsonSafe, writeJsonSafe } from './json-file.mjs';
|
|
14
|
+
import { generateShim } from './shim-script.mjs';
|
|
15
|
+
export { installPathEntry, uninstallPathEntry } from './shell-path.mjs';
|
|
13
16
|
|
|
14
17
|
export const SHIM_DIR = path.join(os.homedir(), '.hcc-shims');
|
|
15
18
|
|
|
@@ -199,468 +202,6 @@ export function findRealBinary(name) {
|
|
|
199
202
|
return null;
|
|
200
203
|
}
|
|
201
204
|
|
|
202
|
-
// ─── PATH entry in shell rc ───────────────────────────────────────────────────
|
|
203
|
-
|
|
204
|
-
function pathEntryLine(shellType) {
|
|
205
|
-
if (shellType === 'fish') return 'set -gx PATH $HOME/.hcc-shims $PATH # hello-cc shims';
|
|
206
|
-
return '_hcc_shims="$HOME/.hcc-shims"; PATH="${PATH//:${_hcc_shims}:/:}"; PATH="${PATH#${_hcc_shims}:}"; PATH="${PATH%:${_hcc_shims}}"; export PATH="${_hcc_shims}:$PATH"; unset _hcc_shims # hello-cc shims';
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function removePathEntryLines(content) {
|
|
210
|
-
return String(content || '')
|
|
211
|
-
.split('\n')
|
|
212
|
-
.filter(line => !line.includes('.hcc-shims'))
|
|
213
|
-
.join('\n')
|
|
214
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
215
|
-
.replace(/\s+$/, '');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function insertPathEntry(content, shellType) {
|
|
219
|
-
const line = pathEntryLine(shellType);
|
|
220
|
-
const cleaned = removePathEntryLines(content);
|
|
221
|
-
if (shellType === 'fish') return `${cleaned}${cleaned ? '\n' : ''}${line}\n`;
|
|
222
|
-
|
|
223
|
-
const lines = cleaned ? cleaned.split('\n') : [];
|
|
224
|
-
let insertAt = 0;
|
|
225
|
-
if (lines[0]?.startsWith('#!')) insertAt = 1;
|
|
226
|
-
for (let i = insertAt; i < lines.length; i += 1) {
|
|
227
|
-
const text = lines[i].trim();
|
|
228
|
-
if (!text || text.startsWith('#')) continue;
|
|
229
|
-
insertAt = i;
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
lines.splice(insertAt, 0, line);
|
|
233
|
-
const withEarlyEntry = lines.join('\n').replace(/\s+$/, '');
|
|
234
|
-
return `${withEarlyEntry}${withEarlyEntry ? '\n' : ''}${line}\n`;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Install SHIM_DIR in the user's shell rc before common early-return guards.
|
|
239
|
-
* Returns { rcFile, shellType, alreadyPresent }.
|
|
240
|
-
*/
|
|
241
|
-
export function installPathEntry() {
|
|
242
|
-
const { path: rcFile, type: shellType } = detectShellRc();
|
|
243
|
-
let content = '';
|
|
244
|
-
try { content = fs.readFileSync(rcFile, 'utf8'); } catch {}
|
|
245
|
-
|
|
246
|
-
const next = insertPathEntry(content, shellType);
|
|
247
|
-
const alreadyPresent = next === (content.endsWith('\n') ? content : `${content}\n`);
|
|
248
|
-
if (!alreadyPresent) fs.writeFileSync(rcFile, next);
|
|
249
|
-
return { rcFile, shellType, alreadyPresent };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Remove SHIM_DIR PATH entry from shell rc.
|
|
254
|
-
*/
|
|
255
|
-
export function uninstallPathEntry() {
|
|
256
|
-
const { path: rcFile } = detectShellRc();
|
|
257
|
-
try {
|
|
258
|
-
const content = fs.readFileSync(rcFile, 'utf8');
|
|
259
|
-
const filtered = content
|
|
260
|
-
.split('\n')
|
|
261
|
-
.filter(line => !line.includes('.hcc-shims'))
|
|
262
|
-
.join('\n');
|
|
263
|
-
fs.writeFileSync(rcFile, filtered);
|
|
264
|
-
return { rcFile };
|
|
265
|
-
} catch {
|
|
266
|
-
return { rcFile, error: 'could not modify shell rc' };
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function detectShellRc() {
|
|
271
|
-
const shell = process.env.SHELL || '';
|
|
272
|
-
if (shell.includes('zsh')) return { path: path.join(os.homedir(), '.zshrc'), type: 'zsh' };
|
|
273
|
-
if (shell.includes('fish')) return { path: path.join(os.homedir(), '.config', 'fish', 'config.fish'), type: 'fish' };
|
|
274
|
-
return { path: path.join(os.homedir(), '.bashrc'), type: 'bash' };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ─── Shim script generation ───────────────────────────────────────────────────
|
|
278
|
-
|
|
279
|
-
function generateShim(hccBin, realBin, tool) {
|
|
280
|
-
if (tool.resumeFlag) {
|
|
281
|
-
// Claude: --resume/--continue/--session-id/--fork-session aware wrapper.
|
|
282
|
-
return `#!/usr/bin/env bash
|
|
283
|
-
# hello-cc shim for ${tool.name} (auto-generated; do not edit manually)
|
|
284
|
-
# Wraps ${tool.name} with hello-cc peer mesh integration.
|
|
285
|
-
# Real binary: ${realBin}
|
|
286
|
-
|
|
287
|
-
set -e
|
|
288
|
-
REAL_BIN="${realBin}"
|
|
289
|
-
HCC_BIN="${hccBin}"
|
|
290
|
-
|
|
291
|
-
if [ "\${HCC_SHIM_ENSURED:-}" != "1" ]; then
|
|
292
|
-
set +e
|
|
293
|
-
"$HCC_BIN" shim ensure "${tool.name}" "$0" >/dev/null 2>&1
|
|
294
|
-
ENSURE_STATUS=$?
|
|
295
|
-
set -e
|
|
296
|
-
if [ "$ENSURE_STATUS" = "75" ]; then
|
|
297
|
-
export HCC_SHIM_ENSURED=1
|
|
298
|
-
exec "$0" "$@"
|
|
299
|
-
fi
|
|
300
|
-
fi
|
|
301
|
-
|
|
302
|
-
should_passthrough() {
|
|
303
|
-
for arg in "$@"; do
|
|
304
|
-
case "$arg" in
|
|
305
|
-
-h|--help|-v|--version|-p|--print|--bare|--safe-mode)
|
|
306
|
-
return 0
|
|
307
|
-
;;
|
|
308
|
-
esac
|
|
309
|
-
done
|
|
310
|
-
|
|
311
|
-
case "\${1:-}" in
|
|
312
|
-
agents|auth|auto-mode|doctor|install|mcp|plugin|plugins|project|setup-token|ultrareview|update|upgrade)
|
|
313
|
-
return 0
|
|
314
|
-
;;
|
|
315
|
-
esac
|
|
316
|
-
|
|
317
|
-
return 1
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if should_passthrough "$@"; then
|
|
321
|
-
exec "$REAL_BIN" "$@"
|
|
322
|
-
fi
|
|
323
|
-
|
|
324
|
-
# Use the current directory as the hello-cc project root unless HCC_ROOT is set.
|
|
325
|
-
HCC_ROOT=$("$HCC_BIN" find-root 2>/dev/null || true)
|
|
326
|
-
|
|
327
|
-
if [ -z "$HCC_ROOT" ]; then
|
|
328
|
-
exec "$REAL_BIN" "$@"
|
|
329
|
-
fi
|
|
330
|
-
|
|
331
|
-
sanitize_peer_part() {
|
|
332
|
-
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9._-]/-/g; s/^-*//; s/-*$//' | cut -c 1-32
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
tty_peer_part() {
|
|
336
|
-
TTY_NUM=$(tty 2>/dev/null | tr -dc '0-9' | tail -c 4)
|
|
337
|
-
PARENT_PID="\${PPID:-$$}"
|
|
338
|
-
if [ -n "$TTY_NUM" ]; then
|
|
339
|
-
printf '%s-%s' "$TTY_NUM" "$PARENT_PID"
|
|
340
|
-
else
|
|
341
|
-
printf '%s' "$PARENT_PID"
|
|
342
|
-
fi
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
RESUME_ID=""
|
|
346
|
-
SESSION_ID=""
|
|
347
|
-
SESSION_NAME=""
|
|
348
|
-
IS_CONTINUE=0
|
|
349
|
-
IS_FORK=0
|
|
350
|
-
PREV_ARG=""
|
|
351
|
-
for arg in "$@"; do
|
|
352
|
-
case "$PREV_ARG" in
|
|
353
|
-
--resume|-r) RESUME_ID="$arg" ;;
|
|
354
|
-
--session-id) SESSION_ID="$arg" ;;
|
|
355
|
-
--name|-n) SESSION_NAME="$arg" ;;
|
|
356
|
-
esac
|
|
357
|
-
case "$arg" in
|
|
358
|
-
--resume=*) RESUME_ID="\${arg#--resume=}" ;;
|
|
359
|
-
--session-id=*) SESSION_ID="\${arg#--session-id=}" ;;
|
|
360
|
-
--name=*) SESSION_NAME="\${arg#--name=}" ;;
|
|
361
|
-
--continue|-c) IS_CONTINUE=1 ;;
|
|
362
|
-
--fork-session) IS_FORK=1 ;;
|
|
363
|
-
esac
|
|
364
|
-
PREV_ARG="$arg"
|
|
365
|
-
done
|
|
366
|
-
|
|
367
|
-
if [ -n "$HCC_PEER" ]; then
|
|
368
|
-
PEER_ID="$HCC_PEER"
|
|
369
|
-
elif [ -n "$SESSION_ID" ]; then
|
|
370
|
-
PEER_ID="${tool.kind}-\$(sanitize_peer_part "\${SESSION_ID:0:8}")"
|
|
371
|
-
elif [ -n "$RESUME_ID" ] && [ "$IS_FORK" != "1" ]; then
|
|
372
|
-
PEER_ID="${tool.kind}-\$(sanitize_peer_part "\${RESUME_ID:0:8}")"
|
|
373
|
-
elif [ -n "$CLAUDE_CODE_SESSION_ID" ]; then
|
|
374
|
-
PEER_ID="${tool.kind}-\$(sanitize_peer_part "\${CLAUDE_CODE_SESSION_ID:0:8}")"
|
|
375
|
-
elif [ -n "$SESSION_NAME" ] && [ "$IS_FORK" != "1" ] && [ "$IS_CONTINUE" != "1" ]; then
|
|
376
|
-
PEER_ID="${tool.kind}-\$(sanitize_peer_part "$SESSION_NAME")"
|
|
377
|
-
else
|
|
378
|
-
PEER_ID="${tool.kind}-\$(tty_peer_part)"
|
|
379
|
-
fi
|
|
380
|
-
|
|
381
|
-
export HCC_PEER="$PEER_ID"
|
|
382
|
-
export HCC_ROOT
|
|
383
|
-
|
|
384
|
-
current_tmux_pane() {
|
|
385
|
-
if [ -n "\${TMUX_PANE:-}" ]; then
|
|
386
|
-
printf '%s\\n' "$TMUX_PANE"
|
|
387
|
-
return 0
|
|
388
|
-
fi
|
|
389
|
-
TTY_PATH=$(tty 2>/dev/null || true)
|
|
390
|
-
if [ -z "$TTY_PATH" ] || [ "$TTY_PATH" = "not a tty" ]; then
|
|
391
|
-
return 1
|
|
392
|
-
fi
|
|
393
|
-
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 }'
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
CURRENT_HCC_PANE=""
|
|
397
|
-
CURRENT_HCC_SESSION=""
|
|
398
|
-
detect_current_hcc_pane() {
|
|
399
|
-
CURRENT_HCC_SESSION=""
|
|
400
|
-
CURRENT_HCC_PANE=$(current_tmux_pane || true)
|
|
401
|
-
if [ -z "$CURRENT_HCC_PANE" ]; then
|
|
402
|
-
return 1
|
|
403
|
-
fi
|
|
404
|
-
CURRENT_HCC_SESSION=$(tmux display-message -p -t "$CURRENT_HCC_PANE" '#S' 2>/dev/null || true)
|
|
405
|
-
case "$CURRENT_HCC_SESSION" in
|
|
406
|
-
hcc-*-*) return 0 ;;
|
|
407
|
-
esac
|
|
408
|
-
CURRENT_HCC_PANE=""
|
|
409
|
-
CURRENT_HCC_SESSION=""
|
|
410
|
-
return 1
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if detect_current_hcc_pane; then
|
|
414
|
-
SESSION_PEER_ID=$(printf '%s' "$CURRENT_HCC_SESSION" | sed 's/^hcc-[^-]*-//')
|
|
415
|
-
if [ -n "$SESSION_PEER_ID" ] && [ "$SESSION_PEER_ID" != "$CURRENT_HCC_SESSION" ]; then
|
|
416
|
-
PEER_ID="$SESSION_PEER_ID"
|
|
417
|
-
fi
|
|
418
|
-
export HCC_PEER="$PEER_ID"
|
|
419
|
-
"$HCC_BIN" web >/dev/null 2>&1 || true
|
|
420
|
-
"$HCC_BIN" peer attach "$PEER_ID" --kind "${tool.kind}" --pane "$CURRENT_HCC_PANE" --cwd "$PWD" --force >/dev/null 2>&1 || true
|
|
421
|
-
exec "$REAL_BIN" "$@"
|
|
422
|
-
fi
|
|
423
|
-
|
|
424
|
-
set +e
|
|
425
|
-
# Wrap in shell so pane_pid stays as bash; Ctrl+C drops back to bash
|
|
426
|
-
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)
|
|
427
|
-
START_STATUS=$?
|
|
428
|
-
set -e
|
|
429
|
-
if [ "$START_STATUS" -ne 0 ]; then
|
|
430
|
-
printf '%s\\n' "$START_OUT" >&2
|
|
431
|
-
exit "$START_STATUS"
|
|
432
|
-
fi
|
|
433
|
-
|
|
434
|
-
PANE=$(printf '%s\\n' "$START_OUT" | sed -n 's/.*pane=\\(%[0-9][0-9]*\\).*/\\1/p' | tail -n 1)
|
|
435
|
-
if [ -z "$PANE" ]; then
|
|
436
|
-
printf '%s\\n' "$START_OUT"
|
|
437
|
-
exit 0
|
|
438
|
-
fi
|
|
439
|
-
|
|
440
|
-
if [ "$HCC_SHIM_NO_ATTACH" = "1" ] || [ ! -t 0 ]; then
|
|
441
|
-
printf '%s\\n' "$START_OUT"
|
|
442
|
-
exit 0
|
|
443
|
-
fi
|
|
444
|
-
|
|
445
|
-
SESSION=$(tmux display-message -p -t "$PANE" '#S' 2>/dev/null || true)
|
|
446
|
-
if [ -z "$SESSION" ]; then
|
|
447
|
-
printf '%s\\n' "$START_OUT"
|
|
448
|
-
exit 0
|
|
449
|
-
fi
|
|
450
|
-
|
|
451
|
-
if [ -n "$TMUX" ]; then
|
|
452
|
-
exec tmux switch-client -t "$SESSION"
|
|
453
|
-
fi
|
|
454
|
-
|
|
455
|
-
exec tmux attach-session -t "$SESSION"
|
|
456
|
-
`;
|
|
457
|
-
} else {
|
|
458
|
-
// Codex: resume/fork subcommand aware wrapper.
|
|
459
|
-
return `#!/usr/bin/env bash
|
|
460
|
-
# hello-cc shim for ${tool.name} (auto-generated; do not edit manually)
|
|
461
|
-
# Wraps ${tool.name} with hello-cc peer mesh integration.
|
|
462
|
-
# Real binary: ${realBin}
|
|
463
|
-
|
|
464
|
-
set -e
|
|
465
|
-
REAL_BIN="${realBin}"
|
|
466
|
-
HCC_BIN="${hccBin}"
|
|
467
|
-
|
|
468
|
-
if [ "\${HCC_SHIM_ENSURED:-}" != "1" ]; then
|
|
469
|
-
set +e
|
|
470
|
-
"$HCC_BIN" shim ensure "${tool.name}" "$0" >/dev/null 2>&1
|
|
471
|
-
ENSURE_STATUS=$?
|
|
472
|
-
set -e
|
|
473
|
-
if [ "$ENSURE_STATUS" = "75" ]; then
|
|
474
|
-
export HCC_SHIM_ENSURED=1
|
|
475
|
-
exec "$0" "$@"
|
|
476
|
-
fi
|
|
477
|
-
fi
|
|
478
|
-
|
|
479
|
-
first_non_option() {
|
|
480
|
-
EXPECT_VALUE=0
|
|
481
|
-
for arg in "$@"; do
|
|
482
|
-
if [ "$EXPECT_VALUE" = "1" ]; then
|
|
483
|
-
EXPECT_VALUE=0
|
|
484
|
-
continue
|
|
485
|
-
fi
|
|
486
|
-
|
|
487
|
-
case "$arg" in
|
|
488
|
-
--)
|
|
489
|
-
EXPECT_VALUE=0
|
|
490
|
-
continue
|
|
491
|
-
;;
|
|
492
|
-
-c|--config|-i|--image|-m|--model|-p|--profile|-s|--sandbox|-C|--cd|--add-dir|-a|--ask-for-approval|--remote|--remote-auth-token-env)
|
|
493
|
-
EXPECT_VALUE=1
|
|
494
|
-
continue
|
|
495
|
-
;;
|
|
496
|
-
--config=*|--image=*|--model=*|--profile=*|--sandbox=*|--cd=*|--add-dir=*|--ask-for-approval=*|--remote=*|--remote-auth-token-env=*)
|
|
497
|
-
continue
|
|
498
|
-
;;
|
|
499
|
-
--*)
|
|
500
|
-
continue
|
|
501
|
-
;;
|
|
502
|
-
-*)
|
|
503
|
-
continue
|
|
504
|
-
;;
|
|
505
|
-
*)
|
|
506
|
-
printf '%s' "$arg"
|
|
507
|
-
return 0
|
|
508
|
-
;;
|
|
509
|
-
esac
|
|
510
|
-
done
|
|
511
|
-
return 1
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
should_passthrough() {
|
|
515
|
-
for arg in "$@"; do
|
|
516
|
-
case "$arg" in
|
|
517
|
-
-h|--help|-V|--version)
|
|
518
|
-
return 0
|
|
519
|
-
;;
|
|
520
|
-
esac
|
|
521
|
-
done
|
|
522
|
-
|
|
523
|
-
SUBCOMMAND=$(first_non_option "$@" || true)
|
|
524
|
-
case "$SUBCOMMAND" in
|
|
525
|
-
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)
|
|
526
|
-
return 0
|
|
527
|
-
;;
|
|
528
|
-
esac
|
|
529
|
-
|
|
530
|
-
return 1
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
if should_passthrough "$@"; then
|
|
534
|
-
exec "$REAL_BIN" "$@"
|
|
535
|
-
fi
|
|
536
|
-
|
|
537
|
-
HCC_ROOT=$("$HCC_BIN" find-root 2>/dev/null || true)
|
|
538
|
-
|
|
539
|
-
if [ -z "$HCC_ROOT" ]; then
|
|
540
|
-
exec "$REAL_BIN" "$@"
|
|
541
|
-
fi
|
|
542
|
-
|
|
543
|
-
sanitize_peer_part() {
|
|
544
|
-
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9._-]/-/g; s/^-*//; s/-*$//' | cut -c 1-32
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
tty_peer_part() {
|
|
548
|
-
TTY_NUM=$(tty 2>/dev/null | tr -dc '0-9' | tail -c 4)
|
|
549
|
-
PARENT_PID="\${PPID:-$$}"
|
|
550
|
-
if [ -n "$TTY_NUM" ]; then
|
|
551
|
-
printf '%s-%s' "$TTY_NUM" "$PARENT_PID"
|
|
552
|
-
else
|
|
553
|
-
printf '%s' "$PARENT_PID"
|
|
554
|
-
fi
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
SUBCMD=""
|
|
558
|
-
RESUME_ID=""
|
|
559
|
-
EXPECT_SESSION=0
|
|
560
|
-
for arg in "$@"; do
|
|
561
|
-
if [ -z "$SUBCMD" ]; then
|
|
562
|
-
if [ "$arg" = "resume" ] || [ "$arg" = "fork" ]; then
|
|
563
|
-
SUBCMD="$arg"
|
|
564
|
-
EXPECT_SESSION=1
|
|
565
|
-
fi
|
|
566
|
-
continue
|
|
567
|
-
fi
|
|
568
|
-
if [ "$EXPECT_SESSION" = "1" ]; then
|
|
569
|
-
case "$arg" in
|
|
570
|
-
--last) EXPECT_SESSION=0 ;;
|
|
571
|
-
-*) ;;
|
|
572
|
-
*) RESUME_ID="$arg"; EXPECT_SESSION=0 ;;
|
|
573
|
-
esac
|
|
574
|
-
fi
|
|
575
|
-
done
|
|
576
|
-
|
|
577
|
-
if [ -n "$HCC_PEER" ]; then
|
|
578
|
-
PEER_ID="$HCC_PEER"
|
|
579
|
-
elif [ "$SUBCMD" = "resume" ] && [ -n "$RESUME_ID" ]; then
|
|
580
|
-
PEER_ID="${tool.kind}-\$(sanitize_peer_part "\${RESUME_ID:0:8}")"
|
|
581
|
-
else
|
|
582
|
-
PEER_ID="${tool.kind}-\$(tty_peer_part)"
|
|
583
|
-
fi
|
|
584
|
-
|
|
585
|
-
export HCC_PEER="$PEER_ID"
|
|
586
|
-
export HCC_ROOT
|
|
587
|
-
|
|
588
|
-
current_tmux_pane() {
|
|
589
|
-
if [ -n "\${TMUX_PANE:-}" ]; then
|
|
590
|
-
printf '%s\\n' "$TMUX_PANE"
|
|
591
|
-
return 0
|
|
592
|
-
fi
|
|
593
|
-
TTY_PATH=$(tty 2>/dev/null || true)
|
|
594
|
-
if [ -z "$TTY_PATH" ] || [ "$TTY_PATH" = "not a tty" ]; then
|
|
595
|
-
return 1
|
|
596
|
-
fi
|
|
597
|
-
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 }'
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
CURRENT_HCC_PANE=""
|
|
601
|
-
CURRENT_HCC_SESSION=""
|
|
602
|
-
detect_current_hcc_pane() {
|
|
603
|
-
CURRENT_HCC_SESSION=""
|
|
604
|
-
CURRENT_HCC_PANE=$(current_tmux_pane || true)
|
|
605
|
-
if [ -z "$CURRENT_HCC_PANE" ]; then
|
|
606
|
-
return 1
|
|
607
|
-
fi
|
|
608
|
-
CURRENT_HCC_SESSION=$(tmux display-message -p -t "$CURRENT_HCC_PANE" '#S' 2>/dev/null || true)
|
|
609
|
-
case "$CURRENT_HCC_SESSION" in
|
|
610
|
-
hcc-*-*) return 0 ;;
|
|
611
|
-
esac
|
|
612
|
-
CURRENT_HCC_PANE=""
|
|
613
|
-
CURRENT_HCC_SESSION=""
|
|
614
|
-
return 1
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
if detect_current_hcc_pane; then
|
|
618
|
-
SESSION_PEER_ID=$(printf '%s' "$CURRENT_HCC_SESSION" | sed 's/^hcc-[^-]*-//')
|
|
619
|
-
if [ -n "$SESSION_PEER_ID" ] && [ "$SESSION_PEER_ID" != "$CURRENT_HCC_SESSION" ]; then
|
|
620
|
-
PEER_ID="$SESSION_PEER_ID"
|
|
621
|
-
fi
|
|
622
|
-
export HCC_PEER="$PEER_ID"
|
|
623
|
-
"$HCC_BIN" web >/dev/null 2>&1 || true
|
|
624
|
-
"$HCC_BIN" peer attach "$PEER_ID" --kind "${tool.kind}" --pane "$CURRENT_HCC_PANE" --cwd "$PWD" --force >/dev/null 2>&1 || true
|
|
625
|
-
exec "$REAL_BIN" "$@"
|
|
626
|
-
fi
|
|
627
|
-
|
|
628
|
-
set +e
|
|
629
|
-
# Wrap in shell so pane_pid stays as bash; Ctrl+C drops back to bash
|
|
630
|
-
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)
|
|
631
|
-
START_STATUS=$?
|
|
632
|
-
set -e
|
|
633
|
-
if [ "$START_STATUS" -ne 0 ]; then
|
|
634
|
-
printf '%s\\n' "$START_OUT" >&2
|
|
635
|
-
exit "$START_STATUS"
|
|
636
|
-
fi
|
|
637
|
-
|
|
638
|
-
PANE=$(printf '%s\\n' "$START_OUT" | sed -n 's/.*pane=\\(%[0-9][0-9]*\\).*/\\1/p' | tail -n 1)
|
|
639
|
-
if [ -z "$PANE" ]; then
|
|
640
|
-
printf '%s\\n' "$START_OUT"
|
|
641
|
-
exit 0
|
|
642
|
-
fi
|
|
643
|
-
|
|
644
|
-
if [ "$HCC_SHIM_NO_ATTACH" = "1" ] || [ ! -t 0 ]; then
|
|
645
|
-
printf '%s\\n' "$START_OUT"
|
|
646
|
-
exit 0
|
|
647
|
-
fi
|
|
648
|
-
|
|
649
|
-
SESSION=$(tmux display-message -p -t "$PANE" '#S' 2>/dev/null || true)
|
|
650
|
-
if [ -z "$SESSION" ]; then
|
|
651
|
-
printf '%s\\n' "$START_OUT"
|
|
652
|
-
exit 0
|
|
653
|
-
fi
|
|
654
|
-
|
|
655
|
-
if [ -n "$TMUX" ]; then
|
|
656
|
-
exec tmux switch-client -t "$SESSION"
|
|
657
|
-
fi
|
|
658
|
-
|
|
659
|
-
exec tmux attach-session -t "$SESSION"
|
|
660
|
-
`;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
205
|
// ─── Codex hooks ─────────────────────────────────────────────────────────────
|
|
665
206
|
|
|
666
207
|
const CODEX_HOOKS_PATH = path.join(os.homedir(), '.codex', 'hooks.json');
|
|
@@ -752,15 +293,3 @@ function mergeCodexHookEntry(existing, command, matcher) {
|
|
|
752
293
|
function fsExists(p) {
|
|
753
294
|
try { fs.accessSync(p); return true; } catch { return false; }
|
|
754
295
|
}
|
|
755
|
-
|
|
756
|
-
function readJsonSafe(filePath) {
|
|
757
|
-
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
|
|
758
|
-
catch { return null; }
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function writeJsonSafe(filePath, data) {
|
|
762
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
763
|
-
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
764
|
-
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n');
|
|
765
|
-
fs.renameSync(tmpPath, filePath);
|
|
766
|
-
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
|
|
5
|
+
export function pathEntryLine(shellType) {
|
|
6
|
+
if (shellType === 'fish') return 'set -gx PATH $HOME/.hcc-shims $PATH # hello-cc shims';
|
|
7
|
+
return '_hcc_shims="$HOME/.hcc-shims"; PATH="${PATH//:${_hcc_shims}:/:}"; PATH="${PATH#${_hcc_shims}:}"; PATH="${PATH%:${_hcc_shims}}"; export PATH="${_hcc_shims}:$PATH"; unset _hcc_shims # hello-cc shims';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function removePathEntryLines(content) {
|
|
11
|
+
return String(content || '')
|
|
12
|
+
.split('\n')
|
|
13
|
+
.filter(line => !line.includes('.hcc-shims'))
|
|
14
|
+
.join('\n')
|
|
15
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
16
|
+
.replace(/\s+$/, '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function insertPathEntry(content, shellType) {
|
|
20
|
+
const line = pathEntryLine(shellType);
|
|
21
|
+
const cleaned = removePathEntryLines(content);
|
|
22
|
+
if (shellType === 'fish') return `${cleaned}${cleaned ? '\n' : ''}${line}\n`;
|
|
23
|
+
|
|
24
|
+
const lines = cleaned ? cleaned.split('\n') : [];
|
|
25
|
+
let insertAt = 0;
|
|
26
|
+
if (lines[0]?.startsWith('#!')) insertAt = 1;
|
|
27
|
+
for (let i = insertAt; i < lines.length; i += 1) {
|
|
28
|
+
const text = lines[i].trim();
|
|
29
|
+
if (!text || text.startsWith('#')) continue;
|
|
30
|
+
insertAt = i;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
lines.splice(insertAt, 0, line);
|
|
34
|
+
const withEarlyEntry = lines.join('\n').replace(/\s+$/, '');
|
|
35
|
+
return `${withEarlyEntry}${withEarlyEntry ? '\n' : ''}${line}\n`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function detectShellRc() {
|
|
39
|
+
const shell = process.env.SHELL || '';
|
|
40
|
+
if (shell.includes('zsh')) return { path: path.join(os.homedir(), '.zshrc'), type: 'zsh' };
|
|
41
|
+
if (shell.includes('fish')) return { path: path.join(os.homedir(), '.config', 'fish', 'config.fish'), type: 'fish' };
|
|
42
|
+
return { path: path.join(os.homedir(), '.bashrc'), type: 'bash' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function installPathEntry() {
|
|
46
|
+
const { path: rcFile, type: shellType } = detectShellRc();
|
|
47
|
+
let content = '';
|
|
48
|
+
try { content = fs.readFileSync(rcFile, 'utf8'); } catch {}
|
|
49
|
+
|
|
50
|
+
const next = insertPathEntry(content, shellType);
|
|
51
|
+
const alreadyPresent = next === (content.endsWith('\n') ? content : `${content}\n`);
|
|
52
|
+
if (!alreadyPresent) fs.writeFileSync(rcFile, next);
|
|
53
|
+
return { rcFile, shellType, alreadyPresent };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function uninstallPathEntry() {
|
|
57
|
+
const { path: rcFile } = detectShellRc();
|
|
58
|
+
try {
|
|
59
|
+
const content = fs.readFileSync(rcFile, 'utf8');
|
|
60
|
+
const filtered = content
|
|
61
|
+
.split('\n')
|
|
62
|
+
.filter(line => !line.includes('.hcc-shims'))
|
|
63
|
+
.join('\n');
|
|
64
|
+
fs.writeFileSync(rcFile, filtered);
|
|
65
|
+
return { rcFile };
|
|
66
|
+
} catch {
|
|
67
|
+
return { rcFile, error: 'could not modify shell rc' };
|
|
68
|
+
}
|
|
69
|
+
}
|