Public dotfiles for fish, git, jed, and VS Code — plus a large collection of agent skills and command-line tools for AI-assisted development with agents such as Claude Code, Codex, and Gemini CLI.
.dotfiles/
├── bin/ # Shell scripts added directly to $PATH
├── fish/
│ ├── config.fish # Main fish config; loads .private overlay if present
│ ├── conf.d/ # Fish startup snippets
│ ├── completions/ # Fish completions
│ └── functions/ # Fish functions (autoloaded)
├── home/ # Dotfiles symlinked into $HOME by install.sh
├── etc/ # Tool-specific config (git templates, VS Code, etc.)
├── skills/ # Agent skills (see Agent skills below)
├── config/ # Plugins for the skill and permission tools
├── docker/ # Dockerfile + notes for building an image from this repo
├── tests/ # TAP tests for bin/ scripts
└── install.sh # Idempotent install/update script
install.sh symlinks home/.* into $HOME, installs packages, and wires up
tool-specific config. It is safe to run multiple times, and doubles as the
updater: an existing checkout is fast-forwarded before applying. It can be run
locally (./install.sh) or piped straight from the network (see
Installation).
The skills/ directory contains
Agent Skills: folders with a SKILL.md (metadata and
instructions) plus supporting scripts and references that teach AI coding agents
how to perform specific tasks — driving Android devices over adb, inspecting
APKs, managing emulators, looking up current AndroidX library facts, enforcing
coding standards, planning award flights, and more.
| Skill | Description |
|---|---|
adb |
ADB device automation with a Wear OS focus: screenshots, screen recording, tiles, dumpsys, logcat |
agent-tools |
CLI tools delegating analysis to AI models: image description, screenshot diffing, deep research |
apk |
Offline APK binary analysis: manifests, resources, launcher icons, split APKs |
coding-standards |
This repo's coding standards: shell/Python/Markdown formatting, CLI design, commit message style |
emumanager |
(Deprecated — prefer android-cli) Legacy Android SDK bootstrap and AVD/emulator manager |
jetpack |
Current AndroidX/Jetpack facts that are wrong from memory: versions, Maven coordinates, dependencies |
More specialised skills: android-testing (system
state and connectivity testing on phone and Wear OS),
wear-widget (reverse-engineering widgets), and
workspace-tools (skill selection and agent tool
permissions). Personal-workflow skills live here too:
agent-review, taskgo,
technical-writing, and
travel.
Skills are intentionally not installed globally — every project seeing every
skill would be noise. Instead, the bundled skill command manages them per
workspace as untracked symlinks in .claude/skills/ or .agents/skills/,
auto-detecting which agent is installed:
skill add --defaults # link the default skills
skill add jetpack # add one skill by name
skill list # what's linked here, 'default' or 'added'Skill names resolve against skills/ in this repo and its .private/.corp
overlays (later layers shadow by name); plugins in config/skill/plugins/
register additional remote skills from third-party catalogs. The default skills
are set by etc/skill/defaults.sh in each layer, which install.sh runs. A
fresh clone links them into the repository, and the agent wrappers link them
into each session.
SECURITY NOTE: The .dotfiles repository is public. To prevent the leakage
of sensitive information (such as API keys, corporate tool configurations, or
internal pathnames), two private companion repositories can optionally be
cloned: ~/.private and ~/.corp.
This architecture is specifically designed to help both humans and AI agents understand what goes where and ensures private information remains strictly out of the public repository. This repository works fine without them.
| Repo | Intended for |
|---|---|
~/.dotfiles |
Public config, tools, and scripts |
~/.private |
Personal secrets, API keys, private tooling |
~/.corp |
Work machine config, internal tooling |
install.sh processes overlays in this order: .dotfiles → .private →
.corp. Later layers win on conflict. A file in ~/.corp always beats the same
path in ~/.private or ~/.dotfiles.
Two functions handle overlay merging, with different semantics:
-
link_overlay_path rel dst— symlinksdstto the single highest-priority source that providesrel. Used for whole-file configs where only one version makes sense (e.g.etc/starship/starship.toml). -
link_overlay_files rel dst— iterates every overlay in order and symlinks each file it finds underrel/intodst/. All overlays contribute; later layers win on filename collision. Used when multiple overlays may each add files to a directory (e.g.etc/code/).
home/ dotfiles are linked by iterating all overlays in order with ln -sf, so
the last overlay to provide a given filename wins.
| Path | Mechanism | Notes |
|---|---|---|
home/.* |
link_home_dotfiles (all overlays) |
Last overlay wins per filename |
etc/agents/AGENTS.md |
overlay_path (highest wins) |
Symlinked to ~/.codex/, ~/.gemini/, ~/.claude/ (gated on their respective binaries) |
etc/agents/agy/hooks.json |
link_overlay_path |
Symlinked to ~/.gemini/config/hooks.json (gated on agy) |
etc/agents/agy/hooks |
link_overlay_path |
Symlinked to ~/.gemini/config/hooks (gated on agy) |
etc/shpool/config.toml |
link_overlay_path |
|
etc/starship/starship.toml |
link_overlay_path |
|
etc/ghostty/config |
link_overlay_path |
|
etc/bat/config |
link_overlay_path |
|
etc/code/ |
link_overlay_files |
VS Code settings.json, keybindings.json, etc. |
etc/macos/KeyBindings |
overlay_path |
rsync'd (not symlinked) due to macOS bug |
etc/macos/Solarized.clr |
overlay_path |
Copied to ~/Library/Colors/ |
etc/skill/defaults.sh |
run by install.sh |
Adds or removes default skills with skill --scope defaults |
skills/*/ |
skill catalog source |
Not linked globally; added per workspace via skill add (see Agent skills) |
After the main install, install.sh runs ~/.private/update and
~/.corp/update if they exist and are executable. These hooks handle
overlay-specific setup that cannot be expressed as file overlays (package
installs, auth setup, etc.).
.private/ # same structure for .corp
├── fish/
│ ├── conf.d/ # Startup snippets sourced by config.fish
│ ├── completions/ # Completions prepended to fish_complete_path
│ ├── functions/ # Functions prepended to fish_function_path
│ └── secrets.fish # API keys and tokens (chmod 600)
├── home/ # Dotfiles symlinked into $HOME (e.g. .gitconfig.local)
├── etc/ # Tool-specific config (see overlay-aware paths above)
├── skills/ # Agent skills that shadow ~/.dotfiles/skills/ by name
└── update # Optional hook run at end of ./install.sh
fish/config.fish prepends ~/.private/fish/functions (and .corp
equivalents) and ~/.private/fish/completions to the fish search paths, and
sources any ~/.private/fish/conf.d/*.fish snippets at shell startup.
To install: clone your private repos to ~/.private and/or ~/.corp, then run
./install.sh again.
Secrets (API keys, tokens) live in ~/.private/fish/secrets.fish and are not
sourced in full at shell startup. Three fish functions provide on-demand access:
| Function | Usage | Description |
|---|---|---|
setsecret |
setsecret NAME [NAME...] |
Load secret(s) into the current shell |
getsecret |
getsecret NAME |
Print secret value(s) to stdout (array-safe) |
envsecret |
envsecret NAME [...] [--] CMD |
Run a command with secret(s) injected |
envsecret is the safest option for scripts: secrets are injected into the
child process and never leak into the calling shell.
A ~/.private/fish/conf.d/ snippet uses setsecret --if-unset to auto-load a
small set of everyday secrets at shell startup.
Run setsecret --help, getsecret --help, or envsecret --help for details.
direnv handles per-project environment switching,
configured via ~/.direnvrc (symlinked from home/.direnvrc). To activate it
in a project, create an .envrc file in the project root.
Node.js, Ruby, and Python versions are selected per directory with envrc
blocks and installed with node-install, ruby-install, and python-install:
node-install 22 # latest 22.x into ~/.local/share/node/versions
envrc create node 22 # use node 22 + layout node (node_modules/.bin on PATH)
ruby-install 3.4 # compiles latest 3.4.x into ~/.local/share/ruby/versions
envrc create ruby 3.4 # use ruby 3.4 + layout ruby (gems in .direnv/ruby)
python-install # checks that uv is installed
envrc create uv # layout uv (creates and activates .venv)Ruby builds need the ruby-build toolchain and its build dependencies, which
install with install.sh --with ruby. The installers live in the
workspace-tools skill; its
Runtime Versions
section covers the full workflow, including diagnosing a project that runs on
the wrong version.
The same install.sh script both installs and updates, and runs in two ways.
git must already be installed either way (see
Prerequisites).
Clones the repo to ~/.dotfiles (if absent), points the push remote at SSH,
then installs — and on later runs, updates (it fast-forwards ~/.dotfiles
before applying):
curl -fsSL https://raw.githubusercontent.com/ithinkihaveacat/dotfiles/main/install.sh | bashIt must be piped to bash, not sh — the script is bash and refuses to run
under other shells. Pass flags after -s --, e.g. … | bash -s -- --force. To
download the whole script before running anything (rather than streaming it into
the interpreter), use the equivalent:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/ithinkihaveacat/dotfiles/main/install.sh)"cd $HOME
git clone https://github.com/ithinkihaveacat/dotfiles.git .dotfiles
cd .dotfiles
git remote set-url origin --push git@github.com:ithinkihaveacat/dotfiles.git
./install.shAfter cloning ~/.private and/or ~/.corp (if available), run ./install.sh
again so the overlay is applied.
Note:
install.shmay overwrite unmanaged files in locations such as~/Library/KeyBindings. It is otherwise safe to run multiple times.
install.sh is split into features (linking dotfiles, packages, uv, agent
setup, and so on), and a profile picks which of them run. The default,
workstation, sets up a long-lived machine for interactive use: dotfiles, fish
and its completions, core packages and interactive extras, and a daily upgrade
of everything installed. Heavier package sets (android, ruby, media,
services) are in no profile and are added with --with:
./install.sh --with android,ruby # also install Android and Ruby tooling
./install.sh --without upgrades # skip the daily upgrades
./install.sh --prune # also remove brew packages no feature installs--only names the features outright, and -n/--dry-run prints the selection
without running anything; ./install.sh --help lists the features and profiles.
Once installed, a package is kept current by later runs whether or not its
feature is selected again.
A failing feature is reported and the rest still run; the exit status is
non-zero if any failed. Set AGENT_OFFLINE=1 to skip every feature that needs
the network.
For the setup script of a disposable cloud or agent container, use the cloud
profile:
curl -fsSL https://raw.githubusercontent.com/ithinkihaveacat/dotfiles/main/install.sh | bash -s -- --profile cloudIt links the dotfiles and fish config, prefetches what the uv-based scripts
need, and sets up skill plugins and default skills. It does not use sudo,
install or upgrade packages, update uv or agent CLIs, pull the repositories, or
change npm's prefix, so it needs no access beyond GitHub and uv's package index.
The platform's own agent setup is left alone.
- Ubuntu/Debian:
sudo apt-get install git - macOS:
xcode-select --install
- Terminal: Import
etc/macos/Solarized Dark.terminaland set it as the default profile. - Keyboard: System Preferences > Keyboard > Shortcuts > Services > File and Folders: enable "New Terminal at Folder".
- Text Replacements: If not shared via iCloud, restore from
etc/macos/Text Replacements.plist— see Back up and share text replacements on Mac. - Lock Screen: Add to Menu Bar via Keychain Access preferences.
- Volume: Add to Menu Bar via Control Center > Sound > Always Show in Menu Bar.
- Time Machine: Disable local snapshots:
sudo tmutil disablelocal - Hot Corners: Disable via System Preferences.
- Fonts: See https://typography.guru/journal/awesome-catalina-fonts/.
- iA Writer theme: https://ia.net/writer/templates/
- Network Link Conditioner (for simulating degraded network conditions): https://developer.apple.com/download/more/?q=Additional%20Tools
Complete setup sequence for a fresh Raspberry Pi OS install.
-
Use the Raspberry Pi Imager to write the OS to an SD card or SSD.
- For a headless install: Raspberry Pi OS (other) > Raspberry Pi OS Lite.
- In OS customisation: enable SSH (Remote access section).
- (Optional) Configure Wi-Fi.
-
SSH in:
ssh mjs@lil.local(substitute your username and hostname).- (Optional) If using Ghostty, copy the terminfo from your local machine:
infocmp -x xterm-ghostty | ssh mjs@lil.local -- tic -x -
- (Optional) If using Ghostty, copy the terminfo from your local machine:
-
Update the OS:
sudo apt-get update && sudo apt-get upgrade -yReboot if
/var/run/reboot-requiredexists:sudo reboot -
Install git:
sudo apt-get install git -
Install Tailscale:
curl -fsSL https://tailscale.com/install.sh | shthensudo tailscale up -
(Optional) Install Node.js and npm:
sudo apt-get install nodejs npm(for newer versions see Node.js). -
(Optional) Disable WiFi and Bluetooth: add these lines under
[all]in/boot/firmware/config.txt:dtoverlay=disable-wifi dtoverlay=disable-bt -
(Optional) Change hostname: edit
/etc/hostnameand/etc/hosts. -
Reboot after any optional steps above:
sudo reboot
Once complete, proceed to Installation.
Tip: Use VS Code's Remote - SSH extension to edit files on the Pi.
Put manually installed binaries in ~/.local/bin.
Scripts are tested with TAP-format tests run via prove. Tests are co-located
with the code they cover: tests/test-* for bin/ scripts and
skills/*/tests/test-* for skill scripts. Run them with:
prove -j 9 tests/test-* skills/*/tests/test-*See tests/README.md for details, including how to run tests offline and in isolated environments.
A GitHub Actions workflow (.github/workflows/lint.yml) runs on every push:
shellcheck and shfmt via skills/coding-standards/scripts/shell-format over
all Bash scripts (bin/, skills/*/scripts/, install.sh), ruff via
skills/coding-standards/scripts/python-format over all Python scripts, plus
the full test suite.
Tools managed by uv, meaning scripts with a
uv run --script shebang and the formatters that run pinned tools via uvx
(ruff, shfmt, shellcheck), download their dependencies on first use. To
prepare a CI job or cloud agent environment that will later lose network access,
run this once while online, e.g. from the environment's setup script:
./install.sh --only uv # install uv; fetch everything into uv's cache
UV_OFFLINE=1 ./install.sh --only uv # check, without the network, that nothing is missingThe check downloads nothing and puts nothing on PATH, but it is not read-only:
it builds each script's environment inside uv's cache from the packages already
there, which is how it proves they work offline.
--only uv runs just the uv feature of install.sh: no git pull, no symlinks
into $HOME, no sudo, and no other packages.
Afterwards, run with UV_OFFLINE=1 and AGENT_OFFLINE=1 (see
tests/README.md). CI does exactly this before running the
tests. Dependencies installed outside uv (e.g. fish, xmllint, java) are
not covered.