-
Notifications
You must be signed in to change notification settings - Fork 1
CommandSystem
runtoolkit edited this page Apr 13, 2026
·
1 revision
Safe, general-purpose command execution. Strings are parsed into name + args; registered handlers are called. Nothing reaches eval or the shell.
import { CommandSystem } from './src/command.js';
const commands = new CommandSystem({
mode: 'series', // default execution mode for runMany: 'series' | 'parallel'
concurrency: 4, // max parallel runners (parallel mode only)
stopOnError: false, // stop runMany on first failure
});commands.register(name, handler, options);The handler receives a single context object:
commands.register('echo', ({ args, command, registry }) => {
return args.join(' ');
});
commands.register('add', ({ args }) => {
return args.map(Number).reduce((a, b) => a + b, 0);
});| Context field | Description |
|---|---|
args |
Array of string arguments (parsed from input) |
command |
Normalized command object (name, args, raw) |
registry |
The CommandRegistry instance |
| + spread | Any fields passed as context to run()
|
Handler may be async.
commands.register('greet', handler, {
aliases: ['hi', 'hello'], // alternative names
description: 'Greets someone',
meta: {}, // arbitrary metadata (deep-cloned)
});register() returns an unregister function.
const result = await commands.run(input, context);input can be:
- a string — parsed as
name arg1 arg2 ...(supports quoting and backslash escaping) - an object —
{ name, args, context, meta } - a function — called directly as
fn({ ...context, command }) - an array — treated as a batch, run in series
await commands.run('echo hello world');
await commands.run('echo "hello world"'); // single quoted arg
await commands.run({ name: 'add', args: ['3', '4'] });
await commands.run((ctx) => ctx.args.join(','), { args: ['a', 'b'] });{
ok: boolean,
value: any, // return value of handler (if ok)
error: Error, // (if not ok)
command: normalized,
durationMs: number,
skipped: boolean, // true if allowUnknown and command not found
}const result = await commands.runMany(inputs, context, options);await commands.runMany(['echo first', 'echo second']);
await commands.runMany(['echo a', 'echo b'], {}, { mode: 'parallel' });Result shape:
{
ok: boolean, // true if all succeeded
total: number,
success: number,
failed: number,
results: [...], // individual result objects
durationMs: number,
}For deferred execution (e.g. within a tick loop):
commands.enqueue('echo queued');
commands.enqueue('add 1 2', { userId: 'alice' });
// Drain the queue (called automatically by Engine every tick)
await commands.flush({ stopOnError: false });
// → same shape as runMany resultcommands.unregister('echo');
commands.has('echo'); // → boolean
commands.list();
// → [{ name, description, meta }, ...]tokenizeCommandLine is exported separately if you need to parse a command line manually:
import { tokenizeCommandLine } from './src/command.js';
tokenizeCommandLine('echo "hello world"'); // → ['echo', 'hello world']
tokenizeCommandLine("say it\\'s fine"); // → ['say', "it's fine"]Throws SyntaxError on unclosed quotes.
Normalizes any supported input shape into a consistent object:
import { normalizeCommand } from './src/command.js';
normalizeCommand('echo hi');
// → { kind: 'command', name: 'echo', args: ['hi'], raw: 'echo hi' }
normalizeCommand(() => {});
// → { kind: 'function', fn: [Function], args: [] }