Skip to content

Commit 41e5282

Browse files
johnnyfishclaude
andcommitted
feat: add Hermes gateway support with skill, plugin, and Docker config
Add multi-layer gateway integration for Hermes agent: - Enhanced gateway skill (v0.7.0) with auth-error-focused description that triggers on NOT_AUTHENTICATED and credential errors - Skip hook for Hermes (doesn't support Claude Code-style hooks) - SOUL.md injection with gateway recovery override instructions - transform_tool_result plugin to intercept auth errors at runtime and append credential stub creation instructions to tool results - Docker env injection (proxy URLs via docker_env) and CA cert mounting (docker_volumes) for containerized tool execution - Universal gateway skill at ~/.onecli/skills/ for unknown agents with ONECLI_GATEWAY and ONECLI_GATEWAY_SKILL_PATH env vars - agent_framework query param on GetGatewaySkill API for per-agent skill content (hook-based vs broad description) Existing agents (Claude Code, Cursor, Codex, OpenCode) are unaffected: they continue to use hooks and receive hook-based skill frontmatter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d7d6c00 commit 41e5282

7 files changed

Lines changed: 549 additions & 41 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""OneCLI Gateway plugin for Hermes.
2+
3+
Intercepts tool results containing auth errors (NOT_AUTHENTICATED, missing
4+
token, etc.) and appends gateway recovery instructions so the agent creates
5+
credential stubs instead of following manual OAuth/API-key setup flows.
6+
"""
7+
8+
import json
9+
import logging
10+
import os
11+
from typing import Any, Optional
12+
13+
log = logging.getLogger("onecli-gateway")
14+
15+
_AUTH_PATTERNS = [
16+
"not_authenticated",
17+
"no token at",
18+
"missing token",
19+
"oauth_client_disabled",
20+
"token_revoked",
21+
"token_invalid",
22+
"token_corrupt",
23+
"credentials not found",
24+
"authentication required",
25+
"not authenticated",
26+
]
27+
28+
_RECOVERY_HINT = """
29+
30+
=== OneCLI Gateway: Auth Error Recovery ===
31+
32+
This auth error is handled by the OneCLI gateway proxy.
33+
Do NOT follow OAuth setup, API key, or App Password instructions.
34+
35+
STEP 1 — Create a stub credential file at the path mentioned in the error.
36+
37+
For Google tokens (google_token.json):
38+
echo '{"type":"authorized_user","access_token":"onecli-managed","refresh_token":"onecli-managed","client_id":"onecli-managed","client_secret":"onecli-managed","token_uri":"https://oauth2.googleapis.com/token","expiry":"2099-01-01T00:00:00+00:00"}' > /root/.hermes/google_token.json && chmod 600 /root/.hermes/google_token.json
39+
40+
For other credentials:
41+
echo '{"api_key":"onecli-managed"}' > <path> && chmod 600 <path>
42+
43+
STEP 2 — Retry the failed operation. The gateway replaces "onecli-managed"
44+
placeholders with real credentials at the network level.
45+
46+
STEP 3 — If the retry returns app_not_connected, show the user the
47+
connect_url from the error response so they can connect the service.
48+
49+
=== End OneCLI Gateway ==="""
50+
51+
52+
def _is_gateway_active():
53+
proxy = os.environ.get("HTTPS_PROXY", "") + os.environ.get("https_proxy", "")
54+
active = "aoc_" in proxy or os.environ.get("ONECLI_GATEWAY") == "true"
55+
return active
56+
57+
58+
def _result_to_str(result):
59+
"""Convert result to a searchable string regardless of type."""
60+
if isinstance(result, str):
61+
return result
62+
if isinstance(result, dict):
63+
return json.dumps(result, default=str)
64+
return str(result) if result is not None else ""
65+
66+
67+
def _has_auth_error(text):
68+
lower = text.lower()
69+
return any(p in lower for p in _AUTH_PATTERNS)
70+
71+
72+
def _on_transform_tool_result(
73+
tool_name: str = "",
74+
args: Any = None,
75+
result: Any = None,
76+
**_: Any,
77+
) -> Optional[str]:
78+
if not _is_gateway_active():
79+
return None
80+
text = _result_to_str(result)
81+
if not _has_auth_error(text):
82+
return None
83+
log.warning("OneCLI gateway intercepted auth error in %s, injecting recovery hint", tool_name)
84+
if isinstance(result, str):
85+
return result + _RECOVERY_HINT
86+
return text + _RECOVERY_HINT
87+
88+
89+
def register(ctx) -> None:
90+
log.info("OneCLI gateway plugin registered (transform_tool_result)")
91+
ctx.register_hook("transform_tool_result", _on_transform_tool_result)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: onecli-gateway
2+
version: "0.7.0"
3+
description: "Intercepts auth errors and injects OneCLI gateway recovery instructions"
4+
author: "OneCLI"
5+
hooks:
6+
- transform_tool_result

0 commit comments

Comments
 (0)