commandmate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.env.example +64 -0
  2. package/LICENSE +21 -0
  3. package/README.md +148 -0
  4. package/bin/commandmate.js +7 -0
  5. package/dist/cli/commands/init.d.ts +11 -0
  6. package/dist/cli/commands/init.d.ts.map +1 -0
  7. package/dist/cli/commands/init.js +126 -0
  8. package/dist/cli/commands/start.d.ts +11 -0
  9. package/dist/cli/commands/start.d.ts.map +1 -0
  10. package/dist/cli/commands/start.js +117 -0
  11. package/dist/cli/commands/status.d.ts +10 -0
  12. package/dist/cli/commands/status.d.ts.map +1 -0
  13. package/dist/cli/commands/status.js +55 -0
  14. package/dist/cli/commands/stop.d.ts +11 -0
  15. package/dist/cli/commands/stop.d.ts.map +1 -0
  16. package/dist/cli/commands/stop.js +82 -0
  17. package/dist/cli/config/cli-dependencies.d.ts +23 -0
  18. package/dist/cli/config/cli-dependencies.d.ts.map +1 -0
  19. package/dist/cli/config/cli-dependencies.js +65 -0
  20. package/dist/cli/index.d.ts +6 -0
  21. package/dist/cli/index.d.ts.map +1 -0
  22. package/dist/cli/index.js +64 -0
  23. package/dist/cli/types/index.d.ts +124 -0
  24. package/dist/cli/types/index.d.ts.map +1 -0
  25. package/dist/cli/types/index.js +20 -0
  26. package/dist/cli/utils/daemon.d.ts +39 -0
  27. package/dist/cli/utils/daemon.d.ts.map +1 -0
  28. package/dist/cli/utils/daemon.js +141 -0
  29. package/dist/cli/utils/env-setup.d.ts +62 -0
  30. package/dist/cli/utils/env-setup.d.ts.map +1 -0
  31. package/dist/cli/utils/env-setup.js +157 -0
  32. package/dist/cli/utils/logger.d.ts +48 -0
  33. package/dist/cli/utils/logger.d.ts.map +1 -0
  34. package/dist/cli/utils/logger.js +99 -0
  35. package/dist/cli/utils/pid-manager.d.ts +42 -0
  36. package/dist/cli/utils/pid-manager.d.ts.map +1 -0
  37. package/dist/cli/utils/pid-manager.js +111 -0
  38. package/dist/cli/utils/preflight.d.ts +34 -0
  39. package/dist/cli/utils/preflight.d.ts.map +1 -0
  40. package/dist/cli/utils/preflight.js +129 -0
  41. package/dist/cli/utils/security-logger.d.ts +29 -0
  42. package/dist/cli/utils/security-logger.d.ts.map +1 -0
  43. package/dist/cli/utils/security-logger.js +53 -0
  44. package/package.json +78 -0
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * Environment Setup Utility
4
+ * Issue #96: npm install CLI support
5
+ * Migrated from scripts/setup-env.sh
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.EnvSetup = exports.ENV_DEFAULTS = void 0;
9
+ exports.sanitizeInput = sanitizeInput;
10
+ exports.sanitizePath = sanitizePath;
11
+ exports.validatePort = validatePort;
12
+ exports.escapeEnvValue = escapeEnvValue;
13
+ const fs_1 = require("fs");
14
+ const path_1 = require("path");
15
+ const crypto_1 = require("crypto");
16
+ /**
17
+ * Default environment configuration values
18
+ * SF-4: DRY - Centralized defaults
19
+ */
20
+ exports.ENV_DEFAULTS = {
21
+ CM_PORT: 3000,
22
+ CM_BIND: '127.0.0.1',
23
+ CM_DB_PATH: './data/cm.db',
24
+ CM_LOG_LEVEL: 'info',
25
+ CM_LOG_FORMAT: 'text',
26
+ };
27
+ /**
28
+ * Sanitize input by removing control characters
29
+ * SF-SEC-3: Input sanitization
30
+ */
31
+ function sanitizeInput(input) {
32
+ // Remove control characters (0x00-0x1F and 0x7F)
33
+ return input.replace(/[\x00-\x1F\x7F]/g, '');
34
+ }
35
+ /**
36
+ * Sanitize path input
37
+ * SF-SEC-3: Path sanitization
38
+ */
39
+ function sanitizePath(input) {
40
+ const sanitized = sanitizeInput(input);
41
+ return (0, path_1.normalize)(sanitized);
42
+ }
43
+ /**
44
+ * Validate port number
45
+ * SF-SEC-3: Port validation
46
+ */
47
+ function validatePort(input) {
48
+ const port = parseInt(input, 10);
49
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
50
+ throw new Error('Port must be an integer between 1 and 65535');
51
+ }
52
+ return port;
53
+ }
54
+ /**
55
+ * Escape value for .env file
56
+ * SF-SEC-3: Safe .env value escaping
57
+ */
58
+ function escapeEnvValue(value) {
59
+ // Escape backslashes and double quotes
60
+ const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
61
+ // Quote if contains special characters
62
+ if (/[\s"'$`!]/.test(value)) {
63
+ return `"${escaped}"`;
64
+ }
65
+ return value;
66
+ }
67
+ /**
68
+ * Environment setup utility
69
+ */
70
+ class EnvSetup {
71
+ envPath;
72
+ constructor(envPath) {
73
+ this.envPath = envPath || (0, path_1.join)(process.cwd(), '.env');
74
+ }
75
+ /**
76
+ * Create .env file
77
+ * SF-SEC-1: Sets file permissions to 0600
78
+ */
79
+ async createEnvFile(config, options = {}) {
80
+ if ((0, fs_1.existsSync)(this.envPath) && !options.force) {
81
+ throw new Error('.env file already exists. Use --force to overwrite.');
82
+ }
83
+ // Build .env content
84
+ const lines = [
85
+ '# CommandMate Environment Configuration',
86
+ '# Generated by commandmate init',
87
+ '',
88
+ `CM_ROOT_DIR=${escapeEnvValue(config.CM_ROOT_DIR)}`,
89
+ `CM_PORT=${config.CM_PORT}`,
90
+ `CM_BIND=${config.CM_BIND}`,
91
+ `CM_DB_PATH=${escapeEnvValue(config.CM_DB_PATH)}`,
92
+ `CM_LOG_LEVEL=${config.CM_LOG_LEVEL}`,
93
+ `CM_LOG_FORMAT=${config.CM_LOG_FORMAT}`,
94
+ ];
95
+ if (config.CM_AUTH_TOKEN) {
96
+ lines.push(`CM_AUTH_TOKEN=${config.CM_AUTH_TOKEN}`);
97
+ }
98
+ lines.push('');
99
+ const content = lines.join('\n');
100
+ // Write with secure permissions
101
+ (0, fs_1.writeFileSync)(this.envPath, content, { mode: 0o600 });
102
+ // Ensure permissions are set (for existing file updates)
103
+ (0, fs_1.chmodSync)(this.envPath, 0o600);
104
+ }
105
+ /**
106
+ * Backup existing .env file
107
+ */
108
+ async backupExisting() {
109
+ if (!(0, fs_1.existsSync)(this.envPath)) {
110
+ return null;
111
+ }
112
+ const timestamp = Date.now();
113
+ const backupPath = `${this.envPath}.backup.${timestamp}`;
114
+ (0, fs_1.copyFileSync)(this.envPath, backupPath);
115
+ return backupPath;
116
+ }
117
+ /**
118
+ * Generate secure authentication token
119
+ */
120
+ generateAuthToken() {
121
+ return (0, crypto_1.randomBytes)(32).toString('hex');
122
+ }
123
+ /**
124
+ * Validate configuration
125
+ */
126
+ validateConfig(config) {
127
+ const errors = [];
128
+ // Validate port
129
+ if (config.CM_PORT < 1 || config.CM_PORT > 65535) {
130
+ errors.push('Invalid port: must be between 1 and 65535');
131
+ }
132
+ // Validate bind address
133
+ const validBinds = ['127.0.0.1', '0.0.0.0', 'localhost'];
134
+ if (!validBinds.includes(config.CM_BIND)) {
135
+ errors.push(`Invalid bind address: must be one of ${validBinds.join(', ')}`);
136
+ }
137
+ // Require auth token for external access
138
+ if (config.CM_BIND === '0.0.0.0' && !config.CM_AUTH_TOKEN) {
139
+ errors.push('auth token is required when binding to 0.0.0.0');
140
+ }
141
+ // Validate log level
142
+ const validLogLevels = ['debug', 'info', 'warn', 'error'];
143
+ if (!validLogLevels.includes(config.CM_LOG_LEVEL)) {
144
+ errors.push(`Invalid log level: must be one of ${validLogLevels.join(', ')}`);
145
+ }
146
+ // Validate log format
147
+ const validLogFormats = ['text', 'json'];
148
+ if (!validLogFormats.includes(config.CM_LOG_FORMAT)) {
149
+ errors.push(`Invalid log format: must be one of ${validLogFormats.join(', ')}`);
150
+ }
151
+ return {
152
+ valid: errors.length === 0,
153
+ errors,
154
+ };
155
+ }
156
+ }
157
+ exports.EnvSetup = EnvSetup;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * CLI Logger with colored output
3
+ * Issue #96: npm install CLI support
4
+ */
5
+ export interface LoggerOptions {
6
+ /** Enable verbose/debug output */
7
+ verbose?: boolean;
8
+ }
9
+ /**
10
+ * CLI Logger with colored output
11
+ */
12
+ export declare class CLILogger {
13
+ private verbose;
14
+ constructor(options?: LoggerOptions);
15
+ /**
16
+ * Log info message
17
+ */
18
+ info(message: string): void;
19
+ /**
20
+ * Log success message with checkmark
21
+ */
22
+ success(message: string): void;
23
+ /**
24
+ * Log warning message
25
+ */
26
+ warn(message: string): void;
27
+ /**
28
+ * Log error message to stderr
29
+ */
30
+ error(message: string): void;
31
+ /**
32
+ * Log debug message (only when verbose is enabled)
33
+ */
34
+ debug(message: string): void;
35
+ /**
36
+ * Print blank line
37
+ */
38
+ blank(): void;
39
+ /**
40
+ * Print formatted header
41
+ */
42
+ header(title: string): void;
43
+ /**
44
+ * Format duration in human-readable format
45
+ */
46
+ static formatDuration(seconds: number): string;
47
+ }
48
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/logger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAoBD;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAU;gBAEb,OAAO,GAAE,aAAkB;IAIvC;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI9B;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5B;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAM5B;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAO3B;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAkB/C"}
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * CLI Logger with colored output
4
+ * Issue #96: npm install CLI support
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CLILogger = void 0;
8
+ /**
9
+ * ANSI color codes for terminal output
10
+ */
11
+ const colors = {
12
+ reset: '\x1b[0m',
13
+ bold: '\x1b[1m',
14
+ dim: '\x1b[2m',
15
+ // Text colors
16
+ red: '\x1b[31m',
17
+ green: '\x1b[32m',
18
+ yellow: '\x1b[33m',
19
+ blue: '\x1b[34m',
20
+ cyan: '\x1b[36m',
21
+ white: '\x1b[37m',
22
+ gray: '\x1b[90m',
23
+ };
24
+ /**
25
+ * CLI Logger with colored output
26
+ */
27
+ class CLILogger {
28
+ verbose;
29
+ constructor(options = {}) {
30
+ this.verbose = options.verbose ?? false;
31
+ }
32
+ /**
33
+ * Log info message
34
+ */
35
+ info(message) {
36
+ console.log(`${colors.blue}[INFO]${colors.reset} ${message}`);
37
+ }
38
+ /**
39
+ * Log success message with checkmark
40
+ */
41
+ success(message) {
42
+ console.log(`${colors.green}[✓]${colors.reset} ${message}`);
43
+ }
44
+ /**
45
+ * Log warning message
46
+ */
47
+ warn(message) {
48
+ console.log(`${colors.yellow}[WARN]${colors.reset} ${message}`);
49
+ }
50
+ /**
51
+ * Log error message to stderr
52
+ */
53
+ error(message) {
54
+ console.error(`${colors.red}[ERROR]${colors.reset} ${message}`);
55
+ }
56
+ /**
57
+ * Log debug message (only when verbose is enabled)
58
+ */
59
+ debug(message) {
60
+ if (this.verbose) {
61
+ console.log(`${colors.gray}[DEBUG]${colors.reset} ${message}`);
62
+ }
63
+ }
64
+ /**
65
+ * Print blank line
66
+ */
67
+ blank() {
68
+ console.log('');
69
+ }
70
+ /**
71
+ * Print formatted header
72
+ */
73
+ header(title) {
74
+ const line = '='.repeat(title.length + 4);
75
+ console.log(`${colors.bold}${colors.cyan}${line}${colors.reset}`);
76
+ console.log(`${colors.bold}${colors.cyan} ${title} ${colors.reset}`);
77
+ console.log(`${colors.bold}${colors.cyan}${line}${colors.reset}`);
78
+ }
79
+ /**
80
+ * Format duration in human-readable format
81
+ */
82
+ static formatDuration(seconds) {
83
+ if (seconds < 60) {
84
+ return `${seconds}s`;
85
+ }
86
+ const days = Math.floor(seconds / 86400);
87
+ const hours = Math.floor((seconds % 86400) / 3600);
88
+ const minutes = Math.floor((seconds % 3600) / 60);
89
+ const secs = seconds % 60;
90
+ if (days > 0) {
91
+ return `${days}d ${hours}h`;
92
+ }
93
+ if (hours > 0) {
94
+ return `${hours}h ${minutes}m`;
95
+ }
96
+ return `${minutes}m ${secs}s`;
97
+ }
98
+ }
99
+ exports.CLILogger = CLILogger;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * PID File Manager
3
+ * Issue #96: npm install CLI support
4
+ * SF-1: SRP - Separated from daemon.ts for single responsibility
5
+ * MF-SEC-2: TOCTOU protection with O_EXCL atomic writes
6
+ */
7
+ /**
8
+ * PID file manager for daemon process tracking
9
+ */
10
+ export declare class PidManager {
11
+ private readonly pidFilePath;
12
+ constructor(pidFilePath: string);
13
+ /**
14
+ * Check if PID file exists
15
+ */
16
+ exists(): boolean;
17
+ /**
18
+ * Read PID from file
19
+ * @returns PID number or null if file doesn't exist or is invalid
20
+ */
21
+ readPid(): number | null;
22
+ /**
23
+ * Write PID to file atomically
24
+ * MF-SEC-2: Uses O_EXCL to prevent TOCTOU race conditions
25
+ *
26
+ * @returns true if successful, false if file already exists
27
+ * @throws Error for other filesystem errors
28
+ */
29
+ writePid(pid: number): boolean;
30
+ /**
31
+ * Remove PID file
32
+ */
33
+ removePid(): void;
34
+ /**
35
+ * Check if the process recorded in PID file is running
36
+ * NTH-1: ISP - Lightweight process check API
37
+ *
38
+ * @returns true if process is running, false otherwise
39
+ */
40
+ isProcessRunning(): boolean;
41
+ }
42
+ //# sourceMappingURL=pid-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pid-manager.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/pid-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH;;GAEG;AACH,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,MAAM;IAEhD;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;;OAGG;IACH,OAAO,IAAI,MAAM,GAAG,IAAI;IAmBxB;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAwB9B;;OAEG;IACH,SAAS,IAAI,IAAI;IAUjB;;;;;OAKG;IACH,gBAAgB,IAAI,OAAO;CAmB5B"}
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ /**
3
+ * PID File Manager
4
+ * Issue #96: npm install CLI support
5
+ * SF-1: SRP - Separated from daemon.ts for single responsibility
6
+ * MF-SEC-2: TOCTOU protection with O_EXCL atomic writes
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.PidManager = void 0;
10
+ const fs_1 = require("fs");
11
+ /**
12
+ * PID file manager for daemon process tracking
13
+ */
14
+ class PidManager {
15
+ pidFilePath;
16
+ constructor(pidFilePath) {
17
+ this.pidFilePath = pidFilePath;
18
+ }
19
+ /**
20
+ * Check if PID file exists
21
+ */
22
+ exists() {
23
+ return (0, fs_1.existsSync)(this.pidFilePath);
24
+ }
25
+ /**
26
+ * Read PID from file
27
+ * @returns PID number or null if file doesn't exist or is invalid
28
+ */
29
+ readPid() {
30
+ if (!this.exists()) {
31
+ return null;
32
+ }
33
+ try {
34
+ const content = (0, fs_1.readFileSync)(this.pidFilePath, 'utf-8').trim();
35
+ const pid = parseInt(content, 10);
36
+ if (isNaN(pid) || pid <= 0) {
37
+ return null;
38
+ }
39
+ return pid;
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ /**
46
+ * Write PID to file atomically
47
+ * MF-SEC-2: Uses O_EXCL to prevent TOCTOU race conditions
48
+ *
49
+ * @returns true if successful, false if file already exists
50
+ * @throws Error for other filesystem errors
51
+ */
52
+ writePid(pid) {
53
+ try {
54
+ // O_EXCL: Fail if file already exists (atomic check-and-create)
55
+ const fd = (0, fs_1.openSync)(this.pidFilePath, fs_1.constants.O_WRONLY | fs_1.constants.O_CREAT | fs_1.constants.O_EXCL, 0o600);
56
+ try {
57
+ (0, fs_1.writeSync)(fd, String(pid));
58
+ return true;
59
+ }
60
+ finally {
61
+ (0, fs_1.closeSync)(fd);
62
+ }
63
+ }
64
+ catch (err) {
65
+ if (err.code === 'EEXIST') {
66
+ // File already exists - another process is likely running
67
+ return false;
68
+ }
69
+ throw err;
70
+ }
71
+ }
72
+ /**
73
+ * Remove PID file
74
+ */
75
+ removePid() {
76
+ if (this.exists()) {
77
+ try {
78
+ (0, fs_1.unlinkSync)(this.pidFilePath);
79
+ }
80
+ catch {
81
+ // Ignore errors during cleanup
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Check if the process recorded in PID file is running
87
+ * NTH-1: ISP - Lightweight process check API
88
+ *
89
+ * @returns true if process is running, false otherwise
90
+ */
91
+ isProcessRunning() {
92
+ const pid = this.readPid();
93
+ if (pid === null) {
94
+ return false;
95
+ }
96
+ try {
97
+ // Sending signal 0 checks if process exists without killing it
98
+ process.kill(pid, 0);
99
+ return true;
100
+ }
101
+ catch (err) {
102
+ if (err.code === 'ESRCH') {
103
+ // Process not found
104
+ return false;
105
+ }
106
+ // Other errors (e.g., EPERM) mean process exists but we can't signal it
107
+ throw err;
108
+ }
109
+ }
110
+ }
111
+ exports.PidManager = PidManager;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Preflight System Dependency Checker
3
+ * Issue #96: npm install CLI support
4
+ * Migrated from scripts/preflight-check.sh
5
+ */
6
+ import { DependencyCheck, DependencyStatus, PreflightResult } from '../types';
7
+ /**
8
+ * Preflight checker for system dependencies
9
+ */
10
+ export declare class PreflightChecker {
11
+ /**
12
+ * Check a single dependency
13
+ * MF-SEC-1: Uses spawnSync with array args (no shell injection)
14
+ */
15
+ checkDependency(dep: DependencyCheck): Promise<DependencyStatus>;
16
+ /**
17
+ * Check all dependencies
18
+ */
19
+ checkAll(): Promise<PreflightResult>;
20
+ /**
21
+ * Extract version number from command output
22
+ */
23
+ private extractVersion;
24
+ /**
25
+ * Compare two version strings
26
+ * @returns -1 if a < b, 0 if a == b, 1 if a > b
27
+ */
28
+ private compareVersions;
29
+ /**
30
+ * Get installation hint for a dependency
31
+ */
32
+ static getInstallHint(name: string): string;
33
+ }
34
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/preflight.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,eAAe,EAChB,MAAM,UAAU,CAAC;AAGlB;;GAEG;AACH,qBAAa,gBAAgB;IAC3B;;;OAGG;IACG,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA6CtE;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;IAoB1C;;OAEG;IACH,OAAO,CAAC,cAAc;IAmBtB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAevB;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAW5C"}
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ /**
3
+ * Preflight System Dependency Checker
4
+ * Issue #96: npm install CLI support
5
+ * Migrated from scripts/preflight-check.sh
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.PreflightChecker = void 0;
9
+ const child_process_1 = require("child_process");
10
+ const cli_dependencies_1 = require("../config/cli-dependencies");
11
+ /**
12
+ * Preflight checker for system dependencies
13
+ */
14
+ class PreflightChecker {
15
+ /**
16
+ * Check a single dependency
17
+ * MF-SEC-1: Uses spawnSync with array args (no shell injection)
18
+ */
19
+ async checkDependency(dep) {
20
+ try {
21
+ const result = (0, child_process_1.spawnSync)(dep.command, [dep.versionArg], {
22
+ encoding: 'utf-8',
23
+ timeout: 5000,
24
+ });
25
+ if (result.error) {
26
+ const errnoError = result.error;
27
+ if (errnoError.code === 'ENOENT') {
28
+ return { name: dep.name, status: 'missing' };
29
+ }
30
+ throw result.error;
31
+ }
32
+ if (result.status !== 0) {
33
+ return { name: dep.name, status: 'missing' };
34
+ }
35
+ // Extract version from output (encoding: 'utf-8' ensures strings)
36
+ const output = ((result.stdout || result.stderr) || '').trim();
37
+ const version = this.extractVersion(output) || output;
38
+ // Check minimum version if specified
39
+ if (dep.minVersion && version) {
40
+ const cleanVersion = version.replace(/^v/, '');
41
+ if (this.compareVersions(cleanVersion, dep.minVersion) < 0) {
42
+ return {
43
+ name: dep.name,
44
+ status: 'version_mismatch',
45
+ version,
46
+ };
47
+ }
48
+ }
49
+ return {
50
+ name: dep.name,
51
+ status: 'ok',
52
+ version,
53
+ };
54
+ }
55
+ catch {
56
+ return { name: dep.name, status: 'missing' };
57
+ }
58
+ }
59
+ /**
60
+ * Check all dependencies
61
+ */
62
+ async checkAll() {
63
+ const dependencies = (0, cli_dependencies_1.getDependencies)();
64
+ const results = [];
65
+ let allRequiredMet = true;
66
+ for (const dep of dependencies) {
67
+ const status = await this.checkDependency(dep);
68
+ results.push(status);
69
+ if (dep.required && status.status !== 'ok') {
70
+ allRequiredMet = false;
71
+ }
72
+ }
73
+ return {
74
+ success: allRequiredMet,
75
+ results,
76
+ };
77
+ }
78
+ /**
79
+ * Extract version number from command output
80
+ */
81
+ extractVersion(output) {
82
+ // Match various version formats:
83
+ // v20.0.0, 2.39.0, "git version 2.39.0", etc.
84
+ const patterns = [
85
+ /v?(\d+\.\d+\.\d+)/,
86
+ /version\s+(\d+\.\d+\.\d+)/i,
87
+ /(\d+\.\d+\.\d+)/,
88
+ ];
89
+ for (const pattern of patterns) {
90
+ const match = output.match(pattern);
91
+ if (match) {
92
+ return match[1];
93
+ }
94
+ }
95
+ return undefined;
96
+ }
97
+ /**
98
+ * Compare two version strings
99
+ * @returns -1 if a < b, 0 if a == b, 1 if a > b
100
+ */
101
+ compareVersions(a, b) {
102
+ const partsA = a.split('.').map(Number);
103
+ const partsB = b.split('.').map(Number);
104
+ const len = Math.max(partsA.length, partsB.length);
105
+ for (let i = 0; i < len; i++) {
106
+ const numA = partsA[i] || 0;
107
+ const numB = partsB[i] || 0;
108
+ if (numA > numB)
109
+ return 1;
110
+ if (numA < numB)
111
+ return -1;
112
+ }
113
+ return 0;
114
+ }
115
+ /**
116
+ * Get installation hint for a dependency
117
+ */
118
+ static getInstallHint(name) {
119
+ const hints = {
120
+ 'Node.js': 'Install with: nvm install 20 or visit https://nodejs.org',
121
+ npm: 'npm is included with Node.js. Install Node.js first.',
122
+ tmux: 'Install with: brew install tmux (macOS) or apt install tmux (Linux)',
123
+ git: 'Install with: brew install git (macOS) or apt install git (Linux)',
124
+ 'Claude CLI': 'Install with: npm install -g @anthropic-ai/claude-cli',
125
+ };
126
+ return hints[name] || `Please install ${name}`;
127
+ }
128
+ }
129
+ exports.PreflightChecker = PreflightChecker;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Security Event Logger
3
+ * Issue #96: npm install CLI support
4
+ * SF-SEC-2: Security event logging for CLI commands
5
+ */
6
+ /**
7
+ * Security event types
8
+ */
9
+ export interface SecurityEvent {
10
+ /** ISO timestamp */
11
+ timestamp: string;
12
+ /** Command that triggered the event */
13
+ command: string;
14
+ /** Event outcome */
15
+ action: 'success' | 'failure' | 'warning';
16
+ /** Additional details */
17
+ details?: string;
18
+ }
19
+ /**
20
+ * Log a security event
21
+ * Silently fails if file operations fail (non-critical)
22
+ */
23
+ export declare function logSecurityEvent(event: SecurityEvent): void;
24
+ /**
25
+ * Mask sensitive data in log messages
26
+ * SF-SEC-2: Mask authentication tokens in logs
27
+ */
28
+ export declare function maskSensitiveData(input: string | undefined): string | undefined;
29
+ //# sourceMappingURL=security-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-logger.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/security-logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oBAAoB;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1C,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAaD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAU3D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAc/E"}