xPulse
🇩🇪 DE

@xpulse/cli – Component Spec

Status: APPROVED · Aktualisiert März 2026 Übergeordnet: GLOBAL_concept-ecosystem.md ADR: COMP_cli_adr-001-command-registration.md


Prinzip

@xpulse/cli ist ein Runner – kein Command-Owner.

Das Package stellt den Einstiegspunkt xpulse, den Command-Parser, die Command-Basisklasse, Input/Output und die Autodiscovery bereit. Welche Commands existieren, entscheidet jedes xPart selbst – durch Mitliefern von Command-Klassen unter src/commands/.

xpulse doc:fetch chat -tag="v1.3.0" --dry-run -V
@xpulse/cli discovert node_modules/@xpulse/*/src/commands/*.js
findet FetchCommand (name = 'doc:fetch' aus configure())
baut Input + Output auf, injiziert keepAlive
ruft new FetchCommand().run(input, output) auf
FetchCommand.run() führt aus, gibt Exit Code zurück
@xpulse/cli process.exit(code)

Dependencies

@xpulse/cli
└── @xpulse/event ← cli:keepalive (intern, xParts sehen das nicht)

Command-Syntax

xpulse namespace:area:command [arguments] [-option="value"] [--flag] [-V] [-q]
Teil Beschreibung Beispiel
namespace Domäne des zuständigen xParts repo, doc, project
area Unterbereich (optional) changelog, fetch
command Aktion generate, fetch, show
argument Positionales Argument chat
-option="value" Benannte Option -tag="v1.3.0"
--flag Boolean, camelCase --dry-rundryRun: true

Command-Basisklasse (`src/command.js`)

xParts importieren Command und extenden sie in src/commands/. Ein Command beschreibt sich in configure() und implementiert run(). Er weiß nichts von Events, Timeouts oder dem CLI selbst.

import { Command } from '@xpulse/cli/command';
export class FetchCommand extends Command {
configure() {
this
.setName('doc:fetch')
.setAlias('df')
.setDescription('Docs via git archive fetchen')
.setHelp('Fetcht Docs aus einem xPulse Repo und legt sie lokal ab.')
.addArgument('tool', null, 'Name des Tools (z.B. chat)')
.addOption('tag', null, 'Git-Tag zum Fetchen')
.addFlag('dry-run', false, 'Nur simulieren, nichts schreiben');
}
async run(input, output) {
const tool = input.getArgument('tool');
const dryRun = input.getFlag('dryRun');
while (!done) {
await doWork();
this.keepAlive(); // Timeout zurücksetzen
}
output.success('Fertig!');
return 0;
}
}

Fluent API

Methode Beschreibung
setName(name) Command-Name – namespace:area:command
setAlias(alias) Kurzname (z.B. df für doc:fetch)
setDescription(text) Kurzbeschreibung für xpulse --help
setHelp(text) Längerer Text für xpulse --help <command>
addArgument(name, default, description) Positionales Argument
addOption(name, default, description) Benannte Option (-key=value)
addFlag(name, default, description) Boolean-Flag (--flag)

Exit Codes

Code Bedeutung
0 Erfolg
1 Fehler
2 Misuse – falsche oder fehlende Argumente

`keepAlive()`

Setzt den Timeout-Timer zurück. Wird von @xpulse/cli vor run() injiziert. Kein Import von @xpulse/event nötig – der Command abstrahiert das vollständig.


Input (`src/input.js`)

input.getArgument('tool') // positionales Argument per Name
input.getOption('tag') // -key=value, kebab-case oder camelCase
input.hasOption('tag') // → true | false
input.getFlag('dryRun') // --flag, kebab-case oder camelCase
input.isVerbose() // --verbose / -V
input.isQuiet() // --quiet / -q
input.isAnsi() // false wenn --no-ansi

Output (`src/output.js`)

Respektiert --no-ansi und --quiet automatisch.

output.write('...') // ohne Zeilenumbruch
output.writeln('...') // mit Zeilenumbruch
output.success('...') // ✓ grün
output.warn('...') // ! gelb
output.error('...') // ✗ rot – ignoriert --quiet
output.table(headers, rows) // formatierte Tabelle

Globale CLI-Flags

Flag Shortcut Beschreibung
--help [command] -h CLI-Hilfe oder Command-Hilfe
--list Alle Command-Namen, maschinenlesbar (einer pro Zeile)
--version -v Version ausgeben
--verbose -V Ausführliche Ausgabe → input.isVerbose()
--quiet -q Nur Errors → input.isQuiet()
--no-ansi Farblos → input.isAnsi() = false

`--help` Verhalten

xpulse --help # alle Commands mit Name, Alias, Description
xpulse --help doc:fetch # Name, Alias, Description, Help-Text,
# Ausführungsbeispiel, Arguments, Options, Flags

`--list` Verhalten

xpulse --list
# doc:fetch
# doc:fetch:changelog
# project:config:show
# repo:changelog:generate

Maschinenlesbar – ein Command-Name pro Zeile, kein Ansi, kein Padding.


Autodiscovery

Suchreihenfolge beim Start:

1. node_modules/@xpulse/*/src/commands/*.js ← installierte xParts
2. src/commands/*.js ← lokales Projekt

Beide Pfade werden zusammengeführt. Das lokale Projekt verhält sich damit wie ein implizites xPart – kein Symlink, kein npm link nötig.


Timeout

CLI_TIMEOUT=30 # Sekunden, Default: 30

Timer startet mit run(). Bei this.keepAlive() vollständig resettet. Kein cli:donerun() resolved mit Exit Code, @xpulse/cli beendet den Prozess.


Ablauf

xpulse doc:fetch chat -tag="v1.3.0" --dry-run -V
@xpulse/cli startet
→ discovert node_modules/@xpulse/*/src/commands/*.js
→ parst Input → { name: 'doc:fetch', args: { tag: 'v1.3.0', dryRun: true },
rawArguments: ['chat'], verbose: true, ansi: true }
→ findet FetchCommand (name = 'doc:fetch')
→ baut Input + Output auf
→ injiziert cmd._keepAliveFn = startTimer
→ startTimer()
→ exitCode = await cmd.run(input, output)
→ clearTimeout + process.exit(exitCode)

Bekannte Commands (Beispiele)

Command Bereitgestellt von
doc:fetch @xpulse/doc
project:config:show @xpulse/config
repo:changelog:generate @xpulse/repo (TBD)
release:prepare @xpulse/release (TBD)

Paket-Struktur

@xpulse/cli/
bin/
xpulse.js ← Einstiegspunkt: Autodiscovery, Parser, Input/Output, Timeout
src/
command.js ← Command-Basisklasse mit configure(), keepAlive(), run()
input.js ← Input-Klasse: getArgument, getOption, getFlag, isVerbose ...
output.js ← Output-Klasse + interne Helfer (--help, --list, --version)
loader.js ← node_modules/@xpulse/*/src/commands/*.js finden + importieren
parser.js ← CLI-Input → { name, args, rawArguments, verbose, quiet, ansi }
docs/
index.md
de/ index, guide, api, schema
en/ index, guide, api, schema
test/
command.test.js
input.test.js
parser.test.js
loader.test.js
README.md
package.json
xpulse.json

Installation & Verwendung

@xpulse/cli wird lokal im Projekt installiert – kein globales Install nötig. Der Aufruf erfolgt immer via npx.

npm install @xpulse/cli

Wichtig: Die package.json des Projekts muss "type": "module" enthalten, da das gesamte xPulse Ökosystem ESM verwendet:

{
"name": "mein-projekt",
"type": "module",
"dependencies": {
"@xpulse/cli": "^1.0.0"
}
}

Aufruf – immer mit `npx`

npx xpulse --version
npx xpulse --list
npx xpulse --help
npx xpulse --help doc:fetch
npx xpulse doc:fetch chat -tag="v1.3.0" --dry-run

npx nutzt immer die lokale Version aus node_modules/.bin/xpulse – konsistent, kein Versions-Mismatch mit global installierten Versionen.


Argument vs. Option – Syntaxregeln

Typ Definition Aufruf
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

Wichtig: Optionen müssen immer mit = übergeben werden:

npx xpulse cmd -name="Johnny" # ✓ korrekt
npx xpulse cmd --name="Johnny" # ✓ korrekt
npx xpulse cmd -name Johnny # ✗ Warnung – Johnny wird als positionales Argument interpretiert
npx xpulse cmd --name Johnny # ✗ Warnung – Johnny wird als positionales Argument interpretiert

Bei falschem Syntax gibt @xpulse/cli eine Warnung aus:

! Ungültige Syntax: "--name Johnny" – meintest du --name="Johnny"?
"Johnny" wird als positionales Argument interpretiert.
de/spec.md 2026-03-27