import { getDesktopHost, isElectronRuntime } from "@/desktop/host"; import { invokeDesktopCommand } from "@/desktop/electron/invoke"; export type DesktopDaemonState = "starting" | "running" | "stopped" | "errored"; export type DesktopDaemonStopReason = | "manual_ipc" | "settings" | "host_remove" | "quit" | "app_update" | "version_mismatch" | "restart"; export interface DesktopDaemonStatus { serverId: string; status: DesktopDaemonState; listen: string | null; hostname: string | null; pid: number | null; home: string; version: string | null; desktopManaged: boolean; error: string | null; } export interface DesktopDaemonLogs { logPath: string; contents: string; } export interface DesktopPairingOffer { relayEnabled: boolean; url: string | null; qr: string | null; } export interface LocalTransportTarget { [key: string]: unknown; transportType: "socket" | "pipe"; transportPath: string; } interface LocalTransportEventPayload { sessionId: string; kind: "open" | "message" | "close" | "error"; text?: string | null; binaryBase64?: string | null; code?: number | null; reason?: string | null; error?: string | null; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function toStringOrNull(value: unknown): string | null { return typeof value === "string" && value.trim().length > 0 ? value : null; } function toNumberOrNull(value: unknown): number | null { return typeof value === "number" && Number.isFinite(value) ? value : null; } function parseDesktopDaemonState(value: unknown): DesktopDaemonState { const normalized = toStringOrNull(value)?.toLowerCase(); switch (normalized) { case "starting": return "starting"; case "running": return "running"; case "errored": case "error": return "errored"; case "stopped": case "stopping": case "unknown": default: return "stopped"; } } function parseDesktopDaemonStatus(raw: unknown): DesktopDaemonStatus { if (!isRecord(raw)) { throw new Error("Unexpected desktop daemon status response."); } return { serverId: toStringOrNull(raw.serverId) ?? "", status: parseDesktopDaemonState(raw.status), listen: toStringOrNull(raw.listen), hostname: toStringOrNull(raw.hostname), pid: toNumberOrNull(raw.pid), home: toStringOrNull(raw.home) ?? "", version: toStringOrNull(raw.version), desktopManaged: raw.desktopManaged === true, error: toStringOrNull(raw.error), }; } function parseDesktopDaemonLogs(raw: unknown): DesktopDaemonLogs { if (!isRecord(raw)) { throw new Error("Unexpected desktop daemon logs response."); } return { logPath: toStringOrNull(raw.logPath) ?? "", contents: typeof raw.contents === "string" ? raw.contents : "", }; } function parseDesktopPairingOffer(raw: unknown): DesktopPairingOffer { if (!isRecord(raw)) { throw new Error("Unexpected desktop daemon pairing response."); } return { relayEnabled: raw.relayEnabled === true, url: toStringOrNull(raw.url), qr: toStringOrNull(raw.qr), }; } export function shouldUseDesktopDaemon(): boolean { return isElectronRuntime(); } export async function getDesktopDaemonStatus(): Promise { return parseDesktopDaemonStatus(await invokeDesktopCommand("desktop_daemon_status")); } export async function startDesktopDaemon(): Promise { return parseDesktopDaemonStatus(await invokeDesktopCommand("start_desktop_daemon")); } export async function stopDesktopDaemon( reason: DesktopDaemonStopReason = "manual_ipc", ): Promise { return parseDesktopDaemonStatus(await invokeDesktopCommand("stop_desktop_daemon", { reason })); } export async function restartDesktopDaemon(): Promise { return parseDesktopDaemonStatus(await invokeDesktopCommand("restart_desktop_daemon")); } export async function getDesktopDaemonLogs(): Promise { return parseDesktopDaemonLogs(await invokeDesktopCommand("desktop_daemon_logs")); } export async function getDesktopDaemonPairing(): Promise { return parseDesktopPairingOffer(await invokeDesktopCommand("desktop_daemon_pairing")); } export async function getCliDaemonStatus(): Promise { const raw = await invokeDesktopCommand("cli_daemon_status"); if (typeof raw !== "string") { throw new Error("Unexpected CLI daemon status response."); } return raw; } export type LocalTransportEventUnlisten = () => void; export type LocalTransportEventHandler = (payload: LocalTransportEventPayload) => void; export async function listenToLocalTransportEvents( handler: LocalTransportEventHandler, ): Promise { const listen = getDesktopHost()?.events?.on; if (typeof listen !== "function") { throw new Error("Desktop events API is unavailable."); } const unlisten = await listen("local-daemon-transport-event", (payload: unknown) => { if (!isRecord(payload)) { return; } handler({ sessionId: toStringOrNull(payload.sessionId) ?? "", kind: (toStringOrNull(payload.kind) ?? "error") as LocalTransportEventPayload["kind"], text: toStringOrNull(payload.text), binaryBase64: toStringOrNull(payload.binaryBase64), code: toNumberOrNull(payload.code), reason: toStringOrNull(payload.reason), error: toStringOrNull(payload.error), }); }); return typeof unlisten === "function" ? unlisten : () => {}; } export async function openLocalTransportSession(target: LocalTransportTarget): Promise { const raw = await invokeDesktopCommand("open_local_daemon_transport", target); if (typeof raw !== "string" || raw.trim().length === 0) { throw new Error("Unexpected local transport session response."); } return raw; } export async function sendLocalTransportMessage(input: { sessionId: string; text?: string; binaryBase64?: string; }): Promise { await invokeDesktopCommand("send_local_daemon_transport_message", { sessionId: input.sessionId, ...(input.text ? { text: input.text } : {}), ...(input.binaryBase64 ? { binaryBase64: input.binaryBase64 } : {}), }); } export async function closeLocalTransportSession(sessionId: string): Promise { await invokeDesktopCommand("close_local_daemon_transport", { sessionId }); } // --------------------------------------------------------------------------- // Integrations // --------------------------------------------------------------------------- export interface InstallStatus { installed: boolean; } function parseInstallStatus(raw: unknown): InstallStatus { if (!isRecord(raw)) { throw new Error("Unexpected install status response."); } return { installed: raw.installed === true }; } export async function getCliInstallStatus(): Promise { return parseInstallStatus(await invokeDesktopCommand("get_cli_install_status")); } export async function installCli(): Promise { return parseInstallStatus(await invokeDesktopCommand("install_cli")); } export type SkillsState = "not-installed" | "up-to-date" | "drift"; export type SkillOp = | { kind: "add"; name: string } | { kind: "update"; name: string } | { kind: "delete"; name: string }; export interface SkillsStatus { state: SkillsState; ops: SkillOp[]; } function parseSkillsState(value: unknown): SkillsState { switch (value) { case "not-installed": case "up-to-date": case "drift": return value; default: throw new Error(`Unexpected skills status state: ${String(value)}`); } } function parseSkillOp(raw: unknown): SkillOp { if (!isRecord(raw)) { throw new Error("Unexpected skill op response."); } const name = toStringOrNull(raw.name); if (!name) throw new Error("Skill op missing name."); switch (raw.kind) { case "add": return { kind: "add", name }; case "update": return { kind: "update", name }; case "delete": return { kind: "delete", name }; default: throw new Error(`Unexpected skill op kind: ${String(raw.kind)}`); } } function parseSkillsStatus(raw: unknown): SkillsStatus { if (!isRecord(raw)) { throw new Error("Unexpected skills status response."); } const ops = Array.isArray(raw.ops) ? raw.ops.map(parseSkillOp) : []; return { state: parseSkillsState(raw.state), ops }; } export async function getSkillsStatus(): Promise { return parseSkillsStatus(await invokeDesktopCommand("get_skills_status")); } export async function installSkills(): Promise { return parseSkillsStatus(await invokeDesktopCommand("install_skills")); } export async function updateSkills(): Promise { return parseSkillsStatus(await invokeDesktopCommand("update_skills")); } export async function uninstallSkills(): Promise { return parseSkillsStatus(await invokeDesktopCommand("uninstall_skills")); }