375e4c5c92
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
254 lines
8.4 KiB
TypeScript
254 lines
8.4 KiB
TypeScript
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<string> {
|
|
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<string> {
|
|
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<void> {
|
|
await invoke("input", { surfaceId, data: Array.from(data) });
|
|
}
|
|
|
|
export async function resizeSurface(surfaceId: string, cols: number, rows: number): Promise<void> {
|
|
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<AttachResult> {
|
|
const channel = new Channel<number[]>();
|
|
channel.onmessage = (msg) => onOutput(new Uint8Array(msg));
|
|
return await invoke<AttachResult>("attach", { surfaceId, onOutput: channel });
|
|
}
|
|
|
|
export async function detachSurface(surfaceId: string): Promise<void> {
|
|
await invoke("detach", { surfaceId });
|
|
}
|
|
|
|
export async function getStatus(): Promise<WorkspaceStatus[]> {
|
|
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<void> {
|
|
await invoke("mark_read", { target });
|
|
}
|
|
|
|
export async function clearEvents(): Promise<void> {
|
|
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<DaemonEvt>("spacesh:evt", (e) => handler(e.payload));
|
|
}
|
|
|
|
export async function focusSurface(surfaceId: string): Promise<void> {
|
|
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<StatusResult> {
|
|
return await invoke<StatusResult>("status");
|
|
}
|
|
|
|
export async function splitSurface(surfaceId: string, dir: "right" | "down", command?: string, args: string[] = []): Promise<string> {
|
|
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<void> {
|
|
await invoke("set_ratios", { workspaceId, nodePath, ratios });
|
|
}
|
|
|
|
export async function moveSurface(surfaceId: string, targetSurfaceId: string, edge: "left" | "right" | "top" | "bottom"): Promise<void> {
|
|
await invoke("move_surface", { surfaceId, targetSurfaceId, edge });
|
|
}
|
|
|
|
export async function applyPreset(workspaceId: string, presetId: string, slots: { command?: string; args?: string[] }[]): Promise<string[]> {
|
|
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<void> {
|
|
await invoke("restart_surface", { surfaceId, resume });
|
|
}
|
|
|
|
export async function closeWorkspaceCmd(workspaceId: string): Promise<void> {
|
|
await invoke("close_workspace", { workspaceId });
|
|
}
|
|
|
|
export async function setWorkspaceMeta(workspaceId: string, meta: { name?: string; groupId?: string | null; unread?: boolean; order?: number; pinned?: boolean }): Promise<void> {
|
|
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<string> {
|
|
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<void> {
|
|
await invoke("set_group", { groupId, name: meta.name ?? null, color: meta.color ?? null, order: meta.order ?? null });
|
|
}
|
|
|
|
export async function deleteGroup(groupId: string): Promise<void> {
|
|
await invoke("delete_group", { groupId });
|
|
}
|
|
|
|
export async function closeSurfaceCmd(surfaceId: string): Promise<void> {
|
|
await invoke("close_surface", { surfaceId });
|
|
}
|
|
|
|
export interface DaemonHealth { version: string; build?: string; pid: number; started_at_ms: number }
|
|
|
|
export async function getHealth(): Promise<DaemonHealth> {
|
|
return await invoke<DaemonHealth>("health");
|
|
}
|
|
|
|
export interface UpdateInfo { current: string; latest: string; has_update: boolean; url: string }
|
|
|
|
export async function checkUpdate(): Promise<UpdateInfo> {
|
|
return await invoke<UpdateInfo>("check_update");
|
|
}
|
|
|
|
export async function openExternal(url: string): Promise<void> {
|
|
await invoke("open_external", { url });
|
|
}
|
|
|
|
export async function listFonts(): Promise<string[]> {
|
|
return await invoke<string[]>("list_fonts");
|
|
}
|
|
|
|
/** Which of the given CLI candidates are actually installed on the daemon's spawn PATH. */
|
|
export async function whichAgents(candidates: string[]): Promise<string[]> {
|
|
const data = await invoke<{ available: string[] }>("which_agents", { candidates });
|
|
return data.available;
|
|
}
|
|
|
|
export async function setZoom(workspaceId: string, surfaceId: string | null): Promise<void> {
|
|
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<ConfigView> {
|
|
return await invoke<ConfigView>("get_config");
|
|
}
|
|
|
|
export async function setConfig(patch: Partial<Pick<ConfigView, "default_shell" | "font_family" | "font_size" | "theme" | "accent">>): Promise<void> {
|
|
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<void> {
|
|
try { await invoke("shutdown_daemon"); } catch { /* connection drops as the daemon exits — expected */ }
|
|
}
|
|
|
|
export async function restartDaemon(): Promise<void> {
|
|
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 */ }
|
|
}
|