npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qodalis/cli-server-node

v1.0.0-beta.4

Published

Node.js CLI server for Qodalis CLI ecosystem — use as a library in existing Express apps or run standalone

Readme

Qodalis CLI Server (Node.js)

A Node.js CLI server framework for the Qodalis CLI ecosystem. Build custom server-side commands that integrate with the Qodalis web terminal.

Installation

npm install @qodalis/cli-server-node

The package exports all types, interfaces, base classes, and built-in processors. TypeScript declarations are included.

Plugin Authors

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-abstractions

This gives you CliCommandProcessor, CliProcessCommand, CliCommandParameterDescriptor, and all other base types with zero dependencies. See @qodalis/cli-server-abstractions for details.

Quick Start

As a Library

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);
});

As a Standalone Server

npx qodalis-cli-server

Or with environment variables:

PORT=9000 npx qodalis-cli-server

Creating Custom Command Processors

Simple Command

Extend 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
    },
});

Command with Parameters

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'.

Sub-commands

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

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.

Defining a Module

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()];
}

Registering a Module

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.

ICliModule Interface

| 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 |

Example: Weather 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

Command Input

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 |

API Versioning

Processors declare which API version they target. The default is version 1.

class DashboardProcessor extends CliCommandProcessor {
    command = 'dashboard';
    description = 'Server dashboard (v2 only)';
    apiVersion = 2;

    async handleAsync(command: CliProcessCommand): Promise<string> {
        return 'Dashboard data...';
    }
}

The server exposes versioned endpoints:

| Method | Path | Description | |--------|------|-------------| | GET | /api/cli/versions | Version discovery (supported versions, preferred version) | | GET | /api/v1/cli/version | V1 server version | | GET | /api/v1/cli/commands | V1 commands (all processors) | | POST | /api/v1/cli/execute | V1 execute | | GET | /api/v2/cli/version | V2 server version | | GET | /api/v2/cli/commands | V2 commands (only apiVersion >= 2) | | POST | /api/v2/cli/execute | V2 execute | | WS | /ws/cli/events | WebSocket events (also /ws/v1/cli/events, /ws/v2/cli/events) |

The Qodalis CLI client auto-negotiates the highest mutually supported version via the /api/cli/versions discovery endpoint.

Processor Base Class Reference

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 | | apiVersion | number | 1 | Target API version | | author | ICliCommandAuthor | default author | Author metadata (name, email) |

Server Options

interface CliServerOptions {
    basePath?: string;                     // API base path (default: '/api/cli')
    cors?: boolean | cors.CorsOptions;     // CORS config (default: false)
    configure?: (builder: CliBuilder) => void;  // Processor registration
}

createCliServer() returns:

{
    app: Express;                          // Configured Express app
    registry: CliCommandRegistry;          // Processor registry
    builder: CliBuilder;                   // Registration builder
    eventSocketManager: CliEventSocketManager;  // WebSocket manager
}

Exported Types

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';

File Storage

The server includes a pluggable file storage system exposed at /api/cli/fs/*. Enable it with setFileStorageProvider() and choose a storage backend.

Filesystem API Endpoints

| Method | Path | Description | |--------|------|-------------| | GET | /api/cli/fs/ls?path=/ | List directory contents | | GET | /api/cli/fs/cat?path=/file.txt | Read file content | | GET | /api/cli/fs/stat?path=/file.txt | File/directory metadata | | GET | /api/cli/fs/download?path=/file.txt | Download file | | POST | /api/cli/fs/upload | Upload file (multipart) | | POST | /api/cli/fs/mkdir | Create directory | | DELETE | /api/cli/fs/rm?path=/file.txt | Delete file or directory |

Storage Providers

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!,
                },
            }),
        );
    },
});

Custom Provider

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());

Built-in Processors

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) | | uuid | UUID generation |

Docker

docker run -p 8047:8047 ghcr.io/qodalis-solutions/cli-server-node

Demo

cd demo
npm install
npm start
# Server starts on http://localhost:8047

Testing

npm test          # Run test suite (Vitest)
npm run test:watch  # Watch mode

Project Structure

packages/
  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/
  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)
  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
  controllers/
    cli-controller.ts                 # V1 REST API (/api/v1/cli)
    cli-controller-v2.ts              # V2 REST API (/api/v2/cli)
    cli-version-controller.ts         # Version discovery (/api/cli/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

License

MIT