@termhub/agent 0.4.0 → 0.4.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +37 -4
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -611,7 +611,7 @@ function claudeConfigDirs(accountDirs) {
611
611
  function expandHome(dir, home) {
612
612
  return dir.startsWith("~/") ? `${home}/${dir.slice(2)}` : dir;
613
613
  }
614
- var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "Notification", "Stop", "SessionEnd"];
614
+ var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "Notification", "Stop", "SessionEnd"];
615
615
  var HOOK_SCRIPT = `#!/bin/sh
616
616
  # termhub monitor hook \u2014 installed by termhub; forwards Claude Code / Codex hook events to
617
617
  # termhub tagged with the tmux session, so the app knows which tab is waiting for you.
@@ -626,6 +626,34 @@ SESSION=$(tmux display-message -p -t "$TMUX_PANE" '#{session_name}' 2>/dev/null)
626
626
  [ -n "$SESSION" ] || exit 0
627
627
  if [ "$TOOL" = codex ]; then EVENT="$2"; else EVENT=$(cat 2>/dev/null); fi
628
628
  [ -n "$EVENT" ] || EVENT='{}'
629
+ # Tool calls: only the tool's name travels (never its input), and only when it changed since the
630
+ # last one for this session \u2014 twenty edits in a row are one request. The marker is per tmux
631
+ # session, under TMPDIR, with the session name reduced to filename-safe characters.
632
+ MARK="\${TMPDIR:-/tmp}/termhub-hook-$(printf '%s' "$SESSION" | tr -c 'A-Za-z0-9_-' '_')"
633
+ case "$EVENT" in
634
+ *'"hook_event_name":"PreToolUse"'*|*'"hook_event_name": "PreToolUse"'*)
635
+ # The event's own tool name is the FIRST "tool_name" of the payload (Claude Code serialises it
636
+ # before tool_input), so the shortest prefix is cut \u2014 a "tool_name" nested in a tool's input
637
+ # must not win. Only letters, digits, "_", "." and "-" are posted (a bare Claude Code tool name,
638
+ # or an MCP tool name such as mcp__claude-in-chrome__click): anything else (a number, a name with
639
+ # a quote or a backslash) is dropped rather than sent \u2014 those are the only characters the
640
+ # hand-built JSON body below cannot survive as-is.
641
+ REST=\${EVENT#*'"tool_name"'}
642
+ [ "$REST" != "$EVENT" ] || exit 0
643
+ REST=\${REST#*'"'}
644
+ NAME=\${REST%%'"'*}
645
+ case "$NAME" in '' | *[!A-Za-z0-9_.-]*) exit 0 ;; esac
646
+ [ "$(cat "$MARK" 2>/dev/null)" = "$NAME" ] && exit 0
647
+ printf '%s' "$NAME" 2>/dev/null > "$MARK"
648
+ EVENT=$(printf '{"hook_event_name":"PreToolUse","tool_name":"%s"}' "$NAME")
649
+ ;;
650
+ # A new turn starts fresh, and so does an answered notification: a permission prompt takes the tab
651
+ # out of working, and the tool the person approves is the same one that set the marker, so without
652
+ # this reset the retry is suppressed and nothing says the tab is working again.
653
+ *'"hook_event_name":"SessionStart"'*|*'"hook_event_name": "SessionStart"'*|*'"hook_event_name":"UserPromptSubmit"'*|*'"hook_event_name": "UserPromptSubmit"'*|*'"hook_event_name":"Notification"'*|*'"hook_event_name": "Notification"'*)
654
+ rm -f "$MARK"
655
+ ;;
656
+ esac
629
657
  { printf '{"tool":"%s","session":"%s","event":' "$TOOL" "$SESSION"; printf '%s' "$EVENT"; printf '}'; } |
630
658
  curl -s -m 5 -o /dev/null -X POST "$TERMHUB_HOOK_URL" \\
631
659
  -H "authorization: Bearer $TERMHUB_HOOK_TOKEN" -H 'content-type: application/json' --data-binary @- >/dev/null 2>&1 &
@@ -649,7 +677,10 @@ function mergeClaudeSettings(current, scriptPath) {
649
677
  for (const event of CLAUDE_HOOK_EVENTS) {
650
678
  const list3 = Array.isArray(hooks[event]) ? hooks[event] : [];
651
679
  const others = list3.filter((e) => !isOurs(e));
652
- others.push({ hooks: [{ type: "command", command: `${scriptPath} claude`, timeout: 10 }] });
680
+ const entry = { hooks: [{ type: "command", command: `${scriptPath} claude`, timeout: 10 }] };
681
+ if (event === "PreToolUse")
682
+ entry.matcher = "*";
683
+ others.push(entry);
653
684
  hooks[event] = others;
654
685
  }
655
686
  settings.hooks = hooks;
@@ -925,7 +956,9 @@ async function install(params, home = os3.homedir()) {
925
956
  async function heal(home = os3.homedir()) {
926
957
  const scriptPath = path3.join(home, HOOK_SCRIPT_REL);
927
958
  const env = await readOrEmpty2(path3.join(home, HOOK_ENV_REL));
928
- if (!env.trim() || !await readOrEmpty2(scriptPath)) return [];
959
+ const script = await readOrEmpty2(scriptPath);
960
+ if (!env.trim() || !script) return [];
961
+ if (script !== HOOK_SCRIPT) await writeAtomic(scriptPath, HOOK_SCRIPT, 493);
929
962
  const healed = [];
930
963
  for (const dir of await discoverClaudeDirs(home)) {
931
964
  const file = path3.join(expandHome(dir, home), "settings.json");
@@ -1833,7 +1866,7 @@ async function status3() {
1833
1866
  }
1834
1867
 
1835
1868
  // src/version.ts
1836
- var AGENT_VERSION = "0.4.0";
1869
+ var AGENT_VERSION = "0.4.1";
1837
1870
 
1838
1871
  // src/rpc/update.ts
1839
1872
  var PACKAGE = "@termhub/agent";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@termhub/agent",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Agente do termhub: conecta esta máquina ao servidor por WebSocket de saída (sem SSH) e expõe os terminais tmux no navegador.",
5
5
  "license": "MIT",
6
6
  "private": false,