Base is a foundational developer tooling repo for a multi-project workspace.
Its job is not to be a product repo, a service repo, or a monorepo. Its job is to provide the common layer that sits above individual project repositories and makes them easier to set up, run, and test in a consistent way.
This repository already existed as a shell-focused project. The next version of Base is a deliberate refactor of that idea into a broader workspace bootstrap and execution layer.
Most real engineering environments are not a single repository.
A developer may need:
- one repo for shared tooling
- several project repos checked out side by side
- a consistent shell environment across machines
- common shell and Python helper libraries
- a clean way to run project commands through wrappers instead of directly
Base exists to provide that missing common layer.
Base is being refactored around three primary goals.
Base should give the user one entry point for setting up and validating a workspace that contains multiple project repositories.
Current implemented commands include:
basectl setupbasectl checkbasectl update-profilebasectl versionbasectl shell
Planned commands include:
basectl setup <project>basectl testbasectl test <project>basectl doctorbasectl projects list
The important idea is that the user should not need to memorize a different bootstrap story for every repository in the workspace. Planned commands are listed here to describe direction, not current availability.
Base should be able to discover participating project repositories checked out next to it under a shared parent directory, for example:
~/work/
base/
banyanlabs/
bankbuddy/
blend/
brew/
Over time, each project repo can declare how Base should interact with it, likely through a small project manifest or well-defined conventions.
Base should manage shell environments at two levels:
- global environment shared across the whole workspace
- project-specific environment layered on top for an individual repo
That includes things like:
- common shell initialization
- PATH management
- shared environment variables
- host and OS detection
- project-local activation hooks
- predictable loading order
The goal is to make shell behavior explicit, inspectable, and repeatable instead of depending on a fragile mix of ad hoc dotfiles and one-off scripts.
Base should provide a stable foundation for controlled CLI execution.
That includes:
- shell libraries for logging, errors, files, Git, networking, and standard helpers
- Python wrappers for running Python-based tooling with the right environment
- shell wrappers for sourcing shared libraries and normalizing execution context
- a consistent convention for passing arguments, setting environment variables, and reporting failures
The wrapper model matters because it keeps command behavior predictable. A CLI should run inside a known environment instead of relying on whoever happened to invoke it from whatever shell state they already had.
Base exposes commands through a single public directory: $BASE_HOME/bin. That
directory is added to PATH by Base's managed shell startup snippets.
bin/basectl is the control-plane command. Additional public commands, when
needed, are tiny real launcher files in bin/ that delegate to basectl; their
implementation remains under cli/bash/commands/<command>/ or, in the future,
cli/python/commands/<command>/.
Example launcher:
#!/usr/bin/env bash
exec "$(dirname "$0")/basectl" caff "$@"basectl setup deliberately pins its default Homebrew Python formula so setup is
reproducible across machines. The current default is python@3.13. Override it
with BASE_SETUP_PYTHON_FORMULA when a workspace needs a different formula.
Base is currently designed to be checked out once per workspace. Until a standalone installer exists, the explicit bootstrap path is:
git clone https://github.com/codeforester/base.git ~/work/base
~/work/base/bin/basectl setup
~/work/base/bin/basectl update-profile
exec "$SHELL" -lAfter the shell restarts, Base's managed startup section adds ~/work/base/bin
to PATH, so basectl can be run without spelling out the full path. Use
basectl version or basectl --version to report the installed Base version.
Base is currently macOS-first. The implemented and tested support contract is macOS with Homebrew, Xcode Command Line Tools, a Homebrew-managed Bash, Git, and Python installed through Base setup.
Intended supported platforms are:
- macOS on Apple Silicon
- macOS on Intel Macs
- at least one Linux variant in the future, with the first target still to be decided
The supported macOS version floor is still TBD. Linux support is a design target, but not yet an implemented or tested support contract. Windows is out of scope.
OS-specific behavior should stay isolated behind small helpers instead of being
scattered through command code. For example, the Base runtime prompt can prefer
macOS scutil names while still falling back to generic hostname.
Base integrates with Bash and Zsh through small managed sections in the user's real dotfiles. Base does not take over whole dotfiles.
The command that installs or refreshes those sections is:
basectl update-profileBy default it updates all four startup files:
~/.bash_profile~/.bashrc~/.zprofile~/.zshrc
Missing files are created. Existing files keep their non-Base content; Base only adds or replaces its marked section.
basectl update-profile also creates ~/.base.d/profile.conf, which records
whether the user has opted into Base's optional shell defaults. The managed
dotfile sections stay minimal and defer PATH/default handling to the sourced
Base snippets.
Run basectl update-profile --defaults to enable those optional defaults, and
run basectl update-profile --no-defaults to disable them again. Plain
basectl update-profile preserves the existing preference.
BASE_PROFILE_VERSION records the schema version of this Base-managed file. It
is reserved for future migrations and is not intended to be edited by users.
Base also reads ~/.baserc when it exists. Unlike profile.conf, ~/.baserc
is user-managed and may be hand-edited. It is intended for simple,
shell-startup-safe Base preferences such as BASE_DEBUG=1; it should not become
a second .bashrc with arbitrary setup logic.
~/.baserc must not set Base-owned runtime or profile variables such as
BASE_HOME, BASE_BIN_DIR, BASE_LIB_DIR, BASE_OS, BASE_SHELL,
BASE_PROFILE_VERSION, BASE_ENABLE_BASH_DEFAULTS, or
BASE_ENABLE_ZSH_DEFAULTS. Base startup snippets reject and restore those
variables if ~/.baserc tries to change them.
Base-managed sections use explicit markers such as:
# --- BEGIN base bashrc MANAGED SECTION - DO NOT EDIT ---
# ... Base-managed content ...
# --- END base bashrc MANAGED SECTION - DO NOT EDIT ---The managed sections source matching snippets under lib/shell/:
lib/shell/bash_profilefor~/.bash_profilelib/shell/bashrcfor~/.bashrclib/shell/zprofilefor~/.zprofilelib/shell/zshrcfor~/.zshrc
The names intentionally mirror the dotfiles they support, without leading dots inside the repository.
Bash snippets and the Bash runtime rcfile share lib/shell/baserc_guard.sh for
safe ~/.baserc loading. Zsh snippets keep their own guard logic for now.
bash_profile and zprofile stay thin.
For Bash, Base makes the login-shell bridge explicit: the Bash profile snippet
sources ~/.bashrc with a guardrail. Bash needs this because login Bash shells
do not automatically read ~/.bashrc.
For Zsh, Base does not source ~/.zshrc from zprofile. Zsh already reads
~/.zshrc for interactive shells.
bashrc and zshrc are where interactive shell behavior belongs.
They are responsible for:
- guarding against non-interactive execution
- guarding against repeated sourcing
- deriving and exporting
BASE_HOMEfrom the sourced Base snippet - adding Base's
bin/directory toPATHsobasectlis available after login - keeping dotfile integration separate from the full Base runtime bootstrap
- optionally enabling shared shell defaults when
basectl update-profile --defaultsis used
They do not source base_init.sh. Base runtime setup happens only when the
basectl command runs a Base command, runs an explicit script path, or starts a
Base-enabled Bash shell.
When basectl starts an interactive Bash runtime shell, it uses Base's runtime
rcfile rather than making Bash read ~/.bashrc directly. That runtime rcfile
loads base_init.sh, sources the user's ~/.bashrc once with guardrails, and
finally sets the Base runtime prompt. This keeps user aliases and normal
interactive Bash behavior available while making Base stdlib functions such as
import_base_lib available during user Bash startup.
Set BASE_DEBUG=1 to make Base-managed shell startup snippets print diagnostic
messages while they run. This is intentionally independent of base_init.sh and
stdlib logging, because dotfile debugging can happen before the Base runtime is
loaded.
For normal terminal startup, put this in ~/.baserc:
BASE_DEBUG=1For one-off checks, use an environment variable:
BASE_DEBUG=1 bash --rcfile ~/.bashrc -i
BASE_DEBUG=1 zsh -i
BASE_DEBUG=1 basectlDiagnostics are printed to stderr and show which Base snippet loaded, how
BASE_HOME was derived, whether $BASE_HOME/bin was added to PATH, whether
optional shell defaults were enabled, and how the Base runtime shell was layered.
For command debugging, basectl -v <command> enables DEBUG logs after the Base
runtime is loaded and the selected command is dispatched. For earlier startup
debugging, use wrapper options that are consumed by bin/basectl before
base_init.sh is sourced:
--debug-wrapperand--verbose-wrapperenableLOG_DEBUG=1before runtime initialization.--utc-wrapperenables UTC log timestamps before runtime initialization.--colorpreserves color-aware wrapper argument handling while keeping the flag out of command arguments.
Prefer -v unless the problem happens before the command implementation starts.
Base can provide optional, opinionated shell defaults, but they are not enabled
by plain basectl update-profile.
Current default-setting scripts are:
lib/shell/base_defaults.shfor shell-neutral defaults shared by Bash and Zshlib/shell/bash_defaults.shfor Bash-specific defaultslib/shell/zsh_defaults.shfor Zsh-specific defaults
Users can opt in during profile updates with:
basectl update-profile --defaultsUsers can opt out again with:
basectl update-profile --no-defaultsThose defaults are intended to stay conservative:
- aliases like
rm -i,cp -i,mv -i - vi-style command editing
- editor defaults
- prompt defaults
- history behavior
Base currently exposes a small number of convenience utilities through
$BASE_HOME/bin, including caff and sort-in-place. These are useful helper
commands that share Base's command conventions, but they are not the core
workspace orchestration surface.
As Base matures, bonus utilities may stay documented as extras or move behind a
clearer namespace. The control-plane surface remains basectl.
Base owns the shared developer-platform layer of the workspace.
That means Base should be responsible for:
- bootstrapping the developer environment
- discovering participating project repos
- orchestrating setup and test flows across repos
- managing shared shell initialization
- providing common shell and Python helper libraries
- providing wrappers and execution conventions for CLIs
Base should not absorb project-specific logic that belongs inside individual repositories.
Each project repo should still own:
- its own source code
- its own business logic
- its own build details
- its own runtime details
- its own tests
- its own project-specific setup steps
Base should orchestrate those things, not replace them.
Think of Base as the workspace control plane for local development.
Each project repo remains independent. Base sits beside those repos and offers:
- one place to bootstrap the workspace
- one place to manage shared environments
- one place to host common execution libraries and wrappers
That gives a multi-repo setup some of the ergonomic benefits people often reach for in a monorepo, without forcing unrelated codebases into a single repository.
The target shape looks roughly like this:
work/
base/
README.md
cli/
bash/
python/
lib/
manifests/
project-a/
project-b/
infra/
Projects should be able to opt into Base with minimal coupling. The exact mechanism is still being designed, but the intent is clear:
- Base discovers projects in the shared workspace
- projects expose a small contract to Base
- Base provides common orchestration on top
The refactor should follow a few simple principles.
- Keep project repos independent.
- Prefer explicit conventions over hidden shell magic.
- Keep wrappers thin but reliable.
- Make setup and test flows idempotent where possible.
- Let Base provide the common layer without turning into a dumping ground for project-specific behavior.
This repository is being repositioned and refactored.
The current contents include useful shell-oriented building blocks from the older version of Base. The goal now is to evolve those foundations into a more general multi-project workspace tool.
For the evolving architecture and product-direction notes behind that refactor,
see docs/design.md. For the current basectl runtime and
dispatch contract, see docs/execution-model.md. For
ecosystem boundary and integration decisions, see
docs/tool-boundaries.md.
The first migration pass has already started: the Base CLI, runtime bootstrap,
setup command, and Bash libraries formerly living in the banyanlabs repo now
live under this repository.
Base is the repo you check out once per workspace so that all the other repos in that workspace become easier to set up, easier to test, and easier to run in a controlled shell environment.