@xpulse/cli β Component Spec
Status: APPROVED Β· Updated March 2026
Parent: GLOBAL_concept-ecosystem.md
ADR: COMP_cli_adr-001-command-registration.md
Principle
@xpulse/cli is a runner β not a command owner.
The package provides the xpulse entry point, the command parser, the
Command base class, Input/Output, and autodiscovery.
Which commands exist is decided by each xPart itself β by shipping
command classes under src/commands/.
1 xpulse doc:fetch chat -tag="v1.3.0" --dry-run -V 2 β 3 @xpulse/cli discovers node_modules/ @xpulse/*/ src/commands/ *.js 4 finds FetchCommand (name = 'doc:fetch' from configure()) 5 builds Input + Output, injects keepAlive 6 calls new FetchCommand().run(input, output) 7 β 8 FetchCommand.run() executes, returns exit code 9 β 10 @xpulse/cli process.exit (code)
Dependencies
1 @xpulse /cli2 βββ @xpulse /event β cli :keepalive (internal, xParts do not see this)
Command Syntax
1 xpulse namespace :area :command [arguments] [-option="value" ] [--flag] [-V] [-q]
Part
Description
Example
namespace
Domain of the responsible xPart
repo, doc, project
area
Sub-area (optional)
changelog, fetch
command
Action
generate, fetch, show
argument
Positional argument
chat
-option="value"
Named option
-tag="v1.3.0"
--flag
Boolean, camelCase
--dry-run β dryRun: true
Command Base Class (`src/command.js`)
xParts import Command and extend it in src/commands/.
A command describes itself in configure() and implements run().
It knows nothing about events, timeouts, or the CLI itself.
1 import { Command } from '@xpulse/cli/command' ;2 3 export class FetchCommand extends Command {4 5 configure ( ) { 6 this 7 .setName ('doc:fetch' ) 8 .setAlias ('df' ) 9 .setDescription ('Fetch docs via git archive' ) 10 .setHelp ('Fetches docs from an xPulse repo and stores them locally.' ) 11 .addArgument ('tool' , null , 'Name of the tool (e.g. chat)' ) 12 .addOption ('tag' , null , 'Git tag to fetch' ) 13 .addFlag ('dry-run' , false , 'Simulate only, write nothing' ); 14 } 15 16 async run (input, output ) { 17 const tool = input.getArgument ('tool' ); 18 const dryRun = input.getFlag ('dryRun' ); 19 20 while (!done) { 21 await doWork (); 22 this .keepAlive (); 23 } 24 25 output.success ('Done!' ); 26 return 0 ; 27 } 28 }
Fluent API
Method
Description
setName(name)
Command name β namespace:area:command
setAlias(alias)
Short name (e.g. df for doc:fetch)
setDescription(text)
Short description for xpulse --help
setHelp(text)
Longer text for xpulse --help <command>
addArgument(name, default, description)
Positional argument
addOption(name, default, description)
Named option (-key=value)
addFlag(name, default, description)
Boolean flag (--flag)
Exit Codes
Code
Meaning
0
Success
1
Error
2
Misuse β wrong or missing arguments
`keepAlive()`
Resets the timeout timer. Injected by @xpulse/cli before run().
No import of @xpulse/event required β the command abstracts this completely.
1 input.getArgument ('tool' ) 2 input.getOption ('tag' ) 3 input.hasOption ('tag' ) 4 input.getFlag ('dryRun' ) 5 input.isVerbose () 6 input.isQuiet () 7 input.isAnsi ()
Output (`src/output.js`)
Respects --no-ansi and --quiet automatically.
1 output.write ('...' ) 2 output.writeln ('...' ) 3 output.success ('...' ) 4 output.warn ('...' ) 5 output.error ('...' ) 6 output.table (headers, rows)
Global CLI Flags
Flag
Shortcut
Description
--help [command]
-h
CLI help or command help
--list
β
All command names, machine-readable (one per line)
--version
-v
Print version
--verbose
-V
Verbose output β input.isVerbose()
--quiet
-q
Errors only β input.isQuiet()
--no-ansi
β
No colour β input.isAnsi() = false
`--help` Behaviour
1 xpulse --help 2 xpulse --help doc:fetch 3
`--list` Behaviour
Machine-readable β one command name per line, no ANSI, no padding.
Autodiscovery
Search order on startup:
1 1 . node_modules/@xpulse/ */src/ commands/*.js β installed xParts2 2 . src/commands/ *.js β local project
Both paths are merged. The local project therefore behaves like an implicit
xPart β no symlink, no npm link required.
Timeout
Timer starts with run(). Fully reset by this.keepAlive().
No cli:done β run() resolves with exit code, @xpulse/cli terminates the process.
Flow
1 xpulse doc:fetch chat -tag="v1.3.0" --dry-run -V 2 β 3 @xpulse/cli starts 4 β discovers node_modules/@xpulse/ */src/ commands/*.js 5 β parses input β { name: 'doc:fetch' , args: { tag: 'v1.3.0' , dryRun: true }, 6 rawArguments: ['chat' ], verbose: true, ansi: true } 7 β finds FetchCommand (name = 'doc:fetch' ) 8 β builds Input + Output 9 β injects cmd._keepAliveFn = startTimer 10 β startTimer() 11 β exitCode = await cmd.run(input, output) 12 β clearTimeout + process.exit (exitCode)
Known Commands (Examples)
Command
Provided by
doc:fetch
@xpulse/doc
project:config:show
@xpulse/config
repo:changelog:generate
@xpulse/repo (TBD)
release:prepare
@xpulse/release (TBD)
Package Structure
1 @xpulse /cli/2 bin/ 3 xpulse.js β entry point: autodiscovery, parser, Input /Output , timeout 4 src/ 5 command.js β Command base class with configure ( ), keepAlive ( ), run ( ) 6 input.js β Input class : getArgument, getOption, getFlag, isVerbose ... 7 output.js β Output class + internal helpers (--help, --list, --version ) 8 loader.js β find + import node_modules/@xpulse 9 parser.js β CLI input β { name, args, rawArguments, verbose, quiet, ansi } 10 docs/ 11 index.md 12 de/ index, guide, api, schema 13 en/ index, guide, api, schema 14 test/ 15 command.test.js 16 input.test.js 17 parser.test.js 18 loader.test.js 19 README.md 20 package.json 21 xpulse.json
Installation & Usage
@xpulse/cli is installed locally in the project β no global install required.
It is always invoked via npx.
Important: The project's package.json must contain "type": "module",
since the entire xPulse ecosystem uses ESM:
1 { 2 "name" : "my-project" , 3 "type" : "module" , 4 "dependencies" : { 5 "@xpulse/cli" : "^1.0.0" 6 } 7 }
Invocation β always with `npx`
1 npx xpulse --version 2 npx xpulse --list 3 npx xpulse --help 4 npx xpulse --help doc:fetch 5 npx xpulse doc:fetch chat -tag="v1.3.0" --dry-run
npx always uses the local version from node_modules/.bin/xpulse β
consistent, no version mismatch with globally installed versions.
Argument vs. Option β Syntax Rules
Type
Definition
Invocation
Argument
addArgument('name', ...)
npx xpulse cmd Johnny (positional)
Option
addOption('name', ...)
npx xpulse cmd -name="Johnny"
Flag
addFlag('dry-run', ...)
npx xpulse cmd --dry-run
Important: Options must always be passed with =:
1 npx xpulse cmd -name="Johnny" 2 npx xpulse cmd --name="Johnny" 3 npx xpulse cmd -name Johnny 4 npx xpulse cmd --name Johnny
If the syntax is wrong, @xpulse/cli outputs a warning:
1 ! Invalid syntax : "--name Johnny" β did you mean --name="Johnny" ? 2 "Johnny" is interpreted as a positional argument .