@g4rcez/bunsen
v0.0.6
Published
NixOS flake-inspired dotfiles manager for TypeScript
Maintainers
Readme
Bunsen
BUNdle SEttings Now - Like Dr. Bunsen Honeydew from the Muppets, this tool brings scientific precision to your dotfiles management!
And yes, it's powered by Bun - because when you're naming a tool after a Muppet scientist, you might as well run it on the fastest runtime that shares part of his name!
A NixOS flake-inspired dotfiles manager built with TypeScript. Manage your entire development environment with a single declarative configuration file.
Features
- Declarative Configuration - Define your entire dotfiles setup in a single
dotfiles.config.tsfile - Symlink Management - GNU stow-like functionality with conflict detection, backup, and state tracking
- Package Management - Unified package installation across Homebrew, APT, Pacman, and DNF
- Karabiner Integration - Generate Karabiner keyboard configurations with TypeScript (including advanced features like layers and hyper keys)
- Window Manager Support - Built-in integrations for AeroSpace and Rectangle window managers
- Espanso Integration - Create Espanso text expansion configs declaratively
- Environment Variables - Manage env vars and inject them into shell configs (zsh, bash, fish)
- Type-Safe - Full TypeScript support with autocomplete and validation
- Native TypeScript - Uses Bun's native TypeScript support (no build step required)
- Idempotent - Safe to run multiple times without side effects
- State Tracking - Maintains operation history for rollback and status checking
Requirements
- Bun v1.0.0 or higher
Installation
bun install -g @g4rcez/bunsenOr use locally in your dotfiles repository:
bun install @g4rcez/bunsenQuick Start
1. Initialize Configuration
bunsen initThis creates a dotfiles.config.ts file in your current directory.
2. Edit Configuration
import { defineConfig, karabiner, espanso, packages } from 'bunsen'
export default defineConfig({
// Symlink management
symlinks: {
'~/.zshrc': '~/dotfiles/zsh/.zshrc',
'~/.config/nvim': '~/dotfiles/nvim',
},
// Environment variables
env: {
variables: {
EDITOR: 'nvim',
PATH: ['$HOME/.local/bin', '$PATH'],
},
shells: ['zsh', 'bash'],
},
// Package management
packages: packages({
brew: ['git', 'neovim', 'tmux', 'fzf'],
apt: ['git', 'neovim', 'tmux'],
}),
// Karabiner keyboard configuration
karabiner: karabiner({
profiles: [{
name: 'Default',
rules: [{
description: 'Caps Lock to Escape',
manipulators: [{
type: 'basic',
from: { key_code: 'caps_lock' },
to: [{ key_code: 'escape' }],
}],
}],
}],
outputPath: '~/.config/karabiner/karabiner.json',
}),
// Espanso text expansion
espanso: espanso({
matches: [
{ trigger: ':shrug', replace: '¯\\_(ツ)_/¯' },
{ trigger: ':email', replace: '[email protected]' },
],
outputPath: '~/.config/espanso/match/base.yml',
}),
})3. Apply Configuration
# Preview changes
bunsen apply --dry-run
# Apply all configurations
bunsen apply
# Apply specific parts
bunsen apply --symlinks-only
bunsen apply --packages-only
bunsen apply --env-only4. Check Status
bunsen statusCommands
bunsen init
Creates a new dotfiles.config.ts template with examples.
Options:
-f, --force- Overwrite existing configuration file
bunsen validate
Validates your configuration file against the schema.
Options:
-c, --config <path>- Path to config file (auto-discovered if not specified)
bunsen apply
Applies your dotfiles configuration (symlinks, packages, env vars, etc.).
Options:
-c, --config <path>- Path to config file--dry-run- Preview changes without applying them-f, --force- Skip confirmation prompts and overwrite existing files--symlinks-only- Only apply symlinks--packages-only- Only install packages--env-only- Only apply environment variables--karabiner-only- Only apply Karabiner configuration--espanso-only- Only apply Espanso configuration-v, --verbose- Enable verbose logging
bunsen status
Shows the current state of your dotfiles (symlinks, env injection status, etc.).
Configuration Guide
Symlinks
Simple mapping:
symlinks: {
'~/.zshrc': '~/dotfiles/zsh/.zshrc',
'~/.config/nvim': '~/dotfiles/nvim',
}Advanced options:
symlinks: {
'~/.ssh/config': {
source: '~/dotfiles/ssh/config',
backup: true, // Create backup if file exists
force: false, // Require confirmation before overwriting
createDirs: true, // Create parent directories
},
}Environment Variables
env: {
variables: {
EDITOR: 'nvim',
VISUAL: 'nvim',
// Control PATH order with $PATH token
PATH: ['$HOME/.local/bin', '$PATH', '$HOME/.cargo/bin'],
NODE_ENV: 'development',
},
shells: ['zsh', 'bash', 'fish'],
exportFile: '~/.config/bunsen/env.sh',
}The generated file is automatically sourced in your shell configs using markers:
# BEGIN BUNSEN
source ~/.config/bunsen/env.sh
# END BUNSENPackage Management
Bunsen supports multiple package managers with a unified interface:
packages: packages({
// Homebrew (macOS)
brew: {
packages: ['git', 'neovim', 'tmux', 'fzf', 'ripgrep'],
// Or import from Brewfile
import: '~/dotfiles/Brewfile',
},
// APT (Debian/Ubuntu)
apt: ['git', 'curl', 'build-essential', 'neovim'],
// Pacman (Arch Linux)
pacman: ['git', 'neovim', 'tmux'],
// DNF (Fedora/RHEL)
dnf: ['git', 'neovim', 'tmux'],
// Import packages from a file
// pacman: importFrom('~/dotfiles/packages.txt'),
// Auto-sudo for system package managers
autoSudo: false, // Set to true to automatically use sudo for apt/pacman/dnf
})Karabiner Configuration
Basic example:
karabiner: karabiner({
profiles: [{
name: 'Default',
rules: [{
description: 'Caps Lock to Escape when pressed alone, Hyper when held',
manipulators: [{
type: 'basic',
from: { key_code: 'caps_lock' },
to: [{
key_code: 'left_shift',
modifiers: ['left_control', 'left_option', 'left_command'],
}],
to_if_alone: [{ key_code: 'escape' }],
}],
}],
}],
outputPath: '~/.config/karabiner/karabiner.json',
})Advanced features with karabiner.ts:
Bunsen includes the powerful karabiner.ts library for advanced keyboard customization:
import { karabinerTs, rule, layer, map, withMapper, ifApp } from 'bunsen'
karabiner: karabinerTs({
rules: [
// Create a Hyper key layer
layer('caps_lock', 'hyper-mode').manipulators([
// Hyper + HJKL for arrow keys
map('h').to('left_arrow'),
map('j').to('down_arrow'),
map('k').to('up_arrow'),
map('l').to('right_arrow'),
// App launching
map('b').to('open', '/Applications/Firefox.app'),
map('t').to('open', '/Applications/Alacritty.app'),
]),
// Application-specific mappings
rule('VSCode shortcuts').condition(ifApp('^com\\.microsoft\\.VSCode$'))
.manipulators([
map('cmd', 't').to('cmd', 'p'),
]),
],
outputPath: '~/.config/karabiner/karabiner.json',
})Window Manager Integration
AeroSpace:
import { aerospace } from 'bunsen'
karabiner: karabiner({
windowManager: aerospace({
modifier: 'hyper', // Use Hyper key for all shortcuts
// Custom key mappings
focus: { h: 'left', j: 'down', k: 'up', l: 'right' },
move: { h: 'left', j: 'down', k: 'up', l: 'right' },
}),
// ... other karabiner config
})Rectangle:
import { rectangle } from 'bunsen'
karabiner: karabiner({
windowManager: rectangle({
modifier: 'ctrl+cmd',
actions: ['maximize', 'left-half', 'right-half', 'center'],
}),
// ... other karabiner config
})Espanso Text Expansion
espanso: espanso({
matches: [
// Simple replacement
{ trigger: ':shrug', replace: '¯\\_(ツ)_/¯' },
{ trigger: ':email', replace: '[email protected]' },
// With variables
{
trigger: ':date',
replace: '{{date}}',
vars: [{
name: 'date',
type: 'date',
params: { format: '%Y-%m-%d' },
}],
},
// Shell command output
{
trigger: ':git',
replace: '{{output}}',
vars: [{
name: 'output',
type: 'shell',
params: { cmd: 'git branch --show-current' },
}],
},
],
outputPath: '~/.config/espanso/match/base.yml',
})Or use helper functions:
import { espanso, textReplacement, dateReplacement, shellReplacement } from 'bunsen'
espanso: espanso({
matches: [
textReplacement(':shrug', '¯\\_(ツ)_/¯'),
dateReplacement(':date', '%Y-%m-%d'),
shellReplacement(':branch', 'git branch --show-current'),
],
outputPath: '~/.config/espanso/match/base.yml',
})Lifecycle Hooks
Execute custom logic before and after applying your configuration:
hooks: {
beforeApply: async () => {
console.log('Running pre-apply checks...')
// Run custom validation, backup operations, etc.
},
afterApply: async () => {
console.log('Done! Remember to restart your shell.')
// Reload services, send notifications, etc.
},
}How It Works
Configuration Loading
Bunsen searches for dotfiles.config.ts in:
- Current working directory
- Path specified via
--configflag ~/.config/bunsen/dotfiles.config.ts~/dotfiles/dotfiles.config.ts~/.dotfiles/dotfiles.config.ts
The configuration is loaded using Bun's native TypeScript support (no transpilation!) and validated against Zod schemas.
State Tracking
All operations are tracked in ~/.config/bunsen/state.json. This enables:
- Status checking - See what Bunsen has created
- Conflict detection - Prevent accidental overwrites
- Idempotent operations - Safe to run
bunsen applymultiple times - Future rollback - (Coming soon) Undo all Bunsen changes
Conflict Resolution
When a symlink target already exists:
- Backup (default) - Creates
.backup.{timestamp}file - Overwrite - Removes existing file (with
--forceflag) - Skip - Leaves existing file unchanged (interactive mode)
Path Resolution
All paths are resolved securely:
- Expand
~to$HOME - Expand environment variables (
$HOME,$USER, etc.) - Resolve to absolute paths
- Validate no directory traversal (
..) attacks
Examples
See the examples directory for complete configuration examples:
dotfiles.config.ts- Full-featured example with all featureskarabiner.config.ts- Karabiner-specific examplesespanso-config/- Espanso configuration exampleswindow-managers.config.ts- AeroSpace and Rectangle examples
Development
Bunsen runs TypeScript directly using Bun's native support - no build step required!
# Clone the repository
git clone https://github.com/g4rcez/bunsen.git
cd bunsen
# Install dependencies
bun install
# Run CLI from TypeScript source
bun run bunsen -- --help
bun run bunsen -- init
bun run bunsen -- apply --dry-run
# Or use the wrapper script
./bunsen --help
# Run tests
bun test
# Run tests with coverage
bun test --coverage
# Type check
bun run typecheck
# Format code
bun run formatArchitecture
CLI Commands (src/cli/commands/)
↓
Config Loader (src/core/config/loader.ts)
↓
Zod Validation (src/core/config/schema.ts)
↓
┌─────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ │ │ │ │
Symlink Manager Generator: Env Generator: Packages Generator: Karabiner Generator: Espanso
↓ ↓ ↓ ↓ ↓
Path Resolver Shell Integration Package Installers karabiner.ts lib YAML serialization
↓
State Tracker (src/core/state/)Key principles:
- Bun Native TypeScript - No transpilation, instant startup
- Dynamic Imports - User configs loaded at runtime
- Zod Validation - Type-safe configuration with helpful errors
- Idempotent Operations - Safe to run multiple times
- Security First - Path validation, no directory traversal
See CLAUDE.md for detailed architecture documentation.
Roadmap
Planned Features
VS Code Integration
- [ ] VS Code Extensions Sync - Declaratively manage VS Code extensions
vscode: { extensions: [ 'vscodevim.vim', 'esbenp.prettier-vscode', 'bradlc.vscode-tailwindcss', ], autoInstall: true, } - [ ] VS Code Settings Link - Symlink VS Code settings from dotfiles
vscode: { settingsPath: '~/dotfiles/vscode/settings.json', keybindingsPath: '~/dotfiles/vscode/keybindings.json', snippetsDir: '~/dotfiles/vscode/snippets', }
Other Planned Features
- [ ] Rollback/Undo - Safely revert all Bunsen changes
- [ ] Diff Command - Preview changes before applying
- [ ] Template Support - Use variables and templates in config files
- [ ] Git Integration - Automatically commit dotfiles changes
- [ ] Homebrew Cask Support - Install GUI applications
- [ ] System Preferences - Manage macOS defaults and system settings
- [ ] Secrets Management - Secure handling of API keys and tokens
- [ ] Multi-Machine Profiles - Different configs for different machines
- [ ] Migration Tool - Import from existing dotfiles managers (stow, chezmoi, yadm)
- [ ] Plugin System - Extend Bunsen with custom generators
Contributing
Contributions are welcome! If you'd like to work on any roadmap items or have ideas for new features, please open an issue or submit a pull request.
Inspiration
Bunsen is inspired by:
- NixOS - Declarative configuration approach
- GNU Stow - Symlink management
- Homebrew Bundle - Package management
- karabiner.ts - TypeScript-first keyboard customization
FAQ
Q: Why Bun instead of Node.js? A: Bun's native TypeScript support means no build step, faster startup, and a better developer experience. Plus, the name synergy with Bunsen Honeydew was too good to pass up!
Q: Can I use this with my existing dotfiles? A: Yes! Bunsen is designed to work alongside your existing setup. Start by symlinking a few files and gradually expand.
Q: What happens if I delete my config file?
A: Your symlinks and generated files remain until you explicitly remove them. Use bunsen status to see what Bunsen has created.
Q: Is this cross-platform? A: Bunsen works on macOS and Linux. Windows support is not planned (WSL2 should work fine though).
Q: How is this different from [chezmoi/yadm/dotbot]? A: Bunsen is TypeScript-first with full type safety, uses Bun for instant execution, and includes specialized integrations for Karabiner and Espanso. It's designed for developers who want to write their dotfiles config in TypeScript with autocomplete and validation.
License
MIT
Credits
Created by g4rcez
