- Rust 93.7%
- Shell 5.2%
- Dockerfile 1.1%
|
All checks were successful
ci / rust (push) Successful in 5m4s
ci / chart (push) Has been skipped
ci / runner-image (push) Has been skipped
ci / images (., images/cli/Containerfile, cli) (push) Successful in 2m33s
ci / images (., images/controller/Containerfile, controller) (push) Successful in 3m31s
ci / images (., images/gateway/Containerfile, gateway) (push) Successful in 3m33s
Publish container images as nested packages under the claudify repo instead of flat qwolff/claudify-* so they auto-associate with the repo. Helm OCI chart + clone URLs unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| crates | ||
| deploy/chart | ||
| docs | ||
| images | ||
| scripts | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| rust-toolchain.toml | ||
Claudify
Collaborative Claude Code multiplexer. A small team runs one Claudify deployment on Kubernetes; team members attach from their laptops to per-session pods that hold a long-running Claude Code instance plus the project worktree. Credentials, project repos, and runtime live in the cluster; the client is thin (one SSH connection for the PTY, one HTTPS bearer for the control plane).
Status: v0.6.x — actively used internally. CLI is stable; CRDs are versioned v1alpha1. See docs/deployment-readiness.md for what's proven end-to-end.
What it does
- One session pod per session. Claude Code runs inside
dtach, a single-PTY detach/reattach wrapper. Multiple teammates can attach concurrently and share one canvas; killing the SSH connection leaves Claude running. - Projects, accounts, settings as CRDs. A project registers a git repo, an account holds per-engineer Claude OAuth + git SSH + Forgejo MCP credentials, a settings CR carries shared dotfiles/agents/plugins/
mcp.json. Sessions pick one of each at create time. - Per-session resource limits. Each session pod has RAM/CPU caps so one runaway agent can't starve the cluster.
- SSH pubkey auth. A flat
claudify-trusted-keysConfigMap lists all allowed clients; anyone listed can do anything. Revocation = remove the key. - HTTPS control plane. Login signs a challenge with the user's SSH key (SSHSIG); the server returns a 15-minute bearer used for the rest of the session.
Install the CLI
macOS / Linux via Homebrew (recommended)
brew install public/brew-tap/claudify
Shell completions for fish / bash / zsh are installed automatically.
From source
cargo install --git ssh://git@code.podesta.ai/qwolff/claudify.git claudify-cli
# or, from a clone:
git clone ssh://git@code.podesta.ai/qwolff/claudify.git && cd claudify
cargo install --path crates/cli
cargo install does not install shell completions automatically — generate them:
# fish (current shell): claudify completions fish | source
# fish (persistent): claudify completions fish > ~/.config/fish/completions/claudify.fish
# bash: claudify completions bash > /usr/local/etc/bash_completion.d/claudify
# zsh: claudify completions zsh > "${fpath[1]}/_claudify"
First-run setup
claudify setup
Two questions, both with defaults — hit Enter to accept. The command writes ~/.config/claudify/config.toml, runs a login (signs a challenge with your SSH key), and verifies access with a list-sessions probe. After it succeeds:
claudify status # cluster health overview
claudify list # your sessions
claudify create --project PROJECT --account ACCOUNT --settings SETTINGS --name NAME --attach
--yes accepts all defaults non-interactively (for dev-box bootstrapping); --skip-login saves the config but doesn't try to talk to the gateway (useful when configuring before a port-forward is up).
Env vars override the config file for one-off invocations:
| Variable | Default |
|---|---|
CLAUDIFY_API |
https://claudify.tu-po.com |
CLAUDIFY_SSH_HOST |
host part of CLAUDIFY_API |
CLAUDIFY_SSH_PORT |
2222 |
CLAUDIFY_KEY |
auto-detect ~/.ssh/id_ed25519, then id_rsa |
Command reference
| Command | What it does |
|---|---|
claudify setup |
First-run interactive config + login |
claudify login |
Re-sign a fresh bearer (auto on 401) |
claudify status |
Cluster health + resource counts (cached, fast) |
claudify list (ls, ps) |
List sessions with phase / project / attach count |
claudify top |
Live cpu/memory per session (5s refresh) |
claudify create --project P --account A --settings S --name N [--attach] |
New session; --attach auto-attaches once Running |
claudify attach <sess> |
Attach to the long-running Claude PTY |
claudify shell <sess> (sh, bash) |
Fresh bash -l in the same pod — escape hatch from Claude |
claudify exec <sess> -- <cmd> (run) |
One-shot non-interactive command; pipes work, exit code propagates |
claudify logs <sess> [--tail N --previous] |
Pod kubelet logs |
claudify end <sess…> (rm, stop, delete) |
Delete session(s); idempotent, accepts multiple names |
claudify project {list,get,apply,rm} |
Manage project CRs (the git-repo defs) |
claudify account {list,get,apply,rm} |
Manage account CRs (Claude+git+fgj creds) |
claudify settings {list,get,apply,rm} |
Manage settings CRs (shared dotfiles/agents) |
claudify whoami |
Current bearer's key fingerprint |
claudify completions {fish,bash,zsh} |
Print a shell completion script |
All session-name positional args TAB-complete from a live name list (5s cache; the lookup runs in well under 300 ms over WAN). Same for --project, --account, --settings flag values.
See docs/cli.md for per-command details (flag matrix, examples, exit codes).
For operators (deploying the cluster side)
See docs/setup.md. Short version:
- Helm-install the chart pointing at a StorageClass on your cluster (
--set rwx.storageClassName=...). - Generate + install the gateway's SSH host key as a Secret (or let the chart auto-generate on first install).
- Add at least one SSH pubkey to
claudify-trusted-keysConfigMap. - Create projects / accounts / settings via the CLI.
Repo layout
claudify/
├── crates/
│ ├── api/ # CRD types, HTTP wire types, SSHSIG canonical payload
│ ├── controller/ # reconciles ClaudifyProject/Account/Settings/Session CRDs
│ ├── gateway/ # SSH server (:2222) + HTTPS control plane (:8443)
│ ├── cli/ # `claudify` CLI
│ └── integration-tests/ # kind-based smoke scenarios (#[ignore] by default)
├── images/
│ ├── controller/ # distroless, static musl
│ ├── gateway/ # distroless, static musl
│ ├── cli/ # distroless, static musl
│ └── runner/ # session pod image (extends public/runner-image)
├── deploy/chart/ # Helm chart
└── docs/
├── setup.md # operator install guide
├── cli.md # full CLI reference
├── architecture.md # one-page architecture
└── deployment-readiness.md
Development
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check
helm lint deploy/chart
helm template claudify deploy/chart > /tmp/render.yaml # render to inspect
Integration tests against a real kind cluster need kind, helm, kubectl, podman on PATH and run with --ignored:
cargo test -p claudify-integration-tests -- --ignored --test-threads=1
Not run in CI (runners are unprivileged; kind needs nested container runtime).
Releasing
Single semver across the whole monorepo. To cut a release:
- Bump
version = "X.Y.Z"in allcrates/*/Cargo.toml, runcargo update -p claudify-* …, commit asrelease: X.Y.Z — <one-line>. - Tag
vX.Y.Z, push commit + tag. - CI builds + pushes the gateway/controller/cli/runner images and the Helm chart on tag.
- Bump the brew formula in
public/brew-tap.
Known limitations
- Multi-attach pair-programming. Concurrent keyboard input interleaves byte-by-byte; use voice to coordinate, or spin separate sessions.
- Credential rotation requires recreating sessions. Updating an account/settings Secret does not propagate into running pods.
- Trusting-group threat model. Any user with a trusted key can read every Secret the gateway can read, create sessions under any account, and exec into any session pod. Don't deploy as multi-tenant SaaS.
License
Apache-2.0.