feat(cli): Preserve CLI environment when adding processes, simplify edit flow, and refresh README docs

This commit is contained in:
2025-08-30 15:11:38 +00:00
parent 2b57251f47
commit 699d07ea36
5 changed files with 368 additions and 198 deletions

View File

@@ -69,6 +69,33 @@ export function registerAddCommand(smartcli: plugins.smartcli.Smartcli) {
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,
@@ -76,9 +103,7 @@ export function registerAddCommand(smartcli: plugins.smartcli.Smartcli) {
args: cmdArgs,
projectDir,
memoryLimitBytes: memoryLimit,
// Persist the PATH from the current CLI environment so managed
// processes see the same PATH they had when added.
env: { PATH: process.env.PATH || '' },
env: essentialEnvVars,
autorestart,
watch,
watchPaths,

View File

@@ -21,95 +21,54 @@ export function registerEditCommand(smartcli: plugins.smartcli.Smartcli) {
// Load current config
const { config } = await tspmIpcClient.request('describe', { id });
const si = plugins.smartinteract;
// 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_'))
),
};
const answers: any = {};
answers.name = await si.question.text(
`Name [${config.name || ''}]`,
config.name || '',
);
answers.command = await si.question.text(
`Command [${config.command}]`,
config.command,
);
const currentArgs = (config.args || []).join(' ');
const argsStr = await si.question.text(
`Args (space separated) [${currentArgs}]`,
currentArgs,
);
answers.args = argsStr.trim() ? argsStr.split(/\s+/) : [];
answers.projectDir = await si.question.text(
`Working directory [${config.projectDir}]`,
config.projectDir,
);
const memStrDefault = formatMemory(config.memoryLimitBytes);
const memStr = await si.question.text(
`Memory limit [${memStrDefault}]`,
memStrDefault,
);
answers.memoryLimitBytes = parseMemoryString(memStr || memStrDefault);
answers.autorestart = await si.question.confirm(
`Autorestart? [${config.autorestart ? 'Y' : 'N'}]`,
!!config.autorestart,
);
const watchEnabled = await si.question.confirm(
`Watch for changes? [${config.watch ? 'Y' : 'N'}]`,
!!config.watch,
);
answers.watch = watchEnabled;
if (watchEnabled) {
const existingWatch = (config.watchPaths || []).join(',');
const watchStr = await si.question.text(
`Watch paths (comma separated) [${existingWatch}]`,
existingWatch,
);
answers.watchPaths = watchStr
.split(',')
.map((s) => s.trim())
.filter(Boolean);
}
// Remove undefined values
Object.keys(essentialEnvVars).forEach(key => {
if (essentialEnvVars[key] === undefined) {
delete essentialEnvVars[key];
}
});
const replacePath = await si.question.confirm(
'Replace stored PATH with current PATH?',
false,
);
// Update environment variables
const updates = {
env: { ...(config.env || {}), ...essentialEnvVars }
};
const updates: any = {};
if (answers.name !== config.name) updates.name = answers.name;
if (answers.command !== config.command) updates.command = answers.command;
if (JSON.stringify(answers.args) !== JSON.stringify(config.args || []))
updates.args = answers.args.length ? answers.args : undefined;
if (answers.projectDir !== config.projectDir)
updates.projectDir = answers.projectDir;
if (answers.memoryLimitBytes !== config.memoryLimitBytes)
updates.memoryLimitBytes = answers.memoryLimitBytes;
if (answers.autorestart !== config.autorestart)
updates.autorestart = answers.autorestart;
if (answers.watch !== config.watch) updates.watch = answers.watch;
if (answers.watch && JSON.stringify(answers.watchPaths || []) !== JSON.stringify(config.watchPaths || []))
updates.watchPaths = answers.watchPaths;
if (replacePath) {
updates.env = { ...(config.env || {}), PATH: process.env.PATH || '' };
}
if (Object.keys(updates).length === 0) {
console.log('No changes. Nothing to update.');
return;
}
const { config: newConfig } = await tspmIpcClient.request('update', {
const updateResponse = await tspmIpcClient.request('update', {
id,
updates,
} as any);
});
console.log('✓ Updated process configuration');
console.log(` ID: ${newConfig.id}`);
console.log(` Command: ${newConfig.command}`);
console.log(` CWD: ${newConfig.projectDir}`);
if (newConfig.env?.PATH) {
console.log(' PATH: [stored]');
}
console.log('✓ Environment variables updated');
console.log(' Process configuration updated successfully');
},
{ actionLabel: 'edit process config' },
);