This repository collects personal macOS/Linux dotfiles, provisioning scripts, and helper utilities. The layout keeps shell configuration, application profiles, and executable helpers in discrete directories while sharing a common Bash library for reusable behaviour.
bin/- Executable utilities referenced below; symlinked into~/.binbymake setup-treehome/- Version-controlled copies of dotfiles such asgitconfig,tmux.conf, andpip.conf;make setup-treelinks them into$HOMElib/- Shared shell libraries.lib/bash/initrcbootstraps a Python-inspired stdlib composed of modules likelogging,strings,os,runtime, andui/core. Scripts import that aggregator for logging, prompting, filesystem helpers, deferred sourcing, locking, and other primitives, while specialised tools still pull in peers such aslib/envdbfor the key/value datastore.profiles/- Application-specific settings, currently an iTerm2 profile inprofiles/iterm2/profile.jsonsetup/- Provisioning assets.setup/macos/mac-provisionnow supports dry-runs, change detection, config syncing, and Homebrew automation.setup/macos/Brewfilecaptures brew dependencies andsetup/macos/defaults.confrecords macOSdefaultsmanaged by the provisionershell/- Interactive shell entrypoints.shell/bash/profileexports environment variables, amendsPATH, and lazy-loads tooling using the helpers fromlib/bash/initrcskel/- Bash script skeletons consumed bybin/script-scaffoldwhen scaffolding new utilitiesMakefile- Convenience tasks:make installruns the macOS provisioner,make setup-treeprepares directories and symlinks in the home directory, and thedeploy-*targets bump the version tag
setup/macos/mac-provision bootstraps and reconciles a macOS workstation. Highlights:
- Performs a dry-run (
setup/macos/mac-provision --dry-run) that shows each step, the detected drift (missing brew formulae, differing defaults, hidden directories, firewall state), and the commands that would run. - Applies configuration with loaders/spinners while writing defaults, refreshing file associations, and regenerating Brewfiles so long-running steps stay responsive.
- Reads desired user defaults from
setup/macos/defaults.conf(domain|key|type|value). Usesetup/macos/mac-provision --update-defaultsto rewrite the file from current system settings (dropping keys that no longer resolve) or edit it manually to add more defaults. - Keeps Homebrew aligned with
setup/macos/Brewfile; runsetup/macos/mac-provision --update-brewfileto dump the current system state back into the repo, or--update-onlyto perform configuration dumps without provisioning. - Ensures the following during a full run: Xcode CLI tools and license acceptance, Homebrew installation, Brew bundle reconciliation, duti-based file associations, curated macOS defaults (keyboard repeat, Finder/Dock tweaks, Bluetooth audio quality, screenshot location, etc.), vendor directory visibility,
pipxinstallation, and enabling the application firewall.
Combine the flags to suit your workflow. Examples:
# Inspect the changes without modifying the system
setup/macos/mac-provision --dry-run
# Sync Brewfile and defaults, but stop before provisioning
setup/macos/mac-provision --update-brewfile --update-defaults --update-only
# Apply everything non-interactively
setup/macos/mac-provision --yes --non-interactivebin-list-scripts- Lists every file in~/.binand prints the second line of each script as its description, making it easy to discover available helperscommand-null-run- Runs a command while discarding stdout and stderr (command-null-run some-noisy-command), convenient in pipelinesespanso-add-typo- Appends a typo correction to the Espanso configuration at~/Library/Application Support/espanso/match/base.ymlfile-mark-executable- Wrapschmod +xso you can make a new script executable withfile-mark-executable path/to/scriptfile-metadata- Delegates tomdlson macOS ormediainfoelsewhere to inspect file metadatafile-permissions- Shows a file or directory's permissions in symbolic and octal form (file-permissions /usr/bin)finder-front-path- Uses AppleScript to print the path of the frontmost Finder window, allowing quickcd "$(finder-front-path)"history-grep- Greps the Bash history file with regex matching while de-duplicating results, handy for retrieving past commandspath-copy-clipboard- Copies the current directory or a named entry in it to the macOS clipboard; useful when sharing absolute pathspath-print- Pretty-prints thePATHenvironment variable one entry per line to confirm search orderscript-scaffold- Interactive scaffold generator: select a skeleton, provide a kebab-case name and description, and it writes an executable script inbin/and opens it in$EDITORshell-add-alias- Appends an alias definition to~/.bash_profileand reminds you to reload it; runshell-add-alias -n gs -c "git status"to register shortcuts without editing the file manuallytext-line- Reads stdin and prints the specified line number (cmd | text-line 5shows the fifth match)trash- Moves files or directories to the macOS Trash instead of deleting them, with optional confirmation flags (t -i big-file)uv-init-helper- Wraps theuvPython tool: foruv initit creates or reuses a virtualenv recorded in.envrc, otherwise it forwards all arguments to the real binary
copyx- Simple backup automation to S3.s3-upload-and-link- Upload files to S3 and copy the shareable URL to the clipboard (similar to CloudApp)path-resolve- Resolves a relative path to an absolute path (path-resolve ../some/file)dir-remove-safe- Safety wrapper aroundrm -rffor directories; it verifies the target exists before deletionpath-expand-tilde- Expands a path containing~to its real location (path-expand-tilde ~/Downloads)archive-extract- Dispatches to the appropriate tool to unpack archives (zip, tar.*, rar, 7z, etc.) in placefind-copy-matches- Recursively finds files matching a glob and copies them to a destination (find-copy-matches "*.svg" ~/vectors)find-move-matches- Same as above but moves matched files (find-move-matches "*.log" archive/)find-extension- Lists files with a given extension under the current tree1find-filename- Recurses for exact filename matches (find-filename "*.plist")1find-directory- Recursively finds directories by name (find-directory build)path-slugify- Renames files and directories to URL-friendly slugs, with dry-run, recursive, and exclusion optionszip-unpack-all- Unpacks every*.zipin the working directory into sibling folders while leaving the archives intact
adobe-font-export- Extracts Adobe Creative Cloud fonts from CoreSync, optionally converting them to TTF or WOFF2 via FontForge/woff2 before copying them outaudio-stereo-merge- Uses ffmpeg to turn two mono audio recordings into a single stereo track (first input becomes the left channel)audio-trim-silence- Uses ffmpeg'ssilenceremovefilter to trim silence from audio filesconvert-to-mp4- Converts a single media file (mkv/mov/avi/webm/gif, etc.) to.mp4convert-to-webp- Converts common image/video formats to.webpvia ffmpegexif-copy-tags- Copies all EXIF metadata from one file to another viaexiftool, useful when transcoding mediasvg-icon-normalize- Cleans SVG assets: optionally removes fixed dimensions, enforcesfill="currentColor", and processes files or directories recursivelysvg-optimize-all- Runssvgo --multipassacross all SVGs in the working directory for batch optimisation
git-branch-to-clipboard- Copies the current branch name to the clipboard for use in tickets or pull requestsgit-export-modified-files- Template for copyinggit statusmodified files into a staging directory; customise the commentedcpcommand to suit your workflowgit-list-ignored- Callsgit status --ignoredto display ignored filesgit-peel-last-commit- Creates a new branch fromHEADand removes the last commit from the current branchgit-prune-merged- Lists or deletes branches fully merged into a target branch, locally or on a remote, with optional fetchgit-rebase-prefer-upstream- Intended helper for rebasing while preferring upstream changes; currently shells out togit revertand should be adjusted before usegit-rebase-verify- Interactively validate a rebased branch against a base branch.git-release-tag-name- Generates deterministic tag release names based off of commit hash.git-reset-stage- Runsgit resetto unstage everythinggit-revert-to- Runsgit revertfor a specific commit hashgit-safe-rebase- Safe rebase workflow: creates a timestamped working branch, runsgit rebase --autostash --rebase-merges -X patience, and guides merging backgit-soft-reset-last- Performsgit reset --soft HEAD~1and shows status to retain working tree changesgit-split-after- Moves all commits after a given hash onto a new branch and rewinds the original branch to the specified tree state
clean-up-open-with-menu- Rebuilds LaunchServices registrations and restarts Finder to clear duplicate "Open With" entriesdocker-wipe-all- Stops and removes all Docker containers, images, volumes, networks, and build cache after an explicit confirmationfinder-hide-desktop/finder-show-desktop- Toggle Finder desktop icon visibilitymacos-audio-reset- Terminatescoreaudiodwhen system audio stops respondingmacos-coremedia-restart- Stops key media-related processes (Dock, WindowServer, etc.) for troubleshooting display/audio issuesmacos-dns-flush- Flushes DNS caches viadscacheutilandmDNSRespondermacos-hostname-set- Updates the system hostname, LocalHostName, and related SMB settings in one stepmacos-notifications-clear- KillsNotificationCenterto empty the notification listpkg-manager- Manage packages via the first supported package manager detected on the system. (wip)postgres-start/postgres-stop- Control Homebrew's PostgreSQL service viabrew services
http-show-headers- Fetches only the HTTP response headers for a URL usingcurl -svnetwork-check-host- Reads a list of URLs from a file and prints those returning HTTP 200, useful for availability checksnetwork-check-port- Uses netcat to probe whether a host:port accepts TCP connectionsnetwork-info- Rich network report: local interfaces, default gateways, DNS servers, and public IP lookups for IPv4/IPv6network-listeners- Summarises listening TCP/UDP sockets grouped by owning processnetwork-measure-ttfb- Measures DNS lookup, connect time, TLS handshake, first byte, and total request time for a given URL usingcurlnetwork-pid-on-port- Displays processes bound to a specific port vialsof -iprocess-kill-pid- Simple wrapper aroundsudo kill -TERM <pid>for explicit process terminationprocess-kill-port- Finds the process listening on a TCP port and kills itprocess-list- Formatsps auxoutput with colour, optional macOS process filtering, and de-duplication
These scripts implement Redis-inspired operations backed by the helper sourced from lib/rdb-common, which stores data in an SQLite database.
kv-set/kv-get- Set or retrieve a string valuekv-delete- Delete one or more keys, returning the count removedkv-exists- Return how many of the specified keys currently existkv-increment/kv-decrement- Increment or decrement an integer value atomicallykv-list-keys- List keys matching a glob-style patternkv-list-length- Report the length of a listkv-list-pop-left/kv-list-pop-right- Pop the first or last list elementkv-list-push-left/kv-list-push-right- Push values onto the head or tail of a list
css-px-to-em- Converts a pixel value toemunits for a given base font size and copies the result to the clipboardtime-epoch- Prints the current Unix epoch timestampnanoid- Generate short, URL safe unique IDs.
copyx runs incremental backups on an interval using a YAML configuration. make setup-tree symlinks home/.config/copyx/config.yml into ~/.config/copyx/config.yml, and shell/bash/profile exports COPYX_CONFIG so the script picks it up without extra flags.
- Configuration - The config supports
backup_root(local path ors3://URI), optionalmachine_id,sources,excludepatterns, andmax_size_bytesto skip files above a byte threshold. Whenbackup_roottargets the filesystem,copyxshells out torsync; S3 destinations useaws s3 sync/cpwith the same include/exclude semantics. - Machine scoping - If
machine_idis omitted, the helper reads~/.machine_id(populated bysetup/macos/mac-provision) and falls back to the current hostname. Each run writes into a<machine_id>/subdirectory so multiple hosts can share the same backup bucket. - Scheduling -
copyx --interval <seconds>keeps a loop running (default 3600s).copyx --launchd-loadgenerates~/Library/LaunchAgents/com.nficano.copyx.plist, bootstraps it withlaunchctl, and tails logs under~/Library/Logs/copyx. Use--launchd-unloadto tear it down. - Spot runs -
copyx --oneshotexecutes a single pass; add--dry-runor--verboseto inspect the planned rsync/S3 operations before committing. - Inspection tools -
copyx --preview /path/to/dirprints the files from that subtree that would be included or skipped after applying exclude rules.--show-filesemits the same include/skip summary for every configured source before syncing (override the default 200-line cap with--show-files=allorCOPYX_SHOW_FILES_LIMIT). - Progress & cleanup -
--progressenables per-file progress output for both rsync and S3.--purge-backup --yeswipes the current machine’s destination directory or bucket prefix, with--dry-runavailable for a safety check.
| Name | Required | Description | Default |
|---|---|---|---|
COPYX_CONFIG |
✓ | Path to YAML config file. | — |
| Name | Required | Description | Default |
|---|---|---|---|
backup_root |
✓ | Destination directory (path or s3:// URI) for synced data. |
— |
machine_id |
✗ | Host directory in backup_root (supports a .machine_id file in $HOME directory.) |
$(hostname) |
sources |
✓ | List of paths/globs to backup. | — |
exclude |
✓ | List of paths/globs to exclude. | — |
max_size_bytes |
✗ | Excludes large files. | -inf |
# Example configuration for the copyx utility.
#
# Configure the root destination for snapshots, the identifier for this
# machine (used to create a sub-folder), and the list of paths or glob
# expressions that should be replicated.
#
# Use the optional `exclude` section to skip matching paths during syncs.
# Set `max_size_bytes` to skip files larger than the specified number of bytes.
#/ backup_root Destination directory (path or s3:// URI) for synced data
#/ machine_id Optional machine identifier (hostname used if omitted)
#/ sources List of paths/globs to replicate
#/ exclude Optional list of rsync/s3 exclude patterns
#/ max_size_bytes Optional per-file size limit; larger files are skipped
backup_root: s3://s3.us-east-1.amazonaws.com/mybucket
# machine_id:
max_size_bytes: 52428800 # 50 MiB
sources:
- $HOME/Desktop
- /etc/hosts
- $HOME/.bash_history
- $HOME/.gitconfig.local
- $HOME/.profile.local
- $HOME/.ssh
exclude:
- '**/.cache'
- '**/.DS_Store'
- '**/.dump'
- '**/.git'
- '**/tmp'`spell-correct' is spell check and correction utility that uses ChatGPT for spelling correction.
Usage
spell-correct leasure
# Copied correction "leisure" to the clipboard| Name | Required | Description | Default |
|---|---|---|---|
OPENAI_API_KEY |
✓ | Your OpenAI API key used for authentication. | — |
SPELL_CORRECT_OPENAI_MODEL |
✗ | Model used for correction. | gpt-4o-mini |
SPELL_CORRECT_OPENAI_AGENT |
✗ | Agent name defined in .agents file. |
spell-check |
SPELL_CORRECT_OPENAI_TEMPERATURE |
✗ | Controls randomness of model output. | 0 |
autoflags converts natural language intents into safe shell commands. (Work in progress.)
Usage
autoflags git "rename current branch to feature/xyz"
# git branch -m feature/xyz
# Proceed with execution? [y/N]
autoflags find "all files with the extension png"
# find . -type f -name "*.png"
# Proceed with execution? [y/N]
autoflags ffmpeg "convent input.mov to output.webm"
# ffmpeg -i input.mov -c:v libvpx-vp9 -b:v 2M -c:a libopus output.webm
# Proceed with execution? [y/N]| Name | Required | Description | Default |
|---|---|---|---|
OPENAI_API_KEY |
✓ | Your OpenAI API key used for authentication. | — |
AUTOFLAGS_YES |
✗ | Run without confirmation prompt. | false |
AUTOFLAGS_PRINT |
✗ | Show suggested command without executing. | false |
AUTOFLAGS_COPY |
✗ | Copy command to clipboard (implies AUTOFLAGS_PRINT=true). |
false |
AUTOFLAGS_ALLOW_ALT |
✗ | Allow AI to suggest alternate tools or commands. | — |
AUTOFLAGS_CONFIRM_DEFAULT |
✗ | Default answer when prompted (Y or N). | N |
AUTOFLAGS_REQUIRE_WHICH |
✗ | Verify that suggested alternative command exists. | false |
AUTOFLAGS_NO_CLIPBOARD |
✗ | Disable automatic clipboard copy. | false |
AUTOFLAGS_CONTEXT |
✗ | Add extra context to AI prompt. | — |
AUTOFLAGS_OPENAI_MODEL |
✗ | Model used for generation. | gpt-4o-mini |
AUTOFLAGS_OPENAI_AGENT |
✗ | Agent name defined in .agents file. |
autoflags |
AUTOFLAGS_OPENAI_TEMPERATURE |
✗ | Controls randomness of model output. | 0 |
Uploads a file to S3 and copies the shareable URL to your clipboard.
Usage
s3-upload-and-link ubuntu-24.04.3-desktop-amd64.iso
# https://s3.us-east-1.amazonaws.com/mybucket/9oe3HVzO.iso| Name | Required | Description | Default |
|---|---|---|---|
S3_UPLOAD_LINK_BUCKET |
✓ | S3 bucket name (supports optional s3:// prefix). |
— |
S3_UPLOAD_LINK_PREFIX |
✗ | Optional key prefix for uploaded objects. | — |
S3_UPLOAD_LINK_URL_BASE |
✗ | Base HTTPS URL used to construct share links. | — |
S3_UPLOAD_LINK_BUCKET_REGION |
✗ | Region used for default URL generation. | — |
S3_UPLOAD_LINK_ACL |
✗ | ACL for aws s3 cp. |
public-read |
S3_UPLOAD_LINK_CACHE_CONTROL |
✗ | Cache-Control header for uploaded object. |
— |
S3_UPLOAD_LINK_CONTENT_TYPE |
✗ | Explicit Content-Type override. |
— |
S3_UPLOAD_LINK_ID_LENGTH |
✗ | Length of generated NanoID filename. | 12 |
S3_UPLOAD_LINK_EXPIRES_IN |
✗ | Expiration time in seconds for uploaded object. | — |
Each skeleton is a Bash template that already sources lib/bash/initrc, enables set -Eeuo pipefail, provides help text with #/ comments, and wires in the shared logging/prompt helpers.
skel/bash/noargs- Minimal command pattern with-h/--helpand optional--verbose, intended for scripts with no positional argumentsskel/bash/args- Adds positional argument collection while preserving the common option parserskel/bash/params-args- Demonstrates combined flag, parameter, and positional handling (e.g.--param valueplus extra arguments)skel/bash/cron-safe- Wraps execution with a file lock so cron jobs do not overlapskel/bash/daemon- Long-running loop with configurable interval and graceful shutdown handlingskel/bash/fileproc- Processes files from arguments or stdin, creating a temporary work directory and supporting a dry-run modeskel/bash/interactive- Interactive workflow with prompts, optional non-interactive mode, and default handlingskel/bash/net- HTTP client scaffold with retries, timeouts, and method/payload parameters usingcurlskel/bash/parallel- Executes tasks concurrently with a configurable job limit and failure tracking
- Run
bin/script-scaffoldfrom the repository root (or ensurebin/is on yourPATH) - Choose a skeleton by number or name; press enter to accept the default first option
- Supply a kebab-case script name (the helper enforces this) and a short description that replaces
{{ description }}in the template - Enter the summary text when prompted. The tool writes the new script into
bin/, marks it executable, and opens it in$EDITOR - Replace the
TODOcomments with your logic, keeping the#/documentation block accurate and theset -Eeuo pipefaildirective intact - When the script reuses shared facilities, keep sourcing
../lib/bash/initrcso you can call helpers such aslog.info,prompt.ask_yes_no,fs.mktmp,lock.acquire, andshell.defer - Use
bin/bin-list-scriptsto confirm the description renders nicely and consider runningbin/file-mark-executableif you edit a script outside ofscript-scaffold
- Keep filenames in kebab-case and store executables under
bin/soshell/bash/profileadds them to thePATH - Document usage with
#/comment lines at the top soscript.usagecan emit help text automatically - Prefer the common option parsing helpers (
script.parse_commonor the patterns shown in the skeletons) to deliver consistent-h/--helpand-v/--verbosebehaviour - Use the logging (
log.info,log.warn,log.error), prompting, locking, and filesystem helpers fromlib/bash/initrcinstead of reimplementing them - When creating new media or conversion scripts, follow the examples that operate on the current working directory and respect standard tools such as ffmpeg or svgo
lib/bash/runtime introduces when.my_machine, a guard that only runs its command list when ~/.machine_id matches the current Mac's hardware UUID (queried via ioreg). setup/macos/mac-provision writes that file during provisioning, and shell/bash/profile uses it to add private content such as ~/.bin/personal:
when.my_machine sys.path.append "$HOME/.bin/personal"If you bootstrap a new host outside the provisioner, populate ~/.machine_id manually with ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}' so the guard succeeds on that machine.