From 3271262ed470aa211bd4b4e05aa2edda90106734 Mon Sep 17 00:00:00 2001 From: johnnyfish Date: Sun, 17 May 2026 19:48:38 +0300 Subject: [PATCH] fix: auto-install gateway detection hook on agent launch --- cmd/onecli/hook_gateway_detect.sh | 6 ++ cmd/onecli/run.go | 83 +++++++++++++++++++++++++++- cmd/onecli/skill_gateway_fallback.md | 11 ++-- 3 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 cmd/onecli/hook_gateway_detect.sh diff --git a/cmd/onecli/hook_gateway_detect.sh b/cmd/onecli/hook_gateway_detect.sh new file mode 100644 index 0000000..7dad097 --- /dev/null +++ b/cmd/onecli/hook_gateway_detect.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# OneCLI gateway detection hook for Claude Code. +# Injected by `onecli run` — outputs context only when the gateway proxy is active. +if echo "$HTTPS_PROXY" | grep -q "aoc_"; then + echo "OneCLI gateway is active. Load /onecli-gateway for any external service access (email, calendar, GitHub, Slack, Stripe, databases, APIs). Never use browser automation or MCP auth flows." +fi diff --git a/cmd/onecli/run.go b/cmd/onecli/run.go index 239a509..cf12a21 100644 --- a/cmd/onecli/run.go +++ b/cmd/onecli/run.go @@ -22,6 +22,9 @@ import ( //go:embed skill_gateway_fallback.md var gatewaySkillFallback string +//go:embed hook_gateway_detect.sh +var gatewayDetectHook string + // RunCmd is `onecli run -- [args...]`. type RunCmd struct { Project string `optional:"" short:"p" help:"Project slug."` @@ -104,7 +107,7 @@ func (c *RunCmd) Run(out *output.Writer) error { // Build child environment. env := buildChildEnv(os.Environ(), cfg.Env, caPath) - // Install skill for known agents (silently updates stale files). + // Install skill and hook for known agents (silently updates stale files). // Fetch the latest skill from the API; fall back to the embedded copy. if name, dir, cfgDir, ok := agentSkillDir(c.Args[0]); ok { skillContent := gatewaySkillFallback @@ -112,6 +115,7 @@ func (c *RunCmd) Run(out *output.Writer) error { skillContent = fetched } maybeInstallGatewaySkill(out, name, dir, skillContent) + maybeInstallGatewayHook(out, name, dir) // Electron-based agents (e.g. Cursor) ignore embedded user:pass in // HTTPS_PROXY and show a native auth dialog. Inject proxy credentials @@ -362,6 +366,83 @@ func maybeInstallGatewaySkill(out *output.Writer, agentName, baseDir, content st out.Stderr(fmt.Sprintf("onecli: installed gateway skill for %s.", agentName)) } +// maybeInstallGatewayHook installs the gateway detection hook script and +// registers it in the agent's settings.json so the agent knows the gateway +// is active without needing to run any visible checks. +func maybeInstallGatewayHook(out *output.Writer, agentName, baseDir string) { + home, err := os.UserHomeDir() + if err != nil { + return + } + + // Write the hook script. + hookPath := filepath.Join(home, baseDir, "hooks", "UserPromptSubmit", "onecli_gateway_detect.sh") + existing, err := os.ReadFile(hookPath) + if err != nil || !bytes.Equal(existing, []byte(gatewayDetectHook)) { + if err := os.MkdirAll(filepath.Dir(hookPath), 0o750); err != nil { + return + } + if err := os.WriteFile(hookPath, []byte(gatewayDetectHook), 0o755); err != nil { + return + } + } + + // Register in settings.json if not already present. + settingsPath := filepath.Join(home, baseDir, "settings.json") + settings := make(map[string]any) + data, readErr := os.ReadFile(settingsPath) + if readErr == nil && len(data) > 0 { + if err := json.Unmarshal(data, &settings); err != nil { + return + } + } + + hookCommand := "bash " + hookPath + hooks, _ := settings["hooks"].(map[string]any) + if hooks == nil { + hooks = make(map[string]any) + } + + entries, _ := hooks["UserPromptSubmit"].([]any) + + // Check if our hook is already registered. + for _, entry := range entries { + e, _ := entry.(map[string]any) + innerHooks, _ := e["hooks"].([]any) + for _, h := range innerHooks { + hm, _ := h.(map[string]any) + if cmd, _ := hm["command"].(string); cmd == hookCommand { + return + } + } + } + + // Add our hook entry. + entries = append(entries, map[string]any{ + "matcher": "", + "hooks": []any{ + map[string]any{ + "type": "command", + "command": hookCommand, + }, + }, + }) + hooks["UserPromptSubmit"] = entries + settings["hooks"] = hooks + + if err := os.MkdirAll(filepath.Dir(settingsPath), 0o750); err != nil { + return + } + out2, err := json.MarshalIndent(settings, "", " ") + if err != nil { + return + } + if err := os.WriteFile(settingsPath, append(out2, '\n'), 0o600); err != nil { + return + } + out.Stderr(fmt.Sprintf("onecli: installed gateway hook for %s.", agentName)) +} + // injectElectronProxySettings writes http.proxy and http.proxyAuthorization // into a VS Code-style settings.json so Electron-based editors authenticate // with the gateway proxy without Chromium's native auth dialog. Returns the diff --git a/cmd/onecli/skill_gateway_fallback.md b/cmd/onecli/skill_gateway_fallback.md index 2e77d45..7040e79 100644 --- a/cmd/onecli/skill_gateway_fallback.md +++ b/cmd/onecli/skill_gateway_fallback.md @@ -2,12 +2,11 @@ name: onecli-gateway description: >- OneCLI Gateway: transparent HTTPS proxy that injects stored credentials - into outbound calls. You MUST use this skill when the user asks you to - read emails, check calendar, access GitHub repos, create issues, check - Stripe payments, or interact with ANY external service or API. Do NOT - use browser extensions or OAuth CLI tools. Make HTTP requests directly; - the gateway injects credentials automatically. -compatibility: Requires HTTPS_PROXY set in environment (automatic when launched via `onecli run`) + into outbound calls. Only use this skill when prompted by the gateway + detection hook or when the user explicitly invokes /onecli-gateway. + Do NOT auto-load this skill based on user intent alone — the hook + handles detection. +compatibility: Only active when HTTPS_PROXY contains aoc_ (automatic when launched via `onecli run`) metadata: author: onecli version: "0.5.0"