manual
Installs files from your tool configuration directory (custom scripts, pre-built binaries) or registers configuration-only tools. The manual method can be called with or without params.
Basic Usage
Section titled “Basic Usage”import { defineTool } from "@alexgorbatchev/dotfiles";
// Install a custom scriptexport default defineTool((install, ctx) => install("manual", { binaryPath: "./scripts/my-tool.sh", }).bin("my-tool"),);// Without params (shell-only or dependency wrapper)export default defineTool((install) => install("manual") .bin("tokscale") .dependsOn("bun") .zsh((shell) => shell.functions({ tokscale: `bun x tokscale@latest`, }), ),);// Configuration-only tool (no binary)export default defineTool((install, ctx) => install().zsh((shell) => shell.aliases({ ll: "ls -la" })));When to Use
Section titled “When to Use”Use install("manual", { binaryPath }) for binaries that ship with your dotfiles:
- You have custom scripts or binaries to include with your dotfiles
- You want the system to manage and version your tool files
- You need shims generated for your custom tools
- You want to distribute pre-built binaries with your dotfiles
Use install() with no arguments for configuration-only tools:
- You only need shell configuration (aliases, environment, symlinks)
- Tools are managed entirely outside the dotfiles system
- You don’t want any binary installation or management
A tool installed by a script that picks its own location (such as
https://claude.ai/install.sh, which installs into ~/.local/bin) is not a manual tool
with the script run from a hook. Use curl-script with binaryPath, which downloads and
runs the script itself and links the binary to where the script put it; see
Scripts That Install Themselves.
Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
binaryPath | string | No | Path to the binary; see binaryPath Resolution |
symlink | boolean | No | If false, copies binaryPath instead of symlinking files (default: true) |
copy | boolean | No | If true, copies binaryPath instead of symlinking files (default: false); copied binaries are static and do not track upstream updates or file edits |
binaryPath Resolution
Section titled “binaryPath Resolution”binaryPath is resolved in this order:
- Placeholders such as
{paths.homeDir}are filled from the project configuration. A placeholder nothing can fill fails the installation, naming the tool and the value. - A leading
~is the project’spaths.homeDir. - A path that is still relative is taken relative to the
.tool.tsfile, or topaths.dotfilesDirfor a configuration that has no tool file.
Symlinks are not followed: the result is the path as written. Each declared .bin()
name is linked to that path in the tool’s current directory (or copied when copy: true),
and the generated shim runs that per-name file so multi-call binaries receive the
declared name in argv[0].
binaryPath and Binary Patterns
Section titled “binaryPath and Binary Patterns”binaryPath names the one file that is installed under every declared .bin() name. Each
declared .bin() name is what the program receives as argv[0], which multi-call binaries
rely on to dispatch the requested sub-command or persona. Because binaryPath directly
names the file to install for each .bin(), nothing is searched for and a
binary pattern has nothing to select. A tool
that sets binaryPath and gives any .bin() a pattern, whether as .bin(name, pattern) or
.bin(name, { pattern }), is rejected when the configuration loads, with an error naming
the tool file, the tool, the binary, the pattern and binaryPath:
invalid tool configuration in "<tool file>": tool "<tool>": binary "<binary>" declares pattern "<pattern>", but manual binaryPath "<binaryPath>" already names the file to install, so the pattern would never be used; drop the pattern from .bin().bin(name, { shim: false }) declares no pattern and is accepted. The same rule applies to
curl-script’s binaryPath. Its error
says curl-script binaryPath and also offers the other fix, since a curl-script without
binaryPath does search stagingDir with the pattern:
...; drop the pattern from .bin(), or drop binaryPath and point the script at {stagingDir} through args or env.
A manual tool without binaryPath requires a before-install hook to stage files into
stagingDir for a pattern to select. A .bin() pattern on a manual tool that has neither
binaryPath nor a before-install hook is rejected when the configuration loads:
invalid tool configuration in "<tool file>": tool "<tool>": binary "<binary>" declares pattern "<pattern>", but manual tool has neither binaryPath nor a before-install hook, so nothing can ever stage files for it to select; drop the pattern from .bin()Examples
Section titled “Examples”Multi-Call Binary (e.g. rustup)
Section titled “Multi-Call Binary (e.g. rustup)”Multi-call binaries inspect argv[0] to select behavior depending on the name used to execute them. Declare multiple .bin() calls pointing to the single dispatcher binary:
export default defineTool((install) => install("manual", { binaryPath: "~/.cargo/bin/rustup", }) .bin("rustup") .bin("cargo") .bin("rustc"),);Pre-built Binary
Section titled “Pre-built Binary”export default defineTool((install, ctx) => install("manual", { binaryPath: "./binaries/linux/x64/custom-tool", }).bin("custom-tool"),);Configuration-Only Tool
Section titled “Configuration-Only Tool”export default defineTool((install, ctx) => install().zsh((shell) => shell.aliases({ ll: "ls -la", la: "ls -A" })));With Shell Configuration
Section titled “With Shell Configuration”export default defineTool((install, ctx) => install("manual", { binaryPath: "./bin/my-tool.sh", }) .bin("my-tool") .zsh((shell) => shell.aliases({ mt: "my-tool" }).completions("./completions/_my-tool")),);With Sudo Prompt
Section titled “With Sudo Prompt”export default defineTool((install) => install("manual", { binaryPath: "/usr/bin/whoami", }) .bin("sudo-prompt-test") .sudo(),);Notes:
- Binary paths are relative to the tool configuration file location
- Binaries are symlinked to
binaryPathby default, keeping tools updated when the target changes; usecopy: true(orsymlink: false) to copy the file instead - A script located through
$0runs asbinaries/<tool>/current/<name>, so$(dirname "$0")/lib.shno longer finds siblings next to the source unless the script resolves symlinks (orcopy: trueis configured) .sudo()acquires sudo credentials interactively before Dotfiles registers the manual binary- A
before-installhook can stage files intostagingDirin place ofbinaryPath; if it leaves the staging directory empty the installation fails rather than producing an empty payload (see lifecycle-hooks.md) - Configuration-only tools use
install()with no arguments and must not define.bin()