A Node.js CLI server framework for the Qodalis CLI ecosystem. Build custom server-side commands that integrate with the Qodalis web terminal.
npm install @qodalis/cli-server-nodeThe package exports all types, interfaces, base classes, and built-in processors. TypeScript declarations are included.
If you're building a command processor plugin and don't need the server runtime (Express, WebSocket), install the abstractions package instead:
npm install @qodalis/cli-server-abstractionsThis gives you CliCommandProcessor, CliProcessCommand, CliCommandParameterDescriptor, and all other base types with zero dependencies. See @qodalis/cli-server-abstractions for details.
import {
createCliServer,
CliCommandProcessor,
CliProcessCommand,
} from '@qodalis/cli-server-node';
class GreetProcessor extends CliCommandProcessor {
command = 'greet';
description = 'Says hello';
async handleAsync(command: CliProcessCommand): Promise<string> {
const name = command.value ?? 'World';
return `Hello, ${name}!`;
}
}
const { app, eventSocketManager } = createCliServer({
configure: (builder) => {
builder.addProcessor(new GreetProcessor());
},
});
const server = app.listen(8047, () => {
console.log('CLI server listening on http://localhost:8047');
});
eventSocketManager.attach(server);
process.on('SIGINT', async () => {
await eventSocketManager.broadcastDisconnect();
server.close();
process.exit(0);
});npx qodalis-cli-serverOr with environment variables:
PORT=9000 npx qodalis-cli-serverExtend CliCommandProcessor and implement command, description, and handleAsync:
import { CliCommandProcessor, CliProcessCommand } from '@qodalis/cli-server-node';
class EchoProcessor extends CliCommandProcessor {
command = 'echo';
description = 'Echoes input text back';
async handleAsync(command: CliProcessCommand): Promise<string> {
return command.value ?? 'Usage: echo <text>';
}
}Register it during server creation:
const { app, eventSocketManager } = createCliServer({
configure: (builder) => {
builder
.addProcessor(new EchoProcessor())
.addProcessor(new AnotherProcessor()); // fluent chaining
},
});Declare parameters with names, types, aliases, and defaults. The CLI client uses this metadata for autocompletion and validation.
import {
CliCommandProcessor,
CliCommandParameterDescriptor,
CliProcessCommand,
} from '@qodalis/cli-server-node';
class TimeProcessor extends CliCommandProcessor {
command = 'time';
description = 'Shows the current server time';
parameters = [
new CliCommandParameterDescriptor(
'utc', // name
'Show UTC time', // description
false, // required
'boolean', // type
),
new CliCommandParameterDescriptor(
'format', // name
'Date/time format string', // description
false, // required
'string', // type
['-f'], // aliases
'ISO', // defaultValue
),
];
async handleAsync(command: CliProcessCommand): Promise<string> {
const useUtc = 'utc' in (command.args ?? {});
const now = new Date();
return useUtc ? `UTC: ${now.toISOString()}` : `Local: ${now.toLocaleString()}`;
}
}Parameter types: 'string', 'number', 'boolean'.
Nest processors to create command hierarchies like math add --a 5 --b 3:
import {
CliCommandProcessor,
CliCommandParameterDescriptor,
CliProcessCommand,
} from '@qodalis/cli-server-node';
class MathAddProcessor extends CliCommandProcessor {
command = 'add';
description = 'Adds two numbers';
parameters = [
new CliCommandParameterDescriptor('a', 'First number', true, 'number'),
new CliCommandParameterDescriptor('b', 'Second number', true, 'number'),
];
async handleAsync(command: CliProcessCommand): Promise<string> {
const a = Number(command.args?.a);
const b = Number(command.args?.b);
return `${a} + ${b} = ${a + b}`;
}
}
class MathMultiplyProcessor extends CliCommandProcessor {
command = 'multiply';
description = 'Multiplies two numbers';
parameters = [
new CliCommandParameterDescriptor('a', 'First number', true, 'number'),
new CliCommandParameterDescriptor('b', 'Second number', true, 'number'),
];
async handleAsync(command: CliProcessCommand): Promise<string> {
const a = Number(command.args?.a);
const b = Number(command.args?.b);
return `${a} * ${b} = ${a * b}`;
}
}
class MathProcessor extends CliCommandProcessor {
command = 'math';
description = 'Performs basic math operations';
allowUnlistedCommands = false;
processors = [
new MathAddProcessor(),
new MathMultiplyProcessor(),
];
async handleAsync(command: CliProcessCommand): Promise<string> {
return 'Usage: math add|multiply --a <number> --b <number>';
}
}Modules group related command processors into a reusable unit. Implement ICliModule (or extend the CliModule base class) to bundle processors under a single name and version.
import { CliModule, CliCommandProcessor, CliProcessCommand, ICliCommandProcessor } from '@qodalis/cli-server-node';
class WeatherCurrentProcessor extends CliCommandProcessor {
command = 'current';
description = 'Shows current weather conditions';
async handleAsync(command: CliProcessCommand): Promise<string> {
return `Weather: Sunny, 22°C`;
}
}
class WeatherForecastProcessor extends CliCommandProcessor {
command = 'forecast';
description = 'Shows a 3-day weather forecast';
async handleAsync(command: CliProcessCommand): Promise<string> {
return `Forecast: Sunny for 3 days`;
}
}
class CliWeatherCommandProcessor extends CliCommandProcessor {
command = 'weather';
description = 'Shows weather information for a location';
processors: ICliCommandProcessor[] = [
new WeatherCurrentProcessor(),
new WeatherForecastProcessor(),
];
async handleAsync(command: CliProcessCommand): Promise<string> {
return `Weather: Sunny, 22°C`;
}
}
export class WeatherModule extends CliModule {
name = 'weather';
version = '1.0.0';
description = 'Provides weather information commands';
processors: ICliCommandProcessor[] = [new CliWeatherCommandProcessor()];
}const { app, eventSocketManager } = createCliServer({
configure: (builder) => {
builder.addModule(new WeatherModule());
},
});addModule() iterates over the module's processors and registers each one, just like calling addProcessor() for each individually.
| Member | Type | Description |
|---|---|---|
name |
string |
Unique module identifier |
version |
string |
Module version |
description |
string |
Short description |
author |
ICliCommandAuthor |
Author metadata (defaults to library author) |
processors |
ICliCommandProcessor[] |
Command processors provided by the module |
The repository includes a weather module under plugins/weather/ as a reference implementation. It registers a weather command with current and forecast sub-commands, using the wttr.in API:
weather # Shows current weather (default: London)
weather current London # Current conditions for London
weather forecast --location Paris # 3-day forecast for Paris
Every processor receives a CliProcessCommand with the parsed command input:
| Property | Type | Description |
|---|---|---|
command |
string |
Command name (e.g., "time") |
value |
string | undefined |
Positional argument (e.g., "hello" in echo hello) |
args |
Record<string, any> |
Named parameters (e.g., --format "HH:mm") |
chainCommands |
string[] |
Sub-command chain (e.g., ["add"] in math add) |
rawCommand |
string |
Original unprocessed input |
data |
any |
Arbitrary data payload from the client |
The server exposes versioned endpoints:
| Method | Path | Description |
|---|---|---|
| GET | /api/qcli/versions |
Version discovery (supported versions, preferred version) |
| GET | /api/v1/qcli/version |
V1 server version |
| GET | /api/v1/qcli/commands |
V1 commands (all processors) |
| POST | /api/v1/qcli/execute |
V1 execute |
| POST | /api/v1/qcli/execute/stream |
V1 SSE streaming execute |
| WS | /ws/qcli/events |
WebSocket events (also /ws/v1/qcli/events) |
| WS | /ws/v1/qcli/shell |
Interactive PTY shell session |
CliCommandProcessor provides these members:
| Member | Type | Default | Description |
|---|---|---|---|
command |
string |
(required) | Command name |
description |
string |
(required) | Help text shown to users |
handleAsync |
method | (required) | Execution logic |
parameters |
ICliCommandParameterDescriptor[] |
undefined |
Declared parameters |
processors |
ICliCommandProcessor[] |
undefined |
Sub-commands |
allowUnlistedCommands |
boolean |
undefined |
Accept sub-commands not in processors |
valueRequired |
boolean |
undefined |
Require a positional value |
version |
string |
'1.0.0' |
Processor version string |
author |
ICliCommandAuthor |
default author | Author metadata (name, email) |
interface CliServerOptions {
basePath?: string; // API base path (default: '/api/qcli')
cors?: boolean | cors.CorsOptions; // CORS config (default: true)
configure?: (builder: CliBuilder) => void; // Processor registration
}createCliServer() returns:
{
app: Express; // Configured Express app
registry: CliCommandRegistry; // Processor registry
builder: CliBuilder; // Registration builder
executor: CliCommandExecutorService; // Command executor
eventSocketManager: CliEventSocketManager; // WebSocket event manager
logSocketManager: CliLogSocketManager; // WebSocket log manager
mountPlugin(plugin: MountablePlugin): void; // Mount a plugin with its routes
}All types are exported from the package root for TypeScript consumers:
// Abstractions (for creating custom processors and modules)
import {
ICliCommandProcessor,
CliCommandProcessor,
ICliCommandParameterDescriptor,
CliCommandParameterDescriptor,
CliProcessCommand,
ICliCommandAuthor,
CliCommandAuthor,
ICliModule,
CliModule,
} from '@qodalis/cli-server-node';
// Models
import {
CliServerResponse,
CliServerOutput,
CliServerCommandDescriptor,
} from '@qodalis/cli-server-node';
// Services (for advanced integration)
import {
ICliCommandRegistry,
CliCommandRegistry,
ICliCommandExecutorService,
CliCommandExecutorService,
ICliResponseBuilder,
CliResponseBuilder,
CliEventSocketManager,
} from '@qodalis/cli-server-node';
// Factory
import {
createCliServer,
CliServerOptions,
} from '@qodalis/cli-server-node';The server includes a pluggable file storage system exposed at /api/qcli/fs/*. Enable it with setFileStorageProvider() and choose a storage backend.
| Method | Path | Description |
|---|---|---|
| GET | /api/qcli/fs/ls?path=/ |
List directory contents |
| GET | /api/qcli/fs/cat?path=/file.txt |
Read file content |
| GET | /api/qcli/fs/stat?path=/file.txt |
File/directory metadata |
| GET | /api/qcli/fs/download?path=/file.txt |
Download file |
| POST | /api/qcli/fs/upload |
Upload file (multipart) |
| POST | /api/qcli/fs/mkdir |
Create directory |
| DELETE | /api/qcli/fs/rm?path=/file.txt |
Delete file or directory |
import { InMemoryFileStorageProvider, OsFileStorageProvider } from '@qodalis/cli-server-plugin-filesystem';
import { JsonFileStorageProvider } from '@qodalis/cli-server-plugin-filesystem-json';
import { SqliteFileStorageProvider } from '@qodalis/cli-server-plugin-filesystem-sqlite';
import { S3FileStorageProvider } from '@qodalis/cli-server-plugin-filesystem-s3';
const { app, eventSocketManager } = createCliServer({
configure: (builder) => {
// In-memory (default) — files lost on restart
builder.setFileStorageProvider(new InMemoryFileStorageProvider());
// OS filesystem
builder.setFileStorageProvider(new OsFileStorageProvider());
// JSON file — persists to a single JSON file
builder.setFileStorageProvider(
new JsonFileStorageProvider({ filePath: './data/files.json' }),
);
// SQLite — persists to a SQLite database
builder.setFileStorageProvider(
new SqliteFileStorageProvider({ dbPath: './data/files.db' }),
);
// Amazon S3
builder.setFileStorageProvider(
new S3FileStorageProvider({
bucket: 'my-cli-files',
region: 'us-east-1',
prefix: 'uploads/',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
}),
);
},
});Implement IFileStorageProvider to add your own backend:
import { IFileStorageProvider, FileEntry, FileStat } from '@qodalis/cli-server-node';
class MyProvider implements IFileStorageProvider {
readonly name = 'my-provider';
async list(path: string): Promise<FileEntry[]> { /* ... */ }
async readFile(path: string): Promise<string> { /* ... */ }
async writeFile(path: string, content: string | Buffer): Promise<void> { /* ... */ }
async stat(path: string): Promise<FileStat> { /* ... */ }
async mkdir(path: string, recursive?: boolean): Promise<void> { /* ... */ }
async remove(path: string, recursive?: boolean): Promise<void> { /* ... */ }
async copy(src: string, dest: string): Promise<void> { /* ... */ }
async move(src: string, dest: string): Promise<void> { /* ... */ }
async exists(path: string): Promise<boolean> { /* ... */ }
async getDownloadStream(path: string): Promise<Readable> { /* ... */ }
async uploadFile(path: string, content: Buffer): Promise<void> { /* ... */ }
}
builder.setFileStorageProvider(new MyProvider());The Data Explorer plugin provides interactive, full-screen REPL access to data sources. It exposes a provider-based API where each data source type (SQL, MongoDB, etc.) is a separate plugin implementing IDataExplorerProvider.
| Method | Path | Description |
|---|---|---|
| GET | /api/qcli/data-explorer/sources |
List registered data sources with metadata |
| POST | /api/qcli/data-explorer/execute |
Execute a query against a named source |
import { SqlDataExplorerProvider } from '@qodalis/cli-server-plugin-data-explorer-sql';
import { DataExplorerLanguage, DataExplorerOutputFormat } from '@qodalis/cli-server-abstractions';
const { app } = createCliServer({
configure: (builder) => {
builder.addDataExplorerProvider(
new SqlDataExplorerProvider({ type: 'sqlite', filename: './data.db' }),
{
name: 'my-database',
description: 'Application database',
language: DataExplorerLanguage.Sql,
defaultOutputFormat: DataExplorerOutputFormat.Table,
timeout: 30000,
maxRows: 1000,
templates: [
{
name: 'list_tables',
query: "SELECT name FROM sqlite_master WHERE type='table'",
description: 'List all tables',
},
],
},
);
},
});import { MongoDataExplorerProvider } from '@qodalis/cli-server-plugin-data-explorer-mongo';
builder.addDataExplorerProvider(
new MongoDataExplorerProvider({
connectionString: 'mongodb://localhost:27017',
database: 'myapp',
}),
{
name: 'mongo-primary',
description: 'Primary MongoDB database',
language: DataExplorerLanguage.Json,
defaultOutputFormat: DataExplorerOutputFormat.Json,
templates: [
{ name: 'show_collections', query: 'show collections', description: 'List all collections' },
{ name: 'find_users', query: 'db.users.find({})', description: 'Find all users' },
],
},
);Supported MongoDB operations: db.collection.find({...}), findOne, aggregate([...]), insertOne, insertMany, updateOne, updateMany, deleteOne, deleteMany, countDocuments, distinct. Convenience commands: show collections, show dbs.
Implement IDataExplorerProvider to add your own data source:
import { IDataExplorerProvider, DataExplorerExecutionContext, DataExplorerResult } from '@qodalis/cli-server-abstractions';
class MyProvider implements IDataExplorerProvider {
async executeAsync(context: DataExplorerExecutionContext): Promise<DataExplorerResult> {
// context.query — the user's query string
// context.parameters — key-value parameters
// context.options — provider options (name, language, etc.)
return {
success: true,
source: context.options.name,
language: context.options.language,
defaultOutputFormat: context.options.defaultOutputFormat,
executionTime: 0,
columns: ['id', 'name'], // null for document-oriented results
rows: [[1, 'Alice'], [2, 'Bob']], // objects when columns is null
rowCount: 2,
truncated: false,
error: null,
};
}
}
builder.addDataExplorerProvider(new MyProvider(), { name: 'custom', ... });The same provider class can be registered multiple times with different configurations (e.g., two databases with different names).
The AWS plugin adds commands for managing AWS resources (S3, EC2, Lambda, CloudWatch, SNS, SQS, IAM, DynamoDB, ECS) directly from the CLI. It uses AWS SDK v3 and supports the full credential chain.
import { AwsModule } from '@qodalis/cli-server-plugin-aws';
const { app } = createCliServer({
configure: (builder) => {
builder.addModule(new AwsModule());
},
});The plugin resolves credentials in this order:
- CLI configure:
aws configure set --key <KEY> --secret <SECRET> --region <REGION> - Environment variables:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION - AWS profiles:
aws configure set --profile <name> - IAM roles: Automatic on EC2/ECS/Lambda
Verify connectivity with aws status.
| Service | Commands |
|---|---|
| configure | aws configure set, aws configure get, aws configure profiles |
| status | aws status — STS GetCallerIdentity connectivity check |
| S3 | aws s3 ls, aws s3 cp, aws s3 rm, aws s3 mb, aws s3 rb, aws s3 presign |
| EC2 | aws ec2 list, aws ec2 describe, aws ec2 start, aws ec2 stop, aws ec2 reboot, aws ec2 sg list |
| Lambda | aws lambda list, aws lambda invoke, aws lambda logs |
| CloudWatch | aws cloudwatch alarms, aws cloudwatch logs, aws cloudwatch metrics |
| SNS | aws sns topics, aws sns publish, aws sns subscriptions |
| SQS | aws sqs list, aws sqs send, aws sqs receive, aws sqs purge |
| IAM | aws iam users, aws iam roles, aws iam policies |
| DynamoDB | aws dynamodb tables, aws dynamodb describe, aws dynamodb scan, aws dynamodb query |
| ECS | aws ecs clusters, aws ecs services, aws ecs tasks |
All commands support --region (-r) for region override and --output (-o) for format selection (table, json, text). Destructive commands support --dry-run.
See plugins/aws/README.md for the full command reference.
These processors ship with the library and are included in the standalone server:
| Command | Description |
|---|---|
echo |
Echoes input text |
status |
Server status (uptime, OS info) |
system |
Detailed system information (memory, CPU, uptime) |
http |
HTTP request operations |
hash |
Hash computation (MD5, SHA1, SHA256, SHA512) |
base64 |
Base64 encode/decode (sub-commands) |
docker run -p 8047:8047 ghcr.io/qodalis-solutions/cli-server-nodecd demo
npm install
npm start
# Server starts on http://localhost:8047npm test # Run test suite (Vitest)
npm run test:watch # Watch modepackages/
abstractions/ # @qodalis/cli-server-abstractions (zero-dep)
src/
cli-command-processor.ts # ICliCommandProcessor interface & base class
cli-module.ts # ICliModule interface & base class
cli-process-command.ts # Command input model
cli-command-parameter-descriptor.ts # Parameter declaration
cli-command-author.ts # Author metadata
plugins/
admin/ # Admin dashboard plugin (auth, status, config, logs)
aws/ # AWS cloud services (S3, EC2, Lambda, CloudWatch, etc.)
data-explorer/ # Core data explorer abstraction
data-explorer-sql/ # SQL data explorer provider (SQLite)
data-explorer-mongo/ # MongoDB data explorer provider
data-explorer-postgres/ # PostgreSQL data explorer provider
data-explorer-mysql/ # MySQL data explorer provider
data-explorer-mssql/ # MS SQL data explorer provider
data-explorer-redis/ # Redis data explorer provider
data-explorer-elasticsearch/ # Elasticsearch data explorer provider
filesystem/ # Core file storage abstraction (IFileStorageProvider, InMemory, OS)
filesystem-json/ # JSON file persistence provider
filesystem-sqlite/ # SQLite persistence provider (better-sqlite3)
filesystem-s3/ # Amazon S3 storage provider (@aws-sdk/client-s3)
jobs/ # Job scheduling plugin
weather/ # Weather module (example plugin)
src/
abstractions/ # Re-exports from @qodalis/cli-server-abstractions
models/
cli-server-response.ts # Response wrapper (exitCode + outputs)
cli-server-output.ts # Output types (text, table, list, json, key-value)
cli-server-command-descriptor.ts # Command metadata for /commands endpoint
services/
cli-command-registry.ts # Processor registry and lookup
cli-command-executor-service.ts # Command execution pipeline
cli-response-builder.ts # Structured output builder
cli-event-socket-manager.ts # WebSocket event broadcasting
cli-log-socket-manager.ts # WebSocket log streaming
cli-shell-session-manager.ts # PTY shell session management
controllers/
cli-controller.ts # V1 REST API (/api/v1/qcli)
cli-version-controller.ts # Version discovery (/api/qcli/versions)
extensions/
cli-builder.ts # Fluent registration API (addProcessor, addModule)
processors/ # Built-in processors
create-cli-server.ts # Factory function
server.ts # Standalone CLI entry point
index.ts # Package exports
demo/ # Demo app with sample processors
MIT