unicommand
v0.0.4
Published
Shared command framework for TypeScript CLIs and MCP servers
Readme
🎺 Overview
Unicommand is a small TypeScript command framework for packages that expose CLIs and MCP servers from the same command definitions.
❓ Motivation
I keep building local tools that need the same boring pieces: command metadata, CLI help, completions, update commands, dev shims, MCP tools, package metadata, and generated README command docs.
Why? Repeating that per package makes command behavior drift. Unicommand centralizes the contract so apps only define commands.
⭐ Features
- One
defineCommandcontract for CLI and MCP metadata. - Package-aware CLI runner using
package.jsonbin, version, name, and description. - Built-in
completionandupdatecommands. - Local dev command shims with active dev-bin detection.
- README command docs generated from command definitions.
- MCP command server helpers for tools and resources.
🚀 Quick Start
Install the package:
pnpm add unicommandDefine a command:
import { createCommandAdapters, defineCommand, z } from 'unicommand'; const metadata = defineCommand({ name: 'hello', description: 'Say hello', arguments: [{ synopsis: '<name>', description: 'Name to greet' }], outputSchema: z.string(), }); export const helloCommand = createCommandAdapters({ metadata, handler: ({ name }: { name: string }) => `Hello, ${name}`, });Create a CLI runner:
#!/usr/bin/env node import { createPackageCommandCliRunner } from 'unicommand'; const cli = createPackageCommandCliRunner({ importMetaUrl: import.meta.url, }); if (cli.isDirectRun()) { process.exit(await cli.runCli()); }
🧰 Usage
import {
createCommandAdapters,
createPackageCommandCliRunner,
defineCommand,
generateReadmeCommandDocs,
installPackageDevCommands,
startPackageCommandMcpServer,
z,
} from 'unicommand';Common entry points:
defineCommanddescribes a command once.createCommandAdaptersexposes CLI and MCP adapters from one handler.createPackageCommandCliRunnerbuilds a package-aware CLI.startPackageCommandMcpServerstarts an MCP server from command exports.installPackageDevCommandsinstalls local dev command shims.generateReadmeCommandDocsupdates README command blocks.
Add markers to a README:
<!-- <DYNFIELD:COMMANDS> -->
<!-- </DYNFIELD:COMMANDS> -->Then generate docs:
import { join } from 'node:path';
import { generateReadmeCommandDocs } from 'unicommand';
await generateReadmeCommandDocs({
binName: 'my-cli',
commandsDir: join(process.cwd(), 'src', 'commands'),
});import { installPackageDevCommands } from 'unicommand';
installPackageDevCommands(import.meta.url, {
commandNames: ['my-clid'],
});The generated shim runs src/cli.ts, sets the package program-name env var, and lets update detect dev mode.
🛠 Development
Install dependencies:
pnpm installRun checks:
pnpm check:fix
pnpm typecheck
pnpm build
pnpm knipCreate a changeset before publishing changes:
pnpm changeset