Skip to content

Advanced Commands

Once you have initialized your basic console loop, you can begin constructing complex, multi-tiered terminal control systems. stREPL uses a structured command declaration tree that decouples routing logic from execution handlers.

Instead of implementing flat, overly lengthy command names (e.g., cluster-node-restart), group your infrastructure workflows into high-level domains using sub-namespace routing branches.

A command definition becomes a namespace whenever it includes a nested commands configuration block instead of an execution handler.

import { Repl } from 'strepl';
const shell = new Repl();
shell.command({
name: 'cluster',
description: 'Manage active cluster deployment partitions',
commands: [
{
name: 'status',
description: 'Check connectivity health of cluster partitions',
run: async (args, context) => {
console.log('\nAll worker partitions report normal operational health.');
}
},
{
name: 'node',
description: 'Isolate or manage individual engine nodes',
commands: [
{
name: 'evict',
description: 'Force-evict an active node from consensus groups',
args: [{ name: 'nodeId', required: true }],
run: async ([nodeId]) => {
console.log(`\nEvicting target engine node: ${nodeId}`);
}
}
]
}
]
});

This structural definition allows users to organically browse your shell by typing cluster to see sub-options, or executing cluster node evict node-01 with automated argument context mapping.


Hardcoded autocomplete arrays are often insufficient when managing stateful data stores or interacting with remote infrastructures. The choices property on any argument definition can ingest an evaluation callback function.

The callback provides the current string buffer, an array of prior parsed argument flags, the active application state memory, and your global tool utilities:

import { Repl } from 'strepl';
const shell = new Repl({
context: {
activeConnections: ['conn_alpha', 'conn_beta', 'conn_gamma']
}
});
shell.command({
name: 'disconnect',
description: 'Sever an active network runtime socket connection',
args: [
{
name: 'connectionId',
required: true,
// Dynamic evaluation callback
choices: (typed, previousArgs, context, globals) => {
// Filter elements matching what the engineer has typed so far
return context.activeConnections.filter((id) => id.startsWith(typed));
}
}
],
run: async ([connectionId], context) => {
context.activeConnections = context.activeConnections.filter(id => id !== connectionId);
console.log(`\nSuccessfully severed target socket pipeline: ${connectionId}`);
}
});

You can leverage global properties inside your autocomplete handlers to build sophisticated native terminal capabilities—such as path mapping utilities:

import { Repl } from 'strepl';
import fs from 'node:fs';
import path from 'node:path';
const shell = new Repl({
globals: { fs, path }
});
shell.command({
name: 'load',
description: 'Ingest local configuration schema files',
args: [
{
name: 'filePath',
required: true,
choices: (typed, previousArgs, context, globals) => {
const fileSystem = globals.fs;
const pathUtil = globals.path;
const searchDir = typed.includes('/') ? pathUtil.dirname(typed) : '.';
const baseName = pathUtil.basename(typed);
try {
if (!fileSystem.existsSync(searchDir)) return [];
const contents = fileSystem.readdirSync(searchDir);
return contents
.filter(item => item.startsWith(baseName))
.map(item => pathUtil.join(searchDir, item));
} catch {
return [];
}
}
}
],
run: async ([filePath]) => {
console.log(`\nProcessing ingestion matrix tracking: ${filePath}`);
}
});