ᓭ⍑ᒷꖎꖎ ᒲᔑ⊣╎ᓵ 1

Preview

A self-contained, cross-platform dotfiles management system with declarative YAML configuration and automated deployment.

Overview

This repository contains my personal configuration files (dotfiles) for macOS and Linux systems. The system is built around a custom "strap" framework that makes it easy to deploy, manage, and synchronize configurations across different machines and platforms.

What's Inside

Configurations for:

  • Terminal: Fish shell, Ghostty
  • Editor: Neovim
  • macOS: Yabai (tiling WM), Skhd (hotkey daemon), Hammerspoon
  • Linux: Hyprland, Waybar, Rofi, Dunst, Darkman
  • Multiplexer: Zellij
  • Agent Harness: Shared skills, instructions, and Codex agents
  • Git: Custom aliases and semantic commit tool

Key Features

  • Self-Contained Bootstrap: Python 3.10+ with vendored interactive UI dependencies
  • Reviewed Setup: Interactive selection and a final deployment review before changes are applied
  • Platform-Specific Configs: Generic configuration combined with macOS or Linux settings
  • Declarative YAML Format: Simple, readable configuration files
  • Automatic Symlink Management: No manual linking required
  • Safe Copy Semantics: Conflicts are preserved unless a strap explicitly authorizes replacement
  • Git Integration: Built-in sync command with auto-commit and push
  • Cron Job Management: Per-strap ownership with validation and rollback
  • Native Notifications: Desktop notifications on macOS and Linux

Quick Start

Clone the repository and run the bootstrap script:

git clone <your-repo-url> ~/.dotfiles
cd ~/.dotfiles
python3 bootstrap.py

Interactive setup lets you choose individual configurations or set up everything, then presents a final review before applying changes. Nothing is selected by default in Choose mode.

For explicit unattended deployment, use --all:

python3 bootstrap.py --all

The bootstrap process will:

  1. Discover and validate all strap configuration files
  2. Combine generic settings with the current Linux or macOS settings
  3. Plan symlinks, copies, and cron changes without modifying the system
  4. Show the complete deployment review
  5. Apply the confirmed plan and abort if reviewed filesystem state changed
  6. Set up the deytefiles command in your PATH

After bootstrap, you can use the deytefiles command from anywhere:

deytefiles sync              # Sync changes with git
deytefiles bootstrap         # Run the interactive bootstrap again
deytefiles bootstrap --all   # Set up every configuration without prompts

Strap Configuration System

The heart of this dotfiles system is the "strap" framework. Each component has a YAML configuration file that defines how it should be deployed.

File Naming

Accepted configuration filenames are:

  • strap.yaml - The primary configuration in a directory
  • {id}.strap.yaml - Additional independently selectable configurations in the same directory

Legacy executable .strap files and platform-suffixed filenames are rejected. Platform-specific actions live inside each strap's platforms mapping; generic actions combine with the current linux or darwin actions.

YAML Format

Basic structure of a strap configuration file:

name: component-name
category: System
description: Description shown during setup
platforms:
  generic:
    link:
      - file.conf
      - [source.conf, ~/.config/target.conf]
    copy:
      - [file.txt, ~/target.txt]
    cron:
      - ["0 * * * *", "~/script.sh"]

Entry Formats

The link and copy sections support multiple formats for flexibility:

1. Simple String (Automatic Target)

platforms:
  generic:
    link:
      - config.fish
      - aliases.fish

The target path is automatically inferred from the directory structure. Example: config.fish in .config/fish/ → ~/.config/fish/config.fish

2. Explicit Target

platforms:
  generic:
    link:
      - [source.conf, ~/.config/app/target.conf]
    copy:
      - [script.sh, ~/.local/bin/script.sh]

Specify both source and target paths explicitly.

3. Current Directory

platforms:
  generic:
    link:
      - [., ~/.config/nvim]

Link the entire directory containing the strap file.

Examples

Simple configuration (.config/fish/strap.yaml):

name: Fish
category: Terminal
description: Fish shell configuration
platforms:
  generic:
    link:
      - config.fish
      - aliases.fish
      - functions.fish

With platform-specific targets (.config/darkman/strap.yaml):

name: Darkman
category: Desktop
description: Automatic light and dark themes
platforms:
  linux:
    link:
      - [config.yaml, ~/.config/darkman/config.yaml]
      - [dark-mode.d, ~/.local/share/dark-mode.d]
      - [light-mode.d, ~/.local/share/light-mode.d]

Directory linking (.config/nvim/strap.yaml):

name: Neovim
category: Editor
description: Neovim editor configuration
platforms:
  generic:
    link:
      - [., ~/.config/nvim]

With copy and cron (.config/app/strap.yaml):

name: App
category: System
description: Example application configuration
platforms:
  generic:
    link:
      - config.conf
    copy:
      - [.theme.conf, ~/.config/app/.theme.conf]
    cron:
      - ["0 * * * *", "~/script.sh"]

Deployment Harness Example

The Agent Harness is split into independently selectable straps. For a Codex setup, choose Shared skills, Shared instructions, and Codex agents during interactive bootstrap.

Joplin knowledge partner

The original code in .local/share/joplin-agent/, the .local/bin/joplin-agent entry point, the shared joplin skill, and the Joplin-specific additions to shared straps, instructions, documentation, and tests are covered by the MIT license. The skill includes its own copy of the license for standalone distribution. Unrelated dotfiles and third-party dependencies retain their existing licensing.

Select Joplin integration, Shared skills, and Shared instructions in python3 bootstrap.py. The integration uses Joplin Desktop's local Web Clipper API, a locked joplin-mcp runtime, and the shared joplin skill for recall, inbox capture, and approved curation. Requires Python 3.11+ and uv on macOS or Linux.

Enable Web Clipper in Joplin's settings. Create the desired root notebook (Work or Personal) in Joplin, then provision this machine locally:

joplin-agent setup --install --profile work --credentials --hierarchy --harness codex

The token prompt is hidden. Use --port PORT if Joplin displays a different port. For a home machine, use --profile home: it grants all notebooks except the root Work and its children. Work mode grants only the root Work and its children. Anchored patterns distinguish the root from an unrelated nested notebook named Work.

Credentials live in ~/.config/joplin-agent/server.json; notebook rules, hierarchy paths, and tool switches live in ~/.config/joplin-agent/policy.json. Both files are private (0600), in a private directory (0700), outside this repository. Examples live in .local/share/joplin-agent/examples. setup --profile explicitly replaces the policy with that preset; keep custom policies by omitting this option. joplin-agent context shows only nonsecret scope and hierarchy information.

The --hierarchy option prepares Agent Inbox, Projects, Areas, Reference, and Archive under the existing chosen root. Existing content is preserved. Agents can create deeper notebooks for meaningful content and can propose approved inbox triage. Custom paths can be set in the private policy before rerunning setup --hierarchy.

For a narrower scope, use an anchored rule such as ["/Work/Agent Inbox"] and place all hierarchy destinations inside that subtree. Create that scope root in Joplin first. Its existing ancestors may be resolved during setup, but only descendants of the existing scope root can be created automatically; notes outside that subtree remain inaccessible.

Register Antigravity when desired:

joplin-agent setup --harness antigravity

Its default configuration is ~/.gemini/config/mcp_config.json. If the installed surface's View raw config points elsewhere, supply --harness-config /absolute/path/to/mcp_config.json. The same option supports a nonstandard Codex configuration. Registration preserves unrelated settings, keeps a private backup when changing an existing file, and is idempotent. It refuses symlinked configurations and destinations inside a checkout or Joplin's private/runtime directories. A legacy ~/.gemini symlink into dotfiles therefore needs a local harness configuration location before registration. Reload the harness's MCP connections after registration.

Each connection owns an independent stdio process. The startup guard validates private settings and removes inherited Joplin overrides before replacing itself with the server process. A small initialization module applies the validated configuration to the upstream tools, publishes the nonsecret hierarchy as server instructions, and suppresses upstream logs that may contain note previews. There is no shared daemon or public endpoint.

Run joplin-agent doctor to verify credentials, destinations, and the MCP handshake without displaying notes. Run a real note query in each harness afterward to verify that it loaded its registration. Missing Joplin does not block unrelated agent work.

Access rules are path-based, with exclusions taking precedence. Renames and moves can change access; upstream caches notebook paths for up to five seconds. Private policy changes require restarting every existing Joplin MCP connection. This does not remove previously retrieved text from conversations. The guard is not an OS sandbox against programs that already possess the same user's credentials.

The pinned upstream release exposes global tag names through listing and lookup errors, so global tag operations and tag mutations are withheld. Note-specific tag inspection remains available. Its ping tool can return credential-bearing error URLs, so use doctor for health checks. Deletion, restore, bulk import, and whole-library enumeration are also disabled. Normal agents must not enable withheld tools or bypass them through the raw API. Explicit deletion requests require separately authorized maintenance.

Concurrent recall and separate captures are supported. Rereading and exact-match editing reduce conflicts, but independent sessions can still overwrite the same note; the integration does not claim atomic edits or distributed locks.

For development, install the locked dependencies in an isolated directory and run the behavioral suites:

UV_PROJECT_ENVIRONMENT=/tmp/joplin-agent-tests uv sync --project .local/share/joplin-agent --frozen --no-dev --no-install-project
/tmp/joplin-agent-tests/bin/python -m unittest discover -s .local/share/joplin-agent/tests -v
python3 -m unittest discover -s .local/share/dotfiles/tests -p test_repository_straps.py
python3 -m unittest discover -s .config/opencode/tests

The MCP tests use a synthetic loopback Clipper service, never your notes. Upgrades must update the lock, reviewed tool set, and pinned-version check together, then rerun these tests before redeploying with setup --install.

Zellij agent status

On another machine, run python3 bootstrap.py and select Zellij, Codex status hooks, and Fish. With another shell, pass --no-daemon when launching Codex inside Zellij. The Zellij strap links the layouts, Python bridge, and prebuilt plugin; the Codex strap links ~/.codex/hooks.json. Use Zellij 0.45.1 for the supplied plugin, and keep python3 and zellij on PATH.

Start zellij --layout agent-status to load the portable status layout and grant the plugin's requested permissions. Installing the plugin alone does not load it into existing layouts or sessions. In Codex, review and trust the hooks with /hooks; new or changed hook definitions remain inactive until trusted.

The Stop hook reports completed Codex turns. For clients that need the legacy notification bridge, add this line to ~/.codex/config.toml only if it has no existing notify handler:

notify = ["sh", "-c", "python3 \"$HOME/.config/zellij/agent-status.py\" codex-notify \"$1\"", "--"]

The harness row sits below the compact bar and combines agent status across all tabs in the current Zellij session, including tabs opened later. Grant both application-state and plugin-messaging permissions so new instances can synchronize.

Each session is displayed as tab name : pane ID ~ Codex, with ~ for running, ? for waiting, * for done, and ! for stopped. Sessions in separate panes of the same tab keep independent statuses.

The Fish configuration launches codex with --no-daemon inside Zellij so hooks inherit the correct pane identity. Install the Fish strap too, or pass --no-daemon explicitly from other shells and layout commands. The shared Codex server inherits its first client's pane environment and can otherwise attribute later sessions to that pane. After updating, reload Fish functions or open a new shell, then exit and resume existing terminal Codex sessions with codex resume. Replacing the plugin file takes effect when the plugin is reloaded or the Zellij session is restarted.

To add status to another layout, include the plugin after its main status bar in the tab template:

pane size=1 borderless=true {
    plugin location="file:~/.config/zellij/plugins/agent-status.wasm"
}

Other agents can publish status from a Zellij pane with python3 ~/.config/zellij/agent-status.py emit NAME STATE ID, where STATE is running, waiting, done, stopped, or clear. Keep any machine-specific layouts and shell settings local.

Forced copies explicitly authorize replacement of managed files while preserving unrelated children in an existing destination directory:

name: Shared skills
category: Agent Harness
description: Portable agent workflows
platforms:
  generic:
    copy:
      - source: skills
        target: ~/.agents/skills
        force: true
Strap Destinations
OpenCode ~/.config/opencode/opencode.jsonc
Shared skills ~/.config/opencode/skills, ~/.agents/skills, ~/.claude/skills, ~/.gemini/config/skills
Shared instructions ~/.config/opencode/AGENTS.md, ~/.codex/AGENTS.md, ~/.claude/CLAUDE.md, ~/.gemini/GEMINI.md
Codex agents ~/.codex/agents
Gemini agents ~/.gemini/config/agents, ~/.gemini/agents

deytefiles CLI Reference

The deytefiles command is the main interface for managing your dotfiles.

Commands

bootstrap

Run the interactive bootstrap process:

deytefiles bootstrap        # Choose configurations and review the plan
deytefiles bootstrap --all  # Set up every configuration without prompts
deytefiles bootstrap -q     # Suppress completion notifications

Without --all, bootstrap requires an interactive terminal. The -q option suppresses notifications only; it does not make setup non-interactive.

sync

Synchronize local changes with the remote git repository:

deytefiles sync                      # Auto-commit with timestamp
deytefiles sync -m "Custom message"  # Custom commit message
deytefiles sync -f                   # Force push (--force-with-lease)
deytefiles sync -q                   # Quiet mode (no notifications)

This command:

  1. Stages all changes (git add -A)
  2. Creates a commit (with timestamp or custom message)
  3. Pulls with rebase from origin
  4. Pushes changes to remote
  5. Handles conflicts gracefully
  6. Sends a completion notification

Options

  • -q, --quiet - Suppress desktop notifications
  • -m, --message MESSAGE - Custom sync commit message
  • -f, --force - Use --force-with-lease when syncing
  • --all - Set up every configuration without interactive prompts

Repository Structure

.dotfiles/
├── bootstrap.py              # Interactive bootstrap entry point
├── strap.yaml                # Root configuration
├── .codex/
│   ├── agents/               # Codex role definitions
│   └── strap.yaml            # Codex agents deployment
├── .config/
│   ├── fish/
│   │   ├── strap.yaml        # Fish shell config
│   │   ├── config.fish
│   │   ├── aliases.fish
│   │   └── functions.fish
│   ├── opencode/
│   │   ├── strap.yaml
│   │   ├── skills.strap.yaml
│   │   └── instructions.strap.yaml
│   └── nvim/
│       └── strap.yaml        # Neovim config
├── .local/
│   ├── bin/
│   │   ├── strap.yaml
│   │   └── deytefiles        # Dotfiles CLI
│   └── share/dotfiles/
│       ├── cli/              # CLI implementation
│       ├── strap/            # Bootstrap discovery, planning, UI, and execution
│       └── vendor/           # Vendored bootstrap dependencies
└── README.md

Technical Details

Vendored Bootstrap Dependencies

The interactive bootstrap uses vendored wheels for Questionary, prompt-toolkit, and wcwidth, so no package installation is needed. Checksums, licenses, upstream projects, and embedded notices are listed in .local/share/dotfiles/vendor/THIRD_PARTY_LICENSES.md.

Reviewed Deployment

Bootstrap discovers and validates every strap before planning any changes. The final review shows links, copies, replacements, and cron updates together. Execution verifies that reviewed filesystem state is still current and aborts safely when it is stale.

Ordinary copies create missing targets, report matching targets as current, and preserve conflicting targets. A force: true copy authorizes replacement. Forced directory copies overlay managed children when the destination is a real directory and preserve unrelated children; otherwise the destination root is atomically replaced with a real directory copy.

Cron Job Management

Cron entries are owned by individually marked strap sections:

  • Selecting one strap updates only that strap's section
  • Complete mode and --all reconcile every managed section
  • Unmanaged crontab content is preserved
  • Failed installation restores the original crontab

Notification System

Desktop notifications use native OS facilities:

  • macOS: AppleScript via osascript
  • Linux: notify-send (works with dunst, mako, notification-daemon, etc.)

Notifications can be disabled with the -q/--quiet flag.

Context-Aware Execution

The deytefiles command automatically determines the repository location based on its installation path, allowing it to work from any directory on your system. This is achieved through:

  1. The deytefiles script calculates the repo root from its own location
  2. All git operations use the repo path explicitly
  3. Commands work regardless of your current working directory

Footnotes

  • [1]: eng. Shell Magic (Standard Galactic Alphabet)
S
Description
No description provided
Readme
6.6 MiB
0 Stars 1 Watchers 0 Forks
Languages
Python 69%
Lua 13.2%
Shell 8.9%
Rust 7.7%
CSS 1.2%