Skip to content

Getting Started

This guide covers how to create .tool.ts configuration files for your CLI tools.

Set up your project configuration first. See Project Configuration for instructions.

To bootstrap a dotfiles project in the current directory, run:

Terminal window
curl -fsSL https://alexgorbatchev.github.io/dotfiles/install.sh | bash

The hosted installer provisions a dotfiles project in the current directory, creates a minimal dotfiles.config.ts when needed, and runs dotfiles generate for you.

Generated shell output includes # /path/to/tool.tool.ts attribution comments before emitted blocks, including generated .once/* helper scripts created from .once(...) shell configuration.

With the default project layout, the generated zsh config lives at .generated/shell-scripts/main.zsh inside your dotfiles directory.

Add it to ~/.zshrc:

Terminal window
source "$HOME/.dotfiles/.generated/shell-scripts/main.zsh"

[!IMPORTANT] Configure bash even if your interactive shell is zsh. AI harnesses and other automation often start bash and rely on ~/.bashrc or ~/.profile to load the same environment.

~/.bashrc
if [ -f "$HOME/.dotfiles/.generated/shell-scripts/main.bash" ]; then
# shellcheck disable=SC1090
. "$HOME/.dotfiles/.generated/shell-scripts/main.bash"
fi
# ~/.profile
if [ -n "${BASH_VERSION:-}" ] && [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi

Then reload zsh:

Terminal window
source ~/.zshrc

Tool configurations are placed in your toolConfigsDir (default: ~/.dotfiles/tools):

tools/
├── fzf.tool.ts
├── ripgrep.tool.ts
└── dev/
├── node.tool.ts
└── rust.tool.ts

Files must be named {tool-name}.tool.ts and export a default using defineTool.

Use dotfiles tool scaffold to provision starter configurations, or dotfiles tool add to download one published by a GitHub repository. The command reference describes accepted repository URLs and the required remote layout.

After adding a configuration, review it and run dotfiles generate to generate its shims and shell integration. Run dotfiles install <tool> to install it immediately, or use one of its generated shims to install on first use. See the post-addition workflow and load the generated shell configuration.

import { defineTool } from "@alexgorbatchev/dotfiles";
export default defineTool((install, ctx) =>
install("github-release", {
repo: "junegunn/fzf",
}).bin("fzf"),
);
import { defineTool } from "@alexgorbatchev/dotfiles";
export default defineTool((install, ctx) =>
install("github-release", {
repo: "BurntSushi/ripgrep",
})
.bin("rg")
.dependsOn("pcre2")
.symlink("./ripgreprc", "~/.ripgreprc")
.zsh((shell) => shell.env({ RIPGREP_CONFIG_PATH: "~/.ripgreprc" }).aliases({ rgi: "rg -i" })),
);

install() returns a builder whose methods declare binaries, shell configuration, symlinks, copies, hooks and platform overrides. They are listed in the builder method table.

import { Architecture, defineTool, Platform } from "@alexgorbatchev/dotfiles";
ExportDescription
defineToolFactory function to create tool configurations
PlatformEnum: Linux, MacOS, Windows, All
ArchitectureEnum: X86_64, Arm64

Tools that only contribute shell configuration (no binary installation):

export default defineTool((install) => install().zsh((shell) => shell.env({ FOO: "bar" })));

When a .tool.ts configuration file is removed, the next dotfiles generate removes everything dotfiles recorded creating for that tool: its shims, symlinks, completions, copies, templates, managed blocks, the directories it created once they are empty, and its install directory under paths.binariesDir with the downloaded binaries in it. Each removal is recorded in the registry, so dotfiles state log <tool> shows what happened to every file. Removing a .bin() declaration from an existing tool also removes that tool’s stale shim on the next dotfiles generate. No manual cleanup is needed.

Cleanup only removes what is still dotfiles’ own:

  • A symlink is removed while it still points where dotfiles pointed it, including when that target was deleted along with the tool’s directory. A link that now points somewhere else, or a file or directory put in its place, is left alone with a warning.
  • A copy or template you edited is moved aside to a .bak file rather than deleted. A copied file is left alone when a directory above it is no longer the one dotfiles copied into, such as a copied directory you replaced with a link to your own.
  • A directory the tool created is kept while it still holds files dotfiles did not create, and a directory that existed before the tool is never removed.
  • A package installed by a package manager (brew, apt, npm, …) or an app bundle placed in /Applications is left installed with a warning, because uninstalling it needs the tool’s configuration. dotfiles tool uninstall removes it while the tool is still configured.

When something cannot be removed, generate reports the failure, exits non-zero, and keeps the records of what is left, so the next run retries it.

A directory under paths.binariesDir whose name matches no active tool and that the registry records nothing for is removed as well when it holds a current entry, the layout every install leaves. That is how the downloaded binaries of tools removed before dotfiles recorded these removals are cleaned up. paths.binariesDir/dotfiles, where the installer deploys the CLI itself, and anything else in paths.binariesDir are left alone.

Running dotfiles generate writes everything type-checking needs under .generated/:

  • node_modules/@alexgorbatchev/dotfiles/ — the authoring package’s declarations, also reachable from the project root through a node_modules symlink the CLI creates
  • tool-types.d.ts — the registry of every configured binary name, including those of disabled tools, which gives dependsOn() its autocomplete and rejects a name no tool declares
  • tsconfig.json — the program the CLI type-checks with: dotfiles.config.ts, every tool configs directory, the registry and the runtime globals, with the compiler options the declarations are written for and no Node or Bun types

If the project has no tsconfig.json, dotfiles generate writes one that only extends the CLI’s:

{
"extends": "./.generated/tsconfig.json"
}

Nothing has to be added by hand: the include list lives in the CLI-owned file and follows the configured tool directories. A tsconfig.json the CLI wrote before it owned one is updated to this form; a file you edited is left alone.

dotfiles tool validate type-checks the configuration with that program, using the TypeScript 7 compiler provisioned like any other tool: dotfiles tool scaffold writes tools/typescript.tool.ts, which installs microsoft/typescript-go from its GitHub release, and dotfiles tool install typescript installs it. The compiler is declared with .bin("tsc", { shim: false }), so it is not put on PATH and cannot shadow the TypeScript your other projects use; tool validate runs it from the tool’s current directory. Until it is installed, tool validate reports that as an error rather than skipping the type-check. Your editor uses the same program through the extending tsconfig.json.

// ❌ Missing required parameter
install('github-release', {}) // Error: 'repo' is required
// ❌ Invalid parameter for method
install('brew', { repo: 'owner/tool' }) // Error: 'repo' not valid for brew
// ❌ String instead of enum
.platform('macos', ...) // Error: use Platform.MacOS