Skip to content
This repository was archived by the owner on Apr 15, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions REFACTOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document provides context for implementing the session-based refactor. Dele

## Summary

We are introducing **sessions** as a new abstraction layer between users and instances. A session is a persistent, attachable/detachable process (shell, agent CLI, etc.) running within an instance, managed by Zellij.
We are introducing **sessions** as a new abstraction layer between users and instances. A session is a persistent, attachable/detachable process (shell, agent CLI, etc.) running within an instance, managed by tmux.

## Why

Expand All @@ -21,7 +21,7 @@ The original design allowed multiple `resume` calls to the same instance but pro
| Concept | Definition |
|---------|------------|
| Instance | Git worktree + container (unchanged) |
| Session | A Zellij-managed process within an instance (new) |
| Session | A tmux-managed process within an instance (new) |

An instance can have zero or more sessions. Sessions persist across detach/attach cycles.

Expand Down Expand Up @@ -82,7 +82,7 @@ This is implemented by teeing stdout/stderr when spawning the session process.

### Technology

Sessions are implemented using [Zellij](https://zellij.dev/). Zellij handles:
Sessions are implemented using [tmux](https://github.com/tmux/tmux). tmux handles:
- Terminal multiplexing
- Attach/detach mechanics
- Process lifecycle within sessions
Expand Down Expand Up @@ -119,7 +119,7 @@ After:
"id": "sess-abc",
"name": "happy-panda",
"type": "claude",
"zellij_session": "hjk-<instance-id>-sess-abc",
"tmux_session": "hjk-<instance-id>-sess-abc",
"created_at": "2025-12-30T10:00:00Z",
"last_accessed": "2025-12-30T14:30:00Z"
}
Expand All @@ -135,16 +135,16 @@ After:

- `internal/cmd/` — Replace `new.go`, `resume.go`, `list.go` with `run.go`, `attach.go`, `ps.go`, `logs.go`, `kill.go`
- `internal/catalog/` — Add session tracking, `last_accessed` updates
- `internal/instance/` — Session CRUD operations, Zellij integration, log file management
- `internal/instance/` — Session CRUD operations, tmux integration, log file management

### May Need Changes

- `internal/container/` — Ensure Zellij is available in containers
- `docs/designs/base-image.md` — Add Zellij to base image
- `internal/container/` — Ensure tmux is available in containers
- `docs/designs/base-image.md` — Add tmux to base image

### New Code Needed

- Zellij interaction layer (create session, attach, list, kill)
- tmux interaction layer (create session, attach, list, kill)
- Session name generator (word-based, Docker-style)
- Session logging layer (tee output to log files, read logs for `hjk logs`)

Expand Down
10 changes: 1 addition & 9 deletions docs/docs/reference/cli/attach.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Attaches to an existing session using a most-recently-used (MRU) strategy:

If no sessions exist for the resolved scope, the command displays an error suggesting `hjk run` to create one.

To detach from a session without terminating it, use the terminal multiplexer detach keybinding (for Zellij: `Ctrl+O, d`). This returns you to your host terminal while the session continues running.
To detach from a session without terminating it, use the tmux detach keybinding (`Ctrl+B, d`). This returns you to your host terminal while the session continues running.

## Arguments

Expand All @@ -33,14 +33,6 @@ To detach from a session without terminating it, use the terminal multiplexer de
| `branch` | Git branch name to filter by (optional) |
| `session` | Session name within the instance (optional, requires branch) |

## Flags

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand Down
8 changes: 0 additions & 8 deletions docs/docs/reference/cli/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,6 @@ This command runs the Codex login flow which:

The stored credentials use your ChatGPT Plus/Pro/Team/Enterprise subscription rather than API billing.

## Flags

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand Down
7 changes: 0 additions & 7 deletions docs/docs/reference/cli/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,6 @@ View and modify Headjack configuration:
|------|------|---------|-------------|
| `--edit` | bool | `false` | Open config file in `$EDITOR` |

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand All @@ -65,7 +59,6 @@ Common configuration keys:
|-----|------|-------------|
| `default.agent` | string | Default agent when `--agent` is used without a value |
| `default.base_image` | string | Default container base image |
| `default.multiplexer` | string | Default terminal multiplexer (`tmux`, `zellij`) |
| `storage.worktrees` | string | Directory for git worktrees |
| `storage.catalog` | string | Path to the instance catalog file |
| `storage.logs` | string | Directory for session logs |
Expand Down
8 changes: 0 additions & 8 deletions docs/docs/reference/cli/kill.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@ The argument must be in the format `<branch>/<session>`, where branch is the ins
|----------|-------------|
| `branch/session` | Combined branch and session name separated by `/` (required) |

## Flags

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand Down
6 changes: 0 additions & 6 deletions docs/docs/reference/cli/logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ Reads from the session's log file, useful for checking on detached agents withou
| `--lines` | `-n` | int | `100` | Number of lines to show |
| `--full` | | bool | `false` | Show entire log from session start |

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand Down
6 changes: 0 additions & 6 deletions docs/docs/reference/cli/ps.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,6 @@ Use `--all` to list instances across all repositories (only applies when listing
|------|-------|------|---------|-------------|
| `--all` | `-a` | bool | `false` | List instances across all repositories |

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Output

### Instance Listing
Expand Down
6 changes: 0 additions & 6 deletions docs/docs/reference/cli/recreate.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,6 @@ Useful when the container environment is corrupted or needs a fresh state. The w
|------|------|---------|-------------|
| `--base` | string | | Use a different base image for the new container |

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand Down
6 changes: 0 additions & 6 deletions docs/docs/reference/cli/rm.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@ Removes an instance entirely. This command:
|------|-------|------|---------|-------------|
| `--force` | `-f` | bool | `false` | Skip confirmation prompt |

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand Down
6 changes: 0 additions & 6 deletions docs/docs/reference/cli/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,6 @@ If an instance exists but is stopped, it is automatically restarted before creat
| `--base` | | string | | Override the default base image |
| `--detached` | `-d` | bool | `false` | Create session but do not attach (run in background) |

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand Down
8 changes: 0 additions & 8 deletions docs/docs/reference/cli/stop.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@ This command is useful for freeing up system resources when an instance is not a
|----------|-------------|
| `branch` | Git branch name of the instance to stop (required) |

## Flags

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand Down
8 changes: 0 additions & 8 deletions docs/docs/reference/cli/version.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ Displays the version, commit hash, and build date of the Headjack installation.

This command takes no arguments.

## Flags

### Inherited Flags

| Flag | Type | Description |
|------|------|-------------|
| `--multiplexer` | string | Terminal multiplexer to use (`tmux`, `zellij`) |

## Examples

```bash
Expand Down
5 changes: 0 additions & 5 deletions docs/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ Default values applied when creating new instances.
|-----|------|---------|-------------|
| `default.agent` | string | `""` (empty) | Default agent to use. Valid values: `claude`, `gemini`, `codex`. Empty means no default. |
| `default.base_image` | string | `ghcr.io/gilmanlab/headjack:base` | Container image to use for instances. Available variants: `:base` (minimal), `:systemd` (with init), `:dind` (with Docker). |
| `default.multiplexer` | string | `tmux` | Terminal multiplexer for session management. Valid values: `tmux`, `zellij`. |

### agents

Expand Down Expand Up @@ -76,7 +75,6 @@ A complete configuration file with all options:
default:
agent: claude
base_image: ghcr.io/gilmanlab/headjack:base
multiplexer: tmux

agents:
claude:
Expand Down Expand Up @@ -118,7 +116,6 @@ hjk config storage.worktrees

```bash
hjk config default.agent claude
hjk config default.multiplexer zellij
hjk config runtime.name apple
```

Expand All @@ -140,7 +137,6 @@ The following environment variables override their corresponding configuration k
|---------------------|-------------------|
| `HEADJACK_DEFAULT_AGENT` | `default.agent` |
| `HEADJACK_BASE_IMAGE` | `default.base_image` |
| `HEADJACK_MULTIPLEXER` | `default.multiplexer` |
| `HEADJACK_WORKTREE_DIR` | `storage.worktrees` |

## Validation
Expand All @@ -149,7 +145,6 @@ Headjack validates configuration values when loading and setting them:

- `default.agent` must be one of: `claude`, `gemini`, `codex` (or empty)
- `default.base_image` is required and cannot be empty
- `default.multiplexer` must be one of: `tmux`, `zellij`
- `runtime.name` must be one of: `podman`, `apple`
- All storage paths are required

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/images/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ base --> systemd --> dind
| Agent CLIs (Claude, Gemini, Codex) | Yes | Yes | Yes |
| Version managers (pyenv, nodenv, goenv, rustup) | Yes | Yes | Yes |
| Development tools (git, gh, vim, ripgrep, etc.) | Yes | Yes | Yes |
| Terminal multiplexer (Zellij) | Yes | Yes | Yes |
| Terminal multiplexer (tmux) | Yes | Yes | Yes |
| systemd init system | No | Yes | Yes |
| Docker CE | No | No | Yes |
| Docker Compose plugin | No | No | Yes |
Expand Down
22 changes: 0 additions & 22 deletions images/base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ ARG NODENV_VERSION=v1.6.2
ARG NODE_BUILD_VERSION=v5.4.22
ARG GOENV_VERSION=2.2.34
ARG RUSTUP_VERSION=1.28.2
ARG ZELLIJ_VERSION=0.43.1
ARG CLAUDE_CODE_VERSION=2.0.76
ARG GEMINI_CLI_VERSION=0.22.5
ARG CODEX_CLI_VERSION=0.77.0
Expand Down Expand Up @@ -130,27 +129,6 @@ RUN : "${TARGETARCH:?TARGETARCH is required for yq install}" && \
install -m 0755 "/tmp/${yq_binary}" /usr/local/bin/yq && \
rm -f "/tmp/${yq_binary}" /tmp/yq_checksums

# =============================================================================
# Zellij (terminal multiplexer)
# =============================================================================

RUN : "${TARGETARCH:?TARGETARCH is required for Zellij install}" && \
case "${TARGETARCH}" in \
amd64) zellij_arch="x86_64" ;; \
arm64) zellij_arch="aarch64" ;; \
*) echo "Unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \
esac && \
zellij_archive="zellij-${zellij_arch}-unknown-linux-musl.tar.gz" && \
zellij_checksums="zellij-${zellij_arch}-unknown-linux-musl.sha256sum" && \
curl -fsSL "https://github.com/zellij-org/zellij/releases/download/v${ZELLIJ_VERSION}/${zellij_archive}" -o "/tmp/${zellij_archive}" && \
curl -fsSL "https://github.com/zellij-org/zellij/releases/download/v${ZELLIJ_VERSION}/${zellij_checksums}" -o "/tmp/${zellij_checksums}" && \
tar -xzf "/tmp/${zellij_archive}" -C /tmp && \
expected_sha256="$(awk '{print $1}' "/tmp/${zellij_checksums}")" && \
actual_sha256="$(sha256sum /tmp/zellij | awk '{print $1}')" && \
[ "${expected_sha256}" = "${actual_sha256}" ] || { echo "Checksum mismatch"; exit 1; } && \
install -m 0755 /tmp/zellij /usr/local/bin/zellij && \
rm -f "/tmp/${zellij_archive}" "/tmp/${zellij_checksums}" /tmp/zellij

# =============================================================================
# Node.js (required for Agent CLIs)
# =============================================================================
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ The command uses an MRU strategy:
If no sessions exist for the resolved scope, the command errors with a message
suggesting 'hjk run' to create one.

To detach from a session without terminating it, use the Zellij keybinding
(default: Ctrl+O, d). This returns you to your host terminal while the
To detach from a session without terminating it, use the tmux detach keybinding
(default: Ctrl+B, d). This returns you to your host terminal while the
session continues running.`,
Example: ` # Attach to whatever you were last working on
hjk attach
Expand Down
32 changes: 4 additions & 28 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,7 @@ enabling safe parallel development across multiple branches.`,
return err
}

// Get multiplexer override from flag
muxOverride, err := cmd.Flags().GetString("multiplexer")
if err != nil {
return fmt.Errorf("get multiplexer flag: %w", err)
}
if muxOverride != "" && !config.IsValidMultiplexer(muxOverride) {
return fmt.Errorf("invalid multiplexer %q (valid: tmux, zellij)", muxOverride)
}

if err := initManager(muxOverride); err != nil {
if err := initManager(); err != nil {
return err
}

Expand All @@ -83,7 +74,6 @@ func Execute() error {

func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().String("multiplexer", "", "terminal multiplexer to use (tmux, zellij)")
}

func initConfig() {
Expand Down Expand Up @@ -140,8 +130,7 @@ func getRuntimeBinary() string {
}

// initManager initializes the instance manager with all dependencies.
// muxOverride can be used to override the configured multiplexer.
func initManager(muxOverride string) error {
func initManager() error {
var worktreesDir string
var catalogPath string
var logsDir string
Expand Down Expand Up @@ -180,21 +169,8 @@ func initManager(muxOverride string) error {

opener := git.NewOpener(executor)

// Select multiplexer: CLI flag > config > default (tmux)
var mux multiplexer.Multiplexer
muxName := "tmux" // default
if appConfig != nil && appConfig.Default.Multiplexer != "" {
muxName = appConfig.Default.Multiplexer
}
if muxOverride != "" {
muxName = muxOverride
}
switch muxName {
case "zellij":
mux = multiplexer.NewZellij(executor)
default:
mux = multiplexer.NewTmux(executor)
}
// Use tmux as the terminal multiplexer
mux := multiplexer.NewTmux(executor)

// Create registry client for fetching image metadata
regClient := registry.NewClient(registry.ClientConfig{})
Expand Down
Loading
Loading