diff --git a/.tspm_home/.npmextra/kv/@git.zone__tspm.json b/.tspm_home/.npmextra/kv/@git.zone__tspm.json deleted file mode 100644 index e69de29..0000000 diff --git a/changelog.md b/changelog.md index 4be5b27..2443fdd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-08-31 - 5.9.0 - feat(cli) +Add interactive edit flow to CLI and improve UX + +- Add -i / --interactive flag to tspm add to open an interactive editor immediately after adding a process +- Implement interactiveEditProcess helper (smartinteract-based) to provide interactive editing for process configs +- Enable tspm edit to launch the interactive editor (replaces prior placeholder flow) +- Improve user-facing message when no processes are configured in tspm list +- Lower verbosity for missing saved configs on daemon startup (changed logger.info → logger.debug) + ## 2025-08-31 - 5.8.0 - feat(core) Add core TypeScript TSPM implementation: CLI, daemon, client, process management and tests diff --git a/readme.md b/readme.md index 2977247..2430a24 100644 --- a/readme.md +++ b/readme.md @@ -72,6 +72,7 @@ Add a new process configuration without starting it. This is the recommended way - `--watch` - Enable file watching for auto-restart - `--watch-paths ` - Comma-separated paths to watch - `--autorestart` - Auto-restart on crash (default: true) +- `-i, --interactive` - Enter interactive edit mode after adding **Examples:** ```bash @@ -86,6 +87,9 @@ tspm add "tsx watch src/index.ts" --name dev-server --watch --watch-paths "src,c # Add without auto-restart tspm add "node worker.js" --name one-time-job --autorestart false + +# Add and immediately edit interactively +tspm add "node server.js" --name api -i ``` #### `tspm start ` diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index c9e6b48..11ebbbb 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tspm', - version: '5.8.0', + version: '5.9.0', description: 'a no fuzz process manager' } diff --git a/ts/cli/commands/process/add.ts b/ts/cli/commands/process/add.ts index 66180ad..4451bf3 100644 --- a/ts/cli/commands/process/add.ts +++ b/ts/cli/commands/process/add.ts @@ -20,6 +20,7 @@ export function registerAddCommand(smartcli: plugins.smartcli.Smartcli) { 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; } @@ -29,6 +30,9 @@ export function registerAddCommand(smartcli: plugins.smartcli.Smartcli) { ? 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]; @@ -112,6 +116,12 @@ export function registerAddCommand(smartcli: plugins.smartcli.Smartcli) { 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' }, ); diff --git a/ts/cli/commands/process/edit.ts b/ts/cli/commands/process/edit.ts index 8d2dea6..3d8d3fa 100644 --- a/ts/cli/commands/process/edit.ts +++ b/ts/cli/commands/process/edit.ts @@ -16,58 +16,12 @@ export function registerEditCommand(smartcli: plugins.smartcli.Smartcli) { return; } - // Resolve and load current config + // Resolve the target to get the process ID 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.'); - console.log('Current configuration:'); - console.log(` Name: ${config.name}`); - console.log(` Command: ${config.command}`); - console.log(` Directory: ${config.projectDir}`); - console.log(` Memory: ${formatMemory(config.memoryLimitBytes)}`); - console.log(` Auto-restart: ${config.autorestart}`); - console.log(` Watch: ${config.watch ? 'enabled' : 'disabled'}`); - // For now, just update environment variables to current - 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]; - } - }); - - // Update environment variables - const updates = { - env: { ...(config.env || {}), ...essentialEnvVars } - }; - - const updateResponse = await tspmIpcClient.request('update', { - id: resolved.id, - updates, - }); - - console.log('āœ“ Environment variables updated'); - console.log(' Process configuration updated successfully'); + // Use the shared interactive edit function + const { interactiveEditProcess } = await import('../../helpers/interactive-edit.js'); + await interactiveEditProcess(resolved.id); }, { actionLabel: 'edit process config' }, ); diff --git a/ts/cli/commands/process/list.ts b/ts/cli/commands/process/list.ts index ffbf4c9..2d2ebe4 100644 --- a/ts/cli/commands/process/list.ts +++ b/ts/cli/commands/process/list.ts @@ -14,7 +14,9 @@ export function registerListCommand(smartcli: plugins.smartcli.Smartcli) { const processes = response.processes; if (processes.length === 0) { - console.log('No processes running.'); + console.log('No processes configured.'); + console.log('Use "tspm add " to add one, e.g.:'); + console.log(' tspm add "pnpm start"'); return; } diff --git a/ts/cli/helpers/interactive-edit.ts b/ts/cli/helpers/interactive-edit.ts new file mode 100644 index 0000000..d0cfacc --- /dev/null +++ b/ts/cli/helpers/interactive-edit.ts @@ -0,0 +1,164 @@ +import * as plugins from '../plugins.js'; +import { tspmIpcClient } from '../../client/tspm.ipcclient.js'; +import { formatMemory, parseMemoryString } from './memory.js'; + +export async function interactiveEditProcess(processId: number): Promise { + // Load current config + const { config } = await tspmIpcClient.request('describe', { id: processId as any }); + + // Create interactive prompts for editing + const smartInteract = new plugins.smartinteract.SmartInteract([ + { + name: 'name', + type: 'input', + message: 'Process name:', + default: config.name, + validate: (input: string) => { + return input && input.trim() !== ''; + } + }, + { + name: 'command', + type: 'input', + message: 'Command to execute:', + default: config.command, + validate: (input: string) => { + return input && input.trim() !== ''; + } + }, + { + name: 'projectDir', + type: 'input', + message: 'Working directory:', + default: config.projectDir, + validate: (input: string) => { + return input && input.trim() !== ''; + } + }, + { + name: 'memoryLimit', + type: 'input', + message: 'Memory limit (e.g., 512M, 1G):', + default: formatMemory(config.memoryLimitBytes), + validate: (input: string) => { + const parsed = parseMemoryString(input); + return parsed !== null; + } + }, + { + name: 'autorestart', + type: 'confirm', + message: 'Enable auto-restart on failure?', + default: config.autorestart + }, + { + name: 'watch', + type: 'confirm', + message: 'Enable file watching for auto-restart?', + default: config.watch || false + }, + { + name: 'updateEnv', + type: 'confirm', + message: 'Update environment variables to current environment?', + default: true + } + ]); + + console.log('\nšŸ“ Edit Process Configuration'); + console.log(` Process ID: ${processId}`); + console.log(' (Press Enter to keep current values)\n'); + + // Run the interactive prompts + const answerBucket = await smartInteract.runQueue(); + + // Get answers from the bucket + const name = answerBucket.getAnswerFor('name'); + const command = answerBucket.getAnswerFor('command'); + const projectDir = answerBucket.getAnswerFor('projectDir'); + const memoryLimit = answerBucket.getAnswerFor('memoryLimit'); + const autorestart = answerBucket.getAnswerFor('autorestart'); + const watch = answerBucket.getAnswerFor('watch'); + const updateEnv = answerBucket.getAnswerFor('updateEnv'); + + // Prepare updates object + const updates: any = {}; + + // Check what has changed + if (name !== config.name) { + updates.name = name; + } + + if (command !== config.command) { + updates.command = command; + } + + if (projectDir !== config.projectDir) { + updates.projectDir = projectDir; + } + + const newMemoryBytes = parseMemoryString(memoryLimit); + if (newMemoryBytes !== config.memoryLimitBytes) { + updates.memoryLimitBytes = newMemoryBytes; + } + + if (autorestart !== config.autorestart) { + updates.autorestart = autorestart; + } + + if (watch !== config.watch) { + updates.watch = watch; + } + + // Handle environment variables update if requested + if (updateEnv) { + 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]; + } + }); + + updates.env = { ...(config.env || {}), ...essentialEnvVars }; + } + + // Only update if there are changes + if (Object.keys(updates).length === 0) { + console.log('\nāœ“ No changes made'); + return; + } + + // Send updates to daemon + await tspmIpcClient.request('update', { + id: processId as any, + updates, + }); + + // Display what was updated + console.log('\nāœ“ Process configuration updated successfully'); + if (updates.name) console.log(` Name: ${updates.name}`); + if (updates.command) console.log(` Command: ${updates.command}`); + if (updates.projectDir) console.log(` Directory: ${updates.projectDir}`); + if (updates.memoryLimitBytes) console.log(` Memory limit: ${formatMemory(updates.memoryLimitBytes)}`); + if (updates.autorestart !== undefined) console.log(` Auto-restart: ${updates.autorestart}`); + if (updates.watch !== undefined) console.log(` Watch: ${updates.watch ? 'enabled' : 'disabled'}`); + if (updateEnv) console.log(' Environment variables: updated'); +} \ No newline at end of file diff --git a/ts/daemon/processmanager.ts b/ts/daemon/processmanager.ts index aad5bac..9248936 100644 --- a/ts/daemon/processmanager.ts +++ b/ts/daemon/processmanager.ts @@ -739,7 +739,8 @@ export class ProcessManager extends EventEmitter { throw configError; } } else { - this.logger.info('No saved process configurations found'); + // First run / no configs yet — keep this quiet unless debugging + this.logger.debug('No saved process configurations found'); } } catch (error: Error | unknown) { // Only throw if it's not the "no configs found" case @@ -748,9 +749,7 @@ export class ProcessManager extends EventEmitter { } // If no configs found or error reading, just continue with empty configs - this.logger.info( - 'No saved process configurations found or error reading them', - ); + this.logger.debug('No saved process configurations found or error reading them'); } }