import { invoke } from "@tauri-apps/api/core"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; export type PaneId = number; /** What to spawn into a fresh PTY. Mirrors the Rust `SpawnSpec` enum. */ export type SpawnSpec = | { kind: "wsl"; distro?: string; cwd?: string } | { kind: "powershell" } | { kind: "ssh"; host: string; user?: string; port?: number; identityFile?: string; jumpHost?: string; extraArgs?: string[]; /** Backend uses this to look up a saved password from keyring at * spawn time. Never echoed back to the frontend. */ hostId?: string; }; /** One saved SSH host. Mirrors the Rust `SshHost` struct (plus the * `hasPassword` flag that the backend sets when listing). */ export interface SshHost { id: string; label: string; hostname: string; user?: string; port?: number; identityFile?: string; jumpHost?: string; extraArgs?: string[]; /** True iff a credential is stored under this host's id in the system * keyring. Set by the backend on `list_ssh_hosts`; the field is * ignored on `save_ssh_hosts` (use the password commands below). */ hasPassword?: boolean; } export const listDistros = (): Promise => invoke("list_distros"); /** Ask the backend whether any built-in "watched" process (currently just * `claude`) is running in the given WSL distro. Cached per-distro for ~3s * on the Rust side. Fail-safe: probe failures resolve to `true` so the * caller suppresses the idle indicator. Only meaningful for WSL panes — * PowerShell + SSH should skip this and fall back to always-notify. */ export const isWatchProcessRunning = (distro: string): Promise => invoke("is_watch_process_running", { distro }); export const spawnPane = (args: { spec: SpawnSpec; cols: number; rows: number; }): Promise => invoke("spawn_pane", args); export const writeToPane = (id: PaneId, dataB64: string): Promise => invoke("write_to_pane", { id, dataB64 }); export const resizePane = (id: PaneId, cols: number, rows: number): Promise => invoke("resize_pane", { id, cols, rows }); export const killPane = (id: PaneId): Promise => invoke("kill_pane", { id }); export const onPaneData = ( id: PaneId, cb: (b64: string) => void, ): Promise => listen<{ b64: string }>(`pane://${id}/data`, (e) => cb(e.payload.b64)); export const onPaneExit = ( id: PaneId, cb: () => void, ): Promise => listen(`pane://${id}/exit`, () => cb()); // ---- workspace persistence ------------------------------------------------- export const saveWorkspace = (json: string): Promise => invoke("save_workspace", { json }); export const loadWorkspace = (): Promise => invoke("load_workspace"); // ---- SSH hosts ------------------------------------------------------------- export const listSshHosts = (): Promise => invoke("list_ssh_hosts"); export const saveSshHosts = (hosts: SshHost[]): Promise => invoke("save_ssh_hosts", { hosts }); /** Store / replace the saved password for this host id. Plaintext is * IPC'd to the Rust side (in-process, no disk hop) and immediately * written to Windows Credential Manager (DPAPI). */ export const setHostPassword = (hostId: string, password: string): Promise => invoke("set_host_password", { hostId, password }); export const deleteHostPassword = (hostId: string): Promise => invoke("delete_host_password", { hostId }); export const hasHostPassword = (hostId: string): Promise => invoke("has_host_password", { hostId }); // ---- MCP server ----------------------------------------------------------- export interface McpStatus { running: boolean; url: string | null; token: string | null; } /** Shape of the cached mirror we push to the backend on every workspace * change. Mirrors src-tauri/src/mcp.rs `McpMirror`. */ export interface McpMirror { layoutJson: string; /** Only includes leaves with mcpAllow === true. */ leaves: Record; hosts: McpMirroredHost[]; } export interface McpMirroredLeaf { paneId: number | null; label?: string; shellKind: "wsl" | "powershell" | "ssh"; distro?: string; sshHostId?: string; broadcast: boolean; active: boolean; } export interface McpMirroredHost { id: string; label: string; hostname: string; user?: string; port?: number; hasPassword: boolean; } export const mcpStart = (): Promise => invoke("mcp_start"); export const mcpStop = (): Promise => invoke("mcp_stop"); export const mcpStatus = (): Promise => invoke("mcp_status"); export const mcpRegenerateToken = (): Promise => invoke("mcp_regenerate_token"); export const mcpUpdateState = (mirror: McpMirror): Promise => invoke("mcp_update_state", { mirror }); // ---- MCP audit log (events) --------------------------------------------- export interface McpAuditEntry { tsMs: number; tool: string; argsSummary: string; // already truncated to 80 chars by backend result: | { kind: "ok" } | { kind: "denied"; reason: string; hard: boolean } | { kind: "failed"; msg: string }; durationMs: number; } export interface McpActionRequest { requestId: string; tool: string; args: unknown; needsConfirm: boolean; reason: string | null; } // ---- MCP policy --------------------------------------------------------- export interface McpPolicy { version: number; permissions: { deny: string[]; ask: string[]; allow: string[]; }; /** SSH-specific capability switches; mirrors Rust SshSafeguards. All * default to false on first load. */ sshSafeguards: { allowOpenSsh: boolean; autoAllowSpawnedSsh: boolean; allowAddHost: boolean; }; } export const mcpPolicyLoad = (): Promise => invoke("mcp_policy_load"); export const mcpPolicySave = (policy: McpPolicy): Promise => invoke("mcp_policy_save", { policy }); /** Compiled-in hard-deny rule labels (the patterns the user CANNOT * override). Loaded once at PolicyTab mount; backend is the SoT. */ export const mcpHardDenyLabels = (): Promise => invoke("mcp_hard_deny_labels"); /** Subscribe to MCP action requests from the backend. Each request is a * tool call the frontend must handle (mutate state) and reply to via * {@link mcpActionReply}. */ export const onMcpRequest = ( cb: (req: McpActionRequest) => void, ): Promise => listen("mcp://request", (e) => cb(e.payload)); /** Reply to an MCP action request. The Rust side expects an externally- * tagged Result — `{ Ok: }` on success, `{ Err: }` on * failure or user rejection. */ export const mcpActionReply = ( requestId: string, result: { Ok: unknown } | { Err: string }, ): Promise => invoke("mcp_action_reply", { requestId, result });