Opinionated configuration files for coding-agent workflows, designed to stay portable across machines and agent ecosystems.
This is my personal "botfiles" repo: a play on Unix-style dotfiles, but focused on agent behavior rather than only shell/editor setup. It keeps Codex, Claude Code, hooks, skills, task-status conventions, zellij helpers, and notification wiring aligned across the machines where I run long-lived coding agents.
Read the companion X Article: Botfiles: A Dotfiles-Inspired Model-Agnostic Setup for 24/7 Agents.
Paste this into your coding agent before deciding what to copy:
Read through https://github.com/ma08/botfiles and grill me on what
parts I should incorporate into my own coding-agent setup.
Focus on reusable patterns, not copying this repo's machine-specific
configuration.
Ask me targeted questions about:
- my agents
- my machines
- notification preferences
- task-tracking habits
- tolerance for automation
Then recommend the smallest useful changes I should make.
Use this as a faster path through the long README. Each image jumps to the section it explains.
![]() Repo map What lives in botfiles and why. |
![]() Remote stack SSH, mosh, zellij, and remote agent sessions. |
![]() Task lifecycle How sessions start, save state, resume, and finish. |
![]() Cross-session work Inspect and message live agent sessions without remote control. |
![]() Notifications Task context, tracker IDs, and zellij links in alerts. |
![]() Hermes experiments Pickup and supervise loops for agent-team UX. |
This repo is public as a reference implementation, not a turnkey product. It reflects my own machines, tools, task folders, and workflow preferences, so the useful move is to copy patterns rather than clone the whole setup unchanged.
- Solo builders using Codex, Claude Code, or other CLI coding agents.
- People who want agent context to survive across models, machines, and sessions.
- Teams exploring long-running remote agent workspaces.
- Anyone who wants examples of task folders, skills, hooks, zellij workflows, and notification loops.
- Durable task folders: keep
status.md,user_inputs/, andtask-progress-artifacts/outside any single chat transcript. - Shared agent instructions: store global preferences in versioned files such as
codex/AGENTS.mdandclaude/CLAUDE.md. - Reusable skills: keep repeatable workflows as file-backed skills that can be mirrored across agents.
- Resume-friendly sessions: use zellij plus task metadata so interrupted work can be found, inspected, and continued.
- Notification hooks: send compact task context and session links to the places where you actually notice them.
If you want to borrow from this repo, paste this into your coding agent:
Read through https://github.com/ma08/botfiles and grill me on what
parts I should incorporate into my own coding-agent setup.
Focus on reusable patterns, not copying this repo's machine-specific
configuration.
Ask me targeted questions about:
- my agents
- my machines
- notification preferences
- task-tracking habits
- tolerance for automation
Then recommend the smallest useful changes I should make.
- My exact machine names, SSH aliases, tailnet hostnames, and model/provider profiles.
- Any local secret values or private workflow assumptions.
- The full setup script if all you want is one pattern. Start with a single idea, adapt it, and expand only when it earns its keep.
- settings.json - Claude Code settings (hooks, plugins, model preferences)
- statusline-simple.sh - Custom statusline script
- hooks/ - Notification hooks for local and WhatsApp alerts
- Sends notifications when Claude finishes responding
- Sends notifications when Claude needs permission
- Sends notifications when Claude asks a question
- skills/ - Claude Code skills for extended capabilities
- claude/agents/ - Source-controlled custom Claude Code subagents, synced into
~/.claude/agents(oracle-awaiter,reviewer) - codex/agents/ - Source-controlled custom Codex agents, synced into
~/.codex/agents(oracle_awaiter,reviewer) - codex/ - Codex CLI config, synced skills, and global AGENTS instructions
- secrets/ - Centralized secret templates and local runtime secret files
- .botenv — Non-interactive-safe core bootstrap (secrets, PATH, EDITOR, TERM, UV_BIN)
- .botrc - Interactive shell layer (aliases, functions) that sources
.botenv - bin/ - Repo-managed command wrappers for Oracle, Google Workspace, zellij session launch, and cross-session helpers
- shell/ - Reusable shell modules loaded by
.botrc(for example SSH workflow helpers) - zellij/ - Canonical Zellij config (including remaps away from
Ctrl+gandCtrl+t) - docs/ - Shared contracts for task-status metadata and cross-session orchestration
- context/ - A few committed examples of task records and artifacts produced by task-related skills
- hermes/ - Early shared workflow assets for Hermes-style agents
- Claude Code CLI installed
- uv - Python package manager
- terminal-notifier - macOS notifications (optional)
- fzf - interactive session picker for
work-*SSH workflows (optional but recommended) - mosh - mobile shell transport for mosh-first workflows (optional; SSH fallback remains available)
- Poppler - PDF CLI tools such as
pdfinfo,pdftoppm, andpdftotextused by PDF workflows (recommended)
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install terminal-notifier (macOS)
brew install terminal-notifier
# Install optional SSH workflow tools (macOS)
brew install fzf mosh
# Install PDF workflow tools (macOS)
brew install popplerUbuntu/Debian PDF workflow install:
sudo apt-get install -y poppler-utilsKeep the curated upstream codex/skills/pdf skill unmodified so future upstream pulls stay simple. Install Poppler at the machine level, and for one-off Python PDF work prefer uv run --with reportlab,pdfplumber,pypdf ... from a task scratchpad instead of mutating arbitrary project environments.
-
Clone the repository
git clone <repo-url> ~/pro/botfiles cd ~/pro/botfiles
-
Run the setup script
./setup.sh
The script now also performs warn-only checks for SSH workflow dependencies (
ssh,fzf,mosh),~/.ssh/config, and machine-appropriate PDF tooling hints. -
Create local secret files from templates
mkdir -p secrets/local cp secrets/templates/claude-bedrock.rc.example secrets/local/claude-bedrock.rc cp secrets/templates/codex-azure.rc.example secrets/local/codex-azure.rc cp secrets/templates/machine.rc.example secrets/local/machine.rc cp secrets/templates/claude-hooks.rc.example secrets/local/claude-hooks.rc # Optional: cp secrets/templates/linear.rc.example secrets/local/linear.rc cp secrets/templates/claude-vertex.rc.example secrets/local/claude-vertex.rc cp secrets/templates/codex-openai.rc.example secrets/local/codex-openai.rc cp secrets/templates/codex-app-server.rc.example secrets/local/codex-app-server.rc cp secrets/templates/opencode-azure.rc.example secrets/local/opencode-azure.rc # Edit secrets/local/*.rc with your values
-
Load shared env config (setup.sh does this automatically)
# For zsh (macOS): echo '[ -f "$HOME/pro/botfiles/.botenv" ] && . "$HOME/pro/botfiles/.botenv"' >> ~/.zshenv echo 'source ~/pro/botfiles/.botrc' >> ~/.zshrc # For bash (Linux) — append to whichever login file bash reads first # (~/.bash_profile > ~/.bash_login > ~/.profile; usually ~/.profile on Ubuntu) echo 'export BASH_ENV="$HOME/pro/botfiles/.botenv"' >> ~/.profile # or ~/.bash_profile echo 'source ~/pro/botfiles/.botrc' >> ~/.bashrc
-
Restart Claude Code
If you prefer manual setup:
# Create symlinks
ln -sf ~/pro/botfiles/claude/settings.json ~/.claude/settings.json
ln -sf ~/pro/botfiles/claude/statusline-simple.sh ~/.claude/statusline-simple.sh
ln -sf ~/pro/botfiles/claude/hooks ~/.claude/hooks
ln -sf ~/pro/botfiles/claude/skills ~/.claude/skills
ln -sf ~/pro/botfiles/claude/agents ~/.claude/agents
ln -sf ~/pro/botfiles/codex/config.toml ~/.codex/config.toml
ln -sf ~/pro/botfiles/codex/agents ~/.codex/agents
ln -sf ~/pro/botfiles/codex/skills ~/.codex/skills
ln -sf ~/pro/botfiles/codex/AGENTS.md ~/.codex/AGENTS.md
mkdir -p ~/.config/zellij
ln -sf ~/pro/botfiles/zellij/config.kdl ~/.config/zellij/config.kdl
# Install Python dependencies
cd ~/pro/botfiles/claude/hooks
uv syncThen wire the two-layer bootstrap into your shell entrypoints (see Shell Environment below).
Botfiles uses a two-layer shell environment model:
.botenv (core, non-interactive safe)
- Secrets from
secrets/local/*.rc - PATH additions (
$BOTFILES_ROOT/bin,~/.local/bin,/usr/local/bin) - Core env:
BOTFILES_ROOT,EDITOR,VISUAL,TERM,UV_BIN - Safe to source from any context: SSH commands, cron, systemd, agent exec
- Exposes repo-managed executables like
oracleandoracle-mcpto non-interactive shells
.botrc (interactive layer)
- Sources
.botenvfirst (idempotent via double-source guard) - Adds aliases (
cc,bedcc,zj, etc.) - Loads interactive shell modules (
20-ssh-workflows.sh,30-oracle.sh) - Defines workflow functions (
work-ml,oracle, etc.) on top of the shared executable wrappers
Shell entrypoint wiring:
For zsh (macOS):
# ~/.zshenv (ALL zsh contexts, including non-interactive)
[ -f "$HOME/pro/botfiles/.botenv" ] && . "$HOME/pro/botfiles/.botenv"
# ~/.zshrc (interactive only)
source ~/pro/botfiles/.botrcFor bash (Linux):
# Effective login file (login shells — sets BASH_ENV for non-interactive children).
# Bash reads the first of ~/.bash_profile, ~/.bash_login, ~/.profile.
# Add this to whichever one your system uses (usually ~/.profile on Ubuntu).
export BASH_ENV="$HOME/pro/botfiles/.botenv"
# ~/.bashrc (interactive shells)
source ~/pro/botfiles/.botrcsetup.sh configures these entrypoints automatically and symlinks oracle / oracle-mcp into ~/.local/bin so command runners like watch can resolve them without sourcing .botrc.
setup.sh also symlinks ~/.claude/agents to claude/agents, ~/.codex/AGENTS.md to codex/AGENTS.md, and ~/.codex/agents to codex/agents.
Codex notify flow:
codex/config.tomlonly callscodex/hooks/run-codex-notify.sh.shell/10-uv-bin.shresolvesUV_BINonce for Linux/macOS portability.
Codex turn-complete notifications still use the normal Codex notify hook above.
For prompts created by Codex's request_user_input tool, use the App Server
notification path:
Codex TUI -> session-scoped local notification proxy -> loopback Codex App Server
The proxy forwards WebSocket frames unchanged, detects
item/tool/requestUserInput, sends Codex Needs Input through the existing
WhatsApp/Gmail channels, dedupes by threadId + turnId + itemId + requestId,
and clears pending state on serverRequest/resolved.
By default, each zellij session gets its own notification proxy port and state
directory derived from ZELLIJ_SESSION_NAME. This keeps zellij session/link
metadata tied to the originating terminal session instead of whichever shared
proxy process happened to start first. Session scope recomputes proxy port/state
from the session key even if those variables were inherited from a parent shell.
The App Server stays a single global listener; only proxy pid/log/event/state
files are session-scoped. Under session scope, inherited CODEX_APP_NOTIFY_STATE_DIR
values that already point into sessions/<name> are normalized back to the
global base before proxy state is recomputed.
Set CODEX_APP_NOTIFY_PROXY_SCOPE=global to opt back into one shared proxy.
Direct terminal aliases:
codex-azure # uses App Server notifications by default
codex-openai # uses App Server notifications by default
codex-chatgpt # uses App Server notifications by default
codexy # yolo/bypass via App Server notifications by default
codexy-azure # yolo/bypass + profile azure via notifications by default
codexn # codex through the App Server notification proxy
codexn-azure # profile azure
codexn-openai # profile openai_api
codexny-azure # bypass approvals/sandbox plus profile azure
codexn-start # start app-server + proxy
codexn-status # show app-server/proxy status
codexn-logs # show sanitized proxy event log
codexn-stop # stop proxy + app-serverThe legacy convenience aliases (codex-azure, codexy-azure, and related
profile variants) read CODEX_APP_NOTIFY_DEFAULT, which defaults to true.
Set it to false for a raw-Codex shell while keeping the explicit codexn*
aliases config-independent:
export CODEX_APP_NOTIFY_DEFAULT=false
codexy-azure
codexny-azure # still always uses the notification pathDetached zellij task launches use the notification path by default when possible:
start-zellij-session-for-task ZON-170Use raw Codex only for fallback/debug work:
start-zellij-session-for-task --no-codex-app-notify ZON-170
start-zellij-session-for-task-raw ZON-170Optional defaults live in secrets/local/codex-app-server.rc; the template is
secrets/templates/codex-app-server.rc.example. The built-in defaults bind
listeners to 127.0.0.1; the App Server keeps a stable default port, while the
notification proxy port is session-scoped unless you opt into global/shared
scope:
export CODEX_APP_SERVER_PORT=17370
export CODEX_APP_NOTIFY_PROXY_SCOPE=session
export CODEX_APP_NOTIFY_DEFAULT=true
export CODEX_APP_NOTIFY_DRY_RUN=falseHands-on dry-run test:
cd ~/pro/botfiles
CODEX_APP_NOTIFY_DRY_RUN=true codexn-start
codex-app-notify-session --profile azureIn the Codex TUI, ask:
Please use request_user_input to ask me one multiple-choice question with A, B, and C.
Expected result:
- the TUI prompt still works normally
codexn-logsshows onerequest_user_inputevent- after answering,
codexn-logsshowsserverRequest/resolved - restarting the proxy with the same state does not resend the same prompt
Real notification smoke test:
~/pro/botfiles/codex/hooks/run-codex-send.sh --title "Codex Notify Smoke" "manual channel test"
codexn-azureThen trigger the same request_user_input prompt and confirm one WhatsApp
message, and one Gmail message when EMAIL_ENABLED=true.
Security notes:
- Keep plain
ws://listeners on loopback by default. - For Mac-to-VM access, prefer SSH port forwarding instead of binding the VM listener to a public or tailnet interface.
- If you intentionally expose App Server beyond loopback, configure WebSocket auth and put authenticated non-local connections behind TLS.
- Only Codex sessions launched through App Server/proxy produce these prompt
notifications; direct
codex*sessions keep the existing turn-complete notification behavior.
All runtime secrets live in secrets/local/*.rc (git-ignored).
All shareable templates live in secrets/templates/*.rc.example (tracked).
Start from templates:
mkdir -p ~/pro/botfiles/secrets/local
cp ~/pro/botfiles/secrets/templates/claude-bedrock.rc.example ~/pro/botfiles/secrets/local/claude-bedrock.rc
cp ~/pro/botfiles/secrets/templates/claude-vertex.rc.example ~/pro/botfiles/secrets/local/claude-vertex.rc
cp ~/pro/botfiles/secrets/templates/codex-azure.rc.example ~/pro/botfiles/secrets/local/codex-azure.rc
cp ~/pro/botfiles/secrets/templates/codex-openai.rc.example ~/pro/botfiles/secrets/local/codex-openai.rc
cp ~/pro/botfiles/secrets/templates/codex-app-server.rc.example ~/pro/botfiles/secrets/local/codex-app-server.rc
cp ~/pro/botfiles/secrets/templates/opencode-azure.rc.example ~/pro/botfiles/secrets/local/opencode-azure.rc
cp ~/pro/botfiles/secrets/templates/machine.rc.example ~/pro/botfiles/secrets/local/machine.rc
cp ~/pro/botfiles/secrets/templates/linear.rc.example ~/pro/botfiles/secrets/local/linear.rc
cp ~/pro/botfiles/secrets/templates/claude-hooks.rc.example ~/pro/botfiles/secrets/local/claude-hooks.rcThen fill in values in each secrets/local/*.rc file.
secrets/local/linear.rc is the default place for LINEAR_API_KEY so shells, hooks, and tracker tooling inherit it through .botenv without scraping another repo's .env.
setup.sh symlinks ~/.config/zellij/config.kdl to zellij/config.kdl in this repo.
Keybinding decision:
- Zellij lock mode is mapped to
Alt+g(notCtrl+g) to avoid conflicts with terminal apps such as Codex/Vim input workflows. - Zellij tab mode is mapped to
Alt+t(notCtrl+t) so Codex keepsCtrl+tavailable for its transcript overlay inside Zellij panes.
Clipboard copy behavior:
zellij/config.kdlusescopy_command "sh -c ~/pro/botfiles/shell/clipboard-copy".shell/clipboard-copyprefers remote Mac clipboard forwarding (BOT_CLIPBOARD_SSH_TARGET, defaultsourya-mac) and falls back to local clipboard tools (pbcopy,wl-copy,xclip,xsel).
The shell/20-ssh-workflows.sh module provides reconnect-friendly helpers for zellij workflows:
work-here # manage zellij sessions on the current machine
work-ml # mosh-first connect to ML VM, pick/create zellij session
work-ml-ssh # SSH-only fallback path for ML VM
work-arya # SSH-first connect to Aryabhatta, pick/create zellij session
work-arya-mosh # mosh-first path for Aryabhatta (use when UDP is available)
work-arya-ssh # explicit SSH path for Aryabhatta
work-agent # SSH-first connect to agent-prod; prefer Tailscale alias, then public alias
work-agent-mosh # mosh-first path for agent-prod with the same alias fallback
work-agent-ssh # explicit SSH path for agent-prod with the same alias fallback
mml # raw shell shortcut (mosh-first, no zellij attach)
marya # raw shell shortcut (mosh-first, no zellij attach)
magent # raw shell shortcut for agent-prod (mosh-first, no zellij attach; Tailscale then public alias)
cursor-ml # open/reuse Cursor window at ML VM home over Remote-SSHDependency behavior:
fzfis required for interactive picker mode.- If
fzfis missing, pass a session name explicitly (example:work-ml my-sessionorwork-here my-session). - Local
zellijis required forwork-here; remote hosts still needzellijfor allwork-*attach/create flows. moshis only required for mosh-first commands (work-ml,work-arya-mosh,work-agent-mosh) and raw shell shortcuts (mml,marya,magent).- If
moshis missing or transport fails, mosh-based workflows fall back to SSH on the selected host alias. - Host selection can include both primary and fallback aliases when configured. ML always probes both. Agent-prod defaults to
ladduu-agent-prodfirst andladduu-agent-prod-publicsecond only when you keep the built-in primary alias; if you override justBOT_AGENT_HOSTorBOT_AGENT_HOST_PRIMARY, the fallback stays empty unless you setBOT_AGENT_HOST_FALLBACKexplicitly. - Cursor Remote-SSH uses SSH transport and cannot run directly over mosh transport.
- Use
cursor-mlfor editor workflow,mmlfor the ML raw terminal workflow, andmagentfor the agent-prod raw terminal workflow.
Interactive picker behavior:
- Current and active sessions are listed first with inline zellij metadata;
EXITEDsessions are grouped below them. Enterattaches the selected session or creates the highlightedCreate new session: <name>row.Ctrl-Kpermanently deletes anEXITEDsession after ay/Nconfirmation.Ctrl-Rrefreshes the session list without leaving the workflow.- When the typed query is a valid new session name, a dynamic create row appears at the top of the picker.
- After create, resurrect, delete, cancel, or failure actions, the helper prints a one-line record back to the shell when control returns.
work-hereis a plain-shell entrypoint. If you are already inside zellij on the current host, useCtrl-o wto open the built-in session manager instead.- The picker owns a zellij-inspired color theme: green session names, magenta age text, red
EXITEDstate text, and mutedfzfchrome for the prompt, border, and header. - The current interaction model and themed output here are the functional baseline to preserve before any later visual refinements.
Use start-zellij-session-for-task when you want to kick off a second Codex run
inside a detached zellij session without stealing the current terminal:
start-zellij-session-for-task "https://linear.app/trymyzone/issue/ZON-39/define-append-only-pr-review-comment-contract-keyed-by-head-sha"
start-zellij-session-for-task ZON-39
start-zellij-session-for-task --target ml "Investigate flaky Linear live-session sync"Behavior:
- Resolves tracker-aware slugs with the same shared task-status tooling used by
start-new-task. - Creates a detached zellij session whose name matches the resolved task slug.
- Renames the initial tab to
[TRACKER-ID]when a tracker is present. - Launches Codex as the session's default shell so attaching lands on the live Codex UI.
- If the detached session comes up but the pane does not visibly show Codex/start-new-task output within a short verification window, the helper fails instead of treating session creation alone as success.
- Launches Codex through
codex-app-notify-sessionby default sorequest_user_inputprompts can send WhatsApp/Gmail notifications through the local App Server proxy. - Pass
--no-codex-app-notifyor usestart-zellij-session-for-task-rawto use raw Codex for fallback/debug work. - Uses the current machine's default Codex profile instead of forcing a profile override.
- Clears inherited
CODEX_*session metadata before starting the child Codex process. - Seeds the child interactive Codex session with the exact initial
$start-new-task <original input>prompt. - That seeded
start-new-taskflow is expected to continue through initial tracker/local context review and first-pass plan approval in the child session when enough information is already available. - Uses
codex-app-notify-session --dangerously-bypass-approvals-and-sandbox, preserving the current CLI equivalent of the older--yoloshorthand while routing through the App Server notification proxy. - Prints the attach hint (
zellij attach ...,work-ml ...,work-arya ..., orwork-agent ...) plus the seeded initial prompt after launch. - Supports
--dry-runfor inspection without starting the session. - For remote targets, fails early if the resolved project root is not checked out on that host instead of launching an immediately exited session.
Use these repo-managed helpers for regular non-Symphony multi-session work:
get-cross-session-context --project-root "$PWD" ZON-71 --include-transcript-tail 3
send-zellij-message --project-root "$PWD" --text "Please post a short status update." ZON-71
send-zellij-message --project-root "$PWD" --text "Please post a short status update." --execute --submit enter ZON-71
pr-autoreview-loop status --repo ~/pro/botfiles --pr 123
pr-autoreview-loop wait --repo ~/pro/botfiles --pr 123Behavior:
get-cross-session-contextresolves another tracked task/session by tracker ref, task slug, or explicit task/session override.- Task/status metadata is the primary contract; transcript tail is the targeted fallback; live zellij inspection is diagnostic only.
send-zellij-messageis dry-run by default and requires--executefor actual writes.send-zellij-messageautomatically adds a delayed confirm Enter for large multiline Codex payloads because one immediate Enter can leave the text staged instead of submitted.send-zellij-messagerefuses ambiguous multi-tab targets and cross-machine targets unless you intentionally use an explicit local--zellij-sessionoverride for debug work.pr-autoreview-loopmatches only current-head reviewer artifacts, preferringreview-run-metatop-level comments and falling back to exact-head GitHub review objects.pr-autoreview-loopis designed to support a semi-autonomous fix loop: wait for review, address findings, push, wait again, and stop only when clean or genuinely blocked.pr-autoreview-loopreportsblockedinstead of waiting forever when a repo has no detectable reviewer infrastructure and no valid current-head reviewer artifact exists yet.
See docs/cross-session-orchestration-contract.md for the shared v1 contract.
Optional environment variables (set before sourcing .botrc) let you override host aliases:
export BOT_ML_HOST_PRIMARY=ladduu-dev-ml-vm-ts
export BOT_ML_HOST_FALLBACK=ladduu-dev-ml-vm
export BOT_ARYA_HOST=ladduu-dev-aryabhatta
export BOT_AGENT_HOST=ladduu-agent-prod
export BOT_AGENT_HOST_PRIMARY=ladduu-agent-prod
export BOT_AGENT_HOST_FALLBACK=ladduu-agent-prod-public
export BOT_CURSOR_ML_HOST_PRIMARY=ladduu-dev-ml-vm-ts
export BOT_CURSOR_ML_HOST_FALLBACK=ladduu-dev-ml-vm
export BOT_CURSOR_ML_PATH=/home/azureuserTo enable WhatsApp notifications, create secrets/local/claude-hooks.rc with:
export WHATSAPP_ENABLED=true
export WHATSAPP_TOKEN="your_whatsapp_cloud_api_token"
export PHONE_NUMBER_ID="your_phone_number_id"
export NOTIFY_PHONE_NUMBER="+1234567890"You'll need a Meta WhatsApp Business API account.
You can include clickable session links (Open Session: ...) in WhatsApp and email alerts.
-
Ensure zellij web server is running locally on the machine:
/opt/homebrew/bin/zellij web --status || /opt/homebrew/bin/zellij web --start --daemonizeLinux path variant:
zellij web --status || zellij web --start --daemonize -
Expose zellij web over your tailnet on
:8443:tailscale serve --bg --https=8443 127.0.0.1:8082 tailscale serve status
macOS app bundle CLI variant:
/Applications/Tailscale.app/Contents/MacOS/Tailscale serve --bg --https=8443 127.0.0.1:8082 /Applications/Tailscale.app/Contents/MacOS/Tailscale serve status
-
Add these to
secrets/local/claude-hooks.rc:ZELLIJ_WEB_ENABLE_LINKS=true ZELLIJ_WEB_BASE_URL=https://<your-tailnet-dns-name>:8443 ZELLIJ_SEND_ATTACH_COMMAND=true
-
Create a one-time zellij web login token:
/opt/homebrew/bin/zellij web --create-token
Save the resulting token securely and open:
https://<your-tailnet-dns-name>:8443/?token=<token> -
Trigger a smoke notification:
/opt/homebrew/bin/uv run --project ~/pro/botfiles/claude/hooks \ python ~/pro/botfiles/codex/hooks/send.py --title "Zellij Link Smoke" "verify zellij link"
To repair the local route after a reboot or Tailscale config drift, run:
~/pro/botfiles/bin/ensure-zellij-web-link-routeNotes:
ZELLIJ_WEB_BASE_URLmust match the machine sending the notification (machine-local setting).- Session URLs are built as:
<ZELLIJ_WEB_BASE_URL>/<url-encoded-zellij-session-name>. start-zellij-session-for-taskwill attempt this repair automatically before it prints a local zellij web link.- If notifications run outside zellij (
ZELLIJ_SESSION_NAMEmissing), link falls back ton/a.
Define machine identity once in secrets/local/machine.rc:
export SYSTEM_NAME="MyMachineName"
export BOT_MACHINE_SSH_ALIAS="my-ssh-host-alias"SYSTEM_NAME is reused across:
- WhatsApp notifications (origin context)
- Task-status metadata sync (
start-new-task,continue-task,save-task-status,get-task-details) - Task closeout orchestration (
finish-task)
BOT_MACHINE_SSH_ALIAS is the SSH host alias that other machines should use to reach this machine. Screenshot local-agent payloads produced by ma08/macos-screenshot-to-s3 use it as their compact machine value.
If not set, tooling falls back to hostname.
ma08/macos-screenshot-to-s3 can put a compact local-agent payload on the clipboard after a screenshot:
screenshot-info:
machine: sourya-mac
path: /Users/sourya4/Pictures/Screenshots/example.png
When a coding agent receives this payload:
- If it is running on the same machine, copy the
pathdirectly into the active task'suser_inputs/input_artifacts/. - If it is running elsewhere, treat
machineas the SSH host alias and retrieve the file withscpintouser_inputs/input_artifacts/. - Update
user_inputs/input_artifacts/index.mdwith the original payload source, capture time, and copied local artifact path. - Analyze the captured local artifact instead of relying on an expiring S3 URL.
- If
machineis not a working SSH alias, consult~/pro/personal_os/context/machine-ssh-aliases.mdand~/.ssh/config; for legacyscreenshot-local:payloads, preferssh_hostwhen present.
For the primary Mac, configure the screenshot app with:
export BOT_MACHINE_SSH_ALIAS="sourya-mac"The launchd wrapper in screenshot-to-s3 sources ~/pro/botfiles/.botenv, so this alias can live in botfiles local env.
Create secrets/local/codex-azure.rc from the template:
cp secrets/templates/codex-azure.rc.example secrets/local/codex-azure.rcThis file is ignored by git and sourced via .botenv.
It now carries the shared Azure key for the Codex Azure profile plus the default Oracle Azure route and optional Azure deep-research endpoint/key/deployment settings.
Skills extend Claude Code and Codex with specialized capabilities.
After running setup.sh:
- Claude skills are available at
~/.claude/skills/(backed byclaude/skills/) - Codex skills are available at
~/.codex/skills/(backed bycodex/skills/) - Codex system skills under
~/.codex/skills/.system/are machine-managed and intentionally git-ignored in this repo (to allow version/platform differences across machines)
Shared workflow skills now cover the full tracked-task lifecycle:
start-new-taskscaffolds a tracker-aware task folder and initial metadata.continue-taskadopts an existing tracked task after an interruption, syncs thatstatus.mdto the new session, and uses transcript tail only as fallback context.get-task-detailsresolves the active task folder, status path, tracker URL, transcript path, and session metadata.save-task-statusupdates the durable task record throughout execution.finish-taskstandardizes closeout when the user asks to wrap up a task: it checks closeout readiness first, syncs status/tracker notes, handles any required downstream heads-up, and performs local cleanup only after confirmation.
An older Claude-only Notion skill is kept under claude/backup_skills/notion/ as an archived reference. It is not part of the active claude/skills/ symlink target installed by setup.sh.
I originally created it to have a skill-only Notion-Claude Code integration that avoided MCPs which were causing context bloating. If you want to revive it, move or copy the archived skill back into claude/skills/notion/ and re-test it against the current Notion SDK.
Setup:
-
Install the Notion SDK:
npm install -g @notionhq/client
-
Create a Notion Integration:
- Go to https://www.notion.so/my-integrations
- Create a new integration
- Copy the "Internal Integration Token" (starts with
ntn_)
-
Set environment variables in an existing botfiles secret file (e.g., append to
secrets/local/claude-hooks.rc):export NOTION_API_KEY="ntn_your_token_here" # read/write permissions to target pages/databases export NOTION_UPDATES_DB_ID="your_database_id" # Optional
.botenvsources the files listed in its fixed sourcing loop. To add a new standalone file likesecrets/local/notion.rc, you would also need to add it to.botenv's loop. The simplest path is appending to an existing file likeclaude-hooks.rc. -
Share pages/databases with your integration in Notion
Test the connection:
node claude/backup_skills/notion/examples/test-connection.jsSee claude/backup_skills/notion/README.md for detailed usage.
botfiles/
├── .botenv
├── .botrc
├── README.md
├── LICENSE
├── .gitignore
├── setup.sh
├── bin/
│ ├── oracle
│ ├── oracle-mcp
│ ├── start-zellij-session-for-task
│ ├── get-cross-session-context
│ ├── send-zellij-message
│ └── ...
├── docs/
│ ├── task-status-tracker-contract.md
│ └── cross-session-orchestration-contract.md
├── context/
│ └── daily/
├── hermes/
│ └── README.md
├── shell/
│ ├── 10-uv-bin.sh
│ ├── 20-ssh-workflows.sh
│ ├── 30-oracle.sh
│ ├── clipboard-copy
│ └── work-zellij
├── zellij/
│ └── config.kdl
├── secrets/
│ ├── README.md
│ └── templates/
│ ├── claude-bedrock.rc.example
│ ├── claude-hooks.rc.example
│ ├── claude-vertex.rc.example
│ ├── codex-azure.rc.example
│ ├── codex-app-server.rc.example
│ ├── codex-openai.rc.example
│ ├── linear.rc.example
│ ├── machine.rc.example
│ └── opencode-azure.rc.example
├── codex/
│ ├── AGENTS.md
│ ├── config.toml
│ ├── agents/
│ │ ├── oracle_awaiter.toml
│ │ └── reviewer.toml
│ └── skills/
│ ├── README.md
│ ├── _shared/
│ ├── start-new-task/
│ ├── continue-task/
│ ├── finish-task/
│ └── ...
└── claude/
├── agents/
│ ├── oracle-awaiter.md
│ └── reviewer.md
├── settings.json
├── statusline-simple.sh
├── hooks/
│ ├── .gitignore
│ ├── pyproject.toml
│ ├── notification.py
│ ├── stop.py
│ ├── pretooluse_notification.py
│ ├── utils.py
│ └── whatsapp.py
└── skills/
├── _shared/
├── start-new-task/
├── continue-task/
├── finish-task/
└── ...
To pull updates on any machine:
cd ~/pro/botfiles
git pull
cd claude/hooks && uv sync # If dependencies changedRestart Claude Code after pulling updates.
git clone <repo-url> ~/pro/botfiles
cd ~/pro/botfiles
./setup.shsetup.sh handles symlinks, Python deps, shell entrypoint wiring, and secret file checks interactively.
1. Clone and run setup
git clone <repo-url> ~/pro/botfiles
cd ~/pro/botfiles
./setup.sh2. Create secret files from templates
mkdir -p secrets/local
cp secrets/templates/machine.rc.example secrets/local/machine.rc
cp secrets/templates/claude-bedrock.rc.example secrets/local/claude-bedrock.rc
cp secrets/templates/claude-hooks.rc.example secrets/local/claude-hooks.rc
# Copy others as needed:
cp secrets/templates/codex-azure.rc.example secrets/local/codex-azure.rc
cp secrets/templates/codex-app-server.rc.example secrets/local/codex-app-server.rc
cp secrets/templates/codex-openai.rc.example secrets/local/codex-openai.rc
cp secrets/templates/claude-vertex.rc.example secrets/local/claude-vertex.rc
cp secrets/templates/opencode-azure.rc.example secrets/local/opencode-azure.rcEdit each file and fill in real values. At minimum, create machine.rc:
# secrets/local/machine.rc
export SYSTEM_NAME="My-Machine-Name"3. Wire shell entrypoints (if setup.sh didn't do it)
For macOS / zsh:
# ~/.zshenv — core env for ALL zsh invocations (interactive + non-interactive)
echo '[ -f "$HOME/pro/botfiles/.botenv" ] && . "$HOME/pro/botfiles/.botenv"' >> ~/.zshenv
# ~/.zshrc — interactive aliases and functions
echo 'source ~/pro/botfiles/.botrc' >> ~/.zshrcFor Linux (Ubuntu, Debian, Azure VMs, etc.) / bash:
# Add BASH_ENV to whichever login file bash reads first.
# Bash checks ~/.bash_profile, ~/.bash_login, ~/.profile in order and
# reads ONLY the first one found. If you have ~/.bash_profile, use that
# instead of ~/.profile.
echo 'export BASH_ENV="$HOME/pro/botfiles/.botenv"' >> ~/.profile # or ~/.bash_profile
# ~/.bashrc — interactive aliases and functions
echo 'source ~/pro/botfiles/.botrc' >> ~/.bashrc4. Restart your shell and verify
# Verify core env loads:
echo "BOTFILES_ROOT=$BOTFILES_ROOT"
echo "SYSTEM_NAME=$SYSTEM_NAME"
echo "UV_BIN=$UV_BIN"
# Verify interactive layer (aliases, functions):
alias cc
type oracle5. Verify non-interactive env parity
This is the whole point of the two-layer model — non-interactive processes should get the same core env:
For zsh machines (test via SSH or a non-interactive zsh):
zsh -c 'echo "BOTFILES_ROOT=$BOTFILES_ROOT SYSTEM_NAME=$SYSTEM_NAME"'For bash machines (test that BASH_ENV propagates):
bash -c 'echo "BOTFILES_ROOT=$BOTFILES_ROOT SYSTEM_NAME=$SYSTEM_NAME"'Both should show the values from your secret files.
BASH_ENV propagates through the process tree from login shells, so it works for:
- Interactive terminals and their child processes
- SSH command execution (bash on Linux sources
.bashrcfor remote commands) - Agent-spawned subprocesses (as long as the parent had BASH_ENV set)
It does not automatically apply to:
- systemd services —
.botenvis a shell script (conditionals, loops), not a plainKEY=VALUEfile, soEnvironmentFile=cannot parse it. Instead, source it from your service command:Or if you only needExecStart=/bin/bash -c '. /home/<user>/pro/botfiles/.botenv && exec <actual-command>'
BASH_ENVpropagation for bash subprocesses:Environment=BASH_ENV=/home/<user>/pro/botfiles/.botenv
- cron jobs —
.botenvis sh-safe (falls back to$HOME/pro/botfilesfor path resolution under plain/bin/sh). Prepend. ~/pro/botfiles/.botenv &&to the command. Alternatively, setSHELL=/bin/bashandBASH_ENV=/home/<user>/pro/botfiles/.botenvin the crontab header
If other machines should be able to connect to this one using the work-* commands:
- Add an SSH host alias in
~/.ssh/configon the connecting machines - Ensure
zellijis installed on the new machine (required forwork-*attach/create) - Optionally set
BOT_*_HOSTenv vars (see SSH Workflow Commands)
The same SSH aliases are used as screenshot local-agent machine values. When a payload includes machine: sourya-mac, remote agents should be able to fetch the file with scp if their SSH config has that alias. See ~/pro/personal_os/context/machine-ssh-aliases.md for the current alias map.
| Machine | OS | Shell | SYSTEM_NAME | Role |
|---|---|---|---|---|
| sourya-mac | macOS (zsh) | zsh | Sourya-Macbook | Primary dev |
| ladduu-dev-ml-vm | Ubuntu (bash) | bash | Azure-A100-GPU-VM | GPU VM, agents |
MIT License. See LICENSE.






