jay-code
Version:
Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability
336 lines (285 loc) • 9.95 kB
text/typescript
/**
* Environment Detection Utility for Jay-Code v2.0
* Detects execution environment and recommends appropriate flags
*/
import chalk from 'chalk';
export interface ExecutionEnvironment {
isInteractive: boolean;
isVSCode: boolean;
isVSCodeInsiders: boolean;
isCI: boolean;
isDocker: boolean;
isSSH: boolean;
isGitBash: boolean;
isWindowsTerminal: boolean;
isWSL: boolean;
isWindows: boolean;
supportsRawMode: boolean;
supportsColor: boolean;
terminalType: string;
recommendedFlags: string[];
warnings: string[];
}
export interface EnvironmentOptions {
skipWarnings?: boolean;
autoApplyDefaults?: boolean;
verbose?: boolean;
}
/**
* Detects the current execution environment
*/
export function detectExecutionEnvironment(options: EnvironmentOptions = {}): ExecutionEnvironment {
const env: ExecutionEnvironment = {
isInteractive: false,
isVSCode: false,
isVSCodeInsiders: false,
isCI: false,
isDocker: false,
isSSH: false,
isGitBash: false,
isWindowsTerminal: false,
isWSL: false,
isWindows: false,
supportsRawMode: false,
supportsColor: true,
terminalType: 'unknown',
recommendedFlags: [],
warnings: [],
};
// Basic TTY check
env.isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
// Terminal program detection
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() || '';
env.isVSCode = termProgram === 'vscode';
env.isVSCodeInsiders = termProgram === 'vscode-insiders';
env.terminalType = termProgram || process.env.TERM || 'unknown';
// CI environment detection
env.isCI = Boolean(
process.env.CI ||
process.env.GITHUB_ACTIONS ||
process.env.GITLAB_CI ||
process.env.JENKINS_URL ||
process.env.CIRCLECI ||
process.env.TRAVIS ||
process.env.BUILDKITE ||
process.env.DRONE,
);
// Docker detection
env.isDocker = Boolean(
process.env.DOCKER_CONTAINER ||
existsSync('/.dockerenv') ||
(existsSync('/proc/1/cgroup') && readFileSync('/proc/1/cgroup', 'utf8').includes('docker')),
);
// SSH detection
env.isSSH = Boolean(process.env.SSH_CLIENT || process.env.SSH_TTY);
// Git Bash detection
env.isGitBash =
process.env.TERM_PROGRAM === 'mintty' || process.env.MSYSTEM?.startsWith('MINGW') || false;
// Windows Terminal detection
env.isWindowsTerminal = Boolean(process.env.WT_SESSION);
// Windows detection
env.isWindows = process.platform === 'win32';
// WSL detection
env.isWSL = Boolean(
process.env.WSL_DISTRO_NAME ||
process.env.WSL_INTEROP ||
(existsSync('/proc/version') &&
readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft')),
);
// Raw mode support check
env.supportsRawMode = checkRawModeSupport();
// Color support check
env.supportsColor =
process.env.NO_COLOR !== '1' &&
process.env.TERM !== 'dumb' &&
(process.stdout.isTTY || process.env.FORCE_COLOR === '1');
// Generate recommendations based on environment
generateRecommendations(env);
// Show warnings if requested
if (!options.skipWarnings && env.warnings.length > 0) {
showEnvironmentWarnings(env);
}
return env;
}
/**
* Checks if raw mode is supported
*/
function checkRawModeSupport(): boolean {
try {
if (!process.stdin.isTTY) return false;
if (typeof process.stdin.setRawMode !== 'function') return false;
// Try to set raw mode and immediately restore
const originalRawMode = process.stdin.isRaw;
process.stdin.setRawMode(true);
process.stdin.setRawMode(originalRawMode);
return true;
} catch {
return false;
}
}
/**
* Generates recommendations based on environment
*/
function generateRecommendations(env: ExecutionEnvironment): void {
// VS Code specific recommendations
if (env.isVSCode || env.isVSCodeInsiders) {
env.recommendedFlags.push('--dangerously-skip-permissions');
env.recommendedFlags.push('--non-interactive');
env.warnings.push('VS Code integrated terminal detected - interactive features may be limited');
}
// CI environment recommendations
if (env.isCI) {
env.recommendedFlags.push('--dangerously-skip-permissions');
env.recommendedFlags.push('--non-interactive');
env.recommendedFlags.push('--json');
env.warnings.push('CI environment detected - running in non-interactive mode');
}
// Docker recommendations
if (env.isDocker && !env.isInteractive) {
env.recommendedFlags.push('--dangerously-skip-permissions');
env.recommendedFlags.push('--non-interactive');
env.warnings.push('Docker container without TTY - interactive features disabled');
}
// SSH without TTY
if (env.isSSH && !env.isInteractive) {
env.recommendedFlags.push('--dangerously-skip-permissions');
env.warnings.push('SSH session without TTY - consider using ssh -t');
}
// Git Bash specific
if (env.isGitBash) {
env.warnings.push('Git Bash detected - some interactive features may not work correctly');
}
// WSL specific recommendations
if (env.isWSL) {
env.recommendedFlags.push('--no-interactive');
env.warnings.push('WSL detected - raw mode may cause hangs, using non-interactive mode');
if (!env.supportsRawMode) {
env.warnings.push('WSL subprocess context detected - interactive features disabled');
}
}
// Windows specific recommendations
if (env.isWindows && !env.isWSL) {
env.recommendedFlags.push('--compatible-ui');
env.warnings.push('Native Windows detected - using compatible UI mode');
}
// Raw mode not supported
if (!env.supportsRawMode && env.isInteractive) {
env.recommendedFlags.push('--compatible-ui');
env.warnings.push('Terminal does not support raw mode - using compatible UI');
}
}
/**
* Shows environment warnings to the user
*/
function showEnvironmentWarnings(env: ExecutionEnvironment): void {
if (env.warnings.length === 0) return;
console.log(chalk.yellow('\n⚠️ Environment Detection:'));
env.warnings.forEach((warning) => {
console.log(chalk.gray(` • ${warning}`));
});
if (env.recommendedFlags.length > 0) {
console.log(chalk.cyan('\n💡 Recommended flags for your environment:'));
console.log(chalk.gray(` ${env.recommendedFlags.join(' ')}`));
}
console.log(); // Empty line for spacing
}
/**
* Applies smart defaults based on environment
*/
export function applySmartDefaults<T extends Record<string, any>>(
options: T,
env?: ExecutionEnvironment,
): T & { appliedDefaults: string[]; skipPermissions?: boolean; dangerouslySkipPermissions?: boolean; nonInteractive?: boolean; json?: boolean; noColor?: boolean; } {
const environment = env || detectExecutionEnvironment({ skipWarnings: true });
const appliedDefaults: string[] = [];
const enhanced = { ...options, appliedDefaults } as T & { appliedDefaults: string[]; skipPermissions?: boolean; dangerouslySkipPermissions?: boolean; nonInteractive?: boolean; json?: boolean; noColor?: boolean; };
// Apply defaults based on environment
if (
(environment.isVSCode || environment.isCI || !environment.supportsRawMode) &&
!options.hasOwnProperty('skipPermissions')
) {
enhanced.skipPermissions = true;
enhanced.dangerouslySkipPermissions = true;
appliedDefaults.push('--dangerously-skip-permissions');
}
if (
(environment.isCI || !environment.isInteractive) &&
!options.hasOwnProperty('nonInteractive')
) {
enhanced.nonInteractive = true;
appliedDefaults.push('--non-interactive');
}
if (environment.isCI && !options.hasOwnProperty('json')) {
enhanced.json = true;
appliedDefaults.push('--json');
}
if (!environment.supportsColor && !options.hasOwnProperty('noColor')) {
enhanced.noColor = true;
appliedDefaults.push('--no-color');
}
// Log applied defaults if verbose
if (options.verbose && appliedDefaults.length > 0) {
console.log(chalk.gray(`ℹ️ Auto-applied flags: ${appliedDefaults.join(' ')}`));
}
return enhanced;
}
/**
* Gets a human-readable environment description
*/
export function getEnvironmentDescription(env?: ExecutionEnvironment): string {
const environment = env || detectExecutionEnvironment({ skipWarnings: true });
const parts: string[] = [];
if (environment.isVSCode) parts.push('VS Code');
if (environment.isCI) parts.push('CI');
if (environment.isDocker) parts.push('Docker');
if (environment.isSSH) parts.push('SSH');
if (environment.isGitBash) parts.push('Git Bash');
if (environment.isWindowsTerminal) parts.push('Windows Terminal');
if (environment.isWSL) parts.push('WSL');
if (environment.isWindows && !environment.isWSL) parts.push('Windows');
if (parts.length === 0) {
parts.push(environment.terminalType);
}
const features: string[] = [];
if (environment.isInteractive) features.push('interactive');
if (environment.supportsRawMode) features.push('raw mode');
if (environment.supportsColor) features.push('color');
return `${parts.join('/')} (${features.join(', ')})`;
}
/**
* Checks if we should use non-interactive mode
*/
export function shouldUseNonInteractiveMode(options?: { force?: boolean }): boolean {
if (options?.force) return true;
const env = detectExecutionEnvironment({ skipWarnings: true });
return (
!env.isInteractive ||
env.isCI ||
env.isVSCode ||
env.isWSL ||
env.isWindows ||
!env.supportsRawMode
);
}
// Helper functions (these would normally be imported)
function existsSync(path: string): boolean {
try {
require('fs').accessSync(path);
return true;
} catch {
return false;
}
}
function readFileSync(path: string, encoding: string): string {
try {
return require('fs').readFileSync(path, encoding);
} catch {
return '';
}
}
export default {
detectExecutionEnvironment,
applySmartDefaults,
getEnvironmentDescription,
shouldUseNonInteractiveMode,
};