One context to rule them all
Tyr is a TypeScript-native CLI framework for building, running, and composing automation
tools — with zero boilerplate. Write a function, tyr takes care of the rest:
argument routing, dependency injection, error formatting, and live documentation.
# install Tyr globally
npm i -g @tyrframework/cli
# initialize it in your project
tyr --config
No more scattered scripts
Tyr solves the fragmentation problem in DevOps scripting. Instead of maintaining scattered shell scripts, ad-hoc Node utilities, and undocumented automation glue, Tyr gives you a single, typed, dependency-injected context from which every command operates.
You write a function
Every command is a function exported by default, receiving a fully-resolved TyrContext. No manual wiring.
Tyr handles the rest
Argument routing, dependency injection, error formatting, and live documentation — all automatic.
Compose & distribute
Call commands from within other commands, ship them as importable modules, or publish your own npm distribution.
Everything the framework brings to the table
A small, predictable core, with optional superpowers when you need them.
Real dependency injection
A container wires every dependency once and injects it into each command through TyrContext. You never import anything by hand.
Zero boilerplate
tyr gen <name> scaffolds the command file and registers it in map.yml automatically.
Live documentation
tyr doc parses the JSDoc across the whole toolkit and serves an interactive HTML reference locally.
Controlled error handling
fail() for expected validation failures, task() to wrap async operations with automatic error context.
A well-stocked toolkit
Shell, filesystem, git, Docker, SQL, MongoDB, web scraping and more — ready to use, no wiring required.
Module ecosystem
Import third-party commands with tyr --add from a simple manifest.json hosted on GitHub.
AI-native
Multi-vendor AI support (Anthropic, OpenAI, Gemini) behind one unified interface, plus a coding agent that can read, edit, and validate your codebase.
Web chat, out of the box
Spin up a local chat UI with a file browser in one command — a ready base for wiring up your own AI workflows.
Cross-platform
CI verified on Ubuntu, Windows, and macOS. The same command behaves the same way everywhere.
From zero to command in three steps
No prior setup. No ceremony. Just write the logic.
tyr gen greet greet
Generates ~/.tyr/commands/greet.tyr.ts and registers it in ~/.tyr/map.yml (run tyr --config once first to set up ~/.tyr).
import { TyrContext } from '@tyrframework/cli';
export default ({ task, fail, logger }: TyrContext) => {
return async (args: string[]) => {
if (args.length === 0) {
fail('A name is required', 'Usage: tyr greet <name>');
}
await task('Greeting', async () => {
logger.success(`Hello, ${args[0]}!`);
});
};
};
tyr greet World
→ ✔ Hello, World!
Commands are registered automatically and become available as tyr sub-commands.
Docker & AI, the Tyr way
Two of Tyr's flagship capabilities — container orchestration and AI-native tooling — as real command code.
import { TyrContext } from '@tyrframework/cli';
// Bring up a full stack, then start a standalone container alongside it.
export default ({ task, fail, docker, logger }: TyrContext) => {
return async (args: string[]) => {
if (!(await docker.isRunning())) {
fail('Docker is not running', 'Start Docker Desktop and try again.');
}
await task('Starting stack', async () => {
await docker.composeUp('infrastructure/docker-compose.yml');
});
await task('Starting cache', async () => {
await docker.run({
name: 'redis-cache',
image: 'redis:7-alpine',
port: '6379:6379',
});
});
logger.success('Stack is up 🐳');
};
};
// $ tyr up
// → ✔ Starting stack
// → ✔ Starting cache
// → Stack is up 🐳
import { TyrContext } from '@tyrframework/cli';
// Let the agent explore your codebase, propose changes as Search/Replace
// blocks, apply them, and validate the result in a sandbox before returning.
export default ({ aiContext, prompts, tokens, logger }: TyrContext) => {
return async (args: string[]) => {
const tree = await aiContext.buildExplorationTree(process.cwd());
const messages = await prompts.build('ai-code', { task: args.join(' '), tree }, process.cwd());
const result = await aiContext.runCodeAgent(process.cwd(), messages, tokens, {
priority: 'mid',
});
logger.success(`${result.filesChanged.length} file(s) changed`);
};
};
// $ tyr code "add input validation to the signup form"
// → ✔ 3 file(s) changed
import { TyrContext } from '@tyrframework/cli';
// Spin up a local chat UI and decide exactly how it answers.
export default ({ chat, aiVendor, logger }: TyrContext) => {
return async (args: string[]) => {
chat.onMessage(async ({ message, history, dir }) => {
const result = await aiVendor.complete([{ role: 'user', content: message.text }]);
return result.content;
});
const session = await chat.open(args[0] ?? process.cwd(), { splitRatio: 0.4 });
logger.success(`Chat ready at: ${session.url}`);
};
};
// $ tyr chat ./my-project --split 0.35
// → Chat ready at: http://localhost:4646
A directory of importable modules
Commands can be packaged and shared as third-party modules.
Loading modules…
Want to be listed? Open a 🧩 Submit a module issue on GitHub and it'll be reviewed for the directory — no email, no PR needed.
One flat file, one map of commands
A module is described by a manifest.json — a flat JSON map of command name
to the raw URL of its .tyr.ts file. The special $env key is
optional ($ can never appear in a real command name) and points to a
template of the environment variables that module's commands expect.
{
"deploy": "https://raw.githubusercontent.com/someuser/tyr-modules/main/deploy.tyr.ts",
"rollback": "https://raw.githubusercontent.com/someuser/tyr-modules/main/rollback.tyr.ts",
"$env": "https://raw.githubusercontent.com/someuser/tyr-modules/main/.env.example"
}
Install someone's module, or ship your own
- Register a module with
tyr --add <manifest-url> [name] - Keep everything in sync with
tyr --modules sync - Force a refresh with
tyr --update - Uninstall cleanly with
tyr --del <name>
# install someone else's module
tyr --add https://raw.githubusercontent.com/someuser/tyr-modules/main/manifest.json my-module
tyr --modules # list registered modules
tyr --modules sync # install anything missing
tyr --update # pull + force-refresh imports
tyr --del my-module # uninstall it entirely
# publish your own collection of commands
tyr --config --repo https://github.com/you/your-commands
tyr --manifest # generates ~/.tyr/manifest.json
What we check on a module submission
Anyone can open a module submission — these are the checks the issue template itself asks you to confirm before review:
- Your
manifest.jsonis reachable at araw.githubusercontent.comURL and follows the format above. tyr --add <your-manifest-url>actually installs it end to end, with no errors.- Command names are namespaced or distinctive enough to avoid obvious collisions with built-ins or other listed modules.
Publish your own distribution
The main repository is open — anyone can fork it and publish their own version under their
own npm scope, no permission required. Five steps and your team has a private npm install.
Fork & clone
Fork TyrFramework/tyr on GitHub, then clone your fork locally.
git clone https://github.com/your-team/tyr.git
cd tyr
Rename the package
Point package.json at your own npm scope — this is the name your team will install. Use a scope you own; @tyrframework/* is reserved for the official distribution and publishing under it isn't allowed.
{
"name": "@your-team/tyr"
}
Add an NPM_TOKEN secret
Generate an npm access token (npmjs.com → your avatar → Access Tokens → Generate New
Token), then add it to your fork: Settings → Secrets and variables → Actions →
New repository secret, named NPM_TOKEN.
Tag a release
The included fork-release workflow installs dependencies, runs your tests, publishes to npm under your scope, and cuts a GitHub Release — all automatically.
git tag v1.0.0
git push origin v1.0.0
Install it across your team
Anyone on your team can now install and initialize your distribution exactly like the original.
npm i -g @your-team/tyr
tyr --config
Share it with the community (optional)
Want your distribution listed alongside the official one? Open a 📦 Submit a distribution issue — no PR needed, it's reviewed and added to COMMUNITY.md directly from there.
What's required to publish or list a fork
Forking, modifying, and publishing your own distribution never requires permission — that's the whole point of the hybrid open community model. Getting it listed on this site, on the other hand, does go through a quick review. These are the rules, taken straight from the submission issue template:
- Your
package.json'snamefield must use a scope you own (e.g.@yourname/tyr) — never@tyrframework/*, which is reserved for the official distribution. - The package must actually be published on npm and installable right now, not a work in progress.
- Your repository needs at least a minimal README explaining what's different from upstream — or stating plainly that it's a plain fork with no changes yet.
- Listed distributions are community-maintained; they aren't officially supported by the Tyr core team, and the submission issue asks you to confirm you understand that.
Stop pasting scripts. Start composing commands.
Install Tyr, scaffold your first command, and get live documentation — in under five minutes.