fix(cli): Use server-side start-by-id flow for starting processes
This commit is contained in:
		@@ -1,5 +1,12 @@
 | 
				
			|||||||
# Changelog
 | 
					# Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-29 - 4.4.1 - fix(cli)
 | 
				
			||||||
 | 
					Use server-side start-by-id flow for starting processes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- CLI: 'tspm start <id>' now calls a new 'startById' IPC method instead of fetching the full config via 'describe' and submitting it back to 'start'.
 | 
				
			||||||
 | 
					- Daemon: Added server-side handler for 'startById' which resolves the stored process config and starts the process on the daemon.
 | 
				
			||||||
 | 
					- Protocol: Added StartByIdRequest/StartByIdResponse types and registered 'startById' in the IPC method map.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-08-29 - 4.4.0 - feat(daemon)
 | 
					## 2025-08-29 - 4.4.0 - feat(daemon)
 | 
				
			||||||
Persist desired process states and add daemon restart command
 | 
					Persist desired process states and add daemon restart command
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,6 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const commitinfo = {
 | 
					export const commitinfo = {
 | 
				
			||||||
  name: '@git.zone/tspm',
 | 
					  name: '@git.zone/tspm',
 | 
				
			||||||
  version: '4.4.0',
 | 
					  version: '4.4.1',
 | 
				
			||||||
  description: 'a no fuzz process manager'
 | 
					  description: 'a no fuzz process manager'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,14 +17,8 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
 | 
				
			|||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const desc = await tspmIpcClient.request('describe', { id }).catch(() => null);
 | 
					      console.log(`Starting process id ${id}...`);
 | 
				
			||||||
      if (!desc) {
 | 
					      const response = await tspmIpcClient.request('startById', { id });
 | 
				
			||||||
        console.error(`Process with id '${id}' not found. Use 'tspm add' first.`);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      console.log(`Starting process id ${id} (${desc.config.name || id})...`);
 | 
					 | 
				
			||||||
      const response = await tspmIpcClient.request('start', { config: desc.config });
 | 
					 | 
				
			||||||
      console.log('✓ Process started');
 | 
					      console.log('✓ Process started');
 | 
				
			||||||
      console.log(`  ID: ${response.processId}`);
 | 
					      console.log(`  ID: ${response.processId}`);
 | 
				
			||||||
      console.log(`  PID: ${response.pid || 'N/A'}`);
 | 
					      console.log(`  PID: ${response.pid || 'N/A'}`);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import * as plugins from './plugins.js';
 | 
					import * as plugins from './plugins.js';
 | 
				
			||||||
 | 
					import { tspmIpcClient } from '../client/tspm.ipcclient.js';
 | 
				
			||||||
import * as paths from '../paths.js';
 | 
					import * as paths from '../paths.js';
 | 
				
			||||||
import { Logger, LogLevel } from '../shared/common/utils.errorhandler.js';
 | 
					import { Logger, LogLevel } from '../shared/common/utils.errorhandler.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -38,6 +39,22 @@ export const run = async (): Promise<void> => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const smartcliInstance = new plugins.smartcli.Smartcli();
 | 
					  const smartcliInstance = new plugins.smartcli.Smartcli();
 | 
				
			||||||
 | 
					  // Intercept -v/--version to show CLI and daemon versions
 | 
				
			||||||
 | 
					  const args = process.argv.slice(2);
 | 
				
			||||||
 | 
					  if (args.includes('-v') || args.includes('--version')) {
 | 
				
			||||||
 | 
					    const cliVersion = tspmProjectinfo.npm.version;
 | 
				
			||||||
 | 
					    console.log(`tspm CLI: ${cliVersion}`);
 | 
				
			||||||
 | 
					    const status = await tspmIpcClient.getDaemonStatus();
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `Daemon: running v${status.version || 'unknown'} (pid ${status.pid})`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      console.log('Daemon: not running');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return; // do not start parser
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // Keep Smartcli version info for help output but not used for -v now
 | 
				
			||||||
  smartcliInstance.addVersion(tspmProjectinfo.npm.version);
 | 
					  smartcliInstance.addVersion(tspmProjectinfo.npm.version);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Register all commands
 | 
					  // Register all commands
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,12 +20,20 @@ export class TspmDaemon {
 | 
				
			|||||||
  private socketPath: string;
 | 
					  private socketPath: string;
 | 
				
			||||||
  private heartbeatInterval: NodeJS.Timeout | null = null;
 | 
					  private heartbeatInterval: NodeJS.Timeout | null = null;
 | 
				
			||||||
  private daemonPidFile: string;
 | 
					  private daemonPidFile: string;
 | 
				
			||||||
 | 
					  private version: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    this.tspmInstance = new ProcessManager();
 | 
					    this.tspmInstance = new ProcessManager();
 | 
				
			||||||
    this.socketPath = plugins.path.join(paths.tspmDir, 'tspm.sock');
 | 
					    this.socketPath = plugins.path.join(paths.tspmDir, 'tspm.sock');
 | 
				
			||||||
    this.daemonPidFile = plugins.path.join(paths.tspmDir, 'daemon.pid');
 | 
					    this.daemonPidFile = plugins.path.join(paths.tspmDir, 'daemon.pid');
 | 
				
			||||||
    this.startTime = Date.now();
 | 
					    this.startTime = Date.now();
 | 
				
			||||||
 | 
					    // Determine daemon version from package metadata
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const proj = new plugins.projectinfo.ProjectInfo(paths.packageDir);
 | 
				
			||||||
 | 
					      this.version = proj.npm.version || 'unknown';
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      this.version = 'unknown';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -128,6 +136,29 @@ export class TspmDaemon {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Start by id (resolve config on server)
 | 
				
			||||||
 | 
					    this.ipcServer.onMessage(
 | 
				
			||||||
 | 
					      'startById',
 | 
				
			||||||
 | 
					      async (request: RequestForMethod<'startById'>) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const config = this.tspmInstance.processConfigs.get(request.id);
 | 
				
			||||||
 | 
					          if (!config) {
 | 
				
			||||||
 | 
					            throw new Error(`Process ${request.id} not found`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          await this.tspmInstance.setDesiredState(request.id, 'online');
 | 
				
			||||||
 | 
					          await this.tspmInstance.start(config);
 | 
				
			||||||
 | 
					          const processInfo = this.tspmInstance.processInfo.get(request.id);
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            processId: request.id,
 | 
				
			||||||
 | 
					            pid: processInfo?.pid,
 | 
				
			||||||
 | 
					            status: processInfo?.status || 'stopped',
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          throw new Error(`Failed to start process: ${error.message}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.ipcServer.onMessage(
 | 
					    this.ipcServer.onMessage(
 | 
				
			||||||
      'stop',
 | 
					      'stop',
 | 
				
			||||||
      async (request: RequestForMethod<'stop'>) => {
 | 
					      async (request: RequestForMethod<'stop'>) => {
 | 
				
			||||||
@@ -321,6 +352,7 @@ export class TspmDaemon {
 | 
				
			|||||||
          processCount: this.tspmInstance.processes.size,
 | 
					          processCount: this.tspmInstance.processes.size,
 | 
				
			||||||
          memoryUsage: memUsage.heapUsed,
 | 
					          memoryUsage: memUsage.heapUsed,
 | 
				
			||||||
          cpuUsage: process.cpuUsage().user / 1000000, // Convert to seconds
 | 
					          cpuUsage: process.cpuUsage().user / 1000000, // Convert to seconds
 | 
				
			||||||
 | 
					          version: this.version,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,6 +66,17 @@ export interface StartResponse {
 | 
				
			|||||||
  status: 'online' | 'stopped' | 'errored';
 | 
					  status: 'online' | 'stopped' | 'errored';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Start by id (server resolves config)
 | 
				
			||||||
 | 
					export interface StartByIdRequest {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface StartByIdResponse {
 | 
				
			||||||
 | 
					  processId: string;
 | 
				
			||||||
 | 
					  pid?: number;
 | 
				
			||||||
 | 
					  status: 'online' | 'stopped' | 'errored';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Stop command
 | 
					// Stop command
 | 
				
			||||||
export interface StopRequest {
 | 
					export interface StopRequest {
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
@@ -191,6 +202,7 @@ export interface DaemonStatusResponse {
 | 
				
			|||||||
  processCount: number;
 | 
					  processCount: number;
 | 
				
			||||||
  memoryUsage?: number;
 | 
					  memoryUsage?: number;
 | 
				
			||||||
  cpuUsage?: number;
 | 
					  cpuUsage?: number;
 | 
				
			||||||
 | 
					  version?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Daemon shutdown command
 | 
					// Daemon shutdown command
 | 
				
			||||||
@@ -238,6 +250,7 @@ export interface RemoveResponse {
 | 
				
			|||||||
// Type mappings for methods
 | 
					// Type mappings for methods
 | 
				
			||||||
export type IpcMethodMap = {
 | 
					export type IpcMethodMap = {
 | 
				
			||||||
  start: { request: StartRequest; response: StartResponse };
 | 
					  start: { request: StartRequest; response: StartResponse };
 | 
				
			||||||
 | 
					  startById: { request: StartByIdRequest; response: StartByIdResponse };
 | 
				
			||||||
  stop: { request: StopRequest; response: StopResponse };
 | 
					  stop: { request: StopRequest; response: StopResponse };
 | 
				
			||||||
  restart: { request: RestartRequest; response: RestartResponse };
 | 
					  restart: { request: RestartRequest; response: RestartResponse };
 | 
				
			||||||
  delete: { request: DeleteRequest; response: DeleteResponse };
 | 
					  delete: { request: DeleteRequest; response: DeleteResponse };
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user