app
Grok Bot
Public Made by Adomby adom
Grok Bridge lets Adom talk to Grok Bot. Adom's AI environment is for electrical engineers: any provider, any desktop power tool, one place.
main
John Lauer
v1.3.15 (insiders): Second route and wake for Grok's agent computer: grok_chat_send types into the grok-bridge chat through UIA when the webhook is refused, grok_wake keeps the paused agent computer running for N minutes, and Grok's rate-limit refusal reports its limit and count.
b702936
5d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
#!/usr/bin/env python3
"""Adom Grok Bot Bridge Server — localhost HTTP server.
Adom Bridge (ab) spawns this on the Windows PC. There is no public Grok HTTP API;
we talk TO Grok Bot the way kicad talks to KiCad: detect the host app,
drive it via ab desktop_* callbacks. Never modifies ab-core.
Usage:
python server.py
python server.py --port 8769
"""
from __future__ import annotations
import json
import time
import os
import sys
import traceback
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
import caller_identity
from grok_detect import detect_host, status_chip
from handlers.app import (
handle_close,
handle_login,
handle_logout,
handle_plugin_install,
handle_record_start,
handle_record_status,
handle_record_stop,
handle_resize,
handle_settings,
)
from handlers.bots import handle_bot_create, handle_bot_list, handle_channel_create
from handlers.describe import handle_describe
from handlers.install import handle_install, handle_install_status
from handlers.readiness import handle_prewarm, handle_readiness, handle_status, handle_uninstall
from handlers.send import handle_send
from handlers.webhook import handle_computer_screenshot, handle_run_status, handle_webhook_bind, handle_webhook_ensure, handle_webhook_fire, handle_webhook_status
from handlers.chat import handle_chat_read, handle_chat_status
from handlers import activity
from handlers.compose import handle_chat_send, handle_wake
from handlers.windows import handle_list_windows, handle_open, handle_screenshot
DEFAULT_PORT = 8769
SERVICE = "grok-bridge"
REPORT_ISSUES = "adom-wiki issue create adom/grok-bridge"
DEFAULT_HINT = (
"Call grok_describe for the verb catalog; grok_readiness for host-app state. "
"CLI: adom-bridge grok_* . Always --ai-thread. Background by default. "
"Host app is Grok Bot.exe. "
f"Bugs: {REPORT_ISSUES}"
)
KNOWN_VERBS = [
"grok_readiness",
"grok_prewarm",
"grok_describe",
"grok_status",
"grok_open",
"grok_screenshot",
"grok_list_windows",
"grok_chat_status",
"grok_chat_read",
"grok_send",
"grok_uninstall",
"grok_close",
"grok_resize",
"grok_record_start",
"grok_record_stop",
"grok_record_status",
"grok_login",
"grok_logout",
"grok_plugin_install",
"grok_webhook_status",
"grok_webhook_bind",
"grok_webhook_ensure",
"grok_webhook_fire",
"grok_computer_screenshot",
"grok_install",
"grok_install_status",
"grok_settings",
"grok_run_status",
"grok_bot_create",
"grok_channel_create",
"grok_bot_list",
]
def _read_bridge_version() -> str:
try:
p = Path(__file__).resolve().parent / "BRIDGE_VERSION"
return p.read_text(encoding="utf-8").strip()
except OSError:
return ""
BRIDGE_VERSION = _read_bridge_version()
def handle_activity(args: dict) -> dict:
"""Who called what, when, and what came back (from ~/.adom/grok-bridge-activity.jsonl)."""
try:
minutes = max(1, min(24 * 60, int(args.get("minutes") or 60)))
except (TypeError, ValueError):
minutes = 60
try:
limit = max(0, min(500, int(args.get("limit") or 40)))
except (TypeError, ValueError):
limit = 40
# Filter is "thread", never "caller": ab injects args["caller"] = {aiThread, ...} into every
# relayed call, and filtering on that dict matched nothing (1.3.13 always reported 0 calls).
thread = args.get("thread")
out = activity.read_recent(minutes, limit, str(args.get("verb") or ""),
thread if isinstance(thread, str) else "")
out.update({"success": True, "logPath": activity.LOG_PATH, "webhookBudget": activity.budget_state()})
w = out["webhook"]
out["_hint"] = (
f"Last {minutes} min on this PC: {out['calls']} calls; webhook fires {w['fires']} "
f"(sent {w['sent']}, throttled by Grok {w['throttledByGrok']}, refused by the local budget "
f"{w['refusedByBudget']}). byCallerVerb names the threads. Filter with verb / thread; "
"limit sets how many recent rows come back."
)
return out
COMMAND_HANDLERS = {
"readiness": handle_readiness,
"prewarm": handle_prewarm,
"describe": handle_describe,
"status": handle_status,
"open": handle_open,
"screenshot": handle_screenshot,
"list_windows": handle_list_windows,
"chat_status": handle_chat_status,
"chat_read": handle_chat_read,
"send": handle_send,
"uninstall": handle_uninstall,
"close": handle_close,
"resize": handle_resize,
"record_start": handle_record_start,
"record_stop": handle_record_stop,
"record_status": handle_record_status,
"login": handle_login,
"logout": handle_logout,
"plugin_install": handle_plugin_install,
"webhook_status": handle_webhook_status,
"webhook_bind": handle_webhook_bind,
"webhook_ensure": handle_webhook_ensure,
"webhook_fire": handle_webhook_fire,
"computer_screenshot": handle_computer_screenshot,
"install": handle_install,
"install_status": handle_install_status,
"settings": handle_settings,
"run_status": handle_run_status,
"bot_create": handle_bot_create,
"channel_create": handle_channel_create,
"bot_list": handle_bot_list,
"activity": handle_activity,
"chat_send": handle_chat_send,
"wake": handle_wake,
}
def _strip_prefix(command: str) -> str:
"""Accept grok_open, grok_list_windows, or bare open / list_windows."""
c = (command or "").strip()
if c.startswith("grok_"):
return c[len("grok_"):]
return c
def dispatch_command(command: str, args: dict) -> dict:
if not isinstance(args, dict):
args = {}
internal = _strip_prefix(command)
handler = COMMAND_HANDLERS.get(internal)
if handler is None:
return {
"success": False,
"errorCode": "unknown_command",
"error": f"Unknown command: {command}",
"_hint": (
f"Unknown command {command!r}. Call grok_describe for the catalog. "
f"Known verbs: {', '.join(KNOWN_VERBS)}."
),
"_next": "grok_describe {}",
}
return handler(args)
def _enrich(data: dict, ok_status: bool) -> dict:
"""Every JSON body gets _hint. Failures also get _next + errorCode + _reportIssues."""
if not isinstance(data, dict):
data = {"success": False, "error": str(data)}
failed = (data.get("success") is False) or (not ok_status)
data.setdefault("_hint", DEFAULT_HINT)
if failed:
data.setdefault("errorCode", data.get("errorCode") or "error")
data.setdefault("_next", "Call grok_describe {} then grok_readiness {}. Surface _hint verbatim.")
data.setdefault("_reportIssues", REPORT_ISSUES)
return data
class GrokBridgeHandler(BaseHTTPRequestHandler):
"""POST {command, args} + GET /status + /health. Bind loopback only (see main)."""
def sendJSON(self, status: int, data: dict) -> None:
body = _enrich(data if isinstance(data, dict) else {"success": False, "error": str(data)},
ok_status=(status < 400))
payload = json.dumps(body).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(payload)))
self.end_headers()
self.wfile.write(payload)
def _status_payload(self) -> dict:
info = detect_host()
chip = status_chip(info)
return {
"status": "ok",
"led": chip["led"],
"summary": chip["summary"],
"tooltip": chip["tooltip"],
"service": SERVICE,
"version": BRIDGE_VERSION,
"bridgeVersion": BRIDGE_VERSION,
"hostAppInstalled": info.get("hostAppInstalled"),
"hostAppRunning": info.get("running"),
"paths": info.get("paths") or [],
"windowsHint": info.get("windowsHint"),
"_hint": chip["tooltip"],
}
def do_GET(self):
path = self.path.split("?", 1)[0]
if path in ("/status", "/health"):
self.sendJSON(200, self._status_payload())
elif path in ("/commands", "/verbs"):
names = list(KNOWN_VERBS)
self.sendJSON(200, {
"success": True,
"prefix": "grok_",
"commands": names,
"verbs": names,
"commandNames": names,
"_hint": (
"Grok verbs are grok_* (verbPrefixes). adom-bridge commands is JSON — "
"grep grok_ against this list or grok_describe.verbs[].name, not unprefixed internals."
),
})
else:
self.sendJSON(404, {
"success": False,
"errorCode": "not_found",
"error": "Not found",
"_hint": "Health is GET /status or GET /health. Verbs are POST /command (or POST /) with {command, args}.",
"_next": "GET /status",
})
def do_POST(self):
path = self.path.split("?", 1)[0]
if path not in ("/command", "/", "/command/"):
self.sendJSON(404, {
"success": False,
"errorCode": "not_found",
"error": "Not found",
"_hint": "POST /command or POST / with body {command, args}. GET /status for the chip.",
"_next": "POST /command {\"command\":\"grok_describe\",\"args\":{}}",
})
return
length = int(self.headers.get("Content-Length", 0) or 0)
raw = self.rfile.read(length) if length else b""
try:
request = json.loads(raw.decode("utf-8") or "{}")
except json.JSONDecodeError as e:
self.sendJSON(400, {
"success": False,
"errorCode": "invalid_json",
"error": f"Invalid JSON: {e}",
"_hint": "Body must be {\"command\":\"grok_describe\",\"args\":{}}.",
})
return
if not isinstance(request, dict):
self.sendJSON(400, {
"success": False,
"errorCode": "invalid_json",
"error": "JSON body must be an object.",
})
return
command = request.get("command") or ""
args = request.get("args") if isinstance(request.get("args"), dict) else {}
caller_identity.set_from_request(self.headers, args)
full = command if str(command).startswith("grok_") else f"grok_{_strip_prefix(str(command))}"
print(f"{activity.now_iso()} [verb] {full}{caller_identity.log_suffix()}", flush=True)
t0 = time.monotonic()
result = None
try:
result = dispatch_command(str(command), args)
self.sendJSON(200, result)
except Exception as e:
traceback.print_exc()
result = {
"success": False,
"errorCode": "internal_error",
"error": f"Internal error: {e}",
"_hint": f"Bridge bug, not your call. {REPORT_ISSUES} with the verb and args.",
}
self.sendJSON(500, result)
finally:
activity.log_call(full, caller_identity.thread_name(), int((time.monotonic() - t0) * 1000), result)
caller_identity.clear()
def log_message(self, fmt, *args):
print(f"{activity.now_iso()} [grok-bridge] {args[0]} {args[1]} {args[2]}", flush=True)
def main() -> None:
try:
sys.stdout.reconfigure(line_buffering=True)
sys.stderr.reconfigure(line_buffering=True)
except Exception:
pass
port = DEFAULT_PORT
if "--port" in sys.argv:
idx = sys.argv.index("--port")
if idx + 1 < len(sys.argv):
port = int(sys.argv[idx + 1])
# Bind loopback ONLY. ADOM_BIND_HOST is always 127.0.0.1 when ab spawns us
# (Bridge SDK Runtime contract, v1.9.63+) — never 0.0.0.0, no firewall prompt.
bind_host = os.environ.get("ADOM_BIND_HOST", "127.0.0.1")
if bind_host not in ("127.0.0.1", "::1", "localhost"):
print(f"[grok-bridge] refusing non-loopback ADOM_BIND_HOST={bind_host!r}; using 127.0.0.1",
flush=True)
bind_host = "127.0.0.1"
class _Server(ThreadingHTTPServer):
allow_reuse_address = False
daemon_threads = True
print(f"[grok-bridge] starting v{BRIDGE_VERSION} on {bind_host}:{port} (pid {os.getpid()})",
flush=True)
try:
server = _Server((bind_host, port), GrokBridgeHandler)
except OSError as e:
print(f"[grok-bridge] Port {port} already in use ({e}). "
"Another instance owns it — exiting cleanly (single-instance guard).",
flush=True)
return
info = detect_host()
print(f"[grok-bridge] detect hostAppInstalled={info.get('hostAppInstalled')} "
f"running={info.get('running')} paths={info.get('paths')}",
flush=True)
print(f"[grok-bridge] Listening on http://{bind_host}:{port} — GET /status POST /command",
flush=True)
try:
server.serve_forever()
except KeyboardInterrupt:
print("\n[grok-bridge] Shutting down.", flush=True)
server.server_close()
if __name__ == "__main__":
main()