#!/usr/bin/env node /** * PreToolUse hook: the remote approval gate. * * Asks the daemon what to do with this tool call. The daemon holds the request open while * your phone decides, then answers allow/deny. Every failure path here is fail-open — * a daemon that is down, slow, or confused must not be able to block your agent. * * Denying is the only outcome that changes behaviour, and approving only lets through a * call Grok was already about to make. This hook can never introduce a new command. */ import { baseUrl, envEnvelope, postJson, readConfig, readStdinJson } from "./glance-lib.mjs"; function allow() { process.exit(0); } function deny(reason) { // Belt and braces: the documented deny signals are a JSON decision on stdout *and* // exit code 2. We emit both so a change in precedence cannot silently allow. process.stdout.write( JSON.stringify({ decision: "deny", reason: reason || "Denied from grok-glance" }), ); process.exit(2); } const payload = envEnvelope(await readStdinJson()); const cfg = readConfig(); // Stay inside the hook timeout declared in hooks/hooks.json (125s). const waitMs = Math.min(115_000, Number(cfg.approval?.timeoutMs ?? 90_000) + 15_000); try { const { data } = await postJson(`${baseUrl(cfg)}/hook/approve`, payload, waitMs); if (data && data.decision === "deny") deny(data.reason); allow(); } catch { allow(); }