TypeScript · CLI · Dependency Injection

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.

npm version MIT License Node 18+ TypeScript
terminal
# install Tyr globally
npm i -g @tyrframework/cli

# initialize it in your project
tyr --config
0Boilerplate per command
3OSes verified in CI
MITLicense, fork-friendly
JSONModule registry, yours to edit
The problem

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.

Why Tyr

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.

Quick Start

From zero to command in three steps

No prior setup. No ceremony. Just write the logic.

1 Create the command
terminal
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).

2 Edit the command
greet.tyr.ts
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]}!`);
        });
    };
};
3 Run it
terminal
tyr greet World
→ ✔ Hello, World!

Commands are registered automatically and become available as tyr sub-commands.

Examples

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
Ecosystem

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.

Manifest format

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.

manifest.json
{
  "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"
}
Add & publish

Install someone's module, or ship your own

  1. Register a module with tyr --add <manifest-url> [name]
  2. Keep everything in sync with tyr --modules sync
  3. Force a refresh with tyr --update
  4. Uninstall cleanly with tyr --del <name>
terminal
# 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
Before you submit

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:

  1. Your manifest.json is reachable at a raw.githubusercontent.com URL and follows the format above.
  2. tyr --add <your-manifest-url> actually installs it end to end, with no errors.
  3. Command names are namespaced or distinctive enough to avoid obvious collisions with built-ins or other listed modules.
For your team

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.

1

Fork & clone

Fork TyrFramework/tyr on GitHub, then clone your fork locally.

terminal
git clone https://github.com/your-team/tyr.git
cd tyr
2

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.

package.json
{
  "name": "@your-team/tyr"
}
3

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.

4

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.

terminal
git tag v1.0.0
git push origin v1.0.0
5

Install it across your team

Anyone on your team can now install and initialize your distribution exactly like the original.

terminal
npm i -g @your-team/tyr
tyr --config
6

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.

Rules for forks

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:

  1. Your package.json's name field must use a scope you own (e.g. @yourname/tyr) — never @tyrframework/*, which is reserved for the official distribution.
  2. The package must actually be published on npm and installable right now, not a work in progress.
  3. 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.
  4. 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.