From 5621fc449a7c00f11168328c87e024a0203792c3 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:51:54 -0700 Subject: [PATCH 01/34] =?UTF-8?q?chore:=20rename=20AI=20Gateway=20?= =?UTF-8?q?=E2=86=92=20Vercel=20AI=20Gateway,=20move=20Xiaomi=20to=20#5=20?= =?UTF-8?q?(#9326)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename 'AI Gateway' to 'Vercel AI Gateway' across auth, models, doctor, setup, and tests. - Move Xiaomi MiMo to position #5 in the provider picker. --- hermes_cli/auth.py | 2 +- hermes_cli/doctor.py | 2 +- hermes_cli/models.py | 4 ++-- hermes_cli/setup.py | 2 +- tests/hermes_cli/test_api_key_providers.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 9d1d82e8c..e63a1ebb6 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -224,7 +224,7 @@ PROVIDER_REGISTRY: Dict[str, ProviderConfig] = { ), "ai-gateway": ProviderConfig( id="ai-gateway", - name="AI Gateway", + name="Vercel AI Gateway", auth_type="api_key", inference_base_url="https://ai-gateway.vercel.sh/v1", api_key_env_vars=("AI_GATEWAY_API_KEY",), diff --git a/hermes_cli/doctor.py b/hermes_cli/doctor.py index 19c332b35..34a57aad2 100644 --- a/hermes_cli/doctor.py +++ b/hermes_cli/doctor.py @@ -729,7 +729,7 @@ def run_doctor(args): # MiniMax: the /anthropic endpoint doesn't support /models, but the /v1 endpoint does. ("MiniMax", ("MINIMAX_API_KEY",), "https://api.minimax.io/v1/models", "MINIMAX_BASE_URL", True), ("MiniMax (China)", ("MINIMAX_CN_API_KEY",), "https://api.minimaxi.com/v1/models", "MINIMAX_CN_BASE_URL", True), - ("AI Gateway", ("AI_GATEWAY_API_KEY",), "https://ai-gateway.vercel.sh/v1/models", "AI_GATEWAY_BASE_URL", True), + ("Vercel AI Gateway", ("AI_GATEWAY_API_KEY",), "https://ai-gateway.vercel.sh/v1/models", "AI_GATEWAY_BASE_URL", True), ("Kilo Code", ("KILOCODE_API_KEY",), "https://api.kilo.ai/api/gateway/models", "KILOCODE_BASE_URL", True), ("OpenCode Zen", ("OPENCODE_ZEN_API_KEY",), "https://opencode.ai/zen/v1/models", "OPENCODE_ZEN_BASE_URL", True), ("OpenCode Go", ("OPENCODE_GO_API_KEY",), "https://opencode.ai/zen/go/v1/models", "OPENCODE_GO_BASE_URL", True), diff --git a/hermes_cli/models.py b/hermes_cli/models.py index a2a33bdd0..a0e021259 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -512,6 +512,7 @@ CANONICAL_PROVIDERS: list[ProviderEntry] = [ ProviderEntry("openrouter", "OpenRouter", "OpenRouter (100+ models, pay-per-use)"), ProviderEntry("anthropic", "Anthropic", "Anthropic (Claude models — API key or Claude Code)"), ProviderEntry("openai-codex", "OpenAI Codex", "OpenAI Codex"), + ProviderEntry("xiaomi", "Xiaomi MiMo", "Xiaomi MiMo (MiMo-V2 models — pro, omni, flash)"), ProviderEntry("qwen-oauth", "Qwen OAuth (Portal)", "Qwen OAuth (reuses local Qwen CLI login)"), ProviderEntry("copilot", "GitHub Copilot", "GitHub Copilot (uses GITHUB_TOKEN or gh auth token)"), ProviderEntry("copilot-acp", "GitHub Copilot ACP", "GitHub Copilot ACP (spawns `copilot --acp --stdio`)"), @@ -525,12 +526,11 @@ CANONICAL_PROVIDERS: list[ProviderEntry] = [ ProviderEntry("minimax", "MiniMax", "MiniMax (global direct API)"), ProviderEntry("minimax-cn", "MiniMax (China)", "MiniMax China (domestic direct API)"), ProviderEntry("alibaba", "Alibaba Cloud (DashScope)","Alibaba Cloud / DashScope Coding (Qwen + multi-provider)"), - ProviderEntry("xiaomi", "Xiaomi MiMo", "Xiaomi MiMo (MiMo-V2 models — pro, omni, flash)"), ProviderEntry("arcee", "Arcee AI", "Arcee AI (Trinity models — direct API)"), ProviderEntry("kilocode", "Kilo Code", "Kilo Code (Kilo Gateway API)"), ProviderEntry("opencode-zen", "OpenCode Zen", "OpenCode Zen (35+ curated models, pay-as-you-go)"), ProviderEntry("opencode-go", "OpenCode Go", "OpenCode Go (open models, $10/month subscription)"), - ProviderEntry("ai-gateway", "AI Gateway", "AI Gateway (Vercel — 200+ models, pay-per-use)"), + ProviderEntry("ai-gateway", "Vercel AI Gateway", "Vercel AI Gateway (200+ models, pay-per-use)"), ] # Derived dicts — used throughout the codebase diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index aadf369f5..6d0ec0f45 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -776,7 +776,7 @@ def setup_model_provider(config: dict, *, quick: bool = False): "minimax": "MiniMax", "minimax-cn": "MiniMax CN", "anthropic": "Anthropic", - "ai-gateway": "AI Gateway", + "ai-gateway": "Vercel AI Gateway", "custom": "your custom endpoint", } _prov_display = _prov_names.get(selected_provider, selected_provider or "your provider") diff --git a/tests/hermes_cli/test_api_key_providers.py b/tests/hermes_cli/test_api_key_providers.py index 0e1183471..0e8badc6e 100644 --- a/tests/hermes_cli/test_api_key_providers.py +++ b/tests/hermes_cli/test_api_key_providers.py @@ -44,7 +44,7 @@ class TestProviderRegistry: ("kimi-coding", "Kimi / Moonshot", "api_key"), ("minimax", "MiniMax", "api_key"), ("minimax-cn", "MiniMax (China)", "api_key"), - ("ai-gateway", "AI Gateway", "api_key"), + ("ai-gateway", "Vercel AI Gateway", "api_key"), ("kilocode", "Kilo Code", "api_key"), ]) def test_provider_registered(self, provider_id, name, auth_type): From bc3844c90721f9667c5ff547869e7f4b77cf839e Mon Sep 17 00:00:00 2001 From: Austin Pickett Date: Tue, 14 Apr 2026 00:01:18 -0400 Subject: [PATCH 02/34] feat: react-router, sidebar layout, sticky header, dropdown component, remove emojis, rounded corners --- web/package-lock.json | 71 +++ web/package.json | 1 + web/src/App.tsx | 113 ++-- web/src/components/AutoField.tsx | 12 +- web/src/components/Markdown.tsx | 6 +- web/src/components/OAuthProvidersCard.tsx | 2 +- web/src/components/ui/card.tsx | 2 +- web/src/components/ui/select.tsx | 197 ++++++- web/src/index.css | 1 - web/src/main.tsx | 5 +- web/src/pages/AnalyticsPage.tsx | 6 +- web/src/pages/ConfigPage.tsx | 71 ++- web/src/pages/CronPage.tsx | 16 +- web/src/pages/LogsPage.tsx | 276 ++++++---- web/src/pages/SessionsPage.tsx | 4 +- web/src/pages/SkillsPage.tsx | 640 ++++++++++++---------- 16 files changed, 914 insertions(+), 509 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index d9aa7a951..8299c8e49 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -14,6 +14,7 @@ "lucide-react": "^0.577.0", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-router-dom": "^7.14.1", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.1" }, @@ -63,6 +64,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1637,6 +1639,7 @@ "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1647,6 +1650,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1706,6 +1710,7 @@ "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", @@ -1983,6 +1988,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2091,6 +2097,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2208,6 +2215,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2354,6 +2374,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3317,6 +3338,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3377,6 +3399,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3386,6 +3409,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3403,6 +3427,44 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.1.tgz", + "integrity": "sha512-5BCvFskyAAVumqhEKh/iPhLOIkfxcEUz8WqFIARCkMg8hZZzDYX9CtwxXA0e+qT8zAxmMC0x3Ckb9iMONwc5jg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.1.tgz", + "integrity": "sha512-ZkrQuwwhGibjQLqH1eCdyiZyLWglPxzxdl5tgwgKEyCSGC76vmAjleGocRe3J/MLfzMUIKwaFJWpFVJhK3d2xA==", + "license": "MIT", + "dependencies": { + "react-router": "7.14.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3473,6 +3535,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3608,6 +3676,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3693,6 +3762,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -3814,6 +3884,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/web/package.json b/web/package.json index 87dbfdb79..09675d283 100644 --- a/web/package.json +++ b/web/package.json @@ -16,6 +16,7 @@ "lucide-react": "^0.577.0", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-router-dom": "^7.14.1", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.1" }, diff --git a/web/src/App.tsx b/web/src/App.tsx index d52757c20..f2c72d5a6 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { Routes, Route, NavLink, Navigate } from "react-router-dom"; import { Activity, BarChart3, Clock, FileText, KeyRound, MessageSquare, Package, Settings } from "lucide-react"; import StatusPage from "@/pages/StatusPage"; import ConfigPage from "@/pages/ConfigPage"; @@ -10,88 +10,58 @@ import CronPage from "@/pages/CronPage"; import SkillsPage from "@/pages/SkillsPage"; const NAV_ITEMS = [ - { id: "status", label: "Status", icon: Activity }, - { id: "sessions", label: "Sessions", icon: MessageSquare }, - { id: "analytics", label: "Analytics", icon: BarChart3 }, - { id: "logs", label: "Logs", icon: FileText }, - { id: "cron", label: "Cron", icon: Clock }, - { id: "skills", label: "Skills", icon: Package }, - { id: "config", label: "Config", icon: Settings }, - { id: "env", label: "Keys", icon: KeyRound }, + { path: "/", label: "Status", icon: Activity }, + { path: "/sessions", label: "Sessions", icon: MessageSquare }, + { path: "/analytics", label: "Analytics", icon: BarChart3 }, + { path: "/logs", label: "Logs", icon: FileText }, + { path: "/cron", label: "Cron", icon: Clock }, + { path: "/skills", label: "Skills", icon: Package }, + { path: "/config", label: "Config", icon: Settings }, + { path: "/env", label: "Keys", icon: KeyRound }, ] as const; -type PageId = (typeof NAV_ITEMS)[number]["id"]; - -const PAGE_COMPONENTS: Record = { - status: StatusPage, - sessions: SessionsPage, - analytics: AnalyticsPage, - logs: LogsPage, - cron: CronPage, - skills: SkillsPage, - config: ConfigPage, - env: EnvPage, -}; - export default function App() { - const [page, setPage] = useState("status"); - const [animKey, setAnimKey] = useState(0); - const initialRef = useRef(true); - - useEffect(() => { - // Skip the animation key bump on initial mount to avoid re-mounting - // the default page component (which causes duplicate API requests). - if (initialRef.current) { - initialRef.current = false; - return; - } - setAnimKey((k) => k + 1); - }, [page]); - - const PageComponent = PAGE_COMPONENTS[page]; - return (
- {/* Global grain + warm glow (matches landing page) */}
- {/* ---- Header with grid-border nav ---- */} -
+
- {/* Brand — abbreviated on mobile */}
Hermes Agent
- {/* Nav — icons only on mobile, icon+label on sm+ */} - {/* Version badge — hidden on mobile */}
Web UI @@ -100,15 +70,20 @@ export default function App() {
-
- +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
- {/* ---- Footer ---- */}