Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.tgz
.DS_Store
.env*
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ dotfiles push -u origin main

```bash
# One command — clones, backs up conflicts, checks out, configures
dotfiles bootstrap YOUR_USERNAME

# Or with a custom repo name
dotfiles bootstrap YOUR_USERNAME/my-dots

# Full URLs also work
dotfiles bootstrap git@github.com:YOUR_USERNAME/dotfiles.git
```

Expand Down Expand Up @@ -63,11 +69,11 @@ graph LR
| Command | What It Does |
|---------|-------------|
| `dotfiles help` | Show all commands |
| `dotfiles init` | Create bare repo, propose initial files to track, add + commit |
| `dotfiles init` | Create bare repo, propose initial files to track, set up GitHub remote |
| `dotfiles sync [message]` | Pull + stage tracked changes + commit + push. Default message: `sync YYYY-MM-DD` |
| `dotfiles ls` | List all tracked files |
| `dotfiles audit` | Scan tracked files for secrets (private keys, .env, API tokens) |
| `dotfiles bootstrap <repo-url>` | Full new-machine setup: clone, backup conflicts, checkout, configure |
| `dotfiles bootstrap <user>[/repo]` | Full new-machine setup: clone, backup conflicts, checkout, configure. Accepts username, user/repo, or full URL. |
| `dotfiles add <path>` | Add file to tracking (warns on directories, shows dry-run first) |
| `dotfiles diff` | Smart diff: fetches remote, shows local changes + incoming changes + diverged files |
| `dotfiles merge` | Interactive per-file resolution of diverged (conflicting) files |
Expand Down Expand Up @@ -463,12 +469,34 @@ Found these config files — select which to track:

**Scan list:** The CLI checks for a built-in list of common dotfile paths. Files that exist are pre-selected (`[x]`). Files that don't exist are shown as `(not found)` and deselected. The user toggles with arrow keys + space, then confirms.

**After selection:** Selected files are added and committed as `"Initial dotfiles"`. The user is then prompted to add a remote:
**After selection:** Selected files are added and committed as `"Initial dotfiles"`. The user is then guided through remote setup:

**If GitHub CLI (`gh`) is installed and authenticated:**

```
Added 9 files.

GitHub CLI detected. Set up a remote?
[c] Create new private repo on GitHub
[e] Enter an existing repo URL
[s] Skip for now

> c
Repo name (default: dotfiles): dotfiles

Created dotfiles on GitHub and added as remote.
Run "dotfiles push -u origin main" to push.
```

**If `gh` is not available:**

```
Added 9 files.

Add a remote? Enter repo URL (or press Enter to skip):
Add a remote to sync across machines.
Tip: Install GitHub CLI (gh) for guided repo creation.

Enter repo URL (or press Enter to skip):
> git@github.com:virtualian/dotfiles.git

Remote added. Run "dotfiles push -u origin main" to push.
Expand Down
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/).

## [Unreleased]

## [1.1.0] - 2026-02-23

### Added

- Guided GitHub repo creation in `dotfiles init` — detects `gh` CLI, offers to create a private repo
- Bootstrap shorthand: `dotfiles bootstrap <user>` resolves to `git@github.com:user/dotfiles.git`
- Bootstrap also accepts `user/repo` shorthand for custom repo names
- README, LICENSE, CONTRIBUTING, and .gitignore

### Changed

- `dotfiles init` remote prompt now offers create/enter/skip when `gh` is available
- `dotfiles init` shows install tip when `gh` is not available
- `dotfiles bootstrap` usage message shows shorthand examples

## [1.0.0] - 2026-02-23

### Added

- `dotfiles init` — create bare repo, propose initial files to track
- `dotfiles sync [message]` — pull + commit tracked changes + push
- `dotfiles ls` — list all tracked files
- `dotfiles audit` — scan tracked files for secrets
- `dotfiles bootstrap <url>` — clone repo + set up new machine
- `dotfiles add <path>` — add file or directory to tracking
- `dotfiles diff` — smart diff (local + remote + diverged)
- `dotfiles merge` — resolve diverged files interactively
- `dotfiles help` — show help message
- Git pass-through for all standard git commands
81 changes: 81 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Contributing

## Prerequisites

- [Bun](https://bun.sh) >= 1.0

## Dev Setup

```bash
git clone https://github.com/virtualian/dotfiles-cli.git
cd dotfiles-cli
bun install
bun test
```

## Project Structure

```
src/
cli.ts # Entry point — dispatches subcommands or forwards to git
commands/ # One file per custom command (init, sync, diff, merge, audit, bootstrap, add, ls, help). init detects gh CLI for guided GitHub repo creation.
git.ts # Git wrapper — handles --git-dir and --work-tree flags
bin/
dotfiles # Executable shim (#!/usr/bin/env bun)
tests/
*.test.ts # Contract, pattern, and functional tests
```

## Testing

```bash
bun test
```

The test suite covers three layers:

- **Contract tests** — verify modules export the right shapes (`run(args): Promise<number>`, dispatcher entries, git wrapper exports)
- **Pattern tests** — verify detection logic in isolation (audit filename patterns, content regexes, negative cases)
- **Functional tests** — execute commands and check output (`help` prints all 9 commands, `gitRaw` runs real git)

Commands that require a bare repo or interactive stdin (`init`, `sync`, `bootstrap`, `diff`, `merge`, `audit`) are tested manually.

## Pull Requests

- One concern per PR
- Include a test plan for interactive commands (what you ran, what you saw)
- Run `bun test` before opening
- Keep changes focused — don't refactor unrelated code

## Releases

This project uses [semantic versioning](https://semver.org/) with git tags.

### When to release

- **Patch** (1.0.1) — bug fixes, typo corrections, no behavior change
- **Minor** (1.1.0) — new features, new commands, non-breaking enhancements
- **Major** (2.0.0) — breaking changes to command syntax or behavior

### How to release

```bash
bun run release patch # 1.1.0 -> 1.1.1 (bug fixes)
bun run release minor # 1.1.0 -> 1.2.0 (new features)
bun run release major # 1.1.0 -> 2.0.0 (breaking changes)
```

The release script (`scripts/release.ts`) handles everything:

1. Computes the next version from the current `package.json` version
2. Checks for a clean working tree
3. Verifies the `## [Unreleased]` section has content
4. Moves Unreleased entries under a new versioned header
5. Bumps `"version"` in `package.json`
6. Commits, tags, and pushes with tags

After the script finishes, publish to npm:

```bash
bun publish --access public
```
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Ian Marr

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# @virtualian/dotfiles-cli

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Bun](https://img.shields.io/badge/Bun-%3E%3D1.0-f9f1e1)](https://bun.sh)

Sync shell and application configuration across machines using a bare git repository.

## Quick Start

```bash
# Install
bun add -g @virtualian/dotfiles-cli

# First machine — initialize, pick files, create GitHub repo, push
dotfiles init
dotfiles push -u origin main

# New machine — one command sets everything up
dotfiles bootstrap YOU

# Daily workflow
dotfiles sync
```

Three commands to set up. One command to sync.

## Command Reference

| Command | Description |
|---------|-------------|
| `dotfiles help` | Show help message |
| `dotfiles init` | Create bare repo, propose files, set up GitHub remote |
| `dotfiles sync [message]` | Pull + commit tracked changes + push |
| `dotfiles ls` | List all tracked files |
| `dotfiles audit` | Scan tracked files for secrets |
| `dotfiles bootstrap <user>` | Clone repo + set up new machine (accepts `user`, `user/repo`, or full URL) |
| `dotfiles add <path>` | Add file or directory to tracking |
| `dotfiles diff` | Smart diff (local + remote + diverged) |
| `dotfiles merge` | Resolve diverged files interactively |

Any unrecognized command is forwarded to git with bare repo flags — `dotfiles status`, `dotfiles log --oneline`, etc. all work as expected.

## How It Works

The CLI wraps the **bare git repository** technique: a git repo at `~/.dotfiles/` stores history while `$HOME` serves as the working tree. Every command translates to `git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME <command>`. Since the working tree is your entire home directory, `status.showUntrackedFiles no` keeps output clean — you explicitly add only what you want to track.

For the full guide (what to track, sensitive data handling, machine-specific configs, troubleshooting), see [`.prd/dotfiles-bare-repo-guide.md`](.prd/dotfiles-bare-repo-guide.md).

## Development

```bash
git clone https://github.com/virtualian/dotfiles-cli.git
cd dotfiles-cli
bun install
bun test
```

### Architecture

```
src/
cli.ts # Entry point + dispatcher
commands/
init.ts # Create bare repo, propose files, set up remote
sync.ts # Pull + commit + push
diff.ts # Smart diff (fetch + 3-section display)
merge.ts # Interactive conflict resolution
audit.ts # Secret scanning
bootstrap.ts # New machine setup
add.ts # Add files to tracking
ls.ts # List tracked files
help.ts # Help output
git.ts # Git wrapper (--git-dir, --work-tree)
bin/
dotfiles # Shim: #!/usr/bin/env bun
```

The CLI is a pass-through dispatcher: known subcommands are handled in `src/commands/`, everything else forwards to git.

## License

[MIT](LICENSE) — Ian Marr
2 changes: 2 additions & 0 deletions bin/dotfiles
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env bun
import "../src/cli.ts";
29 changes: 29 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@virtualian/dotfiles-cli",
"version": "1.0.0",
"type": "module",
"description": "Sync shell and application configuration across machines using a bare git repository",
"bin": {
"dotfiles": "./bin/dotfiles"
},
"scripts": {
"test": "bun test",
"release": "bun scripts/release.ts"
},
"files": [
"bin",
"src"
],
"keywords": [
"dotfiles",
"git",
"bare-repo",
"sync",
"cli"
],
"author": "Ian Marr",
"license": "MIT",
"engines": {
"bun": ">=1.0.0"
}
}
Loading