Cockpit · workflow
Tutorial · Cockpit

Layouts and tasks in Cockpit

Two small files turn “set up my environment” into one click: a .ckp layout that opens the right terminals in the right folders, and a .cockpit/tasks.json that runs your dev servers with buttons, profiles, and reload on save. Both live in the repository, so your teammates — and every new git worktree — get the same setup for free.

What you'll build

A small monorepo with an API and a web app. By the end, opening the project gives you three terminals in the right folders, a task list with both servers ready to play, and a new worktree that recreates the whole thing by itself.

project layout — text
my-app/
├── .cockpit/
│   └── tasks.json     # what runs
├── dev.ckp            # what opens
├── api/
│   └── package.json
└── web/
    └── package.json

Everything here is Cockpit-local: no account, no mesh, no agents required. If you want agents on top of this, follow An agent team in Cockpit next.

Before you start

  • Cockpit installed — macOS, Windows, or Linux, from the download page.
  • A project folder open as a workspace. Anything with a couple of subfolders works; the commands below assume npm, but the runner is generic.

1. Write your first layout

Create dev.ckp at the root of the project. A layout is YAML, and the smallest useful one is three lines:

dev.ckp — yaml
panes:
  - name: Agent
    cwd: .
    command: claude

Right-click the file in Cockpit's tree and choose Open layout. A tab named Agent opens at the project root and types claude for you. Apply it again: nothing happens. A pane whose name already exists is skipped, so a layout is safe to re-run and never closes anything.

The name is the label

name becomes the tab's stable label — the one the cockpit CLI can address by name instead of by a boot-scoped id, and the same label you would otherwise set by double-clicking the tab.

2. Add splits and folders

Now the real geometry. cwd is relative to the .ckp file (forward slashes only, so the file works on every OS), and split says where each pane is born relative to the one created before it: tab (default), right, or down.

dev.ckp — yaml
# dev.ckp — one layout, committed with the project
autorun: worktree
panes:
  - name: Agent
    cwd: .
    command: claude
  - name: API
    cwd: api
    split: right
  - name: Web
    cwd: web
    split: down

You can also apply it from inside any Cockpit terminal, which is how an agent sets up its own workspace:

Cockpit terminal
$
cockpit orchestrate dev.ckp
autorun: worktree

That one line is the payoff. Every time you fork the workspace onto a fresh git worktree, Cockpit applies the layout by itself — and since a new worktree starts empty, the splits come out exactly as written. Keep a single autorun file at the root: with two or more, none of them runs, because ambiguity is never guessed.

3. Declare your tasks

Cockpit already detects tasks from package.json and pubspec.yaml with no configuration — open the Tasks panel and your scripts are there. You write .cockpit/tasks.json when you want more than that: a monorepo, interactive keys, environment profiles.

.cockpit/tasks.json — jsonc
{
  // .cockpit/tasks.json — JSONC: comments and trailing commas are fine
  "tasks": [
    {
      "label": "api",
      "cwd": "api",
      "command": "npm",
      "args": ["run", "dev"],
      "kind": "watch",
      "interactiveKeys": [
        { "key": "q", "label": "Quit", "icon": "stop" }
      ]
    },
    {
      "label": "web",
      "cwd": "web",
      "command": "npm",
      "args": ["run", "dev"],
      "kind": "watch",
      "profiles": [
        { "name": "dev" },
        { "name": "staging", "env": { "API_URL": "https://staging.example.com" } }
      ]
    }
  ]
}

The file goes at the root of the workspace you open — discovery is literal, with no walking up the tree. Each task's cwd is relative to that file, which is what lets one file at the monorepo root drive every subpackage. Hit play and the output streams into its own tab, with stop and restart controls and any interactiveKeysyou declared as buttons that write straight to the process' stdin.

profiles are launch configs: a chip cycles them before you press play, appending args and merging env. The runner stays generic on purpose — a Flutter flavor or a --dart-define is just more args.

Reload on save

For a process that does not watch files itself (flutter run is the classic case), add a watch block: paths to observe, ignore to keep build folders from looping, and onChange pointing at an interactiveKeys label ("Hot reload") or "__restart__". Skip it for Vite or Next, which already reload on their own.

4. Let an agent use them

Both files are reachable from the internal cockpitCLI, which exists only inside Cockpit's terminals. That means an agent in one tab can start your dev server, read what it printed, and open a file for you to look at — without a screenshot or a screen scrape.

Cockpit terminal
$
# what can I run here?
cockpit list-tasks

# tail the dev server's output
cockpit read-task json:api --lines 80

# open a worker tab beside me and drive it
id=$(cockpit new-tab --cwd web --title Web --split h)
cockpit send --tab-id "$id" --enter "npm run build"

Task ids are stable per workspace: json:<label> for the ones you declared, npm:<script> and flutter:run for detected ones. The full command surface is in the Cockpit reference.

5. Commit them

Both files belong in git. dev.ckp is your working geometry, and .cockpit/tasks.json is how the project is run — the same reason a Makefile is committed. A teammate clones, opens the folder in Cockpit, and gets your terminals and your task list. Point $schema at tasks.schema.json from the repository and their editor will autocomplete the fields as well; Cockpit ignores the field when running.

From here: put a team of agents into those panes, or read the Cockpit reference for every field, flag, and the theme format.