Highly opinionated development environment for AI safety research. ZSH, Tmux, Vim, SSH, and AI coding assistants across macOS, Ubuntu, and cloud containers.
This setup reflects workflows optimized for ML research: reproducibility, experiment tracking, async API patterns, and rigorous methodology. The AI assistant configurations enforce research disciplineβinterview before planning, plan before implementing, skepticism of surprisingly good results.
Key highlights:
- π€ AI Coding Assistants - Extensively configured Claude Code and some Codex support
- π» Sensible defaults for Ghostty
- π¦ Rust-powered CLI tools - Modern, blazing-fast replacements for standard Unix utilities
- π§Ή Automatic cleanup - Scheduled cleanup of Downloads/Screenshots (macOS, moves to trash)
Originally forked from jplhughes/dotfiles - thanks John for the solid foundation!
AI agents working here: start with
CLAUDE.mdβ top rules, a common-tasks table, and pointers intodocs/deploy-components.md(deploy behavior, cloud, extending) anddocs/tooling-and-packages.md(packages, symlink-vs-copy, gotchas). This README is human-oriented onboarding; CLAUDE.md is the operational doc.
This project offers two quickstart paths: Local and Cloud.
For setting up on your personal machine (macOS, Linux, desktop/laptop):
git clone https://github.com/yulonglin/dotfiles.git && cd dotfiles
# 1. Install dependencies (zsh, tmux, CLI tools, AI assistants)
./install.sh
# 2. Deploy configurations (symlinks, shell config, secrets, automation)
./deploy.sh
# 3. Restart your shell
source ~/.zshrcinstall.shinstalls required software.deploy.shdeploys config files and settings.- Both scripts are idempotent and safe to re-run.
All configuration options are stored in config.sh. Flags are additive (e.g., --mouseless adds that feature to defaults). Use --minimal to disable most options.
For cloud environments (RunPod, Hetzner, Lambda Labs, etc):
-
SSH into your new remote machine as root.
-
Run the one-liner:
# RunPod (fresh pod) curl -fsSL https://raw.githubusercontent.com/yulonglin/dotfiles/main/scripts/cloud/setup.sh | bash # Hetzner / standard VPS (persistent /home) curl -fsSL https://raw.githubusercontent.com/yulonglin/dotfiles/main/scripts/cloud/setup.sh | USER_HOME=/home bash
This creates a non-root user in persistent storage (
/workspace/yulongon RunPod), copies SSH keys, installs dependencies, clones dotfiles, and runsinstall.sh --profile=cloud+deploy.sh --profile=cloud(a lean remote-dev set β no pueue/zotero/Rust toolchain). It will prompt for GitHub auth.Provisions the
mainbranch by default. To pin another branch, pass--branch(usebash -s --to forward args throughcurl | bash) or setDOTFILES_BRANCH:curl -fsSL https://raw.githubusercontent.com/yulonglin/dotfiles/main/scripts/cloud/setup.sh | bash -s -- --branch yulong -
Reconnect as your user:
ssh yulong@<ip>
-
(Optional) After pod restart (RunPod recreates
/etc/passwd):curl -fsSL https://raw.githubusercontent.com/yulonglin/dotfiles/main/scripts/cloud/restart.sh | bash -
(Optional) Customize components: Edit
config.shto disable resource-intensive options (AI assistants, cleanup automation, etc.) before running install/deploy.
Tip: The setup auto-detects cloud providers and adjusts accordingly (persistent storage paths, SSH config, no macOS-only features). See scripts/cloud/README.md for details.
Install dependencies (e.g. oh-my-zsh and related plugins). The installer auto-detects your OS and applies sensible defaults.
# Install with defaults (recommended)
./install.sh
# Install only specific components
./install.sh --minimal --tmux --zsh # --minimal disables all defaultsDefaults by platform:
| Platform | Defaults |
|---|---|
| macOS | zsh, tmux, AI tools, cleanup + Rust CLI tools via Homebrew |
| Linux | zsh, tmux, AI tools, create-user + Rust CLI tools via Homebrew (Linuxbrew) |
Installation on macOS requires Homebrew - install from brew.sh first if needed.
The Rust CLI tools installed by default: bat (cat), eza (ls), fd (find), ripgrep (grep), delta (diff), zoxide (cd), dust (du), jless (JSON viewer). --extras adds hyperfine, gitui, and code2prompt.
Deploy configurations (sources aliases for .zshrc, applies oh-my-zsh settings, etc.). All settings live in config.sh β edit once, deploy everywhere.
# Deploy with defaults (recommended)
./deploy.sh
# Profiles
./deploy.sh --profile=server # Safe base for shared machines
./deploy.sh --profile=minimal # Nothing enabled β specify what you want
# Deploy only specific components
./deploy.sh --only vim claude # Only vim and claude, nothing else
# Add to defaults
./deploy.sh --mouseless # Defaults + mouselessDefault components:
- Shell: ZSH, tmux, vim, Powerlevel10k
- Editors: VSCode/Cursor/Antigravity (merged settings), Zed (symlinked config + keymap),
.editorconfig,.curlrc,.inputrc - AI tools: Claude Code, Codex CLI, Serena MCP, Ghostty terminal
- Git: gitconfig, global gitignore/gitattributes, global git hooks (secret detection)
- Dev tools: htop, pdb++, matplotlib styles,
claude-toolsRust binary - Secrets: GitHub gist sync, Bitwarden Secrets Manager (BWS)
- Supply chain: 7-day quarantine for npm/bun/pnpm/uv, weekly dep-audit
- Automation: file cleanup (macOS), Claude Code session cleanup, AI tools auto-update, package auto-update, text replacements sync (macOS)
Flags are additive β e.g., ./deploy.sh --mouseless deploys defaults + mouseless. Use --minimal to disable all defaults, then specify only what you want.
This repo is highly personal β it reflects one person's workflow, opinions, and tooling choices. The best way to use it is to point a coding agent at this repo and ask it to extract the parts you find useful into your own dotfiles.
Generalizable (worth extracting):
- Shell config (zsh/tmux/p10k)
- Modern CLI tools (bat, eza, fd, rg, etc.)
- Git config + global gitignore/gitattributes
- Editor settings (VSCode/Cursor merge logic)
- Cleanup automation (Downloads/Screenshots)
- Gist sync (bidirectional SSH config/identity sync)
- BWS encrypted secrets workflow
Personal (skip or replace):
- Claude Code plugins/agents/skills
- Website alias, SSH host colors
- Mouseless config
- Ghostty theme aliases
- Specific API keys and gist IDs
- Cloud setup scripts (RunPod user)
- Plugin marketplace selections
All personal values are centralized in config.sh β edit DOTFILES_USERNAME, DOTFILES_REPO, GIST_SYNC_ID, GIT_USER_NAME, and GIT_USER_EMAIL to make it yours.
API keys are stored in Bitwarden Secrets Manager (BWS) β a hosted, team-shareable secrets vault. The CLI (bws) fetches secrets on demand; nothing is written to disk except a machine access token (at ~/.config/bws/token).
One command, secrets. Typing it bare prints a status header β backend, token path, this repo's bindings, ambiguous env names, permission problems β and then offers a menu, but only at an interactive terminal with gum installed. Piped or scripted (secrets | cat), it prints the header and exits 0, so a hook or CI job can call it unconditionally.
secrets # Status header, plus a menu at an interactive terminal
secrets init # First-time setup on this machine: save the BWS access token
secrets edit # Add/update/delete secrets (fzf TUI, or: secrets edit KEY VALUE)
secrets ls [ENV] # Env names, their active BWS key, blocked siblings
secrets get ENV # Print one value to stdout
secrets use ENV # Choose which BWS key an ambiguous env name resolves to
secrets envrc [KEY...] # Wire this repo's .envrc (direnv, resolved live)
secrets run KEY... -- CMD # Run CMD with only those keys in its environment
secrets doctor # Show backend paths and repair file permissionssecrets get prints a secret; secrets run uses one without printing it. Prefer run in anything whose output is logged or pasted.
New machine setup:
- Run
./install.sh(installs bws CLI) - Run
secrets initand paste your BWS access token from Bitwarden
Per-project usage: Run secrets envrc in any repo to create a .envrc that selectively exposes only the secrets that repo should see. It supports direct exports (KEY), renamed exports (ENV_VAR=SECRET_NAME), and a repo-specific Telegram plugin binding (--telegram-secret SECRET_NAME). If local .env files already exist, the TUI scans the repo root recursively and can offer to delete selected files.
secrets envrc tries direnv allow automatically. If that cannot update direnv's allowlist (for example in a sandboxed environment), it prints the manual direnv allow . command and still completes the rest of the setup.
Daemon repos that never reach direnv β a container, a systemd unit β use secrets envrc --plaintext, which materializes real values into .env at mode 600. It refuses to write unless .gitignore already covers .env, and the values are static: rotating the secret in BWS means re-running the command. Everything with a shell session uses plain secrets envrc instead, which resolves live and leaves no secret at rest.
Multi-layer defense against npm/PyPI supply chain attacks (axios 2026, litellm 2026, shai-hulud 2025). Deployed automatically with ./deploy.sh.
What it does:
| Layer | Defense | What it blocks |
|---|---|---|
| 7-day quarantine | min-release-age on all package managers |
Freshly-published malicious versions (caught within days) |
| Script blocking | ignore-scripts=true in npm/pnpm |
Postinstall scripts that exfiltrate secrets or install RATs |
| Credential isolation | No ambient exports; each repo's .envrc binds only its own keys |
A postinstall script in project A finds none of project B's keys in its environment |
| Lockfile scanning | Pre-commit hook checks changed lockfiles | Known-bad packages entering your lockfile |
| Weekly audit | Scans all repos for known-bad IOCs | Packages you already have that were later found compromised |
| Claude Code hook | Warns before any npm install / pip install |
AI assistant installing packages without checking them first |
Day-to-day workflow:
# Installing packages works normally β quarantine is transparent
npm install express # Works (express is >7 days old)
bun add zod # Works
uv add httpx # Works
# New packages published <7 days ago are blocked (intentional)
npm install some-brand-new-pkg
# Error: min-release-age β package was published 2 days ago
# Override for a specific install (after checking it's safe)
npm install --min-release-age=0 some-brand-new-pkg # npm
bun add --minimumReleaseAge=0 some-brand-new-pkg # bun
UV_EXCLUDE_NEWER= uv pip install some-brand-new-pkg # uvCredential isolation:
API keys are NOT exported into every shell, so a compromised postinstall script finds nothing in its environment. Each project's .envrc binds only the keys it needs:
# Interactive picker (fzf)
cd ~/code/my-project
secrets envrc # Select keys with TAB, confirm with ENTER
# β Creates .envrc with eval-based exports, direnv auto-loads on cd
# Non-interactive
secrets envrc ANTHROPIC_API_KEY OPENAI_API_KEY
# Map a namespaced secret into the env var your app expects
secrets envrc ANTHROPIC_API_KEY TELEGRAM_BOT_TOKEN=NUDGE_TELEGRAM_BOT_TOKEN
# Claude Telegram plugin: keep the token canonical in the secrets store,
# and generate .claude/channels/telegram/.env only at launch time
secrets envrc --telegram-secret AMBASSADOR_TELEGRAM_BOT_TOKEN
# Check what's configured
secrets envrc --list # Show keys in current .envrc
secrets envrc --clean # Remove .envrc
# One-off command with selected keys (no .envrc needed)
secrets run ANTHROPIC_API_KEY OPENAI_API_KEY -- python my_script.pyManual audit:
dep-audit # Scan all repos for known-bad packages now
# Runs automatically every Sunday at 10 AMConfig files deployed:
| File | Deployed to | Purpose |
|---|---|---|
config/npmrc |
~/.npmrc |
ignore-scripts=true + min-release-age=7 |
config/bunfig.toml |
~/.bunfig.toml |
minimumReleaseAge=604800 (seconds) |
config/pnpmrc |
~/Library/Preferences/pnpm/rc |
minimum-release-age=10080 (minutes) |
config/uv.toml |
~/.config/uv/uv.toml |
exclude-newer (via UV_EXCLUDE_NEWER env var) |
Selective deploy:
./deploy.sh --only pkg-configs # Just package manager configs
./deploy.sh --no-pkg-configs # Everything except package configs
./deploy.sh --only dep-audit # Just the weekly auditPre-commit hooks for secret detection across all repositories (./deploy.sh --git-hooks, part of defaults). Scans staged files for API keys, tokens, and credentials before each commit.
Everything else this repo does is documented next to the code it configures:
- Deploy components (every component, mechanisms, gotchas, how to extend) β
docs/deploy-components.md - Terminal, shell & dev tools (Ghostty themes + SSH colors, Powerlevel10k machine ID, Claude Code statusline,
claude-tools ignore, SSH keys, pdb++, htop, media recovery) βdocs/terminal-and-dev-tools.md - Claude Code setup (rules/skills/hooks layout, smart-merge restore) β
CLAUDE.md+docs/deploy-components.md - Plugin marketplaces & management β
docs/plugin-management.md - Codex / Antigravity / OpenCode integration β
docs/cross-tool-extensibility.md - Automation schedules (cleanup, auto-updates, uninstall commands) β
scripts/cleanup/README.md - Cloud provisioning detail β
scripts/cloud/README.md - Packages & tooling strategy (symlink-vs-copy, operational gotchas) β
docs/tooling-and-packages.md