import * as plugins from '../../plugins.js'; import { tspmIpcClient } from '../../../client/tspm.ipcclient.js'; import type { CliArguments } from '../../types.js'; import { parseMemoryString, formatMemory } from '../../helpers/memory.js'; import { registerIpcCommand } from '../../registration/index.js'; export function registerAddCommand(smartcli: plugins.smartcli.Smartcli) { registerIpcCommand( smartcli, 'add', async (argvArg: CliArguments) => { const args = argvArg._.slice(1); if (args.length === 0) { console.error('Error: Please provide a command or .ts file'); console.log('Usage: tspm add [options]'); console.log('\nOptions:'); console.log(' --name Optional name'); console.log(' --memory Memory limit (e.g., 512MB, 2GB)'); console.log(' --cwd Working directory'); console.log(' --watch Watch for file changes'); console.log(' --watch-paths Comma-separated paths'); console.log(' --autorestart Auto-restart on crash (default true)'); console.log(' -i, --interactive Enter interactive edit mode after adding'); return; } const script = args.join(' '); const projectDir = argvArg.cwd || process.cwd(); const memoryLimit = argvArg.memory ? parseMemoryString(argvArg.memory) : 512 * 1024 * 1024; // Check for interactive flag const isInteractive = argvArg.i || argvArg.interactive; // Resolve .ts single-file execution via tsx if needed const parts = script.split(' '); const first = parts[0]; let command = script; let cmdArgs: string[] | undefined; if (parts.length === 1 && first.endsWith('.ts')) { try { const { createRequire } = await import('module'); const require = createRequire(import.meta.url); const tsxPath = require.resolve('tsx/dist/cli.mjs'); const filePath = plugins.path.isAbsolute(first) ? first : plugins.path.join(projectDir, first); command = tsxPath; cmdArgs = [filePath]; } catch { command = 'tsx'; cmdArgs = [first]; } } const name = argvArg.name || script; const watch = argvArg.watch || false; const autorestart = argvArg.autorestart !== false; const watchPaths = argvArg.watchPaths ? typeof argvArg.watchPaths === 'string' ? (argvArg.watchPaths as string).split(',') : argvArg.watchPaths : undefined; console.log('Adding process configuration:'); console.log(` Command: ${script}${parts.length === 1 && first.endsWith('.ts') ? ' (via tsx)' : ''}`); console.log(` Directory: ${projectDir}`); console.log(` Memory limit: ${formatMemory(memoryLimit)}`); console.log(` Auto-restart: ${autorestart}`); if (watch) { console.log(` Watch: enabled`); if (watchPaths) console.log(` Watch paths: ${watchPaths.join(',')}`); } // Capture essential environment variables from the CLI environment // so processes have access to the same environment they were added with const essentialEnvVars: NodeJS.ProcessEnv = { PATH: process.env.PATH || '', HOME: process.env.HOME, USER: process.env.USER, SHELL: process.env.SHELL, LANG: process.env.LANG, LC_ALL: process.env.LC_ALL, // Node.js specific NODE_ENV: process.env.NODE_ENV, NODE_PATH: process.env.NODE_PATH, // npm/pnpm/yarn paths npm_config_prefix: process.env.npm_config_prefix, // Include any TSPM_ prefixed vars ...Object.fromEntries( Object.entries(process.env).filter(([key]) => key.startsWith('TSPM_')) ), }; // Remove undefined values Object.keys(essentialEnvVars).forEach(key => { if (essentialEnvVars[key] === undefined) { delete essentialEnvVars[key]; } }); const response = await tspmIpcClient.request('add', { config: { name, command, args: cmdArgs, projectDir, memoryLimitBytes: memoryLimit, env: essentialEnvVars, autorestart, watch, watchPaths, }, }); console.log('✓ Added'); console.log(` Assigned ID: ${response.id}`); // If interactive flag is set, enter edit mode if (isInteractive) { const { interactiveEditProcess } = await import('../../helpers/interactive-edit.js'); await interactiveEditProcess(response.id); } }, { actionLabel: 'add process config' }, ); }