Project switcher for Claude Code & Codex
To work on a project, one must run claude or codex in the project directory. That can be a lot of cd.
So I’ve made a helper for that.
I type cc (Claude) or cx (Codex) in the terminal. This opens a project switcher:
Good morning, Peter!
Projects & repos:
┌───┬──────────────────┬────┬──────────────────────┐
│ 1 │ Plans and Reviews│ r1 │ wow.pjh.is │
├───┼──────────────────┼────┼──────────────────────┤
│ 2 │ Infra │ r2 │ hartreeworks.org │
├───┼──────────────────┼────┼──────────────────────┤
│ 3 │ AI Wow │ r3 │ api.type3.audio │
├───┼──────────────────┼────┼──────────────────────┤
│ 4 │ TYPE III AUDIO │ r4 │ .agents │
├───┼──────────────────┼────┼──────────────────────┤
│ 5 │ HartreeWorks │ r5 │ skills │
└───┴──────────────────┴────┴──────────────────────┘
Recent:
┌───┬───────────────────────────────────────┐
│ a │ _try/2026-03-04-ai-character │
├───┼───────────────────────────────────────┤
│ b │ _try/2026-01-22-t3a-crunch-accounting │
└───┴───────────────────────────────────────┘
Current: /Users/ph/Documents/www/AI Wow/wow.pjh.is ⏎
I press a key and the helper changes directory and launches the agent. Or, I pass a memorised key directly (e.g. cc 3) to skip the menu.
The project and repo lists are populated from yaml files. 1 The “recents” list contains other directories where I recently ran claude or codex (e.g. my tries).
I also have fast-mode variants that use fast models, invoked with ccf and cxf on the command line.
Setup
Add this to your .zshrc:
# Record a directory in the recents list (the launcher filters out
# dirs that shouldn't be remembered).
_agent_record_recent_dir() {
python3 ~/.agents/scripts/agents/coding_agent_launcher.py --recordable "$1" && echo "$1" >> ~/.cc_recent_dirs
}
cc() {
local select_arg=""
case "$1" in
[1-9]|[a-z]|[r-z][1-9]) select_arg="--select $1"; shift ;;
esac
# Expand --r to --resume (session picker); --resumer/--rr to --continue
# (resume the latest conversation for the directory immediately, no picker)
local args=()
local resumer=0
for arg in "$@"; do
case "$arg" in
--r|-r) args+=(--resume) ;;
--resumer|--rr|-rr) args+=(--continue); resumer=1 ;;
*) args+=("$arg") ;;
esac
done
local result
local exit_code
if [[ $resumer -eq 1 && -z "$select_arg" ]]; then
result="$PWD"
exit_code=0
else
result=$(python3 ~/.agents/scripts/agents/coding_agent_launcher.py "$PWD" ${=select_arg})
exit_code=$?
fi
if [ $exit_code -eq 0 ] && [ -n "$result" ] && [ -d "$result" ]; then
_agent_record_recent_dir "$result"
cd "$result" && claude --chrome "${args[@]}"
fi
}
cx() {
local select_arg=""
case "$1" in
[1-9]|[a-z]|[r-z][1-9]) select_arg="--select $1"; shift ;;
esac
# Codex has no --resume flag; map --resume/--r to the `resume` subcommand.
# --resumer/--rr -> `resume --last` (latest session for the directory, no picker)
local subcmd=()
local args=()
local resumer=0
for arg in "$@"; do
case "$arg" in
--resume|--r|-r) subcmd=(resume) ;;
--resumer|--rr|-rr) subcmd=(resume --last); resumer=1 ;;
*) args+=("$arg") ;;
esac
done
local result
local exit_code
if [[ $resumer -eq 1 && -z "$select_arg" ]]; then
result="$PWD"
exit_code=0
else
result=$(python3 ~/.agents/scripts/agents/coding_agent_launcher.py "$PWD" ${=select_arg})
exit_code=$?
fi
if [ $exit_code -eq 0 ] && [ -n "$result" ] && [ -d "$result" ]; then
_agent_record_recent_dir "$result"
cd "$result" && codex "${subcmd[@]}" "${args[@]}"
fi
}
And the coding_agent_launcher.py script is here.
Footnotes
The project list is managed by my project-management plugin, which maintains a
projects.yamlfile. The repo list comes from a separaterepositories.yamlfile that I created manually. ↩