clikae is a verb (切り替え, switching). The headline action carries no verb of its own — the program name is the verb:
clikae <engine> <tank> # switch <engine> to <tank> and run it
An <engine> is a CLI with an adapter (run clikae adapters); a <tank> is a
name you choose (A-Z a-z 0-9 . _ - allowed) for one account/config. The fuel
metaphor runs throughout: a tank holds an engine's quota (its fuel); when a
tank runs dry you carry your work onward with clikae to.
Run clikae help <command> for the full per-command reference. The full design
of the language is in grammar.md.
Quick tour
# Create a tank for Claude Code, and add the matching shell alias
clikae init claude work --alias
# Switch to it and run — the bare verb (no `run` needed)
clikae claude work
clikae claude work -- --help # args after -- go straight to the engine
# Or pick up the alias and use that
source ~/.zshrc # or your rc file
claude-work
# Generate a macOS launcher you can double-click from ~/Applications
clikae app claude work
# See what you've got
clikae tanks # alias: clikae list
# ENGINE TANK
# claude work
clikae tanks -p # also print the tank directory paths
# Tear it all down (tank dir + alias + .app, asks to confirm)
clikae remove claude work
Commands
clikae is the verb, so switching needs no verb; management commands keep plain, conventional verbs.
Switch (the main thing)
| Command | What it does |
|---|---|
<engine> <tank> [-- args] | Switch <engine> to <tank> and run it. The bare verb. (run is a hidden alias.) |
<engine> <tank> --ephemeral | Switch and run with ephemeral memory — this session's long-term memory is a throwaway, discarded on exit; the tank's real memory is left untouched. Login + transcripts are normal. claude only (clikae must know the memory layout). See below. |
<engine> | One tank → use it; several → list them; none → offer to create. |
to <target> [tank] [-- args] | Carry this shell's current session onto another tank. Same engine → a real resume; a different engine → a written brief (cold start). clikae announces which. Source is auto-detected (env var, else this directory's most recent session). Forwards relay's -y/--fresh/--session. (relay/handoff/continue are hidden aliases.) |
eval "$(clikae env <engine> <tank>)" | Put the current shell on a tank (export its config env var), so the engine's own command and clikae status/to see it. The explicit alternative to the one-shot bare switch. |
Make & manage tanks
| Command | What it does |
|---|---|
init <engine> <tank> [--alias] | Create the tank directory; with --alias, also write a shell alias. |
remove <engine> <tank> [--force] [--keep-data] | Remove dir + alias + .app. --keep-data keeps the directory. |
rename <engine> <old> <new> [--force] | Rename a tank (moves the dir, rewrites the alias, carries the login). |
migrate [<engine>] [--dry-run] [--force] [--keep-login] | Adopt a hand-rolled config-dir + alias setup. |
alias <engine> <tank> [--name <n>] | Write (or replace) a shell alias. Default name <engine>-<tank>. |
app <engine> <tank> [--terminal <app>] [--force] [--out <dir>] | Generate a macOS .app launcher (default ~/Applications). macOS only. --terminal: terminal (default), iterm2, ghostty. |
app --board [--terminal <app>] [--force] [--out <dir>] | Generate a clikae.app that opens the board (the menu of recent sessions + tanks) instead of one tank — a single double-click button for the whole on-ramp. |
Ghostty launchers pass their command through a trusted Ghostty config file (
--config-file=), not-e. Ghostty pops an "Allow Ghostty to execute…?" dialog for an externally-injected-ecommand (so a-elauncher looks like an empty shell until you click Allow); a config file is trusted, so the window just opens. The config lives inside the.appand is found viapath to me, so the launcher keeps working if you move it.
Keep burning when a tank runs dry
| Command | What it does |
|---|---|
to [target] [tank] | Carry this shell's session onward when a tank runs dry. Bare clikae to falls through to the next tank in your burn order (same engine → a real resume; a different engine → a cold-start brief). Your tanks are the reserve — nothing to configure. |
auto [ask|safe|full] | (BETA, claude-launched sessions only) How much clikae carries on its own when a session you launched through clikae hits the limit — it has no effect on alias/.app/other-engine launches. ask (default) prompts; safe auto-resumes same-engine + asks to cross; full keeps going (same-engine = resume, cross-engine = a cold brief). The board's A key cycles it. |
watch <engine> [<tank>] [--auto] [--to <target>] | Watch a session and fall through to the next tank in the burn order when it runs dry (cross-engine via --to). |
burn <engine> <tank> --artifact <path> -- <cmd…> | Run a headless task on a tank; verify it by the artifact (not the exit code); on a dry tank, re-fire the same task on the next reserve tank. The headless sibling of to/watch. See "Headless tasks" below. |
Supervised launch (BETA · claude · feedback welcome). When you start claude through clikae, clikae stays as the parent. When that session ends after hitting its limit — quit the dead session in an interactive run; a headless
claude -pexits on its own — clikae carries you onward to the next tank in your burn order (perclikae auto) in the same terminal (one redraw), and your conversation continues there. Honest limits: it advances on exit, not by killing a live session mid-stream (that needs engine support — see issue anthropics/claude-code#35744); one hop per run; interactive codex can't be auto-detected (no file signal) so it's claude-only for now. Nothing runs in the background unless you launched it through clikae (no daemon) — deliberate.clikae statusshows what it carried (recent carries). Tell us how it feels.
Inspect
| Command | What it does |
|---|---|
| (no args) | Open the home dashboard — your "tank board": every tank grouped by engine, the one active in this shell marked, account + alias name, an "Also available" list of engines/targets you can open without a tank (e.g. codex, agy). On a terminal it's an interactive launcher; press ? for the full key legend. Keys: ↑/↓·j/k·Tab/Shift-Tab move, g/G top/bottom, 1-9 jump, ⏎ open (a Continue row offers resume vs switch fresh), r carry session, x incognito, n new, a rename the tank, d delete, / filter, l pick language, q/Esc quit. Piped/scripted it prints the same board as plain text (CLIKAE_NO_INTERACTIVE forces that). |
lang [en-US|ja-JP|zh-TW] | Show or set the interface language (dashboard + prompts). Persists to $CLIKAE_HOME/lang; the board's l key opens a language picker. Resolution when unset: $CLIKAE_LANG > saved choice > $LC_ALL > $LANG > en-US. |
tanks [-p|--paths] [--json] | List all tanks, with the logged-in account where the adapter can tell. (Aliases: list, ls.) --json emits machine-readable output {cli, profile, account, path} for scripts and the GUI. |
status [<engine>] [--json] | Show which tank each engine is on in this shell. --json emits one object per engine with a state enum. |
doctor | Read-only health check: which supported engines are installed and logged in, how many tanks each has, the environment, and what to do next. |
info [--json] | Show install paths, platform, adapters, and tank count. |
adapters | List supported engines with descriptions. |
demo | A 30-second guided tour in a throwaway sandbox — shows isolated tanks, the tank board, and the to idea (your tanks are the reserve), then cleans up. Touches nothing real; the accounts are simulated, so it needs no installed engine. |
Antigravity (agy) — same verbs, one power mode
agy hardcodes ~/.gemini and ignores env vars, so clikae can't switch it
per-shell like other engines. It folds into the same verbs anyway, via an
opt-in symlink-swap power mode (global: one tank active at a time across all
terminals; reversible):
| Command | What it does |
|---|---|
init agy <tank> | First time: warns and asks before taking ~/.gemini over (backs it up, migrates your current login into a default tank), then creates <tank>. After: just creates the tank. |
agy <tank> | Switch the active tank (refuses if agy is running) and start agy. Prints a global-switch notice. |
remove agy <tank> | Remove the tank. Removing the last tank offers to restore a normal ~/.gemini and turn the power mode off. |
agy --release | Restore a normal single-account ~/.gemini from the active tank, keep the tank dirs. |
Shells
clikae auto-detects your shell from $SHELL and writes the alias to the right
rc file: zsh (~/.zshrc), bash (~/.bash_profile on macOS, else
~/.bashrc), and fish (~/.config/fish/config.fish). For fish it emits fish
syntax — alias <name> 'env VAR=val <binary>' — because fish has no inline
VAR=val cmd; the result behaves identically. clikae remove cleans up the
block in any of them.
Migrating an existing setup
Already juggling accounts by hand — say a ~/.claude-acct-a / ~/.claude-acct-b
pair with aliases in your ~/.zshrc? clikae migrate adopts that into clikae:
clikae migrate --dry-run # preview: which dirs move where, which aliases change
clikae migrate # do it (asks to confirm first)
It scans your shell rc for aliases that set the engine's config env var and invoke the engine. For each one it:
- moves the referenced config directory under
~/.clikae/profiles/<engine>/<p>/, - rewrites the alias into clikae's managed sentinel block.
The rc file is backed up to <rc>.clikae.bak.<timestamp> first, and an existing
clikae tank is never overwritten. Pass an engine name (clikae migrate gh) to
migrate a different tool's aliases. Default is claude.
⚠️ Don't migrate a config dir that's currently in use.
migratemoves the directory, so if a process is running against it right now (e.g. you runclikae migratefrom inside the veryclaudesession whoseCLAUDE_CONFIG_DIRpoints at the dir being moved), you pull the directory out from under that live process — it can fail to write, or recreate an empty dir at the old path and leave you with two half-states. Runmigratefrom a fresh shell with no instance of that engine active.--dry-runis always safe.As of v0.4,
migrateguards against the most common form of this: if$CLAUDE_CONFIG_DIR(or whichever env var the adapter uses) currently points at a directory slated to move, it refuses and tells you to retry from a fresh shell. The guard is not bypassed by--force— it protects your data, it isn't a confirmation prompt.
🔑 macOS + claude: expect a one-time re-login per migrated tank. On macOS, Claude Code keeps its login token in the login Keychain, not inside
CLAUDE_CONFIG_DIR— and the keychain entry is keyed by the config-dir path. Becausemigratemoves the dir to a new path, claude no longer finds the token and asks you to log in once for each migrated tank. Your data is intact; only the saved login doesn't follow the move. To avoid the re-login, pass--keep-login, which copies the saved token from the old path's keychain entry to the new one (macOS only; it never reads or transmits the token anywhere — it stays in your Keychain). macOS may prompt you to allow keychain access.
Carrying a session when you hit a usage limit — clikae to
This is clikae's origin story: you keep a second account precisely because one
account's quota runs out mid-task. clikae to lets you carry the work onward —
like swapping a fuel tank — and keep the same conversation going on a fresh
quota.
# You're working on claude tank `a` and just hit its limit. From the same project
# directory, carry the conversation onto another tank and keep going:
clikae to b # same engine → a real resume, on b's quota
clikae to codex # a different engine → a written brief (cold start)
clikae to codex work # cross to a specific tank of another engine
clikae auto-detects which engine + tank this shell is on: first the live env var,
then — since the bare switch / aliases / .app run the engine with a prefix
assignment that never reaches the parent shell — the tank with this directory's
most recent session (the one you were just in here). So switch → work → to
works from one shell. To pin a shell to a tank explicitly instead, use
eval "$(clikae env <engine> <tank>)". The target resolves engine-name-first:
a known engine name crosses to it; anything else is a tank of your current engine.
clikae always announces which mechanism it used so resume-vs-brief is never a
guess.
Same engine (a resume). For Claude Code, clikae finds the current
directory's most recent transcript under the source tank, copies it into the
target tank, and runs claude --resume <id> there — so the conversation
continues, but every new turn burns the target tank's quota. The source tank is
left completely untouched (it copies, never moves), so you can always go back.
A preview + confirm is shown before anything moves; -y skips it, --fresh
switches tanks without carrying, --session <id> carries a specific session.
Carry-over relies on Claude Code's on-disk transcript layout (
<config-dir>/projects/<slug>/<id>.jsonl) and--resume. It's verified against current Claude Code; if a future version changes that layout, it falls back to a fresh start rather than doing anything destructive.
A different engine (a brief). A different model or vendor can't resume a
foreign session — there's no shared transcript format. So clikae writes a
handoff brief (what you're doing, what's done, what's next) and starts the
target engine seeded with it as the opening prompt. clikae tries to write a
summary automatically: if a local model CLI is on your PATH (apfel,
ollama, or llm), it's used to summarise the brief for free — set
CLIKAE_HANDOFF_AUTOLOCAL=0 to disable that auto-detection. Otherwise the brief
is a raw extract (session metadata + your recent prompts), clearly labelled
as raw. To force a specific summariser, point clikae at any model so writing the
brief costs nothing on the tank that just ran dry:
export CLIKAE_HANDOFF_SUMMARIZER='llm -m my-local-model' # any stdin→stdout command
clikae to codex # the model writes the brief
The summarizer (auto-detected or CLIKAE_HANDOFF_SUMMARIZER) receives, on stdin,
an instruction line followed by the tail of the session transcript, and writes the
brief to stdout. If it produces nothing, clikae falls back to the raw extract so a
handoff is never lost. Tune how much
transcript is fed with $CLIKAE_HANDOFF_LINES (default 60). Carrying onward is
read-only on the source — it never touches the source session or any tank.
Under the hood,
clikae todelegates torelay(same engine) orhandoff(different engine). Both remain available as hidden aliases — e.g.clikae handoff claude --out HANDOFF.mdjust writes a brief to a file without starting anything. Runclikae help to/help relay/help handofffor details.
Ambient: notice a dry tank and switch (watch)
Instead of switching by hand, let clikae watch for the moment a tank runs dry and fall through to the next one. Your tanks are the reserve — there's nothing to set up. Just watch the current session:
clikae watch claude # offer to switch to the next claude tank when dry
clikae watch claude --auto # switch automatically (asks once for consent)
clikae watch claude --to codex/work # cross to a specific tank/engine instead
When it detects a dry tank it carries onward to the next tank of the same engine
(skipping any that are themselves over quota); cross-engine needs an explicit
--to. By default it asks first; --auto switches
automatically after a one-time consent (remembered in
$CLIKAE_HOME/auto-relay-consent — delete that file to revoke), and always tells
you what it did.
Honest caveat. An interactive engine hitting its usage limit doesn't exit, returns no code, and fires no hook — so the only thing clikae can watch is what the limit writes to disk. For claude that's the session transcript; for agy it's
~/.gemini/antigravity-cli/cli.log(agy's-prun exits 0 with empty output, so the log line is the only signal). codex's limit is proven not persisted to its transcript, so a dry tank can't be detected for codex from disk. Confirm/tune the match the first time you actually get limited:clikae watch claude --check # would the pattern fire on this session? CLIKAE_LIMIT_PATTERN='…' clikae watch claude # override the match
Headless tasks across tanks — clikae burn
watch/auto carry an interactive session. For headless grunt work — the
"let the cheaper tank do the dirty work" case — use clikae burn. It runs one
task on a tank and, crucially, knows whether it actually finished: it verifies by
the artifact the task must produce, never the exit code (codex exec exits 0
even when it hit its usage limit and wrote nothing). If the tank ran dry, it
re-fires the same task on the next tank in your reserve.
# Distil a file with codex on tank M; if M is dry, fall through to your next
# codex tank automatically. Success = /tmp/out.md exists.
clikae burn codex M --artifact /tmp/out.md -- \
exec -C /tmp -s workspace-write "read /tmp/in.txt, write /tmp/out.md"
clikae burn codex M --artifact /tmp/out.md --to codex/H -- exec … "<task>" # explicit next hop
clikae burn codex M --artifact /tmp/out.md --timeout 300 -- exec … "<task>" # bound a long run
Outcomes: artifact present → done; dry on every reachable tank → fail; ran but
produced no artifact and showed no limit → a real task failure (not rerouted —
it would fail the same everywhere). --no-reroute runs once and stops on a dry tank.
burn is the single-task unit — batch/parallelism stays your orchestrator's
job (fan several burns out, review the artifacts). Make tasks idempotent and
artifact-checked (fixed input/output paths), and pre-stage inputs to /tmp rather
than handing a tank slow iCloud-backed I/O.
burn won't spend the quota you're using. Its auto-reroute skips a tank an
interactive session is live on (it would otherwise burn the conversation you're
mid-flight in) and tanks that share an already-dry account. Pass --allow-active
to override the in-use skip, or --to <tank> to name a hop explicitly.
A tank is a quota source, not the content. burn <engine> <tank> spends
that tank's quota to run a command; what the command reads/writes is just files,
unrelated to tanks. So you can spend a cheap tank's quota to chew on any file —
including another tank's transcript.
Using agy as a cheap read-only worker. agy can't be a burn tank (it's one
global account — there's no reserve to reroute among), but it's a fine headless
worker — you just invoke it directly, outside burn's reserve wrapper:
# agy as a summariser — content in via stdin, agy's own quota spent. Read-only.
cat /tmp/in.md | agy --sandbox -p "summarise this" > /tmp/out.md
# …or through clikae, on a specific agy account (switches the global ~/.gemini
# symlink to tank R, then runs agy headless on R's quota):
clikae agy R -- -p "summarise this" --sandbox < /tmp/in.md > /tmp/out.md
You give up burn's two guarantees here (no dry→reroute — agy has one account, so
a dry run just fails; no artifact verification), and remember clikae agy switches
a machine-wide symlink, not a per-shell env (and agy has no --model flag — the
model is the app's setting).
Seeing which tank you're on
clikae status # every engine that has a tank
clikae status claude # just one
# ENGINE TANK ACCOUNT SOURCE
# claude cver hi@cver.net CLAUDE_CONFIG_DIR=…/profiles/claude/cver
# aws (default) - AWS_PROFILE unset — system default
status reads the live value of each adapter's env var in the current shell
and resolves it back to a clikae tank. It's a per-shell view: another terminal
(or one launched from a different clikae app) can be on a different tank.
(default) means the env var is unset (the engine's own default); (external)
means it points somewhere that isn't a clikae tank. The ACCOUNT column shows
the logged-in account when the adapter can tell.
Naming your tanks
Name tanks however makes sense to you — work, personal, a client name, or
the account email. You don't have to remember what a bare a/b meant: both
tanks and status show the logged-in account when the adapter can read it.
Changed your mind about a name? clikae rename moves the directory, rewrites the
managed alias, and — for claude on macOS — carries the saved Keychain login
across so you don't have to log in again:
clikae rename claude a cver # a → cver; login + alias follow
It refuses if the new name is taken or if that engine is currently using the tank
in this shell (run it from a fresh shell). A pre-existing .app launcher is left
alone but flagged — recreate it with clikae app claude cver.
Ephemeral memory (--ephemeral)
For the surgical, leave-no-trace run: clikae claude work --ephemeral switches to
the tank and runs it, but points the engine's long-term memory at a throwaway
directory that's discarded when the engine quits. The tank's real memory is
stashed aside and restored, untouched.
clikae claude work --ephemeral # incognito: nothing learned this session is kept
- Login and transcripts are normal — only the memory store is throwaway (you're still you, the conversation is still logged/resumable).
- Honest scope: clikae guarantees the memory directory is a throwaway. It can't promise the engine "remembers nothing anywhere" — caches, shell history, telemetry, the macOS Keychain are outside clikae's reach. So it's ephemeral memory, not guaranteed total amnesia.
- Supported only for engines whose memory layout clikae knows (currently claude); others say so and exit.
- Unlike a normal switch (which
execs the engine),--ephemeralruns it as a child so cleanup can run on exit. A crashed run self-heals on the next--ephemeral(the real memory is recovered from its stash).
How it works
For each tank, clikae:
- Creates
~/.clikae/profiles/<engine>/<tank>/— the directory the engine's env var (e.g.CLAUDE_CONFIG_DIR) points at. (The on-disk path keeps the wordprofilesfor stability; you only ever type/see tank.) - (
alias) Appends a sentinel-wrapped block to your shell rc:
The sentinels make safe, exact removal possible.# >>> clikae:claude.work >>> alias claude-work='CLAUDE_CONFIG_DIR="/Users/you/.clikae/profiles/claude/work" claude' # <<< clikae:claude.work <<< - (
app, macOS) Generates an AppleScript-compiled.appthat opens a terminal, runs the env-var-prefixed engine, and sets the window title toclaude (work)so you can tell windows apart. The terminal is Terminal.app by default;--terminal iterm2and--terminal ghosttytarget those instead (set$CLIKAE_TERMINALto change the default). Terminal.app and iTerm2 are driven by AppleScript; Ghostty has no window-opening CLI on macOS, so its launcher goes throughopen -na Ghostty.app --args … -e ….
No daemons, no global state, no network calls. You can read every line.
Supported engines
| Engine | Strategy | Env var |
|---|---|---|
claude (Anthropic Claude Code) | env-dir | CLAUDE_CONFIG_DIR |
codex (OpenAI Codex CLI) | env-dir | CODEX_HOME |
gh (GitHub CLI) | env-dir | GH_CONFIG_DIR |
gcloud (Google Cloud CLI) | env-dir | CLOUDSDK_CONFIG |
docker (Docker CLI) | env-dir | DOCKER_CONFIG |
helm | env-dir | HELM_CONFIG_HOME |
kubectl | env-file | KUBECONFIG |
aws (AWS CLI) | env-var | AWS_PROFILE |
az (Azure CLI) | env-dir | AZURE_CONFIG_DIR |
npm | env-file | NPM_CONFIG_USERCONFIG |
terraform | env-file | TF_CLI_CONFIG_FILE |
pulumi | env-dir | PULUMI_HOME |
vercel (Vercel CLI) | flag | — (--global-config <dir>) |
agy (Google Antigravity) | opt-in symlink | — (hardcoded ~/.gemini; see above) |
The flag strategy is for engines with no config-directory env var: the tank
directory is injected as a command-line flag (e.g. vercel's --global-config)
in the generated alias / .app / run command instead of an exported variable.
Such an engine shows (n/a) in clikae status (there's nothing in the
environment to read back).
Run clikae adapters to see them with descriptions. Adding your own is ~10
lines of bash — see adding-an-adapter.md.
Note on
aws: unlike the others, the AWS adapter doesn't isolate config into a separate directory —AWS_PROFILEselects a named profile from your existing~/.aws/config. Soclikae init aws workexpects a matching[profile work]entry to exist. See the comment at the top oflib/adapters/aws.shfor the alternativeenv-fileapproach.