GenerateSaaS

Terminal and side panel

The right side panel's two built-in tabs - Chat and Terminal - how to choose which ship and which opens first, and what the Terminal gives the user's own CLI.

The right side panel hosts two built-in surfaces. Chat is the conversational assistant (Chat). Terminal launches the user's OWN coding CLI (Claude Code or Codex) in an embedded terminal, in the app's work folder and wired to the user's own MCP servers - so the user drives the real CLI right where the app works.

Terminal runs on the bundled daemon. The CLI runs on the user's machine, driven by the companion daemon - the app spawns the daemon's terminal command in a real pty and shows it. That daemon ships inside the packaged app, so nothing needs publishing and nothing needs pairing: the tab appears whenever config.desktop.agents.enabled is on, whether or not your product also offers the companion as a web feature. The daemon it runs on comes from generating the project with --desktop --ai - see the AI agents overview for why that half is not a runtime flag.

Choose which tabs ship

Set config.desktop.agents.sidePanel to pick which of the two built-in tabs ship and which opens first. Both ship by default, with Chat as the default tab.

// packages/config/src/desktop.mjs
agents: {
  // ...
  sidePanel: { chat: true, terminal: true, default: "chat" }
}
FieldTypeDefaultEffect
chatbooleantrueWhether the Chat tab ships. Set false to hide it.
terminalbooleantrueWhether the Terminal tab ships. Set false to hide it. (It also needs a companion.)
default"chat" | "terminal""chat"Which tab opens the first time the panel is shown.
  • Only one tab: set the other to false. The panel then renders as a single surface (for example { chat: false, terminal: true, default: "terminal" } for a terminal-only app).
  • No panel: set both false. The panel and its trigger disappear entirely.
  • Default vs. the user's choice: default only picks the tab shown on first open. Once the user switches tabs, their choice is remembered across restarts and wins over default. If default names a disabled tab, the first available tab opens instead.

The whole side panel is gated on config.desktop.agents.enabled. With the AI feature off the panel, both tabs, and their trigger never render - the source still ships, so turning the flag on brings them back.

What the Terminal wires in

The app names only the CLI to run, the terminal's size, and (optionally) a folder the user granted. The rest of the session is composed by the user's companion, on their machine:

  • Their own MCP servers: any server the user added with the daemon's mcp add --local is wired into the session.
  • A working folder: the product's confined work folder by default, or one of the user's own folders once they have allowed it (below).

Nothing extra is asked of the user: the CLI resolves its own login exactly as it does in a plain terminal. A session is recorded in the daemon's local audit log before the CLI starts, so <daemon> log always shows why it ran.

Env-backed MCP servers are terminal-only. A server added with mcp add --local that carries a credential in its environment (--env KEY=VALUE) runs in a Terminal session - the session spawns the CLI and re-hydrates the value - but a local chat run skips it, because the on-device executor cannot inject an environment. <daemon> mcp list --local marks such a server [terminal-only], so the difference is visible where the user manages them.

Working folder, and allowing your own

By default a session runs in the product's confined work folder - the same one dispatched runs use. To work on a real project, the user picks a folder with Choose a folder..., which opens the OS's native dialog.

Picking a folder also allows it. The daemon refuses any working folder the user has not granted on that machine, so the app shows the exact path it is about to allow and says plainly that the grant is durable: every later session may use that folder, until the user removes it. Allowed folders are listed in the tab, each with a way to stop allowing it. Your backend can never add, name, or widen a grant - the session it composes cannot carry a path at all.

Approval prompts

A Terminal session is the user's own CLI on their own machine, so what keeps it in its folder is the CLI's native approval prompts - and the user's companion policy ceiling decides whether those stay on:

CeilingWhat the user sees
fullThe CLI runs with its prompts bypassed (it acts freely, like the built-in chat), and nothing then keeps it in the folder it started in.
auto-edit / read-onlyThe daemon withholds the bypass and the CLI keeps asking before it edits or runs.
network: offA terminal session is refused: the CLIs have no flag that blocks egress, so a policy promising the network is off must not open an online terminal.

The tab shows the effective mode read-only. The ceiling is the user's to set (<daemon> policy set), never the app's - your product can only ever be clamped by it.

An older companion

The daemon updates on the user's schedule, so the app will meet companions installed before the terminal command existed. It probes for the capability (the version the daemon reports in its status) and, when it is missing, the tab shows the one command that fixes it - <daemon> update - instead of controls that would only print a usage banner into the terminal.

On this page