A web-based terminal for Angular, React, and Vue — extensible, themeable, and packed with built-in developer tools.
Live Demo · Documentation · npm
| Package | Description |
|---|---|
@qodalis/cli-core |
Shared interfaces, models, and types |
@qodalis/cli |
Framework-agnostic terminal engine (50+ built-in commands) |
@qodalis/angular-cli |
Angular wrapper |
@qodalis/react-cli |
React wrapper |
@qodalis/vue-cli |
Vue 3 wrapper |
npm install @qodalis/angular-cliimport { CliModule } from '@qodalis/angular-cli';
@NgModule({
imports: [CliModule],
})
export class AppModule {}Add styles to angular.json:
{
"styles": [
"node_modules/@qodalis/angular-cli/src/assets/styles.sass"
]
}<!-- Full terminal -->
<cli [options]="cliOptions" />
<!-- Collapsible panel -->
<cli-panel />npm install @qodalis/react-cliimport { Cli } from '@qodalis/react-cli';
function App() {
return <Cli style={{ width: '100vw', height: '100vh' }} />;
}npm install @qodalis/vue-cli<script setup lang="ts">
import { Cli } from '@qodalis/vue-cli';
</script>
<template>
<Cli :style="{ width: '100vw', height: '100vh' }" />
</template>Pass options to customize the terminal:
const options = {
welcomeMessage: {
message: '-- your custom welcome message --',
show: 'daily', // 'never', 'once', 'daily', 'always'
},
usersModule: {
enabled: true,
},
};| Command | Aliases | Description |
|---|---|---|
help |
man |
Show all commands or help for a specific command |
version |
ver |
Display CLI version and documentation link |
hotkeys |
shortcuts, keys |
Show keyboard shortcuts |
history |
hist |
Browse and clear command history |
theme |
themes |
Apply, customize, and save terminal themes (interactive selection with live preview) |
feedback |
support |
Report bugs or request features on GitHub |
pkg |
packages |
Install, update, remove, and browse packages |
| Command | Aliases | Description |
|---|---|---|
clear |
cls |
Clear the terminal (or Ctrl+L) |
echo |
print |
Print text to the terminal |
eval |
calc, js |
Evaluate JavaScript expressions |
sleep |
wait |
Pause execution for N milliseconds |
time |
date |
Show current local and UTC time |
uptime |
— | Show session uptime |
uname |
sysinfo |
System and browser information |
screen |
display |
Screen, viewport, and terminal dimensions |
open |
— | Open a URL in a new browser tab |
alias / unalias |
— | Create and remove command aliases |
yes |
— | Output a string repeatedly |
seq |
sequence |
Print number sequences |
cal |
calendar |
Display a monthly calendar |
| Command | Aliases | Description |
|---|---|---|
base64 |
b64 |
Encode / decode Base64 |
json |
— | Format, minify, and validate JSON |
url |
— | Encode, decode, and parse URLs |
jwt |
— | Decode and inspect JWT tokens |
hash |
— | SHA-256, SHA-1, SHA-384, SHA-512 hashes |
hex |
— | Hex encode/decode and base conversion (2-36) |
color |
colour |
Convert between hex, rgb, hsl with preview |
random |
rand |
Random numbers, strings, UUIDs, coin flip, dice |
lorem |
lipsum |
Generate placeholder text |
timestamp |
ts, epoch |
Convert between Unix timestamps and dates |
convert |
conv |
Unit conversion (length, weight, temperature, data) |
clipboard |
cb, pbcopy |
Copy to / paste from clipboard |
| Command | Aliases | Description |
|---|---|---|
whoami |
me |
Display current user |
su |
switch-user |
Switch user session |
adduser |
useradd |
Add a new user |
listusers |
users |
List all users |
Install additional commands at runtime from npm — no rebuild needed.
pkg browse # Browse available packages
pkg add guid # Install a package
pkg remove guid # Remove a package
pkg update # Update all packages
pkg update guid@1.0.2 # Pin a specific version
pkg check # Check for updates
pkg versions guid # Show all published versions
pkg source set # Interactively select a package source (CDN)
pkg source set unpkg # Set package source directly| Package | Command | Description |
|---|---|---|
| @qodalis/cli-guid | guid |
Generate and validate UUIDs |
| @qodalis/cli-string | string |
String manipulation (case, trim, reverse, slug, wc, etc.) |
| @qodalis/cli-curl | curl |
HTTP requests (GET, POST, PUT, DELETE) |
| @qodalis/cli-todo | todo |
Task management |
| @qodalis/cli-regex | regex |
Regular expression testing |
| @qodalis/cli-qr | qr |
QR code generation |
| @qodalis/cli-speed-test | speed-test |
Internet speed test |
| @qodalis/cli-server-logs | server-logs |
Live server log streaming |
| @qodalis/cli-browser-storage | local-storage, cookies |
Browser storage operations |
| @qodalis/cli-text-to-image | text-to-image |
Generate images from text |
| @qodalis/cli-password-generator | generate-password |
Password generation |
Any npm package with UMD support can also be loaded:
pkg add lodash
eval _.map([1, 2, 3], n => n * 2)Scaffold a complete plugin project with a single command:
npx @qodalis/create-cli-pluginThe interactive prompts will ask for:
| Prompt | Description | Example |
|---|---|---|
| Plugin name | Lowercase, no spaces, no cli- prefix (added automatically) |
weather |
| Description | Short description of the plugin | Weather forecasts |
| Processor class name | PascalCase name for the command processor class | Weather |
You can also skip the prompts by passing arguments directly:
npx @qodalis/create-cli-plugin --name weather --description "Weather forecasts" --processor-name WeatherOr install globally:
npm install -g @qodalis/create-cli-plugin
create-cli-pluginThe tool auto-detects whether you're inside the web-cli monorepo:
| Mode | Detection | Output directory | Package manager |
|---|---|---|---|
| Standalone | Any directory outside web-cli | ./qodalis-cli-<name>/ |
npm or pnpm (auto-detected) |
| Monorepo | Inside web-cli workspace | packages/plugins/<name>/ |
pnpm (workspace) |
In monorepo mode, the tool also:
- Creates
project.json(Nx build + test targets) - Creates
tsconfig.spec.json(Karma/Jasmine test config) - Updates
tsconfig.base.jsonwith the path alias@qodalis/cli-<name>→dist/<name>
qodalis-cli-weather/ # or packages/plugins/weather/ in monorepo
package.json # npm package with CJS/ESM/UMD exports
tsup.config.ts # Build config (library + IIFE bundles)
tsconfig.json # TypeScript config
README.md # Auto-generated README
src/
public-api.ts # Public exports + ICliModule declaration
cli-entrypoint.ts # IIFE entrypoint for browser runtime loading
lib/
version.ts # LIBRARY_VERSION + API_VERSION constants
index.ts # Barrel re-exports
processors/
cli-weather-command-processor.ts # Command processor (your main logic goes here)
tests/
index.spec.ts # Jasmine test scaffold
npx @qodalis/create-cli-plugin --name weatherEdit src/lib/processors/cli-weather-command-processor.ts:
import {
CliProcessCommand,
DefaultLibraryAuthor,
ICliCommandProcessor,
ICliExecutionContext,
} from '@qodalis/cli-core';
import { LIBRARY_VERSION } from '../version';
export class CliWeatherCommandProcessor implements ICliCommandProcessor {
command = 'weather';
description = 'Weather forecasts';
author = DefaultLibraryAuthor;
version = LIBRARY_VERSION;
// Sub-commands
processors?: ICliCommandProcessor[] = [
{
command: 'forecast',
description: 'Get weather forecast for a city',
parameters: [
{
name: 'city',
aliases: ['c'],
description: 'City name',
required: true,
type: 'string',
},
{
name: 'days',
aliases: ['d'],
description: 'Number of days',
required: false,
type: 'number',
defaultValue: '3',
},
],
processCommand: async (command, context) => {
const city = command.args['city'];
const days = parseInt(command.args['days'] ?? '3');
context.writer.writeln(`Forecast for ${city} (${days} days):`);
context.writer.writeSuccess('Sunny, 25°C');
},
},
{
command: 'current',
description: 'Get current weather',
acceptsRawInput: true, // command.value = text after 'current'
valueRequired: true, // error if no value provided
processCommand: async (command, context) => {
context.writer.writeln(`Current weather in ${command.value}: Sunny, 25°C`);
},
},
];
// Default handler (runs when user types just 'weather')
async processCommand(command: CliProcessCommand, context: ICliExecutionContext): Promise<void> {
context.executor.showHelp(command, context);
}
}Standalone:
cd qodalis-cli-weather
npm run build # or: npx tsupMonorepo:
pnpm nx build weatherBuild output goes to dist/weather/ with three bundles:
public-api.js(CJS) +public-api.mjs(ESM) +public-api.d.ts(types)umd/index.js(IIFE — self-contained browser bundle for runtimepkg add)
Standalone: Add your preferred test runner.
Monorepo:
pnpm nx test weathercd dist/weather # or: cd qodalis-cli-weather/dist
npm publish --access publicUsers can then install your plugin at runtime in any Qodalis CLI terminal:
pkg add @qodalis/cli-weather
weather forecast --city "New York"Every plugin exports an ICliModule object in public-api.ts. This is how frameworks register your plugin:
import { ICliModule } from '@qodalis/cli-core';
import { CliWeatherCommandProcessor } from './lib/processors/cli-weather-command-processor';
import { API_VERSION } from './lib/version';
export const weatherModule: ICliModule = {
apiVersion: API_VERSION, // must be >= 2
name: '@qodalis/cli-weather',
processors: [new CliWeatherCommandProcessor()],
};ICliModule also supports optional lifecycle hooks and configuration:
export const weatherModule: ICliModule = {
apiVersion: 2,
name: '@qodalis/cli-weather',
processors: [new CliWeatherCommandProcessor()],
dependencies: ['@qodalis/cli-curl'], // boot other modules first
priority: 0, // boot order (lower = first)
configure(config) { /* ... */ return this; },
onInit(context) { /* before processors initialize */ },
onAfterBoot(context) { /* after all modules boot */ },
onDestroy(context) { /* teardown */ },
};src/cli-entrypoint.ts enables runtime loading via pkg add. It calls bootCliModule() which registers the module with the global window.__cliModuleRegistry:
import { bootCliModule, ICliModule } from '@qodalis/cli-core';
import { CliWeatherCommandProcessor } from './lib/processors/cli-weather-command-processor';
import { API_VERSION } from './lib/version';
const module: ICliModule = {
apiVersion: API_VERSION,
name: '@qodalis/cli-weather',
processors: [new CliWeatherCommandProcessor()],
};
bootCliModule(module);The ICliExecutionContext passed to processCommand provides:
| Property | Description |
|---|---|
context.writer |
Terminal output — writeln(), writeInfo(), writeSuccess(), writeError(), wrapInColor() |
context.reader |
User input — readLine(), readPassword(), readConfirm(), readSelect(), readMultiSelect(), readNumber() |
context.executor |
Command execution — showHelp(command, context) |
context.clipboard |
Clipboard — write(), read() |
context.state |
Persistent key-value store |
context.progressBar |
Progress bar widget |
context.spinner |
Spinner widget |
context.terminal |
Raw xterm.js Terminal instance |
context.onAbort |
Subject<void> for Ctrl+C cancellation |
context.enterFullScreenMode() |
Switch to full-screen TUI mode |
context.createInterval() / context.createTimeout() |
Managed timers (auto-cleaned on abort) |
import { CliModule, resolveCliModuleProvider } from '@qodalis/angular-cli';
import { weatherModule } from '@qodalis/cli-weather';
@NgModule({
imports: [CliModule],
providers: [resolveCliModuleProvider(weatherModule)],
})
export class AppModule {}import { weatherModule } from '@qodalis/cli-weather';
export class AppComponent {
modules = [weatherModule];
}<cli [modules]="modules" />import { CliConfigProvider, Cli } from '@qodalis/react-cli';
import { weatherModule } from '@qodalis/cli-weather';
function App() {
return (
<CliConfigProvider modules={[weatherModule]}>
<Cli />
</CliConfigProvider>
);
}<script setup lang="ts">
import { CliConfigProvider, Cli } from '@qodalis/vue-cli';
import { weatherModule } from '@qodalis/cli-weather';
</script>
<template>
<CliConfigProvider :modules="[weatherModule]">
<Cli />
</CliConfigProvider>
</template>For quick one-off commands without creating a full plugin, implement ICliCommandProcessor directly in your app:
import {
ICliCommandProcessor,
CliProcessCommand,
ICliExecutionContext,
} from '@qodalis/cli-core';
export class GreetCommandProcessor implements ICliCommandProcessor {
command = 'greet';
description = 'Greet someone by name';
acceptsRawInput = true;
valueRequired = true;
async processCommand(
command: CliProcessCommand,
context: ICliExecutionContext,
): Promise<void> {
context.writer.writeln(`Hello, ${command.value}!`);
}
}Register it with your framework (Angular: resolveCommandProcessorProvider(GreetCommandProcessor), React/Vue: pass via processors prop), or wrap it in an inline ICliModule:
const myModule: ICliModule = {
apiVersion: 2,
name: 'my-app-commands',
processors: [new GreetCommandProcessor()],
};~$ greet World
Hello, World!For advanced use cases or non-framework environments, use CliEngine directly:
import { CliEngine } from '@qodalis/cli';
const engine = new CliEngine(document.getElementById('terminal')!, {
welcomeMessage: { message: 'Welcome!', show: 'always' },
});
engine.registerProcessors([new GreetCommandProcessor()]);
await engine.start();- Multi-framework — Angular, React, Vue, or vanilla JS
- Command chaining with
&&,||,|, and>>operators - Command history with arrow key navigation
- Tab-like completions and keyboard shortcuts (
Ctrl+C,Ctrl+L,Escape) - Theming with built-in themes and custom color support
- User sessions with multi-user support
- State persistence across sessions
- Interactive prompts with live preview (select menus with real-time feedback)
- Full-screen mode API for rich TUI commands
- Progress bars, spinners, and text animations
- Runtime package installation from npm
- Fork this repository
- Create a branch for your feature or bugfix
- Submit a pull request
MIT

