Spaces:
Paused
Paused
| const BASE = ""; | |
| // Ephemeral session token for protected endpoints (reveal). | |
| // Fetched once on first reveal request and cached in memory. | |
| let _sessionToken: string | null = null; | |
| async function fetchJSON<T>(url: string, init?: RequestInit): Promise<T> { | |
| const res = await fetch(`${BASE}${url}`, init); | |
| if (!res.ok) { | |
| const text = await res.text().catch(() => res.statusText); | |
| throw new Error(`${res.status}: ${text}`); | |
| } | |
| return res.json(); | |
| } | |
| async function getSessionToken(): Promise<string> { | |
| if (_sessionToken) return _sessionToken; | |
| const resp = await fetchJSON<{ token: string }>("/api/auth/session-token"); | |
| _sessionToken = resp.token; | |
| return _sessionToken; | |
| } | |
| export const api = { | |
| getStatus: () => fetchJSON<StatusResponse>("/api/status"), | |
| getSessions: (limit = 20, offset = 0) => | |
| fetchJSON<PaginatedSessions>(`/api/sessions?limit=${limit}&offset=${offset}`), | |
| getSessionMessages: (id: string) => | |
| fetchJSON<SessionMessagesResponse>(`/api/sessions/${encodeURIComponent(id)}/messages`), | |
| deleteSession: (id: string) => | |
| fetchJSON<{ ok: boolean }>(`/api/sessions/${encodeURIComponent(id)}`, { | |
| method: "DELETE", | |
| }), | |
| getLogs: (params: { file?: string; lines?: number; level?: string; component?: string }) => { | |
| const qs = new URLSearchParams(); | |
| if (params.file) qs.set("file", params.file); | |
| if (params.lines) qs.set("lines", String(params.lines)); | |
| if (params.level && params.level !== "ALL") qs.set("level", params.level); | |
| if (params.component && params.component !== "all") qs.set("component", params.component); | |
| return fetchJSON<LogsResponse>(`/api/logs?${qs.toString()}`); | |
| }, | |
| getAnalytics: (days: number) => | |
| fetchJSON<AnalyticsResponse>(`/api/analytics/usage?days=${days}`), | |
| getConfig: () => fetchJSON<Record<string, unknown>>("/api/config"), | |
| getDefaults: () => fetchJSON<Record<string, unknown>>("/api/config/defaults"), | |
| getSchema: () => fetchJSON<{ fields: Record<string, unknown>; category_order: string[] }>("/api/config/schema"), | |
| saveConfig: (config: Record<string, unknown>) => | |
| fetchJSON<{ ok: boolean }>("/api/config", { | |
| method: "PUT", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ config }), | |
| }), | |
| getConfigRaw: () => fetchJSON<{ yaml: string }>("/api/config/raw"), | |
| saveConfigRaw: (yaml_text: string) => | |
| fetchJSON<{ ok: boolean }>("/api/config/raw", { | |
| method: "PUT", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ yaml_text }), | |
| }), | |
| getEnvVars: () => fetchJSON<Record<string, EnvVarInfo>>("/api/env"), | |
| setEnvVar: (key: string, value: string) => | |
| fetchJSON<{ ok: boolean }>("/api/env", { | |
| method: "PUT", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ key, value }), | |
| }), | |
| deleteEnvVar: (key: string) => | |
| fetchJSON<{ ok: boolean }>("/api/env", { | |
| method: "DELETE", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ key }), | |
| }), | |
| revealEnvVar: async (key: string) => { | |
| const token = await getSessionToken(); | |
| return fetchJSON<{ key: string; value: string }>("/api/env/reveal", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| body: JSON.stringify({ key }), | |
| }); | |
| }, | |
| // Cron jobs | |
| getCronJobs: () => fetchJSON<CronJob[]>("/api/cron/jobs"), | |
| createCronJob: (job: { prompt: string; schedule: string; name?: string; deliver?: string }) => | |
| fetchJSON<CronJob>("/api/cron/jobs", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(job), | |
| }), | |
| pauseCronJob: (id: string) => | |
| fetchJSON<{ ok: boolean }>(`/api/cron/jobs/${id}/pause`, { method: "POST" }), | |
| resumeCronJob: (id: string) => | |
| fetchJSON<{ ok: boolean }>(`/api/cron/jobs/${id}/resume`, { method: "POST" }), | |
| triggerCronJob: (id: string) => | |
| fetchJSON<{ ok: boolean }>(`/api/cron/jobs/${id}/trigger`, { method: "POST" }), | |
| deleteCronJob: (id: string) => | |
| fetchJSON<{ ok: boolean }>(`/api/cron/jobs/${id}`, { method: "DELETE" }), | |
| // Skills & Toolsets | |
| getSkills: () => fetchJSON<SkillInfo[]>("/api/skills"), | |
| toggleSkill: (name: string, enabled: boolean) => | |
| fetchJSON<{ ok: boolean }>("/api/skills/toggle", { | |
| method: "PUT", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ name, enabled }), | |
| }), | |
| getToolsets: () => fetchJSON<ToolsetInfo[]>("/api/tools/toolsets"), | |
| // Session search (FTS5) | |
| searchSessions: (q: string) => | |
| fetchJSON<SessionSearchResponse>(`/api/sessions/search?q=${encodeURIComponent(q)}`), | |
| // OAuth provider management | |
| getOAuthProviders: () => | |
| fetchJSON<OAuthProvidersResponse>("/api/providers/oauth"), | |
| disconnectOAuthProvider: async (providerId: string) => { | |
| const token = await getSessionToken(); | |
| return fetchJSON<{ ok: boolean; provider: string }>( | |
| `/api/providers/oauth/${encodeURIComponent(providerId)}`, | |
| { | |
| method: "DELETE", | |
| headers: { Authorization: `Bearer ${token}` }, | |
| }, | |
| ); | |
| }, | |
| startOAuthLogin: async (providerId: string) => { | |
| const token = await getSessionToken(); | |
| return fetchJSON<OAuthStartResponse>( | |
| `/api/providers/oauth/${encodeURIComponent(providerId)}/start`, | |
| { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| body: "{}", | |
| }, | |
| ); | |
| }, | |
| submitOAuthCode: async (providerId: string, sessionId: string, code: string) => { | |
| const token = await getSessionToken(); | |
| return fetchJSON<OAuthSubmitResponse>( | |
| `/api/providers/oauth/${encodeURIComponent(providerId)}/submit`, | |
| { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| body: JSON.stringify({ session_id: sessionId, code }), | |
| }, | |
| ); | |
| }, | |
| pollOAuthSession: (providerId: string, sessionId: string) => | |
| fetchJSON<OAuthPollResponse>( | |
| `/api/providers/oauth/${encodeURIComponent(providerId)}/poll/${encodeURIComponent(sessionId)}`, | |
| ), | |
| cancelOAuthSession: async (sessionId: string) => { | |
| const token = await getSessionToken(); | |
| return fetchJSON<{ ok: boolean }>( | |
| `/api/providers/oauth/sessions/${encodeURIComponent(sessionId)}`, | |
| { | |
| method: "DELETE", | |
| headers: { Authorization: `Bearer ${token}` }, | |
| }, | |
| ); | |
| }, | |
| }; | |
| export interface PlatformStatus { | |
| error_code?: string; | |
| error_message?: string; | |
| state: string; | |
| updated_at: string; | |
| } | |
| export interface StatusResponse { | |
| active_sessions: number; | |
| config_path: string; | |
| config_version: number; | |
| env_path: string; | |
| gateway_exit_reason: string | null; | |
| gateway_pid: number | null; | |
| gateway_platforms: Record<string, PlatformStatus>; | |
| gateway_running: boolean; | |
| gateway_state: string | null; | |
| gateway_updated_at: string | null; | |
| hermes_home: string; | |
| latest_config_version: number; | |
| release_date: string; | |
| version: string; | |
| } | |
| export interface SessionInfo { | |
| id: string; | |
| source: string | null; | |
| model: string | null; | |
| title: string | null; | |
| started_at: number; | |
| ended_at: number | null; | |
| last_active: number; | |
| is_active: boolean; | |
| message_count: number; | |
| tool_call_count: number; | |
| input_tokens: number; | |
| output_tokens: number; | |
| preview: string | null; | |
| } | |
| export interface PaginatedSessions { | |
| sessions: SessionInfo[]; | |
| total: number; | |
| limit: number; | |
| offset: number; | |
| } | |
| export interface EnvVarInfo { | |
| is_set: boolean; | |
| redacted_value: string | null; | |
| description: string; | |
| url: string | null; | |
| category: string; | |
| is_password: boolean; | |
| tools: string[]; | |
| advanced: boolean; | |
| } | |
| export interface SessionMessage { | |
| role: "user" | "assistant" | "system" | "tool"; | |
| content: string | null; | |
| tool_calls?: Array<{ | |
| id: string; | |
| function: { name: string; arguments: string }; | |
| }>; | |
| tool_name?: string; | |
| tool_call_id?: string; | |
| timestamp?: number; | |
| } | |
| export interface SessionMessagesResponse { | |
| session_id: string; | |
| messages: SessionMessage[]; | |
| } | |
| export interface LogsResponse { | |
| file: string; | |
| lines: string[]; | |
| } | |
| export interface AnalyticsDailyEntry { | |
| day: string; | |
| input_tokens: number; | |
| output_tokens: number; | |
| cache_read_tokens: number; | |
| reasoning_tokens: number; | |
| estimated_cost: number; | |
| actual_cost: number; | |
| sessions: number; | |
| } | |
| export interface AnalyticsModelEntry { | |
| model: string; | |
| input_tokens: number; | |
| output_tokens: number; | |
| estimated_cost: number; | |
| sessions: number; | |
| } | |
| export interface AnalyticsResponse { | |
| daily: AnalyticsDailyEntry[]; | |
| by_model: AnalyticsModelEntry[]; | |
| totals: { | |
| total_input: number; | |
| total_output: number; | |
| total_cache_read: number; | |
| total_reasoning: number; | |
| total_estimated_cost: number; | |
| total_actual_cost: number; | |
| total_sessions: number; | |
| }; | |
| } | |
| export interface CronJob { | |
| id: string; | |
| name?: string; | |
| prompt: string; | |
| schedule: { kind: string; expr: string; display: string }; | |
| schedule_display: string; | |
| enabled: boolean; | |
| state: string; | |
| deliver?: string; | |
| last_run_at?: string | null; | |
| next_run_at?: string | null; | |
| last_error?: string | null; | |
| } | |
| export interface SkillInfo { | |
| name: string; | |
| description: string; | |
| category: string; | |
| enabled: boolean; | |
| } | |
| export interface ToolsetInfo { | |
| name: string; | |
| label: string; | |
| description: string; | |
| enabled: boolean; | |
| configured: boolean; | |
| tools: string[]; | |
| } | |
| export interface SessionSearchResult { | |
| session_id: string; | |
| snippet: string; | |
| role: string | null; | |
| source: string | null; | |
| model: string | null; | |
| session_started: number | null; | |
| } | |
| export interface SessionSearchResponse { | |
| results: SessionSearchResult[]; | |
| } | |
| // ── OAuth provider types ──────────────────────────────────────────────── | |
| export interface OAuthProviderStatus { | |
| logged_in: boolean; | |
| source?: string | null; | |
| source_label?: string | null; | |
| token_preview?: string | null; | |
| expires_at?: string | null; | |
| has_refresh_token?: boolean; | |
| last_refresh?: string | null; | |
| error?: string; | |
| } | |
| export interface OAuthProvider { | |
| id: string; | |
| name: string; | |
| /** "pkce" (browser redirect + paste code), "device_code" (show code + URL), | |
| * or "external" (delegated to a separate CLI like Claude Code or Qwen). */ | |
| flow: "pkce" | "device_code" | "external"; | |
| cli_command: string; | |
| docs_url: string; | |
| status: OAuthProviderStatus; | |
| } | |
| export interface OAuthProvidersResponse { | |
| providers: OAuthProvider[]; | |
| } | |
| /** Discriminated union — the shape of /start depends on the flow. */ | |
| export type OAuthStartResponse = | |
| | { | |
| session_id: string; | |
| flow: "pkce"; | |
| auth_url: string; | |
| expires_in: number; | |
| } | |
| | { | |
| session_id: string; | |
| flow: "device_code"; | |
| user_code: string; | |
| verification_url: string; | |
| expires_in: number; | |
| poll_interval: number; | |
| }; | |
| export interface OAuthSubmitResponse { | |
| ok: boolean; | |
| status: "approved" | "error"; | |
| message?: string; | |
| } | |
| export interface OAuthPollResponse { | |
| session_id: string; | |
| status: "pending" | "approved" | "denied" | "expired" | "error"; | |
| error_message?: string | null; | |
| expires_at?: number | null; | |
| } | |