mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-12 13:52:15 +00:00
TUI /journey gets d/e with confirm + $EDITOR; desktop gets a right-click context menu with inline edit modal. Both refresh the graph after mutation. Extract openInEditor into the shared TUI editor helper.
878 lines
26 KiB
TypeScript
878 lines
26 KiB
TypeScript
import { JsonRpcGatewayClient } from '@hermes/shared'
|
|
|
|
import type {
|
|
ActionResponse,
|
|
ActionStatusResponse,
|
|
AnalyticsResponse,
|
|
AudioSpeakResponse,
|
|
AudioTranscriptionResponse,
|
|
AuxiliaryModelsResponse,
|
|
BackendUpdateCheckResponse,
|
|
ComputerUseStatus,
|
|
ConfigSchemaResponse,
|
|
CronJob,
|
|
CronJobCreatePayload,
|
|
CronJobUpdates,
|
|
ElevenLabsVoicesResponse,
|
|
EnvVarInfo,
|
|
HermesConfig,
|
|
HermesConfigRecord,
|
|
LogsResponse,
|
|
MemoryProviderConfig,
|
|
MemoryProviderOAuthStatus,
|
|
MessagingPlatformsResponse,
|
|
MessagingPlatformTestResponse,
|
|
MessagingPlatformUpdate,
|
|
MoaConfigResponse,
|
|
ModelAssignmentRequest,
|
|
ModelAssignmentResponse,
|
|
ModelInfoResponse,
|
|
ModelOptionsResponse,
|
|
OAuthPollResponse,
|
|
OAuthProvidersResponse,
|
|
OAuthStartResponse,
|
|
OAuthSubmitResponse,
|
|
PaginatedSessions,
|
|
ProfileCreatePayload,
|
|
ProfileSetupCommand,
|
|
ProfileSoul,
|
|
ProfilesResponse,
|
|
SessionInfo,
|
|
SessionMessagesResponse,
|
|
SessionSearchResponse,
|
|
SkillInfo,
|
|
StarmapGraph,
|
|
StatusResponse,
|
|
ToolsetConfig,
|
|
ToolsetInfo
|
|
} from '@/types/hermes'
|
|
|
|
const DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS = 30_000
|
|
const SESSION_LIST_REQUEST_TIMEOUT_MS = 60_000
|
|
|
|
export type {
|
|
ActionResponse,
|
|
ActionStatusResponse,
|
|
AnalyticsDailyEntry,
|
|
AnalyticsModelEntry,
|
|
AnalyticsResponse,
|
|
AnalyticsSkillEntry,
|
|
AnalyticsSkillsSummary,
|
|
AnalyticsTotals,
|
|
AudioSpeakResponse,
|
|
AudioTranscriptionResponse,
|
|
AuxiliaryModelsResponse,
|
|
BackendUpdateCheckResponse,
|
|
ComputerUseCheck,
|
|
ComputerUsePermissionSource,
|
|
ComputerUseStatus,
|
|
ConfigFieldSchema,
|
|
ConfigSchemaResponse,
|
|
CronJob,
|
|
CronJobCreatePayload,
|
|
CronJobSchedule,
|
|
CronJobUpdates,
|
|
ElevenLabsVoice,
|
|
ElevenLabsVoicesResponse,
|
|
EnvVarInfo,
|
|
GatewayReadyPayload,
|
|
HermesConfig,
|
|
HermesConfigRecord,
|
|
LogsResponse,
|
|
MemoryProviderConfig,
|
|
MemoryProviderOAuthStatus,
|
|
MessagingEnvVarInfo,
|
|
MessagingHomeChannel,
|
|
MessagingPlatformInfo,
|
|
MessagingPlatformsResponse,
|
|
MessagingPlatformTestResponse,
|
|
MessagingPlatformUpdate,
|
|
MoaConfigResponse,
|
|
MoaModelSlot,
|
|
ModelAssignmentRequest,
|
|
ModelAssignmentResponse,
|
|
ModelInfoResponse,
|
|
ModelOptionProvider,
|
|
ModelOptionsResponse,
|
|
PaginatedSessions,
|
|
ProfileCreatePayload,
|
|
ProfileInfo,
|
|
ProfileSetupCommand,
|
|
ProfileSoul,
|
|
ProfilesResponse,
|
|
ProjectFolder,
|
|
ProjectInfo,
|
|
ProjectsPayload,
|
|
RpcEvent,
|
|
SessionCreateResponse,
|
|
SessionInfo,
|
|
SessionMessage,
|
|
SessionMessagesResponse,
|
|
SessionResumeResponse,
|
|
SessionRuntimeInfo,
|
|
SessionSearchResponse,
|
|
SessionSearchResult,
|
|
SkillInfo,
|
|
StaleAuxAssignment,
|
|
StarmapGraph,
|
|
StatusResponse,
|
|
ToolsetConfig,
|
|
ToolsetInfo
|
|
} from '@/types/hermes'
|
|
|
|
export class HermesGateway extends JsonRpcGatewayClient {
|
|
constructor() {
|
|
super({
|
|
closedErrorMessage: 'Hermes gateway connection closed',
|
|
connectErrorMessage: 'Could not connect to Hermes gateway',
|
|
createRequestId: nextId => nextId,
|
|
notConnectedErrorMessage: 'Hermes gateway is not connected',
|
|
requestTimeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS
|
|
})
|
|
}
|
|
}
|
|
|
|
// Profile that profile-scoped REST settings (config/env/skills/tools/model/…)
|
|
// should target. Mirrors $activeGatewayProfile, pushed in from the store via
|
|
// setApiRequestProfile so this module needs no store import (avoids a cycle).
|
|
// Electron main consumes request.profile to pick which backend *process* serves
|
|
// the call; each pooled backend already has its own HERMES_HOME, so no backend
|
|
// change is needed. Null → primary, so single-profile users are unaffected.
|
|
let _apiProfile: null | string = null
|
|
|
|
export function setApiRequestProfile(profile: null | string): void {
|
|
_apiProfile = profile || null
|
|
}
|
|
|
|
function profileScoped(): { profile?: string } {
|
|
return _apiProfile ? { profile: _apiProfile } : {}
|
|
}
|
|
|
|
export async function listSessions(
|
|
limit = 40,
|
|
minMessages = 0,
|
|
archived: 'exclude' | 'include' | 'only' = 'exclude',
|
|
order: 'created' | 'recent' = 'recent'
|
|
): Promise<PaginatedSessions> {
|
|
const result = await window.hermesDesktop.api<PaginatedSessions>({
|
|
path:
|
|
`/api/sessions?limit=${limit}&offset=0&min_messages=${Math.max(0, minMessages)}` +
|
|
`&archived=${archived}&order=${order}`,
|
|
timeoutMs: SESSION_LIST_REQUEST_TIMEOUT_MS
|
|
})
|
|
|
|
return {
|
|
...result,
|
|
sessions: result.sessions.slice(0, limit),
|
|
offset: 0
|
|
}
|
|
}
|
|
|
|
// Unified, read-only session list aggregated across ALL profiles. Served by the
|
|
// primary backend straight off each profile's state.db — no per-profile backend
|
|
// is spawned. Single-profile users get the same rows as listSessions(), tagged
|
|
// profile="default".
|
|
// Source scoping lets callers split the unified list into independent slices:
|
|
// recents pass `excludeSources: ['cron']`, the cron-jobs section passes
|
|
// `source: 'cron'`. Without this a burst of (always-newest) cron sessions
|
|
// consumes the whole recents page and starves real conversations.
|
|
export interface SessionSourceFilter {
|
|
source?: string
|
|
excludeSources?: string[]
|
|
}
|
|
|
|
export async function listAllProfileSessions(
|
|
limit = 40,
|
|
minMessages = 0,
|
|
archived: 'exclude' | 'include' | 'only' = 'exclude',
|
|
order: 'created' | 'recent' = 'recent',
|
|
profile: 'all' | (string & {}) = 'all',
|
|
filter: SessionSourceFilter = {}
|
|
): Promise<PaginatedSessions> {
|
|
const sourceParam = filter.source ? `&source=${encodeURIComponent(filter.source)}` : ''
|
|
|
|
const excludeParam = filter.excludeSources?.length
|
|
? `&exclude_sources=${encodeURIComponent(filter.excludeSources.join(','))}`
|
|
: ''
|
|
|
|
const result = await window.hermesDesktop.api<PaginatedSessions>({
|
|
path:
|
|
`/api/profiles/sessions?limit=${limit}&offset=0&min_messages=${Math.max(0, minMessages)}` +
|
|
`&archived=${archived}&order=${order}&profile=${encodeURIComponent(profile)}${sourceParam}${excludeParam}`,
|
|
timeoutMs: SESSION_LIST_REQUEST_TIMEOUT_MS
|
|
})
|
|
|
|
return {
|
|
...result,
|
|
sessions: result.sessions.slice(0, limit),
|
|
offset: 0
|
|
}
|
|
}
|
|
|
|
// Mutations take the owning `profile` so Electron routes them to that profile's
|
|
// backend (remote pool or local primary) via request.profile — matching the
|
|
// read path. A remote session's row lives only on its remote host, so a mutation
|
|
// that hit the local primary would no-op or 404. Omit for the current/default.
|
|
export function setSessionArchived(id: string, archived: boolean, profile?: string | null): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
...(profile ? { profile } : {}),
|
|
path: `/api/sessions/${encodeURIComponent(id)}`,
|
|
method: 'PATCH',
|
|
body: { archived }
|
|
})
|
|
}
|
|
|
|
export function searchSessions(query: string): Promise<SessionSearchResponse> {
|
|
return window.hermesDesktop.api<SessionSearchResponse>({
|
|
path: `/api/sessions/search?q=${encodeURIComponent(query)}`
|
|
})
|
|
}
|
|
|
|
// Resolves a single session row by id on one backend (the active profile, or
|
|
// the given `profile`). The backend resolves exact ids and unique prefixes and
|
|
// 404s when the id isn't on that profile — so a cheap by-id lookup replaces the
|
|
// cross-profile list scan when locating an unknown id's owner.
|
|
export function getSession(id: string, profile?: string | null): Promise<SessionInfo> {
|
|
const suffix = profile ? `?profile=${encodeURIComponent(profile)}` : ''
|
|
|
|
return window.hermesDesktop.api<SessionInfo>({
|
|
...(profile ? { profile } : {}),
|
|
path: `/api/sessions/${encodeURIComponent(id)}${suffix}`
|
|
})
|
|
}
|
|
|
|
// Reads another profile's transcript. For a remote profile Electron reroutes
|
|
// this GET to the remote backend (which serves its own state.db); for a local
|
|
// profile the primary opens that profile's state.db via ?profile=. Omit for
|
|
// the current/default profile.
|
|
export function getSessionMessages(id: string, profile?: string | null): Promise<SessionMessagesResponse> {
|
|
const suffix = profile ? `?profile=${encodeURIComponent(profile)}` : ''
|
|
|
|
return window.hermesDesktop.api<SessionMessagesResponse>({
|
|
...(profile ? { profile } : {}),
|
|
path: `/api/sessions/${encodeURIComponent(id)}/messages${suffix}`
|
|
})
|
|
}
|
|
|
|
export function deleteSession(id: string, profile?: string | null): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
...(profile ? { profile } : {}),
|
|
path: `/api/sessions/${encodeURIComponent(id)}`,
|
|
method: 'DELETE'
|
|
})
|
|
}
|
|
|
|
export function renameSession(
|
|
id: string,
|
|
title: string,
|
|
profile?: string | null
|
|
): Promise<{ ok: boolean; title: string }> {
|
|
return window.hermesDesktop.api<{ ok: boolean; title: string }>({
|
|
...(profile ? { profile } : {}),
|
|
path: `/api/sessions/${encodeURIComponent(id)}`,
|
|
method: 'PATCH',
|
|
body: { title, ...(profile ? { profile } : {}) }
|
|
})
|
|
}
|
|
|
|
export function getGlobalModelInfo(): Promise<ModelInfoResponse> {
|
|
return window.hermesDesktop.api<ModelInfoResponse>({
|
|
...profileScoped(),
|
|
path: '/api/model/info'
|
|
})
|
|
}
|
|
|
|
export function getStatus(): Promise<StatusResponse> {
|
|
return window.hermesDesktop.api<StatusResponse>({
|
|
...profileScoped(),
|
|
path: '/api/status'
|
|
})
|
|
}
|
|
|
|
export function getLogs(params: {
|
|
component?: string
|
|
file?: string
|
|
level?: string
|
|
lines?: number
|
|
}): Promise<LogsResponse> {
|
|
const query = new URLSearchParams()
|
|
|
|
if (params.file) {
|
|
query.set('file', params.file)
|
|
}
|
|
|
|
if (typeof params.lines === 'number') {
|
|
query.set('lines', String(params.lines))
|
|
}
|
|
|
|
if (params.level && params.level !== 'ALL') {
|
|
query.set('level', params.level)
|
|
}
|
|
|
|
if (params.component && params.component !== 'all') {
|
|
query.set('component', params.component)
|
|
}
|
|
|
|
const suffix = query.toString()
|
|
|
|
return window.hermesDesktop.api<LogsResponse>({
|
|
...profileScoped(),
|
|
path: suffix ? `/api/logs?${suffix}` : '/api/logs'
|
|
})
|
|
}
|
|
|
|
export function getHermesConfig(): Promise<HermesConfig> {
|
|
return window.hermesDesktop.api<HermesConfig>({
|
|
...profileScoped(),
|
|
path: '/api/config'
|
|
})
|
|
}
|
|
|
|
export function getHermesConfigRecord(): Promise<HermesConfigRecord> {
|
|
return window.hermesDesktop.api<HermesConfigRecord>({
|
|
...profileScoped(),
|
|
path: '/api/config'
|
|
})
|
|
}
|
|
|
|
export function getHermesConfigDefaults(): Promise<HermesConfigRecord> {
|
|
return window.hermesDesktop.api<HermesConfigRecord>({
|
|
...profileScoped(),
|
|
path: '/api/config/defaults'
|
|
})
|
|
}
|
|
|
|
export function getHermesConfigSchema(): Promise<ConfigSchemaResponse> {
|
|
return window.hermesDesktop.api<ConfigSchemaResponse>({
|
|
...profileScoped(),
|
|
path: '/api/config/schema'
|
|
})
|
|
}
|
|
|
|
export function saveHermesConfig(config: HermesConfigRecord): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
...profileScoped(),
|
|
path: '/api/config',
|
|
method: 'PUT',
|
|
body: { config }
|
|
})
|
|
}
|
|
|
|
export function getMemoryProviderConfig(provider: string): Promise<MemoryProviderConfig> {
|
|
return window.hermesDesktop.api<MemoryProviderConfig>({
|
|
path: `/api/memory/providers/${encodeURIComponent(provider)}/config`
|
|
})
|
|
}
|
|
|
|
export function saveMemoryProviderConfig(provider: string, values: Record<string, string>): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
path: `/api/memory/providers/${encodeURIComponent(provider)}/config`,
|
|
method: 'PUT',
|
|
body: { values }
|
|
})
|
|
}
|
|
|
|
export function getEnvVars(): Promise<Record<string, EnvVarInfo>> {
|
|
return window.hermesDesktop.api<Record<string, EnvVarInfo>>({
|
|
...profileScoped(),
|
|
path: '/api/env'
|
|
})
|
|
}
|
|
|
|
export function setEnvVar(key: string, value: string): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
...profileScoped(),
|
|
path: '/api/env',
|
|
method: 'PUT',
|
|
body: { key, value }
|
|
})
|
|
}
|
|
|
|
export function validateProviderCredential(
|
|
key: string,
|
|
value: string,
|
|
apiKey?: string
|
|
): Promise<{ ok: boolean; reachable: boolean; message: string; models?: string[] }> {
|
|
return window.hermesDesktop.api<{ ok: boolean; reachable: boolean; message: string; models?: string[] }>({
|
|
...profileScoped(),
|
|
path: '/api/providers/validate',
|
|
method: 'POST',
|
|
body: { key, value, api_key: apiKey ?? '' }
|
|
})
|
|
}
|
|
|
|
export function deleteEnvVar(key: string): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
...profileScoped(),
|
|
path: '/api/env',
|
|
method: 'DELETE',
|
|
body: { key }
|
|
})
|
|
}
|
|
|
|
export function revealEnvVar(key: string): Promise<{ key: string; value: string }> {
|
|
return window.hermesDesktop.api<{ key: string; value: string }>({
|
|
...profileScoped(),
|
|
path: '/api/env/reveal',
|
|
method: 'POST',
|
|
body: { key }
|
|
})
|
|
}
|
|
|
|
export function listOAuthProviders(): Promise<OAuthProvidersResponse> {
|
|
return window.hermesDesktop.api<OAuthProvidersResponse>({
|
|
...profileScoped(),
|
|
path: '/api/providers/oauth'
|
|
})
|
|
}
|
|
|
|
export function disconnectOAuthProvider(providerId: string): Promise<{ ok: boolean; provider: string }> {
|
|
return window.hermesDesktop.api<{ ok: boolean; provider: string }>({
|
|
...profileScoped(),
|
|
path: `/api/providers/oauth/${encodeURIComponent(providerId)}`,
|
|
method: 'DELETE'
|
|
})
|
|
}
|
|
|
|
export function startOAuthLogin(providerId: string): Promise<OAuthStartResponse> {
|
|
return window.hermesDesktop.api<OAuthStartResponse>({
|
|
...profileScoped(),
|
|
path: `/api/providers/oauth/${encodeURIComponent(providerId)}/start`,
|
|
method: 'POST',
|
|
body: {}
|
|
})
|
|
}
|
|
|
|
export function submitOAuthCode(providerId: string, sessionId: string, code: string): Promise<OAuthSubmitResponse> {
|
|
return window.hermesDesktop.api<OAuthSubmitResponse>({
|
|
...profileScoped(),
|
|
path: `/api/providers/oauth/${encodeURIComponent(providerId)}/submit`,
|
|
method: 'POST',
|
|
body: { session_id: sessionId, code }
|
|
})
|
|
}
|
|
|
|
export function pollOAuthSession(providerId: string, sessionId: string): Promise<OAuthPollResponse> {
|
|
return window.hermesDesktop.api<OAuthPollResponse>({
|
|
...profileScoped(),
|
|
path: `/api/providers/oauth/${encodeURIComponent(providerId)}/poll/${encodeURIComponent(sessionId)}`
|
|
})
|
|
}
|
|
|
|
export function cancelOAuthSession(sessionId: string): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
...profileScoped(),
|
|
path: `/api/providers/oauth/sessions/${encodeURIComponent(sessionId)}`,
|
|
method: 'DELETE'
|
|
})
|
|
}
|
|
|
|
// Memory-provider OAuth connect (provider-keyed; 404s for providers without an
|
|
// OAuth flow). Profile-scoped: the grant lands in the active profile's config.
|
|
export function startMemoryProviderOAuth(provider: string): Promise<MemoryProviderOAuthStatus> {
|
|
return window.hermesDesktop.api<MemoryProviderOAuthStatus>({
|
|
...profileScoped(),
|
|
path: `/api/memory/providers/${encodeURIComponent(provider)}/oauth/start`,
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
export function getMemoryProviderOAuthStatus(provider: string): Promise<MemoryProviderOAuthStatus> {
|
|
return window.hermesDesktop.api<MemoryProviderOAuthStatus>({
|
|
...profileScoped(),
|
|
path: `/api/memory/providers/${encodeURIComponent(provider)}/oauth/status`
|
|
})
|
|
}
|
|
|
|
export function getSkills(): Promise<SkillInfo[]> {
|
|
return window.hermesDesktop.api<SkillInfo[]>({
|
|
...profileScoped(),
|
|
path: '/api/skills'
|
|
})
|
|
}
|
|
|
|
export function getStarmapGraph(): Promise<StarmapGraph> {
|
|
return window.hermesDesktop.api<StarmapGraph>({
|
|
...profileScoped(),
|
|
// Backend REST contract — stays /api/learning even though the UI feature is
|
|
// now "star map". Renaming this would break against an un-upgraded backend.
|
|
path: '/api/learning/graph'
|
|
})
|
|
}
|
|
|
|
export interface LearningNodeDetail {
|
|
content: string
|
|
kind: 'memory' | 'skill'
|
|
label: string
|
|
ok: boolean
|
|
}
|
|
|
|
export function getLearningNode(id: string): Promise<LearningNodeDetail> {
|
|
return window.hermesDesktop.api<LearningNodeDetail>({
|
|
...profileScoped(),
|
|
path: `/api/learning/node?id=${encodeURIComponent(id)}`
|
|
})
|
|
}
|
|
|
|
export function deleteLearningNode(id: string): Promise<{ message: string; ok: boolean }> {
|
|
return window.hermesDesktop.api<{ message: string; ok: boolean }>({
|
|
...profileScoped(),
|
|
path: '/api/learning/node',
|
|
method: 'DELETE',
|
|
body: { id }
|
|
})
|
|
}
|
|
|
|
export function editLearningNode(id: string, content: string): Promise<{ message: string; ok: boolean }> {
|
|
return window.hermesDesktop.api<{ message: string; ok: boolean }>({
|
|
...profileScoped(),
|
|
path: '/api/learning/node',
|
|
method: 'PUT',
|
|
body: { content, id }
|
|
})
|
|
}
|
|
|
|
export function toggleSkill(name: string, enabled: boolean): Promise<{ ok: boolean; name: string; enabled: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean; name: string; enabled: boolean }>({
|
|
...profileScoped(),
|
|
path: '/api/skills/toggle',
|
|
method: 'PUT',
|
|
body: { name, enabled }
|
|
})
|
|
}
|
|
|
|
export function getToolsets(): Promise<ToolsetInfo[]> {
|
|
return window.hermesDesktop.api<ToolsetInfo[]>({
|
|
...profileScoped(),
|
|
path: '/api/tools/toolsets'
|
|
})
|
|
}
|
|
|
|
export function toggleToolset(
|
|
name: string,
|
|
enabled: boolean
|
|
): Promise<{ ok: boolean; name: string; enabled: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean; name: string; enabled: boolean }>({
|
|
...profileScoped(),
|
|
path: `/api/tools/toolsets/${encodeURIComponent(name)}`,
|
|
method: 'PUT',
|
|
body: { enabled }
|
|
})
|
|
}
|
|
|
|
export function getToolsetConfig(name: string): Promise<ToolsetConfig> {
|
|
return window.hermesDesktop.api<ToolsetConfig>({
|
|
...profileScoped(),
|
|
path: `/api/tools/toolsets/${encodeURIComponent(name)}/config`
|
|
})
|
|
}
|
|
|
|
export function selectToolsetProvider(
|
|
name: string,
|
|
provider: string
|
|
): Promise<{ ok: boolean; name: string; provider: string }> {
|
|
return window.hermesDesktop.api<{ ok: boolean; name: string; provider: string }>({
|
|
...profileScoped(),
|
|
path: `/api/tools/toolsets/${encodeURIComponent(name)}/provider`,
|
|
method: 'PUT',
|
|
body: { provider }
|
|
})
|
|
}
|
|
|
|
export function runToolsetPostSetup(name: string, key: string): Promise<ActionResponse & { key: string }> {
|
|
return window.hermesDesktop.api<ActionResponse & { key: string }>({
|
|
...profileScoped(),
|
|
path: `/api/tools/toolsets/${encodeURIComponent(name)}/post-setup`,
|
|
method: 'POST',
|
|
body: { key }
|
|
})
|
|
}
|
|
|
|
export function getComputerUseStatus(): Promise<ComputerUseStatus> {
|
|
return window.hermesDesktop.api<ComputerUseStatus>({
|
|
...profileScoped(),
|
|
path: '/api/tools/computer-use/status'
|
|
})
|
|
}
|
|
|
|
export function grantComputerUsePermissions(): Promise<ActionResponse> {
|
|
return window.hermesDesktop.api<ActionResponse>({
|
|
...profileScoped(),
|
|
path: '/api/tools/computer-use/permissions/grant',
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
export function getMessagingPlatforms(): Promise<MessagingPlatformsResponse> {
|
|
return window.hermesDesktop.api<MessagingPlatformsResponse>({
|
|
path: '/api/messaging/platforms'
|
|
})
|
|
}
|
|
|
|
export function updateMessagingPlatform(
|
|
platformId: string,
|
|
body: MessagingPlatformUpdate
|
|
): Promise<{ ok: boolean; platform: string }> {
|
|
return window.hermesDesktop.api<{ ok: boolean; platform: string }>({
|
|
path: `/api/messaging/platforms/${encodeURIComponent(platformId)}`,
|
|
method: 'PUT',
|
|
body
|
|
})
|
|
}
|
|
|
|
export function testMessagingPlatform(platformId: string): Promise<MessagingPlatformTestResponse> {
|
|
return window.hermesDesktop.api<MessagingPlatformTestResponse>({
|
|
path: `/api/messaging/platforms/${encodeURIComponent(platformId)}/test`,
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
export function getCronJobs(): Promise<CronJob[]> {
|
|
return window.hermesDesktop.api<CronJob[]>({
|
|
path: '/api/cron/jobs'
|
|
})
|
|
}
|
|
|
|
export function getCronJob(jobId: string): Promise<CronJob> {
|
|
return window.hermesDesktop.api<CronJob>({
|
|
path: `/api/cron/jobs/${encodeURIComponent(jobId)}`
|
|
})
|
|
}
|
|
|
|
export async function getCronJobRuns(jobId: string, limit = 20): Promise<SessionInfo[]> {
|
|
const { runs } = await window.hermesDesktop.api<{ runs: SessionInfo[] }>({
|
|
path: `/api/cron/jobs/${encodeURIComponent(jobId)}/runs?limit=${limit}`
|
|
})
|
|
|
|
return runs ?? []
|
|
}
|
|
|
|
export function createCronJob(body: CronJobCreatePayload): Promise<CronJob> {
|
|
return window.hermesDesktop.api<CronJob>({
|
|
path: '/api/cron/jobs',
|
|
method: 'POST',
|
|
body
|
|
})
|
|
}
|
|
|
|
export function updateCronJob(jobId: string, updates: CronJobUpdates): Promise<CronJob> {
|
|
return window.hermesDesktop.api<CronJob>({
|
|
path: `/api/cron/jobs/${encodeURIComponent(jobId)}`,
|
|
method: 'PUT',
|
|
body: { updates }
|
|
})
|
|
}
|
|
|
|
export function pauseCronJob(jobId: string): Promise<CronJob> {
|
|
return window.hermesDesktop.api<CronJob>({
|
|
path: `/api/cron/jobs/${encodeURIComponent(jobId)}/pause`,
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
export function resumeCronJob(jobId: string): Promise<CronJob> {
|
|
return window.hermesDesktop.api<CronJob>({
|
|
path: `/api/cron/jobs/${encodeURIComponent(jobId)}/resume`,
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
export function triggerCronJob(jobId: string): Promise<CronJob> {
|
|
return window.hermesDesktop.api<CronJob>({
|
|
path: `/api/cron/jobs/${encodeURIComponent(jobId)}/trigger`,
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
export function deleteCronJob(jobId: string): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
path: `/api/cron/jobs/${encodeURIComponent(jobId)}`,
|
|
method: 'DELETE'
|
|
})
|
|
}
|
|
|
|
export function getProfiles(): Promise<ProfilesResponse> {
|
|
return window.hermesDesktop.api<ProfilesResponse>({
|
|
path: '/api/profiles'
|
|
})
|
|
}
|
|
|
|
export function createProfile(body: ProfileCreatePayload): Promise<{ name: string; ok: boolean; path: string }> {
|
|
return window.hermesDesktop.api<{ name: string; ok: boolean; path: string }>({
|
|
path: '/api/profiles',
|
|
method: 'POST',
|
|
body
|
|
})
|
|
}
|
|
|
|
export function renameProfile(name: string, newName: string): Promise<{ name: string; ok: boolean; path: string }> {
|
|
return window.hermesDesktop.api<{ name: string; ok: boolean; path: string }>({
|
|
path: `/api/profiles/${encodeURIComponent(name)}`,
|
|
method: 'PATCH',
|
|
body: { new_name: newName }
|
|
})
|
|
}
|
|
|
|
export function deleteProfile(name: string): Promise<{ ok: boolean; path: string }> {
|
|
return window.hermesDesktop.api<{ ok: boolean; path: string }>({
|
|
path: `/api/profiles/${encodeURIComponent(name)}`,
|
|
method: 'DELETE'
|
|
})
|
|
}
|
|
|
|
export function getProfileSoul(name: string): Promise<ProfileSoul> {
|
|
return window.hermesDesktop.api<ProfileSoul>({
|
|
path: `/api/profiles/${encodeURIComponent(name)}/soul`
|
|
})
|
|
}
|
|
|
|
export function updateProfileSoul(name: string, content: string): Promise<{ ok: boolean }> {
|
|
return window.hermesDesktop.api<{ ok: boolean }>({
|
|
path: `/api/profiles/${encodeURIComponent(name)}/soul`,
|
|
method: 'PUT',
|
|
body: { content }
|
|
})
|
|
}
|
|
|
|
export function getProfileSetupCommand(name: string): Promise<ProfileSetupCommand> {
|
|
return window.hermesDesktop.api<ProfileSetupCommand>({
|
|
path: `/api/profiles/${encodeURIComponent(name)}/setup-command`
|
|
})
|
|
}
|
|
|
|
export function getUsageAnalytics(days = 30): Promise<AnalyticsResponse> {
|
|
return window.hermesDesktop.api<AnalyticsResponse>({
|
|
...profileScoped(),
|
|
path: `/api/analytics/usage?days=${Math.max(1, Math.floor(days))}`
|
|
})
|
|
}
|
|
|
|
export function getGlobalModelOptions(opts?: { refresh?: boolean }): Promise<ModelOptionsResponse> {
|
|
return window.hermesDesktop.api<ModelOptionsResponse>({
|
|
...profileScoped(),
|
|
path: opts?.refresh ? '/api/model/options?refresh=1' : '/api/model/options'
|
|
})
|
|
}
|
|
|
|
export interface RecommendedDefaultModel {
|
|
provider: string
|
|
model: string
|
|
/** True/false for Nous (free vs paid tier); null for other providers. */
|
|
free_tier: boolean | null
|
|
}
|
|
|
|
// Recommended default model for a freshly-authenticated provider. Mirrors the
|
|
// curation `hermes model` does — for Nous it honors the free/paid tier so a
|
|
// free user gets a free model instead of a paid default.
|
|
export function getRecommendedDefaultModel(provider: string): Promise<RecommendedDefaultModel> {
|
|
return window.hermesDesktop.api<RecommendedDefaultModel>({
|
|
...profileScoped(),
|
|
path: `/api/model/recommended-default?provider=${encodeURIComponent(provider)}`
|
|
})
|
|
}
|
|
|
|
export function setGlobalModel(
|
|
provider: string,
|
|
model: string
|
|
): Promise<{ ok: boolean; provider: string; model: string }> {
|
|
return window.hermesDesktop.api<{ ok: boolean; provider: string; model: string }>({
|
|
...profileScoped(),
|
|
path: '/api/model/set',
|
|
method: 'POST',
|
|
body: {
|
|
scope: 'main',
|
|
provider,
|
|
model
|
|
}
|
|
})
|
|
}
|
|
|
|
export function getAuxiliaryModels(): Promise<AuxiliaryModelsResponse> {
|
|
return window.hermesDesktop.api<AuxiliaryModelsResponse>({
|
|
...profileScoped(),
|
|
path: '/api/model/auxiliary'
|
|
})
|
|
}
|
|
|
|
export function getMoaModels(): Promise<MoaConfigResponse> {
|
|
return window.hermesDesktop.api<MoaConfigResponse>({
|
|
...profileScoped(),
|
|
path: '/api/model/moa'
|
|
})
|
|
}
|
|
|
|
export function saveMoaModels(body: MoaConfigResponse): Promise<MoaConfigResponse & { ok: boolean }> {
|
|
return window.hermesDesktop.api<MoaConfigResponse & { ok: boolean }>({
|
|
...profileScoped(),
|
|
path: '/api/model/moa',
|
|
method: 'PUT',
|
|
body
|
|
})
|
|
}
|
|
|
|
export function setModelAssignment(body: ModelAssignmentRequest): Promise<ModelAssignmentResponse> {
|
|
return window.hermesDesktop.api<ModelAssignmentResponse>({
|
|
...profileScoped(),
|
|
path: '/api/model/set',
|
|
method: 'POST',
|
|
body
|
|
})
|
|
}
|
|
|
|
export function restartGateway(): Promise<ActionResponse> {
|
|
return window.hermesDesktop.api<ActionResponse>({
|
|
...profileScoped(),
|
|
path: '/api/gateway/restart',
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
export function updateHermes(): Promise<ActionResponse> {
|
|
return window.hermesDesktop.api<ActionResponse>({
|
|
...profileScoped(),
|
|
path: '/api/hermes/update',
|
|
method: 'POST'
|
|
})
|
|
}
|
|
|
|
/** Query the connected backend's own update state. In remote mode this is the
|
|
* authoritative source for the backend's behind-count + "what's changed",
|
|
* distinct from the Electron client clone's git state. */
|
|
export function checkHermesUpdate(force = false): Promise<BackendUpdateCheckResponse> {
|
|
return window.hermesDesktop.api<BackendUpdateCheckResponse>({
|
|
...profileScoped(),
|
|
path: `/api/hermes/update/check${force ? '?force=true' : ''}`
|
|
})
|
|
}
|
|
|
|
export function getActionStatus(name: string, lines = 200): Promise<ActionStatusResponse> {
|
|
return window.hermesDesktop.api<ActionStatusResponse>({
|
|
...profileScoped(),
|
|
path: `/api/actions/${encodeURIComponent(name)}/status?lines=${Math.max(1, lines)}`
|
|
})
|
|
}
|
|
|
|
export function transcribeAudio(dataUrl: string, mimeType?: string): Promise<AudioTranscriptionResponse> {
|
|
return window.hermesDesktop.api<AudioTranscriptionResponse>({
|
|
path: '/api/audio/transcribe',
|
|
method: 'POST',
|
|
body: {
|
|
data_url: dataUrl,
|
|
mime_type: mimeType
|
|
}
|
|
})
|
|
}
|
|
|
|
export function speakText(text: string): Promise<AudioSpeakResponse> {
|
|
return window.hermesDesktop.api<AudioSpeakResponse>({
|
|
path: '/api/audio/speak',
|
|
method: 'POST',
|
|
body: { text }
|
|
})
|
|
}
|
|
|
|
export function getElevenLabsVoices(): Promise<ElevenLabsVoicesResponse> {
|
|
return window.hermesDesktop.api<ElevenLabsVoicesResponse>({
|
|
path: '/api/audio/elevenlabs/voices'
|
|
})
|
|
}
|