import { invoke, Channel } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; import type { Group, WorkspaceView } from "./layoutTypes"; export interface WorkspaceStatus { workspace_id: string; path: string; surfaces: string[]; } export async function openWorkspace(path: string): Promise { const data = await invoke<{ workspace_id: string }>("open", { path }); return data.workspace_id; } export async function newSurface( workspaceId: string, cols: number, rows: number, command?: string, args: string[] = [] ): Promise { const data = await invoke<{ surface_id: string }>("new_surface", { workspaceId, command: command ?? null, args, cols, rows, }); return data.surface_id; } export async function sendInput(surfaceId: string, data: Uint8Array): Promise { await invoke("input", { surfaceId, data: Array.from(data) }); } export async function resizeSurface(surfaceId: string, cols: number, rows: number): Promise { await invoke("resize", { surfaceId, cols, rows }); } export interface AttachResult { snapshot: string; cols: number; rows: number; cursor_row?: number; cursor_col?: number; stopped?: boolean; } export async function attachSurface( surfaceId: string, onOutput: (bytes: Uint8Array) => void ): Promise { const channel = new Channel(); channel.onmessage = (msg) => onOutput(new Uint8Array(msg)); return await invoke("attach", { surfaceId, onOutput: channel }); } export async function detachSurface(surfaceId: string): Promise { await invoke("detach", { surfaceId }); } export async function getStatus(): Promise { const data = await invoke<{ workspaces: WorkspaceStatus[] }>("status"); return data.workspaces; } export interface EventRecord { id: number; surface_id: string; workspace_id: string; workspace_name: string; agent_label: string | null; kind: "done" | "wait" | "error" | "exit"; ts: number; read: boolean; } export type MarkReadTarget = | { target: "all" } | { target: "ids"; value: number[] } | { target: "surface"; value: string }; export async function getEventLog(limit?: number): Promise<{ events: EventRecord[]; unread: number }> { return await invoke<{ events: EventRecord[]; unread: number }>("event_log", { limit: limit ?? null }); } export async function markEventsRead(target: MarkReadTarget): Promise { await invoke("mark_read", { target }); } export async function clearEvents(): Promise { await invoke("clear_events"); } export type DaemonEvt = | { evt: "exit"; data: { surface_id: string; code: number } } | { evt: "surface_created"; data: { surface_id: string; workspace_id: string } } | { evt: "surface_closed"; data: { surface_id: string } } | { evt: "state"; data: { surface_id: string; state: import("./layoutTypes").SurfaceState } } | { evt: "layout_changed"; data: { workspace_id: string } } | { evt: "workspace_changed"; data: unknown } | { evt: "groups_changed"; data: unknown } | { evt: "config_changed"; data: { config: ConfigView } } | { evt: "event"; data: { record: EventRecord } } | { evt: "events_read"; data: { ids: number[] } } | { evt: "events_cleared"; data: unknown }; export function onDaemonEvent(handler: (evt: DaemonEvt) => void): Promise<() => void> { return listen("spacesh:evt", (e) => handler(e.payload)); } export async function focusSurface(surfaceId: string): Promise { await invoke("focus", { surfaceId }); } export function onDaemonRawEvent(name: string, handler: () => void): Promise<() => void> { return listen(name, () => handler()); } // ---- M2 additions ---- export interface StatusResult { groups: Group[]; workspaces: WorkspaceView[]; } export async function getStatusFull(): Promise { return await invoke("status"); } export async function splitSurface(surfaceId: string, dir: "right" | "down", command?: string, args: string[] = []): Promise { const data = await invoke<{ surface_id: string }>("split_surface", { surfaceId, dir, command: command ?? null, args }); return data.surface_id; } export async function setRatios(workspaceId: string, nodePath: number[], ratios: number[]): Promise { await invoke("set_ratios", { workspaceId, nodePath, ratios }); } export async function moveSurface(surfaceId: string, targetSurfaceId: string, edge: "left" | "right" | "top" | "bottom"): Promise { await invoke("move_surface", { surfaceId, targetSurfaceId, edge }); } export async function applyPreset(workspaceId: string, presetId: string, slots: { command?: string; args?: string[] }[]): Promise { const data = await invoke<{ surface_ids: string[] }>("apply_preset", { workspaceId, presetId, slots: slots.map((s) => ({ command: s.command ?? null, args: s.args ?? [] })), }); return data.surface_ids; } export async function restartSurface(surfaceId: string, resume = false): Promise { await invoke("restart_surface", { surfaceId, resume }); } export async function closeWorkspaceCmd(workspaceId: string): Promise { await invoke("close_workspace", { workspaceId }); } export async function setWorkspaceMeta(workspaceId: string, meta: { name?: string; groupId?: string | null; unread?: boolean; order?: number; pinned?: boolean }): Promise { await invoke("set_workspace_meta", { workspaceId, name: meta.name ?? null, groupId: meta.groupId === undefined ? null : meta.groupId, unread: meta.unread ?? null, order: meta.order ?? null, pinned: meta.pinned ?? null, }); } export async function createGroup(name: string, color: string): Promise { const data = await invoke<{ group_id: string }>("create_group", { name, color }); return data.group_id; } export async function setGroup(groupId: string, meta: { name?: string; color?: string; order?: number }): Promise { await invoke("set_group", { groupId, name: meta.name ?? null, color: meta.color ?? null, order: meta.order ?? null }); } export async function deleteGroup(groupId: string): Promise { await invoke("delete_group", { groupId }); } export async function closeSurfaceCmd(surfaceId: string): Promise { await invoke("close_surface", { surfaceId }); } export interface DaemonHealth { version: string; build?: string; pid: number; started_at_ms: number } export async function getHealth(): Promise { return await invoke("health"); } export interface UpdateInfo { current: string; latest: string; has_update: boolean; url: string } export async function checkUpdate(): Promise { return await invoke("check_update"); } export async function openExternal(url: string): Promise { await invoke("open_external", { url }); } export async function listFonts(): Promise { return await invoke("list_fonts"); } /** Which of the given CLI candidates are actually installed on the daemon's spawn PATH. */ export async function whichAgents(candidates: string[]): Promise { const data = await invoke<{ available: string[] }>("which_agents", { candidates }); return data.available; } export async function setZoom(workspaceId: string, surfaceId: string | null): Promise { await invoke("set_zoom", { workspaceId, surfaceId }); } // ---- Settings ---- export interface ConfigView { default_shell: string; font_family: string; font_size: number; theme: "dark" | "light"; accent: string; } export async function getConfig(): Promise { return await invoke("get_config"); } export async function setConfig(patch: Partial>): Promise { await invoke("set_config", { defaultShell: patch.default_shell ?? null, fontFamily: patch.font_family ?? null, fontSize: patch.font_size ?? null, theme: patch.theme ?? null, accent: patch.accent ?? null, }); } export async function shutdownDaemon(): Promise { try { await invoke("shutdown_daemon"); } catch { /* connection drops as the daemon exits — expected */ } } export async function restartDaemon(): Promise { await shutdownDaemon(); // Let the old process exit; the next request triggers the bridge's // ensure_daemon respawn (or launchd KeepAlive) and reconnects. await new Promise((r) => setTimeout(r, 600)); try { await getHealth(); } catch { /* reconnect loop will retry */ } }