feat(cli/daemon/processmonitor): Add flexible target resolution and search command; improve restart/backoff and error handling

This commit is contained in:
2025-08-30 16:55:10 +00:00
parent 22a43204d4
commit ebc20a9232
17 changed files with 327 additions and 109 deletions

View File

@@ -22,13 +22,14 @@ export function registerDefaultCommand(smartcli: plugins.smartcli.Smartcli) {
);
console.log(' disable Disable TSPM system service');
console.log('\nProcess Commands:');
console.log(' start <script> Start a process');
console.log(' start <id|id:N|name:LBL> Start a process');
console.log(' list List all processes');
console.log(' stop <id> Stop a process');
console.log(' restart <id> Restart a process');
console.log(' delete <id> Delete a process');
console.log(' describe <id> Show details for a process');
console.log(' logs <id> Show logs for a process');
console.log(' stop <id|id:N|name:LBL> Stop a process');
console.log(' restart <id|id:N|name:LBL> Restart a process');
console.log(' delete <id|id:N|name:LBL> Delete a process');
console.log(' describe <id|id:N|name:LBL> Show details for a process');
console.log(' logs <id|id:N|name:LBL> Show logs for a process');
console.log(' search <query> Find processes by id/name');
console.log(' start-all Start all saved processes');
console.log(' stop-all Stop all processes');
console.log(' restart-all Restart all processes');

View File

@@ -8,23 +8,25 @@ export function registerDeleteCommand(smartcli: plugins.smartcli.Smartcli) {
smartcli,
['delete', 'remove'],
async (argvArg: CliArguments) => {
const id = argvArg._[1];
if (!id) {
console.error('Error: Please provide a process ID');
console.log('Usage: tspm delete <id> | tspm remove <id>');
const target = argvArg._[1];
if (!target) {
console.error('Error: Please provide a process target');
console.log('Usage: tspm delete <id|id:N|name:LABEL> | tspm remove <id|id:N|name:LABEL>');
return;
}
// Determine if command was 'remove' to use the new IPC route, otherwise 'delete'
const cmd = String(argvArg._[0]);
const useRemove = cmd === 'remove';
console.log(`${useRemove ? 'Removing' : 'Deleting'} process: ${id}`);
const response = await tspmIpcClient.request(useRemove ? 'remove' : 'delete', { id } as any);
const isRemoveAlias = cmd === 'remove';
console.log(`${isRemoveAlias ? 'Removing' : 'Deleting'} process: ${target}`);
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
// Always call daemon 'delete'; 'remove' is CLI alias only
const response = await tspmIpcClient.request('delete', { id: resolved.id } as any);
if (response.success) {
console.log(`${response.message || (useRemove ? 'Removed successfully' : 'Deleted successfully')}`);
console.log(`${response.message || (isRemoveAlias ? 'Removed successfully' : 'Deleted successfully')}`);
} else {
console.error(`✗ Failed to ${useRemove ? 'remove' : 'delete'} process: ${response.message}`);
console.error(`✗ Failed to ${isRemoveAlias ? 'remove' : 'delete'} process: ${response.message}`);
}
},
{ actionLabel: 'delete/remove process' },

View File

@@ -9,16 +9,17 @@ export function registerDescribeCommand(smartcli: plugins.smartcli.Smartcli) {
smartcli,
'describe',
async (argvArg: CliArguments) => {
const id = argvArg._[1];
if (!id) {
console.error('Error: Please provide a process ID');
console.log('Usage: tspm describe <id>');
const target = argvArg._[1];
if (!target) {
console.error('Error: Please provide a process target');
console.log('Usage: tspm describe <id | id:N | name:LABEL>');
return;
}
const response = await tspmIpcClient.request('describe', { id });
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
const response = await tspmIpcClient.request('describe', { id: resolved.id });
console.log(`Process Details: ${id}`);
console.log(`Process Details: ${response.config.name || resolved.id}`);
console.log('─'.repeat(40));
console.log(`Status: ${response.processInfo.status}`);
console.log(`PID: ${response.processInfo.pid || 'N/A'}`);

View File

@@ -9,17 +9,16 @@ export function registerEditCommand(smartcli: plugins.smartcli.Smartcli) {
smartcli,
'edit',
async (argvArg: CliArguments) => {
const idRaw = argvArg._[1];
if (!idRaw) {
console.error('Error: Please provide a process ID to edit');
console.log('Usage: tspm edit <id>');
const target = argvArg._[1];
if (!target) {
console.error('Error: Please provide a process target to edit');
console.log('Usage: tspm edit <id | id:N | name:LABEL>');
return;
}
const id = idRaw;
// Load current config
const { config } = await tspmIpcClient.request('describe', { id });
// Resolve and load current config
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
const { config } = await tspmIpcClient.request('describe', { id: resolved.id });
// Interactive editing is temporarily disabled - needs smartinteract API update
console.log('Interactive editing is temporarily disabled.');
@@ -63,7 +62,7 @@ export function registerEditCommand(smartcli: plugins.smartcli.Smartcli) {
};
const updateResponse = await tspmIpcClient.request('update', {
id,
id: resolved.id,
updates,
});
@@ -73,4 +72,3 @@ export function registerEditCommand(smartcli: plugins.smartcli.Smartcli) {
{ actionLabel: 'edit process config' },
);
}

View File

@@ -11,10 +11,10 @@ export function registerLogsCommand(smartcli: plugins.smartcli.Smartcli) {
smartcli,
'logs',
async (argvArg: CliArguments) => {
const id = argvArg._[1];
if (!id) {
console.error('Error: Please provide a process ID');
console.log('Usage: tspm logs <id> [options]');
const target = argvArg._[1];
if (!target) {
console.error('Error: Please provide a process target');
console.log('Usage: tspm logs <id | id:N | name:LABEL> [options]');
console.log('\nOptions:');
console.log(' --lines <n> Number of lines to show (default: 50)');
console.log(' --follow Stream logs in real-time (like tail -f)');
@@ -24,6 +24,8 @@ export function registerLogsCommand(smartcli: plugins.smartcli.Smartcli) {
const lines = getNumber(argvArg, 'lines', 50);
const follow = getBool(argvArg, 'follow', 'f');
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
const id = resolved.id;
const response = await tspmIpcClient.request('getLogs', { id, lines });
if (!follow) {
@@ -44,7 +46,7 @@ export function registerLogsCommand(smartcli: plugins.smartcli.Smartcli) {
}
// Streaming mode
console.log(`Logs for process: ${id} (streaming...)`);
console.log(`Logs for process: ${resolved.name || id} (streaming...)`);
console.log('─'.repeat(60));
let lastSeq = 0;

View File

@@ -1,6 +1,5 @@
import * as plugins from '../../plugins.js';
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
import { toProcessId } from '../../../shared/protocol/id.js';
import type { CliArguments } from '../../types.js';
import { registerIpcCommand } from '../../registration/index.js';
@@ -11,9 +10,9 @@ export function registerRestartCommand(smartcli: plugins.smartcli.Smartcli) {
async (argvArg: CliArguments) => {
const arg = argvArg._[1];
if (!arg) {
console.error('Error: Please provide a process ID or "all"');
console.error('Error: Please provide a process target or "all"');
console.log('Usage:');
console.log(' tspm restart <id>');
console.log(' tspm restart <id | id:N | name:LABEL>');
console.log(' tspm restart all');
return;
}
@@ -33,12 +32,13 @@ export function registerRestartCommand(smartcli: plugins.smartcli.Smartcli) {
return;
}
const id = String(arg);
console.log(`Restarting process: ${id}`);
const response = await tspmIpcClient.request('restart', { id: toProcessId(id) });
const target = String(arg);
console.log(`Restarting process: ${target}`);
const resolved = await tspmIpcClient.request('resolveTarget', { target });
const response = await tspmIpcClient.request('restart', { id: resolved.id });
console.log(`✓ Process restarted successfully`);
console.log(` ID: ${response.processId}`);
console.log(` ID: ${response.processId}${resolved.name ? ` (name: ${resolved.name})` : ''}`);
console.log(` PID: ${response.pid || 'N/A'}`);
console.log(` Status: ${response.status}`);
},

View File

@@ -0,0 +1,62 @@
import * as plugins from '../../plugins.js';
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
import type { CliArguments } from '../../types.js';
import { registerIpcCommand } from '../../registration/index.js';
export function registerSearchCommand(smartcli: plugins.smartcli.Smartcli) {
registerIpcCommand(
smartcli,
'search',
async (argvArg: CliArguments) => {
const query = String(argvArg._[1] || '').trim();
if (!query) {
console.error('Error: Please provide a search query');
console.log('Usage: tspm search <name-fragment | id-fragment>');
return;
}
// Fetch list of processes, then enrich with names via describe
const listRes = await tspmIpcClient.request('list', {});
const processes = listRes.processes;
// If there are no processes, short-circuit
if (processes.length === 0) {
console.log('No processes found.');
return;
}
const lowerQ = query.toLowerCase();
const matches: Array<{ id: number; name?: string }> = [];
// Collect describe calls to obtain names
for (const proc of processes) {
try {
const desc = await tspmIpcClient.request('describe', { id: proc.id });
const name = desc.config.name || '';
const idStr = String(proc.id);
if (name.toLowerCase().includes(lowerQ) || idStr.includes(query)) {
matches.push({ id: proc.id, name });
}
} catch {
// Ignore describe errors for individual processes
}
}
if (matches.length === 0) {
console.log(`No matches for "${query}"`);
return;
}
console.log(`Matches for "${query}":`);
for (const m of matches) {
if (m.name) {
console.log(`- id:${m.id}\tname:${m.name}`);
} else {
console.log(`- id:${m.id}`);
}
}
},
{ actionLabel: 'search processes' },
);
}

View File

@@ -10,17 +10,18 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
smartcli,
'start',
async (argvArg: CliArguments) => {
const id = argvArg._[1];
if (!id) {
console.error('Error: Please provide a process ID to start');
console.log('Usage: tspm start <id>');
const target = argvArg._[1];
if (!target) {
console.error('Error: Please provide a process target to start');
console.log('Usage: tspm start <id | id:N | name:LABEL>');
return;
}
console.log(`Starting process id ${id}...`);
const response = await tspmIpcClient.request('startById', { id });
console.log(`Starting process: ${target}...`);
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
const response = await tspmIpcClient.request('startById', { id: resolved.id });
console.log('✓ Process started');
console.log(` ID: ${response.processId}`);
console.log(` ID: ${response.processId}${resolved.name ? ` (name: ${resolved.name})` : ''}`);
console.log(` PID: ${response.pid || 'N/A'}`);
console.log(` Status: ${response.status}`);
},

View File

@@ -8,15 +8,16 @@ export function registerStopCommand(smartcli: plugins.smartcli.Smartcli) {
smartcli,
'stop',
async (argvArg: CliArguments) => {
const id = argvArg._[1];
if (!id) {
console.error('Error: Please provide a process ID');
console.log('Usage: tspm stop <id>');
const target = argvArg._[1];
if (!target) {
console.error('Error: Please provide a process target');
console.log('Usage: tspm stop <id | id:N | name:LABEL>');
return;
}
console.log(`Stopping process: ${id}`);
const response = await tspmIpcClient.request('stop', { id });
console.log(`Stopping process: ${target}`);
const resolved = await tspmIpcClient.request('resolveTarget', { target: String(target) });
const response = await tspmIpcClient.request('stop', { id: resolved.id });
if (response.success) {
console.log(`${response.message}`);

View File

@@ -10,6 +10,7 @@ import { registerAddCommand } from './commands/process/add.js';
import { registerStopCommand } from './commands/process/stop.js';
import { registerRestartCommand } from './commands/process/restart.js';
import { registerDeleteCommand } from './commands/process/delete.js';
import { registerSearchCommand } from './commands/process/search.js';
import { registerListCommand } from './commands/process/list.js';
import { registerDescribeCommand } from './commands/process/describe.js';
import { registerLogsCommand } from './commands/process/logs.js';
@@ -74,6 +75,7 @@ export const run = async (): Promise<void> => {
registerDescribeCommand(smartcliInstance);
registerLogsCommand(smartcliInstance);
registerEditCommand(smartcliInstance);
registerSearchCommand(smartcliInstance);
// Batch commands
registerStartAllCommand(smartcliInstance);