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

@supernal/universal-command

v0.1.0

Published

Define commands once, deploy to CLI, API, and MCP automatically

Readme

@supernal/universal-command

npm version npm downloads License: MIT TypeScript

Universal Command Abstraction for CLI, API, and MCP

Define your command once, deploy it everywhere.


The Problem

Building a command-line tool with API and AI integration requires maintaining three separate implementations:

// CLI (Commander.js)
program.command('requirement list')
  .option('--status <status>')
  .action(async (options) => { /* implementation */ });

// API (Next.js)
export async function GET(request: NextRequest) {
  const status = request.nextUrl.searchParams.get('status');
  // duplicate implementation
}

// MCP (Model Context Protocol)
{
  name: 'requirement_list',
  inputSchema: { /* duplicate schema */ },
  handler: async (args) => { /* duplicate implementation */ }
}

Result: 3x maintenance burden, drift risk, duplicated logic.


The Solution

Define once, deploy everywhere:

import { UniversalCommand } from '@supernal/universal-command';

export const requirementList = new UniversalCommand({
  name: 'requirement list',
  description: 'List all requirements',

  input: {
    parameters: [
      {
        name: 'status',
        type: 'string',
        description: 'Filter by status',
        enum: ['draft', 'in-progress', 'done'],
      },
    ],
  },

  // Single handler works everywhere
  handler: async (args, context) => {
    return await fetchRequirements({ status: args.status });
  },

  // Optional: CLI-specific formatting
  cli: {
    format: (results) => results.map((r) => `${r.id}: ${r.title}`).join('\n'),
  },
});

Deploy automatically:

// Generate CLI
const program = requirementList.toCLI();

// Generate Next.js API
export const GET = requirementList.toNextAPI();

// Generate MCP tool
const mcpTool = requirementList.toMCP();

Features

🎯 Single Source of Truth

  • Define command logic once
  • CLI, API, MCP stay in sync automatically
  • No duplication, no drift

🚀 Zero Overhead

  • Thin wrappers around your handler
  • No performance penalty
  • Direct function calls

🔧 Framework Agnostic

  • Bring your own handler implementation
  • Works with any framework
  • No vendor lock-in

📦 Interface-Specific Overrides

  • CLI: Custom formatting, progress bars
  • API: Caching, rate limiting, auth
  • MCP: Resource links, capabilities

🛡️ Type Safe

  • Full TypeScript support
  • Input/output validation
  • Compile-time error detection

🧪 Testable

  • Test handler once, works everywhere
  • Mock context for testing
  • Integration test helpers

Installation

npm install @supernal/universal-command

Peer dependencies:

# For CLI generation
npm install commander

# For Next.js API generation
npm install next

# For MCP generation
npm install @modelcontextprotocol/sdk

Quick Start

1. Define Your Command

// commands/user-create.ts
import { UniversalCommand } from '@supernal/universal-command';

export const userCreate = new UniversalCommand({
  name: 'user create',
  description: 'Create a new user',
  category: 'users',

  input: {
    parameters: [
      {
        name: 'name',
        type: 'string',
        description: 'User name',
        required: true,
      },
      {
        name: 'email',
        type: 'string',
        description: 'User email',
        required: true,
      },
      {
        name: 'role',
        type: 'string',
        description: 'User role',
        default: 'user',
        enum: ['user', 'admin'],
      },
    ],
  },

  output: {
    type: 'json',
    schema: {
      type: 'object',
      properties: {
        id: { type: 'string' },
        name: { type: 'string' },
        email: { type: 'string' },
      },
    },
  },

  handler: async (args, context) => {
    // Your implementation
    const user = await createUser({
      name: args.name,
      email: args.email,
      role: args.role,
    });

    return user;
  },
});

2. Generate CLI

// cli.ts
import { Command } from 'commander';
import { userCreate } from './commands/user-create';

const program = new Command();
program.addCommand(userCreate.toCLI());
program.parse();
$ mycli user create --name "Alice" --email "[email protected]"
Created user: [email protected] (ID: user-123)

3. Generate Next.js API

// app/api/users/create/route.ts
import { userCreate } from '@/commands/user-create';

export const POST = userCreate.toNextAPI();
$ curl -X POST https://api.example.com/users/create \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"[email protected]"}'

{"id":"user-123","name":"Alice","email":"[email protected]"}

4. Generate MCP Tool

// mcp-server.ts
import { Server } from '@modelcontextprotocol/sdk/server';
import { userCreate } from './commands/user-create';

const server = new Server({ name: 'my-mcp-server' });

server.setRequestHandler('tools/list', async () => ({
  tools: [userCreate.toMCP()],
}));

server.setRequestHandler('tools/call', async (request) => {
  if (request.params.name === 'user_create') {
    return await userCreate.executeMCP(request.params.arguments);
  }
});

Advanced Usage

Interface-Specific Options

new UniversalCommand({
  name: 'data export',
  // ... base definition ...

  // CLI-specific
  cli: {
    format: (data) => {
      // Custom formatting for terminal
      return JSON.stringify(data, null, 2);
    },
    streaming: true, // Support streaming output
    progress: true, // Show progress bar
  },

  // API-specific
  api: {
    method: 'GET', // Default: infer from handler
    cacheControl: {
      maxAge: 300,
      staleWhileRevalidate: 60,
    },
    rateLimit: {
      requests: 100,
      window: '1m',
    },
    auth: {
      required: true,
      roles: ['admin'],
    },
  },

  // MCP-specific
  mcp: {
    resourceLinks: ['export://results'],
    capabilities: ['streaming'],
  },
});

Execution Context

The context parameter provides interface-specific information:

handler: async (args, context) => {
  switch (context.interface) {
    case 'cli':
      // CLI-specific logic
      console.log('Running from CLI');
      break;

    case 'api':
      // Access request object
      const userId = context.request.headers.get('x-user-id');
      break;

    case 'mcp':
      // MCP-specific logic
      break;
  }

  return result;
};

Validation

Input validation is automatic based on parameter definitions:

input: {
  parameters: [
    {
      name: 'age',
      type: 'number',
      required: true,
      min: 0,
      max: 120,
    },
    {
      name: 'email',
      type: 'string',
      required: true,
      pattern: '^[^@]+@[^@]+\\.[^@]+$',
    },
  ];
}

Error Handling

handler: async (args, context) => {
  if (!isValid(args.email)) {
    throw new CommandError('Invalid email address', { code: 'INVALID_EMAIL', status: 400 });
  }

  return result;
};

Errors are automatically formatted for each interface:

  • CLI: Formatted error message with exit code
  • API: JSON error response with status code
  • MCP: MCP error format

Runtime Registration (No Code Generation)

For the simplest setup, use RuntimeServer to register commands and serve them directly:

import { createRuntimeServer } from '@supernal/universal-command';

const server = createRuntimeServer();

// Register commands
server.register(userCreate);
server.register(userList);
server.register(userDelete);

// Or define inline
server.command({
  name: 'health check',
  description: 'Check system health',
  input: { parameters: [] },
  output: { type: 'json' },
  handler: async () => ({ status: 'ok' }),
});

Serve as Next.js API

// app/api/[...path]/route.ts
import { createServer } from '@/lib/commands';

const server = createServer();
const handlers = server.getNextHandlers();

export const GET = handlers.GET;
export const POST = handlers.POST;
export const PUT = handlers.PUT;
export const DELETE = handlers.DELETE;

Serve as Express API

import express from 'express';
import { createServer } from './commands';

const app = express();
app.use(express.json());
app.use('/api', createServer().getExpressRouter());
app.listen(3000);

Serve as MCP Server

import { createServer } from './commands';

const server = createServer();
await server.startMCP({
  name: 'my-mcp-server',
  version: '1.0.0',
  transport: 'stdio',
});

List All Commands

for (const cmd of server.listCommands()) {
  console.log(`${cmd.name} → API: ${cmd.apiPath}, MCP: ${cmd.mcpTool}`);
}

Registry Pattern

For multiple commands, use a registry:

// commands/index.ts
import { CommandRegistry } from '@supernal/universal-command';
import { userCreate } from './user-create';
import { userList } from './user-list';
import { userDelete } from './user-delete';

export const registry = new CommandRegistry();

registry.register(userCreate);
registry.register(userList);
registry.register(userDelete);

Generate CLI Program

import { Command } from 'commander';
import { registry } from './commands';

const program = new Command();

for (const cmd of registry.getAll()) {
  program.addCommand(cmd.toCLI());
}

program.parse();

Generate API Routes (Build-time)

// scripts/generate-routes.ts
import { registry } from '../commands';
import { generateNextRoutes } from '@supernal/universal-command/codegen';

await generateNextRoutes(registry, {
  outputDir: 'app/api',
  typescript: true,
});

Generates:

app/api/
  users/
    create/
      route.ts  (auto-generated)
    list/
      route.ts  (auto-generated)
    delete/
      route.ts  (auto-generated)

Generate MCP Server

import { Server } from '@modelcontextprotocol/sdk/server';
import { registry } from './commands';
import { createMCPServer } from '@supernal/universal-command/mcp';

const server = createMCPServer(registry, {
  name: 'my-mcp-server',
  version: '1.0.0',
});

server.connect(transport);

Code Generation

CLI Generation (Runtime)

const cli = command.toCLI();
// Returns: Commander.Command

API Generation (Build-time or Runtime)

// Runtime (Next.js App Router)
export const GET = command.toNextAPI();

// Or build-time generation
import { generateNextRoutes } from '@supernal/universal-command/codegen';
await generateNextRoutes(registry, { outputDir: 'app/api' });

MCP Generation (Runtime)

const mcpTool = command.toMCP();
// Returns: MCPToolDefinition

Testing

Test the Handler Once

import { userCreate } from './user-create';

test('creates user', async () => {
  const result = await userCreate.execute(
    { name: 'Alice', email: '[email protected]' },
    { interface: 'test' } // Test context
  );

  expect(result.name).toBe('Alice');
});

Integration Testing Helpers

import { testCLI, testAPI, testMCP } from '@supernal/universal-command/testing';

test('CLI integration', async () => {
  const output = await testCLI(userCreate, {
    args: ['--name', 'Alice', '--email', '[email protected]'],
  });

  expect(output).toContain('Created user');
});

test('API integration', async () => {
  const response = await testAPI(userCreate, {
    method: 'POST',
    body: { name: 'Alice', email: '[email protected]' },
  });

  expect(response.status).toBe(200);
});

test('MCP integration', async () => {
  const result = await testMCP(userCreate, {
    arguments: { name: 'Alice', email: '[email protected]' },
  });

  expect(result.content[0].text).toContain('Alice');
});

Architecture

┌─────────────────────────────────────────────────────┐
│          UniversalCommand Definition                │
│  • name, description, category                      │
│  • input schema (parameters)                        │
│  • output schema                                     │
│  • handler (core logic)                             │
│  • interface overrides (cli, api, mcp)              │
└────────────────┬────────────────────────────────────┘
                 │
        ┌────────┼────────┐
        │        │        │
    ┌───▼──┐ ┌──▼───┐ ┌──▼───┐
    │ CLI  │ │ API  │ │ MCP  │
    │ Gen  │ │ Gen  │ │ Gen  │
    └──┬───┘ └──┬───┘ └──┬───┘
       │        │        │
    ┌──▼──┐  ┌──▼──┐  ┌──▼──┐
    │ CLI │  │ API │  │ MCP │
    │ App │  │ App │  │ App │
    └─────┘  └─────┘  └─────┘

API Reference

UniversalCommand

class UniversalCommand<TInput, TOutput> {
  constructor(schema: CommandSchema<TInput, TOutput>);

  // Execute command
  execute(args: TInput, context: ExecutionContext): Promise<TOutput>;

  // Generate interfaces
  toCLI(): Command;
  toNextAPI(): NextAPIRoute;
  toExpressAPI(): ExpressRoute;
  toMCP(): MCPToolDefinition;

  // Utilities
  validateArgs(args: unknown): ValidationResult<TInput>;
  getAPIRoutePath(): string;
  getMCPToolName(): string;
}

CommandSchema

interface CommandSchema<TInput, TOutput> {
  name: string;
  description: string;
  category?: string;

  input: {
    parameters: Parameter[];
  };

  output: {
    type: 'json' | 'text' | 'stream';
    schema?: JSONSchema;
  };

  handler: (args: TInput, context: ExecutionContext) => Promise<TOutput>;

  // Interface-specific options
  cli?: CLIOptions;
  api?: APIOptions;
  mcp?: MCPOptions;
}

Parameter

interface Parameter {
  name: string;
  type: 'string' | 'number' | 'boolean' | 'array' | 'object';
  description: string;
  required?: boolean;
  default?: any;

  // Validation
  enum?: any[];
  min?: number;
  max?: number;
  pattern?: string;
  items?: Parameter; // For array type
}

ExecutionContext

interface ExecutionContext {
  interface: 'cli' | 'api' | 'mcp' | 'test';
  projectRoot?: string;

  // API-specific
  request?: NextRequest | Request;

  // CLI-specific
  stdout?: NodeJS.WriteStream;
  stderr?: NodeJS.WriteStream;
}

Comparison

| Feature | Universal Command | Manual Implementation | | -------------------- | -------------------- | ------------------------- | | Maintenance | Define once | Define 3x (CLI/API/MCP) | | Drift risk | None (single source) | High (separate codebases) | | Type safety | Full TypeScript | Varies by interface | | Validation | Automatic | Manual per interface | | Testing | Test once | Test 3x | | Documentation | Auto-generated | Manual | | Feature velocity | High (add once) | Low (add 3x) |


Real-World Examples

GitHub CLI + API + MCP

// Single definition
const issueCreate = new UniversalCommand({
  name: 'issue create',
  description: 'Create a GitHub issue',

  input: {
    parameters: [
      { name: 'title', type: 'string', required: true },
      { name: 'body', type: 'string' },
      { name: 'labels', type: 'array', items: { type: 'string' } },
    ],
  },

  handler: async (args) => {
    return await octokit.issues.create({
      owner: 'org',
      repo: 'repo',
      title: args.title,
      body: args.body,
      labels: args.labels,
    });
  },
});

// Deploy everywhere
const cli = issueCreate.toCLI(); // gh issue create
const api = issueCreate.toNextAPI(); // POST /api/issues
const mcp = issueCreate.toMCP(); // issue_create tool

Roadmap

v1.0 (Current)

  • ✅ Core abstraction
  • ✅ CLI generation (Commander.js)
  • ✅ Next.js API generation
  • ✅ MCP generation
  • ✅ TypeScript support

v1.1 (Planned)

  • 🔄 Express.js API generation
  • 🔄 Streaming support
  • 🔄 Progress indicators
  • 🔄 Auto-generated docs

v2.0 (Future)

  • 📋 Hono API generation
  • 📋 FastAPI generation (Python)
  • 📋 gRPC generation
  • 📋 GraphQL generation

Development Workflow

This project uses the Supernal Coding workflow system for requirement tracking and test traceability.

Requirements

All features are documented as requirements in .supernal/requirements/:

| Requirement | Description | Tests | | ------------------------------------------------------------------------- | ---------------------- | ----- | | REQ-UC-001 | Universal Command Core | 20 | | REQ-UC-002 | Command Registry | 16 | | REQ-UC-003 | Code Generators | 18 | | REQ-UC-004 | Scope Registry | 30 | | REQ-UC-005 | Testing Utilities | 9 |

Git Hooks

Pre-commit hooks ensure code quality:

  • Lint-staged: Format and lint staged files
  • Type check: TypeScript compilation
  • Tests: All 93 tests must pass
# Hooks run automatically on commit
git commit -m "feat: add new feature"

# Run manually
npm run type-check && npm run test:ci

Publishing to npm

For Maintainers

# Ensure you're logged in to npm
npm login

# Run tests and build
pnpm test:ci
pnpm build

# Publish (prepublishOnly will run automatically)
npm publish

# Or publish a beta version
npm publish --tag beta

Version Bumping

# Patch release (0.1.0 -> 0.1.1)
npm version patch

# Minor release (0.1.0 -> 0.2.0)
npm version minor

# Major release (0.1.0 -> 1.0.0)
npm version major

Contributing

Contributions welcome! See CONTRIBUTING.md


License

MIT License - See LICENSE


Credits

Developed by Supernal Intelligence as part of the Supernal Coding project.

Inspired by the need to maintain CLI, API, and MCP interfaces for AI-assisted development tools.


Related Projects


Support