Commands and events
The frontend talks to the Rust backend through Tauri's invoke, and receives pushed state through events. This is the whole surface, which is useful when you're reading the code or extending it. The typed wrappers live in src/ipc.ts, the handlers in src-tauri/src/commands.rs.
What the UI receives#
RepoNode { repoId, name, path, worktrees: WorktreeNode[] }
WorktreeNode { wtKey, branch, path, isMain, git | null, dbName | null, services: ServiceNode[] }
ServiceNode { svcKey, serviceId, name, kind, port | null, status }
GitMeta { ahead, behind, dirty, lastCommitTs, lastCommitMsg }
wtKey = the worktree's absolute path
svcKey = `${wtKey}::${serviceId}`
status ∈ stopped | starting | running | stopping | error
kind ∈ web | server | worker
Commands#
Tree and settings#
Command Signature Notes get_tree() → RepoNode[]Full hydrate. refresh(wtKey?) → voidRescan everything, or one worktree. get_settings() → Settingssave_settings(newSettings) → voidadd_repo(path) → RepoCfgRegisters a repository. detect_repo(path) → RepoDetectionName, branch, origin, stack, package.json scripts. remove_repo(repoId) → voidStops tracking. Touches nothing on disk. get_repo_config(repoId) → RepoConfigFileReads .worktreemanager.json. save_repo_config(repoId, provision, setup) → voidWrites it. save_text_file(path, contents) → voidUsed by config export.
Worktrees#
Command Signature Notes create_worktree({repoId, branch, base?, createBranch}) → pathadd → submodules → provision → setup. run_worktree_setup(wtKey) → voidRe-provision and re-run setup. remove_worktree(wtKey, deleteBranch, dropDb) → voidremove_worktrees(wtKeys[], deleteBranch, dropDb) → voidThe multi-select path. list_prunable_worktrees() → PrunableWorktree[]Folders deleted on disk. prune_worktrees(items[]) → voidPer item: branch and database choices. switch_worktree_branch(wtKey, branch, create, base?) → voidIn-place switch. worktree_dirty_report(wtKey) → {dirty, details[], total}Up to ten paths, submodules included. worktree_status(wtKey) → StatusEntry[]Resolved git status --porcelain rows. worktree_commit(wtKey, message, addUntracked) → voidworktree_stash(wtKey, name?, includeUntracked) → summaryworktree_discard(wtKey, cleanUntracked) → void
Git and submodules#
Command Signature Notes git_pull(wtKey) → summary--ff-only, then advances submodules.submodule_status(wtKey) → SubmoduleStatus[]branch, sha, dirty, ahead-of-pin. pull_submodule(wtKey, path) → summaryswitch_submodule_branch(wtKey, path, branch) → voidlist_submodule_branches(wtKey, path) → Branchesfetch_submodules(wtKey) → countsync_submodules(wtKey) → summaryRe-pin to the recorded commits. list_branches(repoId) → Brancheslocal, remote, tags. fetch_branches(repoId) → BranchesFetch, then list.
Services#
Command Signature service_start / service_stop / service_restart(svcKey) → voidworktree_start_all / worktree_stop_all(wtKey) → voidset_service_port(svcKey, port) → void, validated 1024–65535get_logs(svcKey) → LogLine[], the ring buffer snapshot
Databases#
Command Signature list_databases(wtKey) → string[]current_database`(wtKey) → string \ null` switch_database(wtKey, dbName) → voidsnapshot_database(wtKey, name) → voidexport_database(wtKey, filePath) → voidrestore_database(wtKey, filePath) → voidreset_db(wtKey) → voidrun_migration(wtKey) → void
Terminals and agents#
Command Signature Notes terminal_open(id, cwd, cols, rows, command?) → voidIdempotent per id. terminal_write(id, data) → voidterminal_resize(id, cols, rows) → voidterminal_get_buffer`(id) → {buffer, seq} \ null` Race-free rehydrate. terminal_close(id) → voidwrite_worktree_context(wtPath, contents) → voidWrites .canopy/context.md and a self-ignoring .gitignore. resolve_agent_command(wtKey) → string
Shell-outs and windows#
Command Signature run_custom_command(wtKey, command) → voidopen_in_editor(wtKey) → voidopen_file_in_editor(wtKey, path) → voidreveal_in_finder(wtKey) → voidopen_terminal(wtKey) → voidopen_port(port) → voidshow_main_window() → voidquit_app() → void
Events#
Pushed from Rust to whichever windows are listening. High-volume events are filtered to the windows that consume them, and suppressed entirely while every window is hidden.
Event Payload Meaning tree:changedRepoNode[]A structural change: repo, worktree, service or settings. service:status{svcKey, status, startedAt?, exitCode?}A service's lifecycle moved. service:log{svcKey, lines[]}Batched log lines, every ~200 ms. service:stats{entries: [{svcKey, cpu, memMb, uptimeSec}]}The 2-second stats poll. worktree:git{wtKey, ahead, behind, dirty, lastCommitTs, lastCommitMsg}Git metadata refreshed. reset:status{wtKey, state, message?}Database reset progress. worktree:op{wtKey, op, state, detail}create / remove / setup / migrate / snapshot progress, carrying the [k/n]: step markers. terminal:data{id, data (base64), seq}Raw PTY bytes. terminal:exit{id}A session's process ended. tray:new-worktree · tray:overview · tray:settings— Emitted by the popover for the main window to act on.
Errors#
Every command rejects with a structured error:
{ code: "git" | "db" | "setup" | "process" | "terminal" | "config"
| "not_found" | "invalid_input" | "conflict" | "internal",
message: string }
The frontend reads it with errText() and errCode() instead of String(e), so a plugin's plain-string rejection and a backend error both come out useful.
Detecting the backend#
hasBackend() checks for __TAURI_INTERNALS__ on window. It's false in a plain browser, which is what enables mock mode , and why features that need the backend say so with a toast instead of failing silently.
On this page