Phase 3: Reorganize CLI with subcommand structure

Major Changes:
- Reorganized commands into logical groups (service, ups, group)
- Added new subcommand structure:
  - nupst service <enable|disable|start|stop|restart|status|logs|start-daemon>
  - nupst ups <add|edit|remove|list|test>
  - nupst group <add|edit|remove|list>
  - nupst config [show]
- Added --version/-v flag support
- Added restart subcommand for service
- Added command aliases (ls, rm)
- Renamed delete() to remove() in handlers
- Maintained backward compatibility with deprecation warnings
- Updated all help messages to reflect new structure
- Added showVersion(), showServiceHelp(), showUpsHelp() methods
- Fixed readline imports to use node:readline

Breaking Changes:
- Old command format (e.g. 'nupst add') is deprecated
- Users should migrate to new format (e.g. 'nupst ups add')
- Backward compatibility maintained with warnings for now
This commit is contained in:
2025-10-18 12:36:35 +00:00
parent 5903ae71be
commit 9efcc4b437
3 changed files with 282 additions and 97 deletions

363
ts/cli.ts
View File

@@ -21,7 +21,7 @@ export class NupstCli {
* @param args Command line arguments (process.argv) * @param args Command line arguments (process.argv)
*/ */
public async parseAndExecute(args: string[]): Promise<void> { public async parseAndExecute(args: string[]): Promise<void> {
// Extract debug flag from any position // Extract debug and version flags from any position
const debugOptions = this.extractDebugOptions(args); const debugOptions = this.extractDebugOptions(args);
if (debugOptions.debugMode) { if (debugOptions.debugMode) {
logger.log('Debug mode enabled'); logger.log('Debug mode enabled');
@@ -29,6 +29,12 @@ export class NupstCli {
this.nupst.getSnmp().enableDebug(); this.nupst.getSnmp().enableDebug();
} }
// Check for version flag
if (debugOptions.cleanedArgs.includes('--version') || debugOptions.cleanedArgs.includes('-v')) {
this.showVersion();
return;
}
// Get the command (default to help if none provided) // Get the command (default to help if none provided)
const command = debugOptions.cleanedArgs[2] || 'help'; const command = debugOptions.cleanedArgs[2] || 'help';
const commandArgs = debugOptions.cleanedArgs.slice(3); const commandArgs = debugOptions.cleanedArgs.slice(3);
@@ -61,17 +67,92 @@ export class NupstCli {
const upsHandler = this.nupst.getUpsHandler(); const upsHandler = this.nupst.getUpsHandler();
const groupHandler = this.nupst.getGroupHandler(); const groupHandler = this.nupst.getGroupHandler();
const serviceHandler = this.nupst.getServiceHandler(); const serviceHandler = this.nupst.getServiceHandler();
// Handle service subcommands
if (command === 'service') {
const subcommand = commandArgs[0] || 'status';
switch (subcommand) {
case 'enable':
await serviceHandler.enable();
break;
case 'disable':
await serviceHandler.disable();
break;
case 'start':
await serviceHandler.start();
break;
case 'stop':
await serviceHandler.stop();
break;
case 'restart':
await serviceHandler.stop();
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s
await serviceHandler.start();
break;
case 'status':
await serviceHandler.status();
break;
case 'logs':
await serviceHandler.logs();
break;
case 'start-daemon':
await serviceHandler.daemonStart(debugMode);
break;
default:
this.showServiceHelp();
break;
}
return;
}
// Handle UPS subcommands
if (command === 'ups') {
const subcommand = commandArgs[0] || 'list';
const subcommandArgs = commandArgs.slice(1);
switch (subcommand) {
case 'add':
await upsHandler.add();
break;
case 'edit':
const upsId = subcommandArgs[0];
await upsHandler.edit(upsId);
break;
case 'remove':
case 'rm': // Alias
case 'delete': // Backward compatibility
const upsIdToRemove = subcommandArgs[0];
if (!upsIdToRemove) {
logger.error('UPS ID is required for remove command');
this.showUpsHelp();
return;
}
await upsHandler.remove(upsIdToRemove);
break;
case 'list':
case 'ls': // Alias
await upsHandler.list();
break;
case 'test':
await upsHandler.test(debugMode);
break;
default:
this.showUpsHelp();
break;
}
return;
}
// Handle group subcommands // Handle group subcommands
if (command === 'group') { if (command === 'group') {
const subcommand = commandArgs[0] || 'list'; const subcommand = commandArgs[0] || 'list';
const subcommandArgs = commandArgs.slice(1); const subcommandArgs = commandArgs.slice(1);
switch (subcommand) { switch (subcommand) {
case 'add': case 'add':
await groupHandler.add(); await groupHandler.add();
break; break;
case 'edit': case 'edit':
const groupId = subcommandArgs[0]; const groupId = subcommandArgs[0];
if (!groupId) { if (!groupId) {
@@ -81,21 +162,21 @@ export class NupstCli {
} }
await groupHandler.edit(groupId); await groupHandler.edit(groupId);
break; break;
case 'remove':
case 'delete': case 'rm': // Alias
const groupIdToDelete = subcommandArgs[0]; case 'delete': // Backward compatibility
if (!groupIdToDelete) { const groupIdToRemove = subcommandArgs[0];
logger.error('Group ID is required for delete command'); if (!groupIdToRemove) {
logger.error('Group ID is required for remove command');
this.showGroupHelp(); this.showGroupHelp();
return; return;
} }
await groupHandler.delete(groupIdToDelete); await groupHandler.remove(groupIdToRemove);
break; break;
case 'list': case 'list':
case 'ls': // Alias
await groupHandler.list(); await groupHandler.list();
break; break;
default: default:
this.showGroupHelp(); this.showGroupHelp();
break; break;
@@ -103,82 +184,100 @@ export class NupstCli {
return; return;
} }
// Handle main commands // Handle config subcommand
if (command === 'config') {
const subcommand = commandArgs[0] || 'show';
switch (subcommand) {
case 'show':
case 'display':
await this.showConfig();
break;
default:
await this.showConfig();
break;
}
return;
}
// Handle top-level commands and backward compatibility
switch (command) { switch (command) {
// Backward compatibility - old UPS commands
case 'add': case 'add':
logger.log("Note: 'nupst add' is deprecated. Use 'nupst ups add' instead.");
await upsHandler.add(); await upsHandler.add();
break; break;
case 'edit': case 'edit':
const upsId = commandArgs[0]; logger.log("Note: 'nupst edit' is deprecated. Use 'nupst ups edit' instead.");
await upsHandler.edit(upsId); await upsHandler.edit(commandArgs[0]);
break; break;
case 'delete': case 'delete':
const upsIdToDelete = commandArgs[0]; logger.log("Note: 'nupst delete' is deprecated. Use 'nupst ups remove' instead.");
if (!upsIdToDelete) { if (!commandArgs[0]) {
logger.error('UPS ID is required for delete command'); logger.error('UPS ID is required for delete command');
this.showHelp(); this.showHelp();
return; return;
} }
await upsHandler.delete(upsIdToDelete); await upsHandler.remove(commandArgs[0]);
break; break;
case 'list': case 'list':
logger.log("Note: 'nupst list' is deprecated. Use 'nupst ups list' instead.");
await upsHandler.list(); await upsHandler.list();
break; break;
case 'test':
case 'setup': logger.log("Note: 'nupst test' is deprecated. Use 'nupst ups test' instead.");
// Backward compatibility: setup is now an alias for edit with no specific UPS ID await upsHandler.test(debugMode);
break;
case 'setup':
logger.log("Note: 'nupst setup' is deprecated. Use 'nupst ups edit' instead.");
await upsHandler.edit(undefined); await upsHandler.edit(undefined);
break; break;
// Backward compatibility - old service commands
case 'enable': case 'enable':
logger.log("Note: 'nupst enable' is deprecated. Use 'nupst service enable' instead.");
await serviceHandler.enable(); await serviceHandler.enable();
break; break;
case 'disable':
logger.log("Note: 'nupst disable' is deprecated. Use 'nupst service disable' instead.");
await serviceHandler.disable();
break;
case 'start':
logger.log("Note: 'nupst start' is deprecated. Use 'nupst service start' instead.");
await serviceHandler.start();
break;
case 'stop':
logger.log("Note: 'nupst stop' is deprecated. Use 'nupst service stop' instead.");
await serviceHandler.stop();
break;
case 'status':
logger.log("Note: 'nupst status' is deprecated. Use 'nupst service status' instead.");
await serviceHandler.status();
break;
case 'logs':
logger.log("Note: 'nupst logs' is deprecated. Use 'nupst service logs' instead.");
await serviceHandler.logs();
break;
case 'daemon-start': case 'daemon-start':
logger.log("Note: 'nupst daemon-start' is deprecated. Use 'nupst service start-daemon' instead.");
await serviceHandler.daemonStart(debugMode); await serviceHandler.daemonStart(debugMode);
break; break;
case 'logs': // Top-level commands (no changes)
await serviceHandler.logs();
break;
case 'stop':
await serviceHandler.stop();
break;
case 'start':
await serviceHandler.start();
break;
case 'status':
await serviceHandler.status();
break;
case 'disable':
await serviceHandler.disable();
break;
case 'test':
await upsHandler.test(debugMode);
break;
case 'update': case 'update':
await serviceHandler.update(); await serviceHandler.update();
break; break;
case 'uninstall': case 'uninstall':
await serviceHandler.uninstall(); await serviceHandler.uninstall();
break; break;
case 'config':
await this.showConfig();
break;
case 'help': case 'help':
case '--help':
case '-h':
this.showHelp();
break;
default: default:
logger.error(`Unknown command: ${command}`);
logger.log('');
this.showHelp(); this.showHelp();
break; break;
} }
@@ -328,45 +427,123 @@ export class NupstCli {
} }
} }
/**
* Display version information
*/
private showVersion(): void {
const version = this.nupst.getVersion();
logger.log(`NUPST version ${version}`);
logger.log('Deno-powered UPS monitoring tool');
}
/** /**
* Display help message * Display help message
*/ */
private showHelp(): void { private showHelp(): void {
logger.log(` logger.log(`
NUPST - Node.js UPS Shutdown Tool NUPST - UPS Shutdown Tool
Usage: Usage:
nupst enable - Install and enable the systemd service (requires root) nupst <command> [options]
nupst disable - Stop and uninstall the systemd service (requires root)
nupst daemon-start - Start the daemon process directly
nupst logs - Show logs of the systemd service
nupst stop - Stop the systemd service
nupst start - Start the systemd service
nupst status - Show status of the systemd service and UPS status
UPS Management: Commands:
nupst add - Add a new UPS device service <subcommand> - Manage systemd service
nupst edit [id] - Edit an existing UPS (default UPS if no ID provided) ups <subcommand> - Manage UPS devices
nupst delete <id> - Delete a UPS by ID group <subcommand> - Manage UPS groups
nupst list - List all configured UPS devices config [show] - Display current configuration
nupst setup - Alias for 'nupst edit' (backward compatibility) update - Update NUPST from repository (requires root)
uninstall - Completely remove NUPST from system (requires root)
Group Management: help, --help, -h - Show this help message
nupst group list - List all UPS groups --version, -v - Show version information
nupst group add - Add a new UPS group
nupst group edit <id> - Edit an existing UPS group Service Subcommands:
nupst group delete <id> - Delete a UPS group nupst service enable - Install and enable systemd service (requires root)
nupst service disable - Stop and disable systemd service (requires root)
System Commands: nupst service start - Start the systemd service
nupst test - Test the current configuration by connecting to all UPS devices nupst service stop - Stop the systemd service
nupst config - Display the current configuration nupst service restart - Restart the systemd service
nupst update - Update NUPST from repository and refresh systemd service (requires root) nupst service status - Show service and UPS status
nupst uninstall - Completely uninstall NUPST from the system (requires root) nupst service logs - Show service logs in real-time
nupst help - Show this help message nupst service start-daemon - Start daemon process directly
UPS Subcommands:
nupst ups add - Add a new UPS device
nupst ups edit [id] - Edit a UPS device (default if no ID)
nupst ups remove <id> - Remove a UPS device by ID
nupst ups list (or ls) - List all configured UPS devices
nupst ups test - Test UPS connections
Group Subcommands:
nupst group add - Add a new UPS group
nupst group edit <id> - Edit an existing UPS group
nupst group remove <id> - Remove a UPS group by ID
nupst group list (or ls) - List all UPS groups
Options: Options:
--debug, -d - Enable debug mode for detailed SNMP logging --debug, -d - Enable debug mode for detailed SNMP logging
(Example: nupst test --debug) (Example: nupst ups test --debug)
Examples:
nupst service enable - Install and start the service
nupst ups add - Add a new UPS interactively
nupst group list - Show all configured groups
nupst config - Display current configuration
Note: Old command format (e.g., 'nupst add') still works but is deprecated.
Use the new format (e.g., 'nupst ups add') going forward.
`);
}
/**
* Display help message for service commands
*/
private showServiceHelp(): void {
logger.log(`
NUPST - Service Management Commands
Usage:
nupst service <subcommand>
Subcommands:
enable - Install and enable the systemd service (requires root)
disable - Stop and disable the systemd service (requires root)
start - Start the systemd service
stop - Stop the systemd service
restart - Restart the systemd service
status - Show service status and UPS information
logs - Show service logs in real-time
start-daemon - Start the daemon process directly (for testing)
Options:
--debug, -d - Enable debug mode for detailed logging
`);
}
/**
* Display help message for UPS commands
*/
private showUpsHelp(): void {
logger.log(`
NUPST - UPS Management Commands
Usage:
nupst ups <subcommand> [arguments]
Subcommands:
add - Add a new UPS device interactively
edit [id] - Edit a UPS device (edits default if no ID provided)
remove <id> - Remove a UPS device by ID (alias: rm)
list - List all configured UPS devices (alias: ls)
test - Test connections to all configured UPS devices
Options:
--debug, -d - Enable debug mode for detailed SNMP logging
Examples:
nupst ups add - Add a new UPS device
nupst ups edit ups-1 - Edit UPS with ID 'ups-1'
nupst ups remove ups-1 - Remove UPS with ID 'ups-1'
nupst ups test --debug - Test all UPS connections with debug output
`); `);
} }
@@ -378,13 +555,21 @@ Options:
NUPST - Group Management Commands NUPST - Group Management Commands
Usage: Usage:
nupst group list - List all UPS groups nupst group <subcommand> [arguments]
nupst group add - Add a new UPS group
nupst group edit <id> - Edit an existing UPS group Subcommands:
nupst group delete <id> - Delete a UPS group add - Add a new UPS group interactively
edit <id> - Edit an existing UPS group
remove <id> - Remove a UPS group by ID (alias: rm)
list - List all UPS groups (alias: ls)
Options: Options:
--debug, -d - Enable debug mode for detailed logging --debug, -d - Enable debug mode for detailed logging
Examples:
nupst group add - Create a new group
nupst group edit dc-1 - Edit group with ID 'dc-1'
nupst group remove dc-1 - Remove group with ID 'dc-1'
`); `);
} }
} }

View File

@@ -88,7 +88,7 @@ export class GroupHandler {
public async add(): Promise<void> { public async add(): Promise<void> {
try { try {
// Import readline module for user input // Import readline module for user input
const readline = await import('readline'); const readline = await import('node:readline');
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
@@ -198,7 +198,7 @@ export class GroupHandler {
public async edit(groupId: string): Promise<void> { public async edit(groupId: string): Promise<void> {
try { try {
// Import readline module for user input // Import readline module for user input
const readline = await import('readline'); const readline = await import('node:readline');
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
@@ -306,7 +306,7 @@ export class GroupHandler {
* Delete an existing UPS group * Delete an existing UPS group
* @param groupId ID of the group to delete * @param groupId ID of the group to delete
*/ */
public async delete(groupId: string): Promise<void> { public async remove(groupId: string): Promise<void> {
try { try {
// Try to load configuration // Try to load configuration
try { try {
@@ -335,7 +335,7 @@ export class GroupHandler {
const groupToDelete = config.groups[groupIndex]; const groupToDelete = config.groups[groupIndex];
// Get confirmation before deleting // Get confirmation before deleting
const readline = await import('readline'); const readline = await import('node:readline');
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout, output: process.stdout,

View File

@@ -24,7 +24,7 @@ export class UpsHandler {
public async add(): Promise<void> { public async add(): Promise<void> {
try { try {
// Import readline module for user input // Import readline module for user input
const readline = await import('readline'); const readline = await import('node:readline');
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
@@ -155,7 +155,7 @@ export class UpsHandler {
public async edit(upsId?: string): Promise<void> { public async edit(upsId?: string): Promise<void> {
try { try {
// Import readline module for user input // Import readline module for user input
const readline = await import('readline'); const readline = await import('node:readline');
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
@@ -284,7 +284,7 @@ export class UpsHandler {
* Delete a UPS by ID * Delete a UPS by ID
* @param upsId ID of the UPS to delete * @param upsId ID of the UPS to delete
*/ */
public async delete(upsId: string): Promise<void> { public async remove(upsId: string): Promise<void> {
try { try {
// Try to load configuration // Try to load configuration
try { try {
@@ -318,7 +318,7 @@ export class UpsHandler {
const upsToDelete = config.upsDevices[upsIndex]; const upsToDelete = config.upsDevices[upsIndex];
// Get confirmation before deleting // Get confirmation before deleting
const readline = await import('readline'); const readline = await import('node:readline');
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout, output: process.stdout,