feat(dashboard): full tool backend configuration in the GUI (#40418)
Replicate the `hermes tools` configurator in the dashboard Skills →
Toolsets view. Each toolset now opens a config drawer that covers the
full lifecycle the CLI offers: enable/disable, pick a provider/backend,
enter and save API keys, and run a provider's post-setup install hook
with a live log tail.
The toolset view was previously read+toggle only — the provider matrix
and key-status endpoints existed but the page never called them, and
there was no way to save a key or run a backend install (npm/pip/binary)
from the browser.
Backend:
- New CLI subcommand `hermes tools post-setup <KEY>` — non-interactive,
scriptable target that runs a provider's install hook (agent_browser,
camofox, cua_driver, kittentts, piper, ddgs, spotify, langfuse,
xai_grok). Validated against valid_post_setup_keys() so an arbitrary
key can't drive _run_post_setup.
- PUT /api/tools/toolsets/{name}/env — save API keys to ~/.hermes/.env
via save_env_value (same store the CLI writes), validated against the
toolset category's env-var allowlist; blank values skipped.
- POST /api/tools/toolsets/{name}/post-setup — spawn-action that runs
`hermes tools post-setup <key>`; frontend tails the log via the
existing /api/actions/tools-post-setup/status. Registered in
_ACTION_LOG_FILES.
Frontend:
- New ToolsetConfigDrawer component (provider radios, password key
inputs with saved-state, get-a-key links, Run-setup + live install
log). Toolset cards get a Configure button + the drawer also exposes
the enable toggle.
- api.ts: toggleToolset, getToolsetConfig, selectToolsetProvider,
saveToolsetEnv, runToolsetPostSetup + ToolsetConfig/Provider/EnvVar/
EnvResult types.
Validation: 56 admin-endpoint tests pass (10 new: env save w/ CLI
parity + allowlist reject + blank-skip, post-setup spawn validation,
auth gate); 232 web_server tests pass; web npm run build + eslint clean;
HTTP E2E exercises save-key (CLI reads it back) and spawn+poll
post-setup to exit 0.
This commit is contained in:
@@ -513,6 +513,46 @@ export const api = {
|
||||
body: JSON.stringify({ name, enabled }),
|
||||
}),
|
||||
getToolsets: () => fetchJSON<ToolsetInfo[]>("/api/tools/toolsets"),
|
||||
toggleToolset: (name: string, enabled: boolean) =>
|
||||
fetchJSON<{ ok: boolean; name: string; enabled: boolean }>(
|
||||
`/api/tools/toolsets/${encodeURIComponent(name)}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ enabled }),
|
||||
},
|
||||
),
|
||||
getToolsetConfig: (name: string) =>
|
||||
fetchJSON<ToolsetConfig>(
|
||||
`/api/tools/toolsets/${encodeURIComponent(name)}/config`,
|
||||
),
|
||||
selectToolsetProvider: (name: string, provider: string) =>
|
||||
fetchJSON<{ ok: boolean; name: string; provider: string }>(
|
||||
`/api/tools/toolsets/${encodeURIComponent(name)}/provider`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ provider }),
|
||||
},
|
||||
),
|
||||
saveToolsetEnv: (name: string, env: Record<string, string>) =>
|
||||
fetchJSON<ToolsetEnvResult>(
|
||||
`/api/tools/toolsets/${encodeURIComponent(name)}/env`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ env }),
|
||||
},
|
||||
),
|
||||
runToolsetPostSetup: (name: string, key: string) =>
|
||||
fetchJSON<ActionResponse & { key: string }>(
|
||||
`/api/tools/toolsets/${encodeURIComponent(name)}/post-setup`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ key }),
|
||||
},
|
||||
),
|
||||
|
||||
// Session search (FTS5)
|
||||
searchSessions: (q: string) =>
|
||||
@@ -1619,6 +1659,39 @@ export interface ToolsetInfo {
|
||||
tools: string[];
|
||||
}
|
||||
|
||||
export interface ToolsetProviderEnvVar {
|
||||
key: string;
|
||||
prompt: string;
|
||||
url: string | null;
|
||||
default: string | null;
|
||||
is_set: boolean;
|
||||
}
|
||||
|
||||
export interface ToolsetProvider {
|
||||
name: string;
|
||||
badge: string;
|
||||
tag: string;
|
||||
env_vars: ToolsetProviderEnvVar[];
|
||||
post_setup: string | null;
|
||||
requires_nous_auth: boolean;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export interface ToolsetConfig {
|
||||
name: string;
|
||||
has_category: boolean;
|
||||
providers: ToolsetProvider[];
|
||||
active_provider: string | null;
|
||||
}
|
||||
|
||||
export interface ToolsetEnvResult {
|
||||
ok: boolean;
|
||||
name: string;
|
||||
saved: string[];
|
||||
skipped: string[];
|
||||
is_set: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export interface SessionSearchResult {
|
||||
session_id: string;
|
||||
snippet: string;
|
||||
|
||||
Reference in New Issue
Block a user