Skip to content
Development
Hook

Hooks

What remote-workstreams runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
remote-workstreams
34 skills1 command1 hook
Install
> /plugin marketplace add ryan-scheinberg/remote-workstreams
> /plugin install remote-workstreams@remote-workstreams

Ships with remote-workstreams. Installing the plugin gets these hooks.

Where it lives

  • hooks/ask_phone.pyGitHub
    Read the script
    #!/usr/bin/env python3
    """remote-workstreams phone-approval relay: PreToolUse hook JSON on stdin, verdict on stdout.
    
    Two modes:
    - default (pure relay): POST the payload to the local remote-workstreams service and wait
      for the phone's allow/deny.
    - --gate-bash: only relay Bash commands matching the short destructive list below;
      everything else exits silently and instantly.
    
    Any user hook can exec this script. On timeout, non-200, or unreachable service it
    prints nothing and exits 0 — Claude Code's native permission behavior takes over.
    """
    
    import argparse
    import json
    import re
    import sys
    import urllib.request
    
    DESTRUCTIVE = [
        r"\bsudo\b",
        r"\brm\s+-\w*r\w*f",
        r"\brm\s+-\w*f\w*r",
        r"\bgit\s+push\b.*(--force\b|\s-f\b)",
        r"\bgit\s+reset\s+--hard\b",
        r"\bgit\s+branch\s+-D\b",
        r"\bgit\s+clean\b",
        r"\blaunchctl\b",
        r"\bkill\s+-9\b",
    ]
    
    
    def main() -> None:
        parser = argparse.ArgumentParser()
        parser.add_argument("--port", type=int, required=True)
        parser.add_argument("--token", required=True)
        parser.add_argument("--wait", type=float, default=90)
        parser.add_argument("--gate-bash", action="store_true")
        args = parser.parse_args()
        payload = json.load(sys.stdin)
    
        if args.gate_bash:
            if payload.get("tool_name") != "Bash":
                return
            command = str((payload.get("tool_input") or {}).get("command", ""))
            if not any(re.search(pattern, command) for pattern in DESTRUCTIVE):
                return
    
        request = urllib.request.Request(
            f"http://127.0.0.1:{args.port}/approvals",
            data=json.dumps(payload).encode(),
            headers={"Content-Type": "application/json", "X-Workstreams-Token": args.token},
        )
        try:
            with urllib.request.urlopen(request, timeout=args.wait) as response:
                decision = json.load(response).get("decision")
        except Exception:
            return  # silence: Claude Code's native permission prompt takes over
        if decision in ("allow", "deny"):
            output = {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": decision,
                    "permissionDecisionReason": "remote-workstreams phone approval",
                }
            }
            print(json.dumps(output))
    
    
    if __name__ == "__main__":
        main()
    

Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.

Ships withremote-workstreams

Hold a natural spoken conversation with a coding agent — Claude Code or OpenAI's Codex — while it does real agentic work. Your Mac runs everything — the audio pipeline, the sessions, the state.

Get the whole plugin
Stats
3
Stars
1
Forks
Maintained
Maintenance
Python
Language
GPL-3.0
License
2mo ago
Last commit
2mo ago
Created

Repo: ryan-scheinberg/remote-workstreams