Skip to content

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.

import { defineTool } from "@alexgorbatchev/dotfiles";
// Install a custom script
export 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" })));

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.

ParameterTypeRequiredDescription
binaryPathstringNoPath to the binary; see binaryPath Resolution
symlinkbooleanNoIf false, copies binaryPath instead of symlinking files (default: true)
copybooleanNoIf true, copies binaryPath instead of symlinking files (default: false); copied binaries are static and do not track upstream updates or file edits

binaryPath is resolved in this order:

  1. 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.
  2. A leading ~ is the project’s paths.homeDir.
  3. A path that is still relative is taken relative to the .tool.ts file, or to paths.dotfilesDir for 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 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()

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"),
);
export default defineTool((install, ctx) =>
install("manual", {
binaryPath: "./binaries/linux/x64/custom-tool",
}).bin("custom-tool"),
);
export default defineTool((install, ctx) => install().zsh((shell) => shell.aliases({ ll: "ls -la", la: "ls -A" })));
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")),
);
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 binaryPath by default, keeping tools updated when the target changes; use copy: true (or symlink: false) to copy the file instead
  • A script located through $0 runs as binaries/<tool>/current/<name>, so $(dirname "$0")/lib.sh no longer finds siblings next to the source unless the script resolves symlinks (or copy: true is configured)
  • .sudo() acquires sudo credentials interactively before Dotfiles registers the manual binary
  • A before-install hook can stage files into stagingDir in place of binaryPath; 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()