a modular and extensible shell framework inspired by the natural composition of chitin — the material forming the shells of many organisms
The framework's taxonomy mirrors how chitin builds shells:
helper: individual shell functionschain: collections ofhelpersfocused on specific domains. Chains are composed of interlinked chitin molecules.fiber: top-level modules grouped by domain. Fibers provide structure by linkingchainstogether.module: general term for forchainsandfibers, used when something is applicable to either
chitin-core
├── chitin-dev [fiber]
│ ├── docker [chain]
│ │ ├── build_image() [helper]
│ │ ├── run_container() [helper]
│ ├── git [chain]
│ ├── clone_repo() [helper]
│ ├── commit_changes() [helper]
├── chitin-cloud [fiber]
├── aws [chain]
│ ├── launch_instance() [helper]
│ ├── list_buckets() [helper]
├── kubernetes [chain]
├── deploy_pod() [helper]
├── scale_deployment() [helper]
-
Clone this repository to your
project dir(the directory where you usually rungit clone) -
Add the following line to your profile (ie
.zshrcor.bashrc), substituting<project dir>:source <project dir>/chitin/init.sh
-
Start a new shell session, and follow the instructions to configure
chitinby runningchiConfigUserModify
Note: if you would prefer to not automatically load fibers (such as for performance reasons), set
CHI_AUTOINIT_DISABLED=true, and use the commandchiShellwhen you want to load them on-demand
Recovery: a fiber whose
fiberDepscannot be satisfied (misspelled, not cloned, or disabled in your userConfig) is reported withgiving up on fiberand skipped; the shell still starts. If startup itself breaks,export CHI_AUTOINIT_DISABLED=truein the terminal before opening a new shell, fix the problem, then runchiShell.
The framework can be configured at three levels: user, fiber, and chain.
*Dir fields support path expansions:
~: ->$HOMElocalshare->$HOME/.local/share
The user configuration contains global settings, and allows you to set machine-specific overrides:
dotfilesDir: directory where you store your dotfiles (absolute path)projectDir: directory you clone git repositories to (absolute path)installToolDeps: whether any missing tool dependencies should be automatically installed
Modify this config by running chiConfigUserModify.
Both fiber and chain configurations support the following fields:
enabled: whether this module should be loaded (default: true)tools: allows you to declare and configure tools that modules can depend on- see Tool Configuration for details
toolDeps: which tools this module depends on, and should not be loaded without
Tools can be declared in any module, and then referenced in the toolDeps of that module or any depenendent ones, such as a child chain or a dependent fiber and its chains.
Tools can be configured with:
- an install method (optional)
- a presence check
- required, unless the tool is set as
optional.
- required, unless the tool is set as
- a version check (optional)
In addition, the following configuration options can be set:
optional: whether this tool should be checked for presencepostInstall: a command to be run after a successful install
Tools can be automatically installed if installToolDeps is set, using the following install methods:
brew: install with brew; all subfields are optionalname: can be used to override the tool namecask: indicates that the tool is abrew casktap: set to the name of abrew tapif the formula requires ittapUrl: set if thetaprequires a specific URL
git: install with agit cloneurl: the URL of the git repo to clonetarget: the directory to clone the repo into
script: install by fetching and running abashscriptartifact: install by fetching an artifact to a certain pathurl: the URL of the artifact to fetchtarget: the directory to fetch the artifact intoappendFilename: whether to append the lasturlsegment to thetarget
command: install by running a givenbashcommand
On module load, all tools will be checked for presence on the system, even if they are not explicitly set in toolDeps, unless the tool is marked as optional.
The following checks can be configured:
checkCommand: check for the presence of an executable in thePATH(default)- the default check method. if not explicitly set, will use the tool name
- can be set to
trueto explicitly override one of the other check methods, such as when using thebrewinstall method
checkBrew: check withbrewif its been installed- automatically used when the tool is using the
brewinstall method with thecaskoption set totrue
- automatically used when the tool is using the
checkPath: check for the presence of the given filecheckEval: run the given command and check the exit code
In addition to presence checking, a tool can optionally be configured to require a specific version (or range):
version: the expected semantic version- will validate up to the
minorversion, not checkingpatch
- will validate up to the
versionCommand: the command with which to get the tool's installed version
The following fields are unique to fiber configurations:
fiberDeps: whichfibersmust be loaded prior to this onemoduleConfig: allows you to set overrides for child chains' configuration
Modify this config by running chiConfigModuleModify <fiber name>.
chain configurations are optional, and are merged with any matching parent moduleConfig fields, higher-level ones overriding lower.
Modify this config by running chiConfigModuleModify <fiber name:chain name>.
This chain provides a configurable secrets-management interface for other chains to use; it was designed with the pass command in mind, but can be used with others, given a compatible CLI.
Configuration:
Add a section to your chiConfig with the command to use:
{
"chains": {
"secret": {
"command": "pass"
}
}
}Functions:
chiSecretGet: retrieves a secret with the given name from the secret store
Chitin loads in every interactive shell, so its startup cost is the shell's startup cost.
Measure it with the benchmark, never with time zsh -i -c exit: a non-TTY shell trips the
lite-mode guard in ~/.zshenv and skips chitin entirely, reporting ~0.03s.
just bench # 3 timed startups of ~/.zshrc under a pseudo-terminal
just bench --profile # plus zprof's top functions by self time
just bench --trace # plus the slowest call sites and external-command counts
just bench --init ./init.sh # measure this checkout instead of ~/Projects/chitin
just check # zsh -n on every shell file; shellcheck on *.sh when installedBaseline recorded 2026-09-25 on an M-series MacBook with the core, dev, cloud and dotfiles
fibers enabled and tool checks off: ~20s per shell (21.8 / 20.0 / 19.6s). About 17.6s is
the loader's config plumbing: 47 chain loads at ~320ms each and roughly 2,900 external
commands per startup (just bench --trace: sed 1,708, jq 487, paste 447, envsubst 155, yq 16
at 150 to 400ms each) plus thousands of command-substitution subshells. Sourcing the 94 chain
files without the loader takes ~130ms, which is the floor a startup cache can reach.
The config-merge work above is a pure function of about 30 YAML files that rarely change, so
after a full ("cold") load chitin persists its results under ~/.cache/chitin/snapshot/<checkout>/
and replays them on the next startup when none of those files changed: a warm shell starts in
about 2 seconds instead of 20. The check itself is fork-free (zsh's builtin stat), and the
snapshot is keyed by checkout path, so a session clone never shares one with ~/Projects/chitin.
chiSnapshotStatussays whether the current snapshot is valid and, if not, which file changed.chiShellRebuildclears it and does a full load;chiSnapshotClearonly clears.CHI_SNAPSHOT_DISABLED=trueforces a full load and writes nothing.- A stale snapshot logs which input changed and rebuilds on its own; a missing one says so.
Known limits: replay re-sources chain files without the positional parameters chiLoadDir
happened to have, and a new file inside a nested chain subdirectory is only picked up once
something else changes (or on chiShellRebuild). zsh only; under bash every load is cold.
This project is used by: