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.
- package/.env.example +64 -0
- package/LICENSE +21 -0
- package/README.md +148 -0
- package/bin/commandmate.js +7 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +126 -0
- package/dist/cli/commands/start.d.ts +11 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +117 -0
- package/dist/cli/commands/status.d.ts +10 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +55 -0
- package/dist/cli/commands/stop.d.ts +11 -0
- package/dist/cli/commands/stop.d.ts.map +1 -0
- package/dist/cli/commands/stop.js +82 -0
- package/dist/cli/config/cli-dependencies.d.ts +23 -0
- package/dist/cli/config/cli-dependencies.d.ts.map +1 -0
- package/dist/cli/config/cli-dependencies.js +65 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +64 -0
- package/dist/cli/types/index.d.ts +124 -0
- package/dist/cli/types/index.d.ts.map +1 -0
- package/dist/cli/types/index.js +20 -0
- package/dist/cli/utils/daemon.d.ts +39 -0
- package/dist/cli/utils/daemon.d.ts.map +1 -0
- package/dist/cli/utils/daemon.js +141 -0
- package/dist/cli/utils/env-setup.d.ts +62 -0
- package/dist/cli/utils/env-setup.d.ts.map +1 -0
- package/dist/cli/utils/env-setup.js +157 -0
- package/dist/cli/utils/logger.d.ts +48 -0
- package/dist/cli/utils/logger.d.ts.map +1 -0
- package/dist/cli/utils/logger.js +99 -0
- package/dist/cli/utils/pid-manager.d.ts +42 -0
- package/dist/cli/utils/pid-manager.d.ts.map +1 -0
- package/dist/cli/utils/pid-manager.js +111 -0
- package/dist/cli/utils/preflight.d.ts +34 -0
- package/dist/cli/utils/preflight.d.ts.map +1 -0
- package/dist/cli/utils/preflight.js +129 -0
- package/dist/cli/utils/security-logger.d.ts +29 -0
- package/dist/cli/utils/security-logger.d.ts.map +1 -0
- package/dist/cli/utils/security-logger.js +53 -0
- package/package.json +78 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Stop Command
|
|
4
|
+
* Issue #96: npm install CLI support
|
|
5
|
+
* Stop CommandMate server
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.stopCommand = stopCommand;
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const types_1 = require("../types");
|
|
11
|
+
const logger_1 = require("../utils/logger");
|
|
12
|
+
const daemon_1 = require("../utils/daemon");
|
|
13
|
+
const security_logger_1 = require("../utils/security-logger");
|
|
14
|
+
const logger = new logger_1.CLILogger();
|
|
15
|
+
const PID_FILE = (0, path_1.join)(process.cwd(), '.commandmate.pid');
|
|
16
|
+
/**
|
|
17
|
+
* Execute stop command
|
|
18
|
+
*/
|
|
19
|
+
async function stopCommand(options) {
|
|
20
|
+
try {
|
|
21
|
+
const daemonManager = new daemon_1.DaemonManager(PID_FILE);
|
|
22
|
+
// Check if running
|
|
23
|
+
if (!(await daemonManager.isRunning())) {
|
|
24
|
+
const status = await daemonManager.getStatus();
|
|
25
|
+
if (status === null) {
|
|
26
|
+
logger.info('Server is not running (no PID file found)');
|
|
27
|
+
logger.info('Status: Stopped');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
logger.info('Server is not running (stale PID file)');
|
|
31
|
+
}
|
|
32
|
+
process.exit(types_1.ExitCode.SUCCESS);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const status = await daemonManager.getStatus();
|
|
36
|
+
const pid = status?.pid;
|
|
37
|
+
if (options.force) {
|
|
38
|
+
logger.warn(`Force stopping server (PID: ${pid})...`);
|
|
39
|
+
(0, security_logger_1.logSecurityEvent)({
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
command: 'stop',
|
|
42
|
+
action: 'warning',
|
|
43
|
+
details: `--force flag used (SIGKILL) on PID ${pid}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
logger.info(`Stopping server (PID: ${pid})...`);
|
|
48
|
+
}
|
|
49
|
+
const result = await daemonManager.stop(options.force);
|
|
50
|
+
if (result) {
|
|
51
|
+
logger.success('Server stopped');
|
|
52
|
+
(0, security_logger_1.logSecurityEvent)({
|
|
53
|
+
timestamp: new Date().toISOString(),
|
|
54
|
+
command: 'stop',
|
|
55
|
+
action: 'success',
|
|
56
|
+
details: `Server stopped (PID: ${pid})`,
|
|
57
|
+
});
|
|
58
|
+
process.exit(types_1.ExitCode.SUCCESS);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
logger.error('Failed to stop server');
|
|
62
|
+
(0, security_logger_1.logSecurityEvent)({
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
command: 'stop',
|
|
65
|
+
action: 'failure',
|
|
66
|
+
details: `Failed to stop PID ${pid}`,
|
|
67
|
+
});
|
|
68
|
+
process.exit(types_1.ExitCode.STOP_FAILED);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
73
|
+
logger.error(`Stop failed: ${message}`);
|
|
74
|
+
(0, security_logger_1.logSecurityEvent)({
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
command: 'stop',
|
|
77
|
+
action: 'failure',
|
|
78
|
+
details: message,
|
|
79
|
+
});
|
|
80
|
+
process.exit(types_1.ExitCode.UNEXPECTED_ERROR);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Dependencies Configuration
|
|
3
|
+
* Issue #96: npm install CLI support
|
|
4
|
+
* SF-2: OCP - External configuration for extensibility
|
|
5
|
+
*/
|
|
6
|
+
import { DependencyCheck } from '../types';
|
|
7
|
+
/**
|
|
8
|
+
* System dependencies required by CommandMate
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEPENDENCIES: DependencyCheck[];
|
|
11
|
+
/**
|
|
12
|
+
* Get all dependencies
|
|
13
|
+
*/
|
|
14
|
+
export declare function getDependencies(): DependencyCheck[];
|
|
15
|
+
/**
|
|
16
|
+
* Get only required dependencies
|
|
17
|
+
*/
|
|
18
|
+
export declare function getRequiredDependencies(): DependencyCheck[];
|
|
19
|
+
/**
|
|
20
|
+
* Get only optional dependencies
|
|
21
|
+
*/
|
|
22
|
+
export declare function getOptionalDependencies(): DependencyCheck[];
|
|
23
|
+
//# sourceMappingURL=cli-dependencies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-dependencies.d.ts","sourceRoot":"","sources":["../../../src/cli/config/cli-dependencies.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,eAAe,EAgCzC,CAAC;AAEF;;GAEG;AACH,wBAAgB,eAAe,IAAI,eAAe,EAAE,CAEnD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,eAAe,EAAE,CAE3D;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,eAAe,EAAE,CAE3D"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI Dependencies Configuration
|
|
4
|
+
* Issue #96: npm install CLI support
|
|
5
|
+
* SF-2: OCP - External configuration for extensibility
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.DEPENDENCIES = void 0;
|
|
9
|
+
exports.getDependencies = getDependencies;
|
|
10
|
+
exports.getRequiredDependencies = getRequiredDependencies;
|
|
11
|
+
exports.getOptionalDependencies = getOptionalDependencies;
|
|
12
|
+
/**
|
|
13
|
+
* System dependencies required by CommandMate
|
|
14
|
+
*/
|
|
15
|
+
exports.DEPENDENCIES = [
|
|
16
|
+
{
|
|
17
|
+
name: 'Node.js',
|
|
18
|
+
command: 'node',
|
|
19
|
+
versionArg: '-v',
|
|
20
|
+
required: true,
|
|
21
|
+
minVersion: '20.0.0',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'npm',
|
|
25
|
+
command: 'npm',
|
|
26
|
+
versionArg: '-v',
|
|
27
|
+
required: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'tmux',
|
|
31
|
+
command: 'tmux',
|
|
32
|
+
versionArg: '-V',
|
|
33
|
+
required: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'git',
|
|
37
|
+
command: 'git',
|
|
38
|
+
versionArg: '--version',
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Claude CLI',
|
|
43
|
+
command: 'claude',
|
|
44
|
+
versionArg: '--version',
|
|
45
|
+
required: false,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Get all dependencies
|
|
50
|
+
*/
|
|
51
|
+
function getDependencies() {
|
|
52
|
+
return [...exports.DEPENDENCIES];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get only required dependencies
|
|
56
|
+
*/
|
|
57
|
+
function getRequiredDependencies() {
|
|
58
|
+
return exports.DEPENDENCIES.filter(d => d.required);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get only optional dependencies
|
|
62
|
+
*/
|
|
63
|
+
function getOptionalDependencies() {
|
|
64
|
+
return exports.DEPENDENCIES.filter(d => !d.required);
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CommandMate CLI Entry Point
|
|
4
|
+
* Issue #96: npm install CLI support
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const init_1 = require("./commands/init");
|
|
9
|
+
const start_1 = require("./commands/start");
|
|
10
|
+
const stop_1 = require("./commands/stop");
|
|
11
|
+
const status_1 = require("./commands/status");
|
|
12
|
+
// Read version from package.json
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
14
|
+
const pkg = require('../../package.json');
|
|
15
|
+
const program = new commander_1.Command();
|
|
16
|
+
program
|
|
17
|
+
.name('commandmate')
|
|
18
|
+
.description('Git worktree management with Claude CLI and tmux sessions')
|
|
19
|
+
.version(pkg.version);
|
|
20
|
+
// Init command
|
|
21
|
+
program
|
|
22
|
+
.command('init')
|
|
23
|
+
.description('Initialize CommandMate configuration')
|
|
24
|
+
.option('-d, --defaults', 'Use default values (non-interactive)')
|
|
25
|
+
.option('-f, --force', 'Overwrite existing configuration')
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
await (0, init_1.initCommand)({
|
|
28
|
+
defaults: options.defaults,
|
|
29
|
+
force: options.force,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
// Start command
|
|
33
|
+
program
|
|
34
|
+
.command('start')
|
|
35
|
+
.description('Start the CommandMate server')
|
|
36
|
+
.option('--dev', 'Start in development mode')
|
|
37
|
+
.option('--daemon', 'Run in background')
|
|
38
|
+
.option('-p, --port <number>', 'Override port number', parseInt)
|
|
39
|
+
.action(async (options) => {
|
|
40
|
+
await (0, start_1.startCommand)({
|
|
41
|
+
dev: options.dev,
|
|
42
|
+
daemon: options.daemon,
|
|
43
|
+
port: options.port,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
// Stop command
|
|
47
|
+
program
|
|
48
|
+
.command('stop')
|
|
49
|
+
.description('Stop the CommandMate server')
|
|
50
|
+
.option('-f, --force', 'Force stop (SIGKILL)')
|
|
51
|
+
.action(async (options) => {
|
|
52
|
+
await (0, stop_1.stopCommand)({
|
|
53
|
+
force: options.force,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
// Status command
|
|
57
|
+
program
|
|
58
|
+
.command('status')
|
|
59
|
+
.description('Show server status')
|
|
60
|
+
.action(async () => {
|
|
61
|
+
await (0, status_1.statusCommand)();
|
|
62
|
+
});
|
|
63
|
+
// Parse and execute
|
|
64
|
+
program.parse();
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Common Type Definitions
|
|
3
|
+
* Issue #96: npm install CLI support
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Exit codes for CLI commands
|
|
7
|
+
* NTH-4: DRY - centralized exit code definitions
|
|
8
|
+
*/
|
|
9
|
+
export declare enum ExitCode {
|
|
10
|
+
SUCCESS = 0,
|
|
11
|
+
DEPENDENCY_ERROR = 1,
|
|
12
|
+
CONFIG_ERROR = 2,
|
|
13
|
+
START_FAILED = 3,
|
|
14
|
+
STOP_FAILED = 4,
|
|
15
|
+
UNEXPECTED_ERROR = 99
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Options for init command
|
|
19
|
+
*/
|
|
20
|
+
export interface InitOptions {
|
|
21
|
+
/** Use default values (non-interactive) */
|
|
22
|
+
defaults?: boolean;
|
|
23
|
+
/** Overwrite existing configuration */
|
|
24
|
+
force?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Options for start command
|
|
28
|
+
*/
|
|
29
|
+
export interface StartOptions {
|
|
30
|
+
/** Start in development mode */
|
|
31
|
+
dev?: boolean;
|
|
32
|
+
/** Run in background */
|
|
33
|
+
daemon?: boolean;
|
|
34
|
+
/** Override port number */
|
|
35
|
+
port?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Options for stop command
|
|
39
|
+
*/
|
|
40
|
+
export interface StopOptions {
|
|
41
|
+
/** Force stop (SIGKILL) */
|
|
42
|
+
force?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Daemon process status
|
|
46
|
+
*/
|
|
47
|
+
export interface DaemonStatus {
|
|
48
|
+
/** Whether the daemon is running */
|
|
49
|
+
running: boolean;
|
|
50
|
+
/** Process ID (if running) */
|
|
51
|
+
pid?: number;
|
|
52
|
+
/** Port number (if running) */
|
|
53
|
+
port?: number;
|
|
54
|
+
/** Uptime in seconds (if running) */
|
|
55
|
+
uptime?: number;
|
|
56
|
+
/** URL to access the server (if running) */
|
|
57
|
+
url?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* System dependency definition
|
|
61
|
+
* SF-2: OCP - external configuration for extensibility
|
|
62
|
+
*/
|
|
63
|
+
export interface DependencyCheck {
|
|
64
|
+
/** Display name */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Command to check */
|
|
67
|
+
command: string;
|
|
68
|
+
/** Argument to get version */
|
|
69
|
+
versionArg: string;
|
|
70
|
+
/** Whether this dependency is required */
|
|
71
|
+
required: boolean;
|
|
72
|
+
/** Minimum version (optional) */
|
|
73
|
+
minVersion?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Result of a single dependency check
|
|
77
|
+
*/
|
|
78
|
+
export interface DependencyStatus {
|
|
79
|
+
/** Dependency name */
|
|
80
|
+
name: string;
|
|
81
|
+
/** Check status */
|
|
82
|
+
status: 'ok' | 'missing' | 'version_mismatch';
|
|
83
|
+
/** Detected version (if available) */
|
|
84
|
+
version?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Result of preflight checks
|
|
88
|
+
*/
|
|
89
|
+
export interface PreflightResult {
|
|
90
|
+
/** Whether all required dependencies are satisfied */
|
|
91
|
+
success: boolean;
|
|
92
|
+
/** Individual dependency results */
|
|
93
|
+
results: DependencyStatus[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Environment configuration for CLI
|
|
97
|
+
* Used by env-setup.ts for .env file generation
|
|
98
|
+
*/
|
|
99
|
+
export interface EnvConfig {
|
|
100
|
+
CM_ROOT_DIR: string;
|
|
101
|
+
CM_PORT: number;
|
|
102
|
+
CM_BIND: string;
|
|
103
|
+
CM_AUTH_TOKEN?: string;
|
|
104
|
+
CM_DB_PATH: string;
|
|
105
|
+
CM_LOG_LEVEL: string;
|
|
106
|
+
CM_LOG_FORMAT: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Options for env file creation
|
|
110
|
+
*/
|
|
111
|
+
export interface EnvSetupOptions {
|
|
112
|
+
/** Force overwrite existing file */
|
|
113
|
+
force?: boolean;
|
|
114
|
+
/** Path to .env file (defaults to .env in cwd) */
|
|
115
|
+
envPath?: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Validation result
|
|
119
|
+
*/
|
|
120
|
+
export interface ValidationResult {
|
|
121
|
+
valid: boolean;
|
|
122
|
+
errors: string[];
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,oBAAY,QAAQ;IAClB,OAAO,IAAI;IACX,gBAAgB,IAAI;IACpB,YAAY,IAAI;IAChB,YAAY,IAAI;IAChB,WAAW,IAAI;IACf,gBAAgB,KAAK;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,CAAC;IAClB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,kBAAkB,CAAC;IAC9C,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI Common Type Definitions
|
|
4
|
+
* Issue #96: npm install CLI support
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ExitCode = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Exit codes for CLI commands
|
|
10
|
+
* NTH-4: DRY - centralized exit code definitions
|
|
11
|
+
*/
|
|
12
|
+
var ExitCode;
|
|
13
|
+
(function (ExitCode) {
|
|
14
|
+
ExitCode[ExitCode["SUCCESS"] = 0] = "SUCCESS";
|
|
15
|
+
ExitCode[ExitCode["DEPENDENCY_ERROR"] = 1] = "DEPENDENCY_ERROR";
|
|
16
|
+
ExitCode[ExitCode["CONFIG_ERROR"] = 2] = "CONFIG_ERROR";
|
|
17
|
+
ExitCode[ExitCode["START_FAILED"] = 3] = "START_FAILED";
|
|
18
|
+
ExitCode[ExitCode["STOP_FAILED"] = 4] = "STOP_FAILED";
|
|
19
|
+
ExitCode[ExitCode["UNEXPECTED_ERROR"] = 99] = "UNEXPECTED_ERROR";
|
|
20
|
+
})(ExitCode || (exports.ExitCode = ExitCode = {}));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon Process Manager
|
|
3
|
+
* Issue #96: npm install CLI support
|
|
4
|
+
* SF-1: SRP - Process management only (PID handling delegated to PidManager)
|
|
5
|
+
*/
|
|
6
|
+
import { DaemonStatus, StartOptions } from '../types';
|
|
7
|
+
/**
|
|
8
|
+
* Daemon manager for background server process
|
|
9
|
+
*/
|
|
10
|
+
export declare class DaemonManager {
|
|
11
|
+
private pidManager;
|
|
12
|
+
constructor(pidFilePath: string);
|
|
13
|
+
/**
|
|
14
|
+
* Start daemon process
|
|
15
|
+
* @returns Process ID of the started daemon
|
|
16
|
+
* @throws Error if already running
|
|
17
|
+
*/
|
|
18
|
+
start(options: StartOptions): Promise<number>;
|
|
19
|
+
/**
|
|
20
|
+
* Stop daemon process
|
|
21
|
+
* @param force Use SIGKILL instead of SIGTERM
|
|
22
|
+
* @returns true if stopped successfully, false if not running
|
|
23
|
+
*/
|
|
24
|
+
stop(force?: boolean): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Get daemon status
|
|
27
|
+
* @returns Status object or null if no PID file
|
|
28
|
+
*/
|
|
29
|
+
getStatus(): Promise<DaemonStatus | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Check if daemon is running
|
|
32
|
+
*/
|
|
33
|
+
isRunning(): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Wait for process to exit
|
|
36
|
+
*/
|
|
37
|
+
private waitForExit;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/daemon.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGtD;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAa;gBAEnB,WAAW,EAAE,MAAM;IAI/B;;;;OAIG;IACG,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IA6CnD;;;;OAIG;IACG,IAAI,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IA8BpD;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IA6B/C;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAInC;;OAEG;YACW,WAAW;CAiB1B"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Daemon Process Manager
|
|
4
|
+
* Issue #96: npm install CLI support
|
|
5
|
+
* SF-1: SRP - Process management only (PID handling delegated to PidManager)
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.DaemonManager = void 0;
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const pid_manager_1 = require("./pid-manager");
|
|
11
|
+
/**
|
|
12
|
+
* Daemon manager for background server process
|
|
13
|
+
*/
|
|
14
|
+
class DaemonManager {
|
|
15
|
+
pidManager;
|
|
16
|
+
constructor(pidFilePath) {
|
|
17
|
+
this.pidManager = new pid_manager_1.PidManager(pidFilePath);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Start daemon process
|
|
21
|
+
* @returns Process ID of the started daemon
|
|
22
|
+
* @throws Error if already running
|
|
23
|
+
*/
|
|
24
|
+
async start(options) {
|
|
25
|
+
if (this.pidManager.isProcessRunning()) {
|
|
26
|
+
const pid = this.pidManager.readPid();
|
|
27
|
+
throw new Error(`Server is already running (PID: ${pid})`);
|
|
28
|
+
}
|
|
29
|
+
// Clean up stale PID file
|
|
30
|
+
this.pidManager.removePid();
|
|
31
|
+
const npmScript = options.dev ? 'dev' : 'start';
|
|
32
|
+
const cwd = process.cwd();
|
|
33
|
+
// Build environment
|
|
34
|
+
const env = { ...process.env };
|
|
35
|
+
if (options.port) {
|
|
36
|
+
env.CM_PORT = String(options.port);
|
|
37
|
+
}
|
|
38
|
+
// Spawn detached process
|
|
39
|
+
const child = (0, child_process_1.spawn)('npm', ['run', npmScript], {
|
|
40
|
+
cwd,
|
|
41
|
+
env,
|
|
42
|
+
detached: true,
|
|
43
|
+
stdio: 'ignore',
|
|
44
|
+
});
|
|
45
|
+
// Unref to allow parent to exit
|
|
46
|
+
child.unref();
|
|
47
|
+
const pid = child.pid;
|
|
48
|
+
// Write PID file atomically
|
|
49
|
+
if (!this.pidManager.writePid(pid)) {
|
|
50
|
+
// Failed to write PID - another process may have started
|
|
51
|
+
try {
|
|
52
|
+
process.kill(pid, 'SIGTERM');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Ignore kill errors
|
|
56
|
+
}
|
|
57
|
+
throw new Error('Failed to write PID file - server may already be running');
|
|
58
|
+
}
|
|
59
|
+
return pid;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Stop daemon process
|
|
63
|
+
* @param force Use SIGKILL instead of SIGTERM
|
|
64
|
+
* @returns true if stopped successfully, false if not running
|
|
65
|
+
*/
|
|
66
|
+
async stop(force = false) {
|
|
67
|
+
const pid = this.pidManager.readPid();
|
|
68
|
+
if (pid === null) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (!this.pidManager.isProcessRunning()) {
|
|
72
|
+
// Process not running - clean up stale PID file
|
|
73
|
+
this.pidManager.removePid();
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const signal = force ? 'SIGKILL' : 'SIGTERM';
|
|
77
|
+
try {
|
|
78
|
+
process.kill(pid, signal);
|
|
79
|
+
// Wait for process to exit
|
|
80
|
+
await this.waitForExit(pid, 10000);
|
|
81
|
+
// Clean up PID file
|
|
82
|
+
this.pidManager.removePid();
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get daemon status
|
|
91
|
+
* @returns Status object or null if no PID file
|
|
92
|
+
*/
|
|
93
|
+
async getStatus() {
|
|
94
|
+
const pid = this.pidManager.readPid();
|
|
95
|
+
if (pid === null) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const running = this.pidManager.isProcessRunning();
|
|
99
|
+
if (!running) {
|
|
100
|
+
return { running: false };
|
|
101
|
+
}
|
|
102
|
+
// Get port from environment or default
|
|
103
|
+
const port = parseInt(process.env.CM_PORT || '3000', 10);
|
|
104
|
+
const bind = process.env.CM_BIND || '127.0.0.1';
|
|
105
|
+
const url = `http://${bind === '0.0.0.0' ? '127.0.0.1' : bind}:${port}`;
|
|
106
|
+
// Note: Getting accurate uptime would require storing start time
|
|
107
|
+
// For now, we don't track uptime
|
|
108
|
+
return {
|
|
109
|
+
running: true,
|
|
110
|
+
pid,
|
|
111
|
+
port,
|
|
112
|
+
url,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check if daemon is running
|
|
117
|
+
*/
|
|
118
|
+
async isRunning() {
|
|
119
|
+
return this.pidManager.isProcessRunning();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Wait for process to exit
|
|
123
|
+
*/
|
|
124
|
+
async waitForExit(pid, timeout) {
|
|
125
|
+
const startTime = Date.now();
|
|
126
|
+
const checkInterval = 100;
|
|
127
|
+
while (Date.now() - startTime < timeout) {
|
|
128
|
+
try {
|
|
129
|
+
process.kill(pid, 0);
|
|
130
|
+
// Process still exists, wait
|
|
131
|
+
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Process exited
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Timeout - process may still be running
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.DaemonManager = DaemonManager;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Setup Utility
|
|
3
|
+
* Issue #96: npm install CLI support
|
|
4
|
+
* Migrated from scripts/setup-env.sh
|
|
5
|
+
*/
|
|
6
|
+
import { EnvConfig, EnvSetupOptions, ValidationResult } from '../types';
|
|
7
|
+
/**
|
|
8
|
+
* Default environment configuration values
|
|
9
|
+
* SF-4: DRY - Centralized defaults
|
|
10
|
+
*/
|
|
11
|
+
export declare const ENV_DEFAULTS: {
|
|
12
|
+
readonly CM_PORT: 3000;
|
|
13
|
+
readonly CM_BIND: "127.0.0.1";
|
|
14
|
+
readonly CM_DB_PATH: "./data/cm.db";
|
|
15
|
+
readonly CM_LOG_LEVEL: "info";
|
|
16
|
+
readonly CM_LOG_FORMAT: "text";
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Sanitize input by removing control characters
|
|
20
|
+
* SF-SEC-3: Input sanitization
|
|
21
|
+
*/
|
|
22
|
+
export declare function sanitizeInput(input: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Sanitize path input
|
|
25
|
+
* SF-SEC-3: Path sanitization
|
|
26
|
+
*/
|
|
27
|
+
export declare function sanitizePath(input: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Validate port number
|
|
30
|
+
* SF-SEC-3: Port validation
|
|
31
|
+
*/
|
|
32
|
+
export declare function validatePort(input: string): number;
|
|
33
|
+
/**
|
|
34
|
+
* Escape value for .env file
|
|
35
|
+
* SF-SEC-3: Safe .env value escaping
|
|
36
|
+
*/
|
|
37
|
+
export declare function escapeEnvValue(value: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Environment setup utility
|
|
40
|
+
*/
|
|
41
|
+
export declare class EnvSetup {
|
|
42
|
+
private envPath;
|
|
43
|
+
constructor(envPath?: string);
|
|
44
|
+
/**
|
|
45
|
+
* Create .env file
|
|
46
|
+
* SF-SEC-1: Sets file permissions to 0600
|
|
47
|
+
*/
|
|
48
|
+
createEnvFile(config: EnvConfig, options?: EnvSetupOptions): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Backup existing .env file
|
|
51
|
+
*/
|
|
52
|
+
backupExisting(): Promise<string | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Generate secure authentication token
|
|
55
|
+
*/
|
|
56
|
+
generateAuthToken(): string;
|
|
57
|
+
/**
|
|
58
|
+
* Validate configuration
|
|
59
|
+
*/
|
|
60
|
+
validateConfig(config: EnvConfig): ValidationResult;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=env-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-setup.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/env-setup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,OAAO,EACL,SAAS,EACT,eAAe,EACf,gBAAgB,EACjB,MAAM,UAAU,CAAC;AAElB;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAEX;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUpD;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,CAAC,EAAE,MAAM;IAI5B;;;OAGG;IACG,aAAa,CACjB,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAiChB;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAY9C;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,gBAAgB;CAoCpD"}
|