@orxataguy/tyr
v1.0.17
Published
Tyr is a TypeScript-based environment that resolves this fragmentation through the creation, execution, and automation of CLI tools. Its architecture is built on dependency injection: the "Kernel" provides an execution context where "Managers" expose thei
Maintainers
Readme
What is Tyr?
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. Tyr takes care of everything else — argument routing, dependency resolution, error formatting, and live documentation.
Installation
npm install -g @orxataguy/tyrOr as a project dependency:
npm install @orxataguy/tyrAfter installing, run the setup command to initialize Tyr in your project:
tyr installThis creates the expected folder structure and registers the tyr alias in your shell config.
Quick Start
1. Create a command
tyr gen greet greetThis generates src/commands/greet.tyr.ts and registers it in config/map.yml.
2. Edit the command
import { TyrContext } from '../core/Kernel';
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
tyr greet WorldCore Concepts
Kernel
The Kernel is the main engine. It boots on every invocation, loads config/map.yml, resolves the command name to a module, and executes it with a fully-wired TyrContext.
You never instantiate the Kernel directly — it runs automatically via bin/tyr.js.
Container & Dependency Injection
All services (Managers) are instantiated once by the Container and injected into every command via TyrContext. You never import a Manager directly — you destructure it from the context:
export default ({ shell, fs, git, logger }: TyrContext) => {
return async (args: string[]) => {
const branch = await git.currentBranch();
logger.info(`On branch: ${branch}`);
};
};TyrContext
The object injected into every command. It exposes all available Managers plus framework utilities:
| Property | Type | Description |
|---|---|---|
| logger | Logger | Colored terminal output |
| task | Function | Wraps async ops with error context |
| fail | Function | Throws a controlled TyrError |
| run | Function | Invokes another Tyr command programmatically |
| shell | ShellManager | Execute shell commands |
| fs | FileSystemManager | File and directory operations |
| pkg | PackageManager | npm package management |
| docker | DockerManager | Docker container operations |
| git | GitManager | Git operations |
| system | SystemManager | OS-level utilities |
| sql | SQLManager | MSSQL database queries |
| web | WebManager | HTTP requests and web scraping |
Commands
A command is an exported default function that takes a TyrContext and returns an async handler:
export default (context: TyrContext) => {
return async (args: string[]) => {
// your logic here
};
};Commands are registered in config/map.yml:
commands:
greet: ./src/commands/greet.tyr.ts
deploy: ./src/commands/deploy.tyr.tsBuilt-in Commands
tyr gen <command-name> [output-file]
Scaffolds a new command file and registers it in config/map.yml.
tyr gen deploy deploy
# creates src/commands/deploy.tyr.tstyr rem <command-name>
Removes the command file and its entry from config/map.yml.
tyr rem deploytyr doc
Parses JSDoc from all Managers and serves an interactive HTML reference at http://localhost:3000. Useful for discovering available methods without leaving your terminal.
tyr doc
# → Open http://localhost:3000Context API Reference
task(label, fn)
Wraps an async operation. If it throws, Tyr captures the error, adds the label as context, and formats a clean error message — no manual try/catch needed.
const result = await task('Building project', async () => {
return await shell.exec('npm run build');
});fail(message, suggestion?)
Immediately stops command execution with a formatted error. Optionally include a suggestion to guide the user.
if (!args[0]) {
fail('Missing required argument: name', 'Run: tyr greet <name>');
}run(commandName, args)
Invokes another registered Tyr command from within a command. Enables command composition.
await run('install', ['--force']);logger
Standardized colored output. Warnings and errors are suppressed unless --debug is passed.
logger.info('Starting...');
logger.success('Done.');
logger.warn('Skipping optional step.');
logger.error('Something went wrong.');Error Handling
Tyr distinguishes between controlled and uncontrolled errors.
Controlled — use fail() for expected validation failures:
fail('Config file not found', 'Create a config.yml in the project root.');Uncontrolled — wrap risky operations in task():
await task('Connecting to database', async () => {
await sql.connect(connectionString);
});Run any command with --debug to see the full stack trace:
tyr deploy --debugProject Structure
├── bin/
│ └── tyr.js # CLI entry point
├── src/
│ ├── core/
│ │ ├── Kernel.ts # Command router and execution engine
│ │ ├── Container.ts # Dependency injection container
│ │ ├── TyrError.ts # Structured error type
│ │ └── sys/
│ │ ├── gen.ts # Built-in: scaffold a command
│ │ ├── rem.ts # Built-in: remove a command
│ │ └── doc.ts # Built-in: serve live documentation
│ ├── commands/
│ │ └── *.tyr.ts # Your custom commands go here
│ └── lib/
│ ├── ShellManager.ts
│ ├── FileSystemManager.ts
│ ├── PackageManager.ts
│ ├── DockerManager.ts
│ ├── GitManager.ts
│ ├── SystemManager.ts
│ ├── SQLManager.ts
│ └── WebManager.ts
├── config/
│ └── map.yml # Command registry
└── tests/
├── setup.ts # Mock context factory
└── test-runner.ts # Smoke test runnerTesting
Unit tests
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
npm run test:ui # Interactive Vitest UISmoke tests
Validates that every registered command loads correctly, exports a default function, and can be instantiated without errors:
npm run test:smokeWriting tests
Use createMockContext() from tests/setup.ts to get a fully mocked TyrContext:
import { createMockContext } from '../tests/setup';
import myCommand from '../src/commands/my-command.tyr';
test('my command runs', async () => {
const ctx = createMockContext();
const handler = myCommand(ctx);
await handler(['arg1']);
expect(ctx.logger.success).toHaveBeenCalled();
});Dependencies
| Package | Purpose |
|---|---|
| chalk | Terminal colors |
| execa | Shell command execution |
| axios | HTTP requests |
| inquirer | Interactive prompts |
| js-yaml | YAML config parsing |
| mssql | MSSQL database driver |
| mongodb | MongoDB driver |
| cheerio | HTML parsing / web scraping |
| dotenv | Environment variable loading |
| tsx | TypeScript execution |
NPM & Community
Tyr is published on npm as @orxataguy/tyr.
The project follows a hybrid open community model — the main repository is open, and contributors are encouraged to publish their own forks under their own npm scope. You do not need permission from the maintainer to fork, improve, or publish your own version.
Publishing your own distribution
- Fork this repository
- Make your changes
- Update the
namefield inpackage.jsonto your own npm scope:"name": "@yourname/tyr" - Add your
NPM_TOKENas a secret in your forked repo settings, then push a tag likev1.0.0— the release workflow will handle the rest automatically - Users can then install your version with:
npm i @yourname/tyr
If you want your fork listed as a community distribution, open a PR adding it to COMMUNITY.md.
License
MIT — Manel Andreu Pérez
