# Automation: CLI and MCP BashCut can be driven by agents, scripts and the built-in terminal dock. This guide covers how they connect, and every command they can run. The design rationale is in [05 — Agent integration](../specs/05-agent-integration.md). ## Overview The app runs one local JSON-RPC server on a Unix socket. Two thin clients talk to it: - `bashcut`, a command-line tool bundled with the app. - `bashcut-mcp`, a stdio MCP server built with the official MCP Swift SDK. It exposes each command as a tool named `bashcut__` (for example `bashcut_timeline_get`). Every result is the tool's text: string results as they are, everything else as compact JSON. There is no `structuredContent` (the SDK re-decodes it slowly, and compact text is also fewer tokens for the agent). While BashCut runs, the tool list also has one `bashcut_action_` tool per installed plugin action, with the action's parameters as its input schema; calling it is `plugins run --params …`. At most 40 are listed, so hundreds of plugins do not crowd out the built-in tools: actions available with the current selection come first, then the most recently run. Every other action is still found with `plugins actions ` and run with `bashcut_plugins_run`, and its `bashcut_action_…` name still works. Both clients forward to the same handlers the UI uses. Edits go through the validated `EditOperation` and history path, so permissions, revision checks, audit records and undo behave the same whether a change comes from a click, the CLI or MCP. ### Socket protocol - The socket is `~/Library/Application Support/BashCut/automation.sock`; set `BASHCUT_SOCKET` to use another path. The folder is `0700` and the socket `0600`. Run one BashCut instance per socket. - Each connection carries one newline-delimited JSON-RPC 2.0 request with `id`, `method`, `params` and an optional `token`. Messages are limited to 8 MiB. The client waits 30 s for an answer (longer for calls that wait on their own work: `jobs.wait`, `review.run --measure`, `review.verify`, `export.start --wait`, `project.create --import`, `color.measure`, `agent.ask`); a late answer is reported as a retryable failure. - Metadata-only audit records go to `~/Library/Application Support/BashCut/audit.jsonl`. Request contents and credentials are never recorded. - This is a local automation service, not a security boundary against other software already running as the same user. Common error codes: | Code | Meaning | |---|---| | `-32001` | The command needs a live session token | | `-32002` | Stale revision: re-read the timeline and retry | | `-32003` | Busy: a dialog is open, another approval is pending, or the action is not available now | | `-32004` | The edit is outside the attached scope, and the user rejected it or the scope guard blocks it | | `-32601` | Unknown command | | `-32602` | Invalid or missing parameters, or a rejected edit | Every error's `data` also carries `category`, `retryable` and, where a factual next step exists, `remediation` (`{command?, hint}`); fields a command sets itself (such as a stale revision's `expected` and `actual`) stay. Categories: `malformed`, `invalid_request`, `unknown_command`, `invalid_arguments`, `permission`, `stale_revision`, `out_of_scope`, `unavailable`, `capability_missing` (no plugin provider can serve; `data.reason` is `missing`, `not_configured` or `unhealthy` and `data.providers` lists each provider's state, as `capabilities get` does), `unsupported_media` (this Mac cannot decode a media's video; `data.media` lists it with its codec and timeline frames), `internal`, and for `-32003`: `busy_dialog` (answer or close the open dialog: `ui dialog`), `busy_approval` (an earlier request waits for the user), `busy_running` (the same work is running), `file_conflict`, `not_available_now`, and the workflow guards for agents (never the user): `audit_missing` (a final export without a passing draft audit of the current timeline or the user's G5 approval, or G2 without a strategy audit), `recipe_unread` (G2 before the plan's recipe skill was read) and `graphics_draft` (a final export over graphics a plugin rendered as drafts, `data.graphics.draft`; `data.drafts` lists each media, its name and the items that use it) and `stage_pending` (review done, G5 or a final export while a stage the plan marks required before review is pending or started; `data.stages`), each with `remediation.command` such as `review packet --point draft` or `bin/graphic plan --final`. `context get` › `recentFailures` lists your session's failures of the last 15 minutes and how many in a row repeat. ## Terminal dock Open a terminal with **Agent → + → Claude terminal / Codex terminal / Shell terminal**. Each tab is a real SwiftTerm terminal. Claude and Codex use your installed CLI and its existing login. - **Workspace.** Choose a workspace before opening a tab. Claude runs in the workspace folder. Codex runs in `~/Library/Application Support/BashCut/agent-workspace`; the project path and project knowledge reach it through the session context. - **Knowledge.** The book button opens the **Knowledge** window, which can stay open while agents work and shows their changes as they happen. Its sections are Lessons, Preferences, Project facts, Notes (the memos) and Skills; `ui view --knowledge-section
` switches between them. The project memo and project skills live in the project folder (`.bashcut/agent-memory.md`, `.bashcut/skills`, linked into the project's `.claude/skills` and `.agents/skills`), never in the workspace or home folder, so each project keeps its own. **Notes for every project** (`~/Library/Application Support/BashCut/Knowledge/agent-memory.md`) hold your taste and rules that every project reads. Without a saved project only those notes are available. A memo that older versions kept in the workspace or home folder is offered once: move it to the notes for every project (`knowledge migrate`) or to this project (`knowledge migrate --to project`). Agents write the notes with `knowledge memo FILE --scope user`, which asks for your approval. A memo with text is offered once for a split into lessons, preferences and facts: **Ask the agent to split it** puts the request in the open agent's input, and the agent runs `knowledge split-memo ENTRIES.json [--scope user]` (`{"lessons": [{"title", "symptom", "cause", "fix", "tags"}], "prefs": [{"key", "value"}], "facts": [{"key", "value"}]}`). What it finds waits in the **Inbox**; the memo stays as notes. **Keep as notes** (`knowledge split-memo --keep`) stops the offer. - **Lessons, preferences and facts.** Next to the memos, agents keep structured knowledge as JSON files in the same two places (`.bashcut/knowledge/` in the project, `Knowledge/` for every project): - **Lessons** (`knowledge lessons`, `add-lesson`, `update-lesson`, `remove-lesson`): a title, the symptom, its cause, what to do next time, evidence and tags. Each has a status: `active` lessons are followed, `proposed` ones wait for your review, `disabled` ones are kept for the record. The source records which agent wrote it, in which session and when. Each lesson also counts its sightings: `occurrences` (1 when recorded), `lastSeen` and `sources` (the projects it was seen in); `update-lesson ID --seen` counts one more, without approval even for a lesson for every project, since it changes no rule. - **Preferences** (`knowledge prefs`, `set-pref`): your taste as key/value pairs, for every project by default. A value set for one project wins over the one for every project. `confirmedBy` says whether you said or approved it (`user`) or an agent inferred it (`inferred`, the default for agents; `set-pref --confirmed-by user` when you said it in the chat). Approving an agent's proposal makes it `user`. - **Facts** (`knowledge facts`, `set-fact`): people, places, footage notes and what was approved, for this project, with optional `evidence` {`source` (URL, file, frame, user), `measure` (the command or result path), `n` (sample size), `status` measured, estimated or user}: `set-fact KEY VALUE --status measured --measure "review measure shots.median" --n 48`. Plans and skills refer to a fact by its key. - **Proposals** (`knowledge proposals`, `approve`, `reject`): what waits in the Knowledge window's **Inbox**. A lesson an agent records for every project always starts as a proposal (ID `l-…`); approving makes it active, rejecting removes it. An agent's preference change for every project (`set-pref`) is not applied at once: it waits as a value proposal (ID `p-…`, kept in that scope's `proposals.json`) and `set-pref` returns `{"approval": "proposed", "proposal": …}`. A newer proposal for the same key replaces the older one. `approve p-… --value TEXT` applies your edited value instead. Kit change proposals (lessons tagged `kit`, written by the `bc:self-learn` skill) show their diff. When Settings lets agents act without confirmation, preferences are applied at once as before. - **History** (`knowledge history [--kind] [--target]`, `revert`): every change to lessons, preferences, facts, memos and project skills, newest first, with who made it, the entry before and after and a line `diff` (`history.jsonl`). The Knowledge window's **History** section shows the diff; **Revert…** or `knowledge revert ` puts the entry back to how it was before that change, undoing later changes to it too, and is recorded as a change of its own. Rejecting a preference proposal changed nothing, so it has nothing to revert. - **Claims** (`knowledge claims`, `set-claim KEY --text "528 ha" --source URL|MEDIA@SECONDS|user [--remove]`): the numbers, prices, superlatives and names the video states, each with its source (`.bashcut/knowledge/claims.json`). Review lists those in titles (not captions) and in `plan.cut.voiceover` lines that no claim's text covers, as `unsourced-claim` at info severity, with the words found by type. Any `plan.cut` entry may cite what it rests on with `facts: [key]`; `plan check` warns `unknown-fact` for a key that is neither a project fact (`set-fact`) nor a claim, so a typo or a fact never recorded shows before the build. - **Creator profile**: preferences under `creator.` (`name`, `handle`, `channel`, `platforms`, `language`, `niche`, `audience`, `tone`, `cta`, `voiceSamples` as media IDs of the open project, `disclosure`; lists comma-separated) are checked by `set-pref` and shown as one `creator` object in the session summary. An agent's change to them for every project always waits in the Inbox, even when agents may act without confirmation. - **Verdicts** (`knowledge verdicts [--verdict] [--subject] [--limit]`): what you did with agents' changes in this project, as BashCut saw it, in `.bashcut/knowledge/verdicts.jsonl`: `keep` (you dismissed the change notice, or your next edit left the agent's items alone), `undo` (you undid the agent's step, also an older one), `edit` (your next edit changed the same items) and `reject` (Reject on an edit the scope guard held; Allow is a `keep`). Each row names the command that made the change (`timeline.apply`, `library.apply`, `library.place`, `plan.apply`…, or `edit`), its label, the agent, the revision and the items; counts are per command and verdict. Facts only: `bc:self-learn` decides what they mean. - **Skills** (`skills list [--scope project|user|plugin|kit]`, `get`, `save`, `enable`, `disable`, `remove`, `propose`): the agent kit's skills are read-only; `skills propose --summary TEXT` sends the line diff against the kit's SKILL.md to the Inbox as a lesson for every project tagged `kit`. Skills you or agents write live in the project (`.bashcut/skills/`, linked into the project's `.claude/skills` and `.agents/skills`) or, with `--scope user`, for every project (`Application Support/BashCut/Knowledge/skills/`, listed with its path in BashCut agents' knowledge). `disable` turns one off without deleting it: a project skill is unlinked, a skill for every project gets a `.disabled` marker and is left out of the agents' knowledge. Saves and removals are in History, so `knowledge revert` brings a deleted skill back. Skills that trusted, enabled plugins ship (API 7) are in the read-only `plugin` scope as `:`: listed in BashCut agents' knowledge with their paths and linked into the project's agent folders as `--` while the plugin is ready (see [Agent skills](plugins.md#agent-skills)); copy one with `skills get` and `skills save` to change it. Without a scope, `skills get` looks in the project, then every project, then plugins, then the kit. The Knowledge window's **Skills** section lists every group with an editor and Markdown preview. An agent's request that otherwise changes knowledge for every project (memo, lesson edits and removals, skills for every project, reverts), or approves or rejects a proposal, asks for your approval. Changes to this project's lessons and facts do not. The book button in the agent dock shows an orange count while proposals wait (clicking it opens the Inbox) and a dot when agents changed knowledge since you last closed the window. In the Knowledge window, Lessons can be searched, filtered by scope, status and tag, and sorted newest or oldest first (`knowledge lessons --query --scope --status --tag --sort`). Select a lesson to edit its fields, approve or reject a proposal, enable or disable it, or delete it (history keeps it). Preferences and facts are edited in place. Each entry shows which agent and session added it and when, and entries changed since you last opened the window carry a **New** badge. Your changes are recorded with the source `user`. Every agent session starts with a short summary: the active lessons (at most 20, this project's first, with their sightings), the preferences (marked when inferred) and the project facts (with status and n), and how many proposals wait for you. Proposed and disabled lessons are not in it. `context get` returns the same summary as `knowledge`, so an agent outside BashCut's terminals sees it too. - **Resuming.** When BashCut has found the last Claude or Codex conversation for the project, the dock offers **Continue Claude/Codex** or **New conversation**. You never see or type session IDs. - **Codex defaults.** Codex starts idle on `gpt-5.6-luna` with low reasoning effort, under a named permission profile that extends its workspace profile with the BashCut folder and the exact BashCut socket. It does not get a broad socket allowlist. - **Requests.** The quick actions under the agent (Survey footage, Rough cut, Write VO… depending on the project) and the quick-start cards paste a short request into the terminal for you to review before pressing Enter; agents read the selection and playhead themselves with `context get`. **New brief** (⌘K, or the "Tell Claude Code what to make…" bar under a terminal tab) opens a sheet with 25 request templates, your recent and saved briefs, and Send, which pastes the request and presses Enter. It can attach the current viewer frame: BashCut renders a bounded PNG into `.bashcut/cache/agent-context`, keeps the ten newest frames and passes the absolute path to the terminal. - **Footage.** The **Footage** row shows **Project media · N** and takes files and folders (**Add files or folder…**, or drop them on the sheet). They are sent as a last line, `Footage to import: …`, for the agent to import. Templates made from what was shot ask for footage in orange while there is none. - **To.** The menu next to Send picks the agent: an open agent tab or chat agent, or **Start new** for Claude Code, Codex or a terminal plugin. Without a pick the brief goes to the agent tab or chat on show, else the default agent's tab, else it starts the default agent (**Start & send**); never to a Shell tab. **Settings → Agents → Default agent** is **Last used** by default: the agent you last opened or sent a brief to, else Claude Code. - **Waiting.** A starting or busy agent gets the brief only once it takes input (it printed, stayed quiet for 0.8 s and shows no menu). When the CLI shows a menu, the sheet closes so you can answer it in the dock, and the brief follows. Cancel stops a waiting Send; a failure stays in the sheet with the brief. - **Folder trust.** Before Claude Code or Codex starts in a folder it does not trust yet, BashCut asks in its own sheet, and for Claude Code also accepts Bypass Permissions mode, so the CLIs' own menus do not show (see [Dialogs and UI actions](#dialogs-and-ui-actions)). - **Switching projects.** Tabs stay open when the project changes, also when their own agent creates or opens one (before, the switch closed the tab mid-task). Every token, in-app or external, must then read the new project (`context get`, `timeline get` or `project get`) before its next edit; until then edits fail with "The open project changed to …", so an agent cannot apply what it remembers of the old project. Open conversations are bookmarked for the new project too, so **Continue** there resumes them. Each child process gets a filtered environment plus: - `BASHCUT_SOCKET`, `BASHCUT_PROJECT` and an in-memory `BASHCUT_SESSION_TOKEN`; - a `PATH` that includes the bundled `bashcut` and `bashcut-mcp`; - for Claude and Codex, a temporary MCP server definition on the command line. No MCP config or token is written into the project. Claude does not inherit `ANTHROPIC_API_KEY`, so it uses your CLI login. Shell tabs edit as the user; Claude and Codex tabs have their own authors. Closing a tab revokes its token. Keep tokens out of scripts, logs and project files. **Settings → Agents → Agent permissions → Allow agent timeline edits** is on by default. When it is off, Claude and Codex tabs get no token, so they can read and point at things but cannot edit. **Settings → Agents → Agent permissions → Dangerously allow all agent actions** (on by default; it asks once when turned back on, and no command can change it) overrides the switches above it: agents get edit tokens, privileged requests are approved at once, the scope guard is off, and plugin actions that declare `confirm` run without the dialog when an agent starts them (audited as `plugin.action..auto-approved`). Installing and trusting plugins still needs the user. `context get` reports what agents may do as `agentPermissions` (`{edits, autoApprove, scopeGuard, allowAll}`). ## Agent kit The [agent kit](https://github.com/dongnguyenvie/bashcut-agent-kit) is a set of editing skills (footage survey, beat cuts, audio mix, captions, colour, effects, voiceover…) for Claude Code and Codex. BashCut ships a copy in `Contents/Resources/AgentKit` (`scripts/bundle-agent-kit.sh`, run by `scripts/run.sh` and the Xcode build, copies the tracked files of a `bashcut-agent-kit` checkout next to the repo, or `$BASHCUT_AGENT_KIT`). **Settings → Agents** and `agent status` / `agent setup` manage it. The kit is the plugin `bc`, so Claude Code and Codex show its skills as `bc:audio-mix`, `bc:edit-workflow`, … (kits before 0.1.0 were the `bashcut` plugin, `bashcut:bashcut-`). - **Finding it.** The agent dock shows a setup card, "Set up BashCut for Claude Code · 1 of 2 done", the first time it sees a kit version: ① **BashCut skills**, with **Install skills** or **Update** (what `agent setup` does); an agent tab that was already running gets **Restart … tab**, which starts it again on the same conversation so the CLI loads the new skills. ② **Editing tools** (captions, Vietnamese voice, motion graphics, stock search), with **Install…**, which opens Plugins › Browse with the Recommended bundle, like `/bc:setup` without the agent. Steps that do not apply are left out of the count. **What's this?** opens Settings → Agents (`ui action show.agent-kit`; also Agent › Agent Skills… and ☰), and **Later** (`ui action agent.kit-later`) folds the card into a **Setup 1/2** chip in the dock header until BashCut has a newer kit. The UI actions are `agent.setup-show`, `agent.kit-install`, `agent.tools-install` and `agent.restart-tab`; `ui view` reports the card as `agentSetup`. - **Kit updates.** Settings → Agents checks the kit's signed releases (`releases.json` on bashcut-agent-kit's `main`) and offers **Download & Update** (`agent kit-check`, `agent kit-update`; the update asks for approval). BashCut installs a release only over HTTPS from GitHub, with a first-party ed25519 signature checked before the download and the SHA-256 after it, into `~/Library/Application Support/BashCut/agent-kits/`. The newer of that download and the built-in kit is used; a chosen folder is never updated. After installing, Claude Code and Codex are refreshed where the kit is set up. Claude Code caches the plugin per version, so its row shows "Older kit set up" until **Update** runs. - **BashCut's tabs** load it by default (`agent setup in-app`, `--remove` to stop). The built-in kit is copied to `~/Library/Application Support/BashCut/agent-kit`. Claude tabs get a skills-only plugin (`agent-kit-claude`, passed with `--plugin-dir`; the tab already has the BashCut MCP server). Codex tabs see the skills as links (`bc-`) in `agent-workspace/.agents/skills`, which holds only the kit's skills. - **Another kit folder** (a checkout being worked on): **Choose…** in Settings or `agent setup in-app --kit /abs/path` (`--kit built-in` to go back). It is used in place, so edits show up in the next tab. - **Claude Code and Codex outside BashCut**: `agent setup claude` adds the kit as the `bashcut-agent-kit` marketplace and installs the `bc` plugin (skills and MCP server), uninstalling the old `bashcut` plugin; `agent setup codex` links the skills into `~/.agents/skills` as `bc-` (never over a folder that is not a link), removes the old kit's `bashcut-` links, and runs `codex mcp add bashcut`. An old `bashcut` plugin shows as an older kit until **Update** runs. `--remove` undoes either. From the CLI this asks for approval like an export, because it changes the agents' own configuration. - **Configuration folders.** Claude Code and Codex may keep their login and settings elsewhere (`CLAUDE_CONFIG_DIR`, `CODEX_HOME`), often set in a shell profile that BashCut does not see when it starts from Finder. Each folder comes from Settings (`--claude-config-dir`, `--codex-home`; `default` detects again), else BashCut's environment, else the login shell (`$SHELL -ilc`, read once at launch), else `~/.claude` / `~/.codex`. The result is used for setup, for the tabs' environment and for finding their sessions to resume. `agent status` shows each folder and where it came from. ## Agents outside BashCut Claude Code in another terminal, scripts and other MCP clients need no copied token. While BashCut runs, it writes a fresh token to `~/Library/Application Support/BashCut/automation-token` with mode `0600`, next to the `0600` socket, so the file grants nothing beyond what can already open the socket. The CLI and `bashcut-mcp` read it whenever `BASHCUT_SESSION_TOKEN` is not set. - Edits are attributed to `agent` and get the same change markers and Undo toast as Claude and Codex. - Unlike in-app terminal tokens, this token survives project switches. - BashCut deletes the file when it quits. **Settings → Agents outside BashCut** turns it off or issues a new token. - Exports still wait for your approval in the app. ## Version and updates `app version` returns this copy's version, build, plugin API and how it was installed: `homebrew`, `direct` (a zip or dmg), `app-store` (App Store or TestFlight) or `development` (`scripts/run.sh`). `app update-check` asks GitHub for the latest release (`api.github.com/repos///releases/latest`, from `BCRepositoryURL`) and returns `updateAvailable`, the `latest` release, `update` (the Homebrew command or the release page) and `canUpdateNow`. It never installs anything, and App Store copies are not checked. `app update` installs that release on a Homebrew copy, like **Update Now** in Software Update (also `ui respond update` while the `updates` sheet is open); the user approves it first. Homebrew updates its taps and downloads the release (`brew update`, `brew fetch --cask bashcut`) while BashCut runs, then BashCut saves the project, quits, and a helper runs `brew upgrade --cask bashcut` and opens the new version (log: `~/Library/Logs/BashCut/update.log`). A failure before quitting keeps BashCut running and `app update-check` reports `installError`; a failed upgrade is reported the same way on the next launch. Downloaded copies update from the release page. The UI does the same in **BashCut › Check for Updates…** (`ui action show.updates`, also ☰ and Settings › General). Homebrew and downloaded copies also check once a day when a project opens (**Settings › General › Check for BashCut updates daily**). A newer release then opens Software Update by itself, once per launch, as the `updates` sheet with the options `update` (**Update Now**, Homebrew copies), `skip` (**Skip This Version**, `ui action app.update-skip`: no reminders until a newer release), `later` (**Remind Me Later**, `ui action app.update-later`: not before the next day) and `close`. Until a release is skipped, ☰ shows a dot and an **Update to BashCut …** item. The last answer is remembered, so the prompt and the dot work without asking GitHub at every launch. `ui action show.about` opens About BashCut. Development builds (`scripts/run.sh`) check only on demand. To try the prompt, start one with `BASHCUT_UPDATE_FEED=` (a file shaped like GitHub's release JSON: `tag_name`, `html_url`, `published_at`, `body`; read instead of GitHub) and `BASHCUT_UPDATE_INSTALL=homebrew` or `direct`, for example `open --env BASHCUT_UPDATE_FEED=/tmp/release.json --env BASHCUT_UPDATE_INSTALL=homebrew build/BashCut.app`. Update Now runs in a development build only with `BASHCUT_UPDATE_BREW=