Getting Started

Install Ouijit and launch your first project in under a minute.

Requirements

  • macOS 13+ (Ventura) or Linux (x64)
  • Git 2.20+

Download & Install

Download the latest release for your platform:

Unzip the archive and move Ouijit.app to your Applications folder (macOS) or extract the binary to a location on your PATH (Linux).

Building from source

Requires Node.js 20+ and C/C++ build tools for native modules (better-sqlite3, node-pty, koffi):

  • macOS: xcode-select --install
  • Linux: sudo apt install build-essential python3
git clone https://github.com/ouijit/ouijit.git
cd ouijit
npm install
npm start

First Launch

When you first open Ouijit you'll see the home view with a sidebar listing your projects on the left. The main area shows terminal sessions grouped by project, empty on first launch until you add a project and start working.

Adding a project

  1. Click the + button in the sidebar.
  2. Select a folder containing a git repository.

Opening a project

Click any project to open it. You'll start on the kanban board, and opening a task launches its terminal.

Worktree Isolation

Every task gets its own git worktree, so you can work on multiple features simultaneously without branch switching.

What are git worktrees?

Git worktrees let you check out multiple branches of the same repository into separate directories at the same time. Each worktree has its own working directory with its own checked-out branch.

Ouijit creates a worktree when you start a task, so each piece of work gets its own directory and branch.

Task lifecycle

A task has one of four statuses: To Do, In Progress, In Review, Done. Each event below changes the status and may run a hook.

EventStatusWhat Ouijit does
Create → To Do Adds a task card to the To Do column.
Start To Do → In Progress Creates a branch (e.g. T-42) and a worktree at ~/Ouijit/worktrees/<project>/T-42, opens a terminal in it, runs the start hook.
Continue In Review or Done → In Progress Opens a new terminal in the existing worktree, runs the continue hook.
Review → In Review Runs the review hook.
Done → Done Runs the done hook.
Delete Task removed Moves the worktree to the trash, deletes the branch.

Copy-on-Write cloning

Ouijit clones worktrees using Copy-on-Write (CoW) via the native file system (APFS on macOS, reflinks on Linux with btrfs/XFS). Disk blocks are shared with the original until a file is modified.

  • Worktree creation is fast regardless of repo size.
  • Disk usage is proportional to your actual changes, not the full repo.
  • Gitignored files (dependencies, build caches) are cloned too, so the worktree is ready to run.

Creation modes

Each project picks how new worktrees are populated. Set this in Project Settings → Worktree.

ModeWhat lands in the worktreeWhen to pick it
Quick start (default) Tracked files plus a Copy-on-Write clone of every gitignored path (node_modules, .env, build caches). You want a worktree that's ready to run immediately with no setup step.
Clean checkout Just git worktree add. Only tracked files appear. You want a reproducible setup driven by your Start hook (e.g. npm ci), or you don't want gitignored secrets copied across tasks.

Sandboxed tasks always use clean checkout, regardless of this setting. Provision the VM-side environment via the Sandbox Setup hook.

Directory structure

~/Ouijit/worktrees/
  my-project/
    T-42/                # Worktree for task 42
    T-43/                # Worktree for task 43

Worktrees live under ~/Ouijit/worktrees/, organized by project name.

Manual worktree operations

Ouijit manages worktrees for you, but they're standard git worktrees. You can inspect them with regular git commands:

# List all worktrees
git worktree list

# From inside a worktree, check which branch it's on
git branch --show-current

Kanban Board

A four-column board for organizing tasks with drag-and-drop.

Columns

The four columns map to the four task statuses: To Do, In Progress, In Review, Done. Dragging a card between columns changes its status, which can fire a hook. See Task lifecycle for the mapping.

Creating tasks

Click the + button on any column header, or press ⌘N (Ctrl+N on Linux) to create a new task. Give it a title and optional description.

Each task gets an auto-incrementing number (e.g. T-1, T-2) used for branch names and worktree directories.

Drag and drop

Drag task cards between columns to change their status. You can also reorder tasks within a column.

When dragging a task from To Do to In Progress, Ouijit automatically creates a git worktree and branch for the task.

Task actions

Right-click a task card to access its context menu:

  • Open in Terminal. Launch a terminal in the task's worktree.
  • Open in Sandbox. Launch a sandboxed terminal (if Lima is available).
  • Open in Editor. Open the worktree in your configured editor.
  • Rename. Edit the task's display name inline.
  • Move to Done / Reopen. Complete an active task or reopen a finished one.
  • Delete. Permanently remove the task.

If a task already has running terminals, they'll appear at the top of the menu so you can jump straight to them.

Terminal Sessions

Integrated terminal sessions with a card stack UI for multi-terminal workflows.

Card stack

Terminals are displayed as stacked cards. The active terminal is in front; background terminals peek out behind it so you can see what's running.

Each terminal is a full PTY session running your default shell, opened in the task's worktree directory.

Opening terminals

When you start a task (move it to In Progress), a terminal opens automatically in the task's worktree. You can open additional terminals for the same task: one for a dev server, one for an editor, one for an agent, or any combination you need.

Keyboard shortcuts

Click on a background terminal card to bring it to the front, or use keyboard shortcuts:

ShortcutAction
⌘I / Ctrl+INew terminal
⌘W / Ctrl+WClose terminal
⌘1⌘9 / Ctrl+1Ctrl+9Select terminal by position
⌘⇧← / Ctrl+Shift+LeftPrevious stack page
⌘⇧→ / Ctrl+Shift+RightNext stack page
⌘T / Ctrl+TToggle between kanban board and terminal stack
⌘N / Ctrl+NShow kanban board and focus new task input
⌘P / Ctrl+PToggle runner panel
⌘D / Ctrl+DToggle diff panel

Runner panel

A secondary terminal alongside the main stack, for long-running processes like dev servers. Toggle it with ⌘P (Ctrl+P on Linux) or the Run button. The runner executes the project's run hook (e.g. npm run dev).

Diff panel

Shows what changed in the current worktree relative to the target branch. Files are listed with per-file diffs organized by hunk. Toggle it with ⌘D (Ctrl+D on Linux).

Tags

Tag terminals to organize them. Tags appear on terminal cards, and the home view can group by tag.

Working with CLI agents

Each terminal is a normal shell, so any CLI tool runs in it. Claude Code, Codex, and Pi get live status indicators and can drive Ouijit through the CLI.

VM Sandbox

Sandboxed terminals run inside a Linux VM, on a parallel git branch, in a worktree that contains only tracked files.

What it does

For a sandboxed task, Ouijit creates a second git worktree at ~/Ouijit/sandbox-views/<project>/T-N-sandbox on a child branch s/<your-branch>. Because it's a plain git worktree add, only tracked files exist there: no .env, node_modules, build caches, or anything else gitignored. That sandbox-view directory is what gets mounted into a Lima VM as the agent's working directory. Your real worktree is never mounted.

When the agent commits inside the VM, the commit lands on s/<your-branch>. A host-side watcher fast-forwards your branch to match. If you committed in parallel on the host, the fast-forward fails and Ouijit surfaces a divergence affordance.

How it works

  1. Enable sandbox on a task. Per-task, not per-project. The run dialog has a Sandbox toggle.
  2. First terminal. Ouijit lazily creates an Ubuntu 24.04 VM via Lima on the first sandboxed spawn for a project. One VM per project, reused across that project's sandboxed tasks.
  3. Spawn progress. The dialog shows each step: Checking VM status, Creating / Starting, SSH connecting, Provisioning, Launching shell. First boot takes 1-2 minutes; later spawns reuse the running VM.
  4. Shell sessions. Terminals run inside the VM via limactl shell, with cwd set to the sandbox-view path.
  5. VM lifecycle & resources. Manage the VM from Project Settings → Sandbox: see status (Running / Stopped / Broken / Not created), create, start, stop, recreate, open a console, and edit the merged Lima YAML config (memory, CPU, disk, mounts). Resource changes prompt a recreate.

Mount surface

Only two host paths are exposed to the VM:

Host pathIn guestWhy
~/Ouijit/sandbox-views/<proj>/Read/writeThe agent's tracked-files-only worktree.
<project>/.git/Read-only base, with read/write overlays at objects/, refs/, logs/, worktrees/Commits need to write packs, refs, and reflogs. hooks/, config, and packed-refs stay read-only.

Your home directory, source tree, system binaries, and any other host paths are not mounted at all.

Isolation properties

  • Gitignored content is not in the mount. .env, .npmrc, secrets/, node_modules, and build artifacts never leave the host.
  • Host toolchains stay separate. darwin-arm64 node_modules, Rust target/, and Python .venv don't collide with the Linux guest.
  • Parallel sandboxed tasks on the same project share the VM but work in separate sandbox-view directories.
  • .git/hooks/ and .git/config are read-only in the mount, so writes from the VM that would land there fail with EROFS.
  • No ouijit CLI in the VM. The binary isn't installed; the /api/* routes return 403 for sandbox-scoped tokens. Only the /hook endpoint accepts them.

Caveats

  • Committed files are always visible. The sandbox view is populated from the index, so anything you committed lives there regardless of .gitignore. To remove: git rm --cached <file> and rotate.
  • Commits round-trip through fast-forward. Parallel commits on the host cause the ff-merge to fail; Ouijit surfaces a divergence affordance in that case.
  • The VM has no git credentials. Pushes happen from the host. Commits staged inside the VM ride the next host-side push.
  • The VM's $HOME is out of scope. The isolation described above applies to mounts. Files copied into the VM's home (~/.netrc, ~/.git-credentials, etc.) aren't.

Customizing the VM

VMs start from a clean Ubuntu 24.04 image with bash, git, curl, wget, nodejs, npm, python3, and build-essential pre-installed. Defaults are 4 GiB memory, 2 CPUs, 10 GiB disk.

Resource limits, extra packages, additional mounts, and provisioning scripts are controlled by the Lima YAML config in Project Settings → Sandbox. The editor shows the merged effective config and lets you write overrides; Ouijit prompts you to recreate the VM when changes require it.

See the Lima configuration reference for the available keys.

Requirements

Ouijit bundles limactl; nothing extra to install.

  • macOS. Apple's Virtualization.framework (vz) with vzNAT networking.
  • Linux. QEMU with user-mode networking.

Limitations

  • First boot downloads an Ubuntu cloud image and provisions packages. Requires internet and takes 1-2 minutes.
  • GPU passthrough is not supported.
  • macOS-specific tooling (Xcode, Swift) is not available inside the Linux VM.
  • Docker-in-VM requires additional Lima configuration.
  • The VM home directory is ephemeral. Anything outside the mounted sandbox-views and .git overlays is lost on recreate.

Hooks

Shell commands that run at key moments in a task's lifecycle. Configure per project.

Lifecycle hooks

These fire when a task changes status. Configure them from the kanban column headers.

HookWhen it runsExample
StartTask moves from To Do to In Progressnpm install && claude "$OUIJIT_TASK_PROMPT"
ContinueReopening a task already In Progressclaude -c
ReviewTask moves to In Reviewgh pr create --fill
DoneTask moves to Donegit push origin HEAD

Other hooks

These run on demand rather than on lifecycle transitions. Configure them from the header bar.

HookWhat it doesExample
RunCommand for the Run button (dev server, etc.)npm run dev
EditorOpens the worktree in your editoropen -a "Visual Studio Code"

Environment variables

Hooks receive these env vars:

OUIJIT_PROJECT_PATH=/path/to/my-project
OUIJIT_WORKTREE_PATH=~/Ouijit/worktrees/my-project/T-42
OUIJIT_TASK_BRANCH=T-42
OUIJIT_TASK_NAME="Add user auth"
OUIJIT_TASK_PROMPT="implement oauth login"
OUIJIT_HOOK_TYPE=start

A common pattern is wiring the start hook to an agent: claude "$OUIJIT_TASK_PROMPT" launches Claude Code with the task description as its prompt the moment a task moves to In Progress.

Agents

Claude Code, Codex, and Pi get live status indicators and can drive Ouijit through its CLI. No configuration required.

What you get

Run claude, codex, or pi in any Ouijit terminal and two things happen automatically:

  • Live status. The terminal card shows whether the agent is thinking or idle, visible across the entire terminal stack. You can tell which sessions need attention without bringing each one to the front.
  • CLI awareness. The ouijit CLI reference is appended to the agent's system prompt. The agent can create tasks, flip statuses, manage hooks and scripts, and update plans from the shell, the same as you would.

How it works

Ouijit installs a thin wrapper for each supported agent into the terminal's PATH. The wrapper shadows the real binary, injects the CLI reference into the agent's system prompt, and wires status hooks back to Ouijit. The mechanism differs per agent:

AgentStatus wiringSystem prompt
Claude Code --settings with UserPromptSubmit / Stop hooks --append-system-prompt-file
Codex -c hooks.<Event>=... overrides, pre-trusted so they fire without /hooks approval -c developer_instructions=...
Pi --extension loading a generated TypeScript extension that subscribes to turn_start / turn_end --append-system-prompt

When OUIJIT_API_URL is unset (i.e. the agent runs outside an Ouijit terminal), the wrappers exec the real binary unmodified.

Sandboxed terminals

Status indicators work inside the VM sandbox: Claude settings, Codex config, and the Pi extension are written into the VM's home directory, and their hooks reach back over host.lima.internal.

The CLI integration does not extend into the sandbox. The ouijit binary isn't installed in the VM, and the /api/* routes reject sandbox-scoped tokens.

Notifications

When an agent finishes a turn, Ouijit plays a sound. If the window isn't focused, you also get an OS notification.

CLI

Drive Ouijit from inside any terminal it spawns. Create tasks, flip statuses, manage hooks, run scripts, all from the shell.

Where it lives

The ouijit command is auto-installed into every terminal Ouijit opens, host and sandboxed alike. It talks to the running app over a localhost API authenticated by a per-session token, so it only works from inside an Ouijit terminal.

Output format

Every successful command writes a single JSON value to stdout and exits 0. Errors write {"error": "..."} to stderr and exit non-zero. Pipe to jq for grepping fields:

ouijit task list | jq '.[] | select(.status == "in_progress") | .name'

Project detection

By default the CLI auto-detects the active project from your current directory by walking up to the nearest git root. If you're inside a worktree it follows the .git file back to the main repo. Override with --project <path>.

Tasks

Statuses are todo, in_progress, in_review, and done.

ouijit task list                              # array of tasks
ouijit task get 5                             # single task
ouijit task current                           # task owning this terminal
ouijit task create "Fix login bug"
ouijit task create "Refactor auth" --prompt "Extract middleware"
ouijit task start 5                           # creates worktree, sets in_progress
ouijit task start 5 --branch custom-name
ouijit task create-and-start "Add 2FA"        # create + start in one step
ouijit task spawn "Add 2FA"                   # alias for create-and-start
ouijit task set-status 5 in_review
ouijit task set-name 5 "Better name"
ouijit task set-description 5 "More detail"
ouijit task set-merge-target 5 develop
ouijit task delete 5

ouijit task current resolves the task owning the current terminal via the OUIJIT_PTY_ID env var that Ouijit sets on every spawn. Useful inside agent scripts:

TASK=$(ouijit task current | jq -r .taskNumber)
ouijit task set-status "$TASK" in_review

Hooks

CRUD for the lifecycle hooks. Hook types are start, continue, run, review, done, editor.

ouijit hook list
ouijit hook get start
ouijit hook set start --name "Install & agent" --command 'npm install && claude "$OUIJIT_TASK_PROMPT"'
ouijit hook delete done

Tags

Tag tasks for organization in the home view.

ouijit tag list                               # all tags across projects
ouijit tag list --task 5                      # tags on one task
ouijit tag add 5 bug
ouijit tag remove 5 bug
ouijit tag set 5 bug priority urgent          # replace all tags

Scripts

Scripts are saved shell commands that show up in the runner panel. Set them once, then trigger from the UI or the CLI. Unlike hooks, scripts are ad-hoc: they don't fire on lifecycle events.

ouijit script list
ouijit script set --name "Lint" --command "npm run lint"
ouijit script set --name "Tests" --command "npm test"
ouijit script run Lint                        # by name
ouijit script run <id>                        # by id
ouijit script run Tests --task 5              # run inside task 5's worktree
ouijit script delete <id>

script run streams the command's output through your terminal and exits with the command's exit code. script set upserts by name, so re-running with the same --name updates the existing script's command.

Plan files

Each terminal can have a Markdown plan file associated with it. The app shows the plan inline so an agent and you can collaborate on a shared TODO list. The CLI is how the agent updates that association.

ouijit plan set ./plan.md                     # uses current OUIJIT_PTY_ID
ouijit plan set ./plan.md pty_abc123          # explicit pty id
ouijit plan get
ouijit plan unset

Projects

List every project registered with the app.

ouijit project list

Environment variables

Ouijit sets these on every terminal it spawns. Your scripts and agents can read them directly:

VariableWhat it is
OUIJIT_API_URLBase URL for the local REST API the CLI talks to.
OUIJIT_API_TOKENBearer token scoped to this terminal session.
OUIJIT_PTY_IDUnique id for this terminal session. Used by task current and plan.

Hooks get extra context on top of these. See Hooks → Environment variables for the full list of OUIJIT_TASK_* variables available to start, continue, review, and done commands.

Patterns

Move a task to review when an agent finishes

Add this to the end of an agent's workflow to advance the task automatically:

TASK=$(ouijit task current | jq -r .taskNumber)
ouijit task set-status "$TASK" in_review

Tag a task with the language it touches

TASK=$(ouijit task current | jq -r .taskNumber)
ouijit tag add "$TASK" typescript

Create and start a task in one call

The start hook fires immediately, so any agent invocation in that hook runs as part of creation:

ouijit task create-and-start "Add 2FA" --prompt "Implement TOTP-based 2FA"