feat(daemon): Add central TSPM daemon and IPC client; refactor CLI to use daemon and improve monitoring/error handling
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -16,4 +16,8 @@ node_modules/
 | 
				
			|||||||
dist/
 | 
					dist/
 | 
				
			||||||
dist_*/
 | 
					dist_*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# AI
 | 
				
			||||||
 | 
					.claude/
 | 
				
			||||||
 | 
					.serena/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#------# custom
 | 
					#------# custom
 | 
				
			||||||
							
								
								
									
										25
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								changelog.md
									
									
									
									
									
								
							@@ -1,6 +1,20 @@
 | 
				
			|||||||
# Changelog
 | 
					# Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-25 - 1.6.0 - feat(daemon)
 | 
				
			||||||
 | 
					Add central TSPM daemon and IPC client; refactor CLI to use daemon and improve monitoring/error handling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add central daemon implementation (ts/classes.daemon.ts) to manage all processes via a single background service and Unix socket.
 | 
				
			||||||
 | 
					- Introduce IPC client and typed IPC contracts (ts/classes.ipcclient.ts, ts/ipc.types.ts) so CLI communicates with the daemon.
 | 
				
			||||||
 | 
					- Refactor CLI to use the daemon for commands (ts/cli.ts): start/stop/restart/delete/list/describe/logs/start-all/stop-all/restart-all and new daemon start/stop/status commands.
 | 
				
			||||||
 | 
					- Enhance process monitoring and wrapping: ProcessMonitor and ProcessWrapper improvements (ts/classes.processmonitor.ts, ts/classes.processwrapper.ts) with better logging, memory checks, and restart behavior.
 | 
				
			||||||
 | 
					- Improve centralized error handling and Logger behavior (ts/utils.errorhandler.ts).
 | 
				
			||||||
 | 
					- Persist and load process configurations via TspmConfig and config storage changes (ts/classes.config.ts, ts/classes.tspm.ts).
 | 
				
			||||||
 | 
					- Bump dependency and devDependency versions and add packageManager entry in package.json.
 | 
				
			||||||
 | 
					- Add ts/daemon entrypoint and export daemon/ipc types from ts/index.ts; add paths for tspm runtime dir (ts/paths.ts).
 | 
				
			||||||
 | 
					- Update tests and test tooling imports (test/test.ts) and adjust commitinfo and readme hints.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-03-10 - 1.5.1 - fix(core)
 | 
					## 2025-03-10 - 1.5.1 - fix(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Improve error handling, logging, and test suite; update dependency versions
 | 
					Improve error handling, logging, and test suite; update dependency versions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated devDependencies versions in package.json (@git.zone/tsbuild, @push.rocks/tapbundle, and @push.rocks/smartdaemon)
 | 
					- Updated devDependencies versions in package.json (@git.zone/tsbuild, @push.rocks/tapbundle, and @push.rocks/smartdaemon)
 | 
				
			||||||
@@ -8,6 +22,7 @@ Improve error handling, logging, and test suite; update dependency versions
 | 
				
			|||||||
- Improved test structure by adding clear module import tests and usage examples in test files
 | 
					- Improved test structure by adding clear module import tests and usage examples in test files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-03-04 - 1.5.0 - feat(cli)
 | 
					## 2025-03-04 - 1.5.0 - feat(cli)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Enhance CLI with new process management commands
 | 
					Enhance CLI with new process management commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added comprehensive CLI commands for process management including start, stop, restart, list, describe and logs.
 | 
					- Added comprehensive CLI commands for process management including start, stop, restart, list, describe and logs.
 | 
				
			||||||
@@ -15,6 +30,7 @@ Enhance CLI with new process management commands
 | 
				
			|||||||
- Enhanced CLI output with formatted table listings for active processes.
 | 
					- Enhanced CLI output with formatted table listings for active processes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-03-03 - 1.4.0 - feat(core)
 | 
					## 2025-03-03 - 1.4.0 - feat(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Introduced process management features using ProcessWrapper and enhanced configuration.
 | 
					Introduced process management features using ProcessWrapper and enhanced configuration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added ProcessWrapper for wrapping and managing child processes.
 | 
					- Added ProcessWrapper for wrapping and managing child processes.
 | 
				
			||||||
@@ -23,12 +39,14 @@ Introduced process management features using ProcessWrapper and enhanced configu
 | 
				
			|||||||
- Enhanced CLI to support new process management commands like 'startAsDaemon'.
 | 
					- Enhanced CLI to support new process management commands like 'startAsDaemon'.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-03-01 - 1.3.1 - fix(test)
 | 
					## 2025-03-01 - 1.3.1 - fix(test)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update test script to fix type references and remove private method call
 | 
					Update test script to fix type references and remove private method call
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Corrected type references in test script for IMonitorConfig.
 | 
					- Corrected type references in test script for IMonitorConfig.
 | 
				
			||||||
- Fixed test script to use console.log instead of private method monitor.log.
 | 
					- Fixed test script to use console.log instead of private method monitor.log.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-03-01 - 1.3.0 - feat(cli)
 | 
					## 2025-03-01 - 1.3.0 - feat(cli)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Add CLI support with command parsing and version display
 | 
					Add CLI support with command parsing and version display
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added a basic CLI interface using smartcli.
 | 
					- Added a basic CLI interface using smartcli.
 | 
				
			||||||
@@ -36,6 +54,7 @@ Add CLI support with command parsing and version display
 | 
				
			|||||||
- Integrated project version display in the CLI.
 | 
					- Integrated project version display in the CLI.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-03-01 - 1.2.0 - feat(core)
 | 
					## 2025-03-01 - 1.2.0 - feat(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Introduce ProcessMonitor with memory management and spawning features
 | 
					Introduce ProcessMonitor with memory management and spawning features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added ProcessMonitor class with functionality to manage process execution and memory usage.
 | 
					- Added ProcessMonitor class with functionality to manage process execution and memory usage.
 | 
				
			||||||
@@ -45,12 +64,14 @@ Introduce ProcessMonitor with memory management and spawning features
 | 
				
			|||||||
- Updated test file to include example usage of ProcessMonitor.
 | 
					- Updated test file to include example usage of ProcessMonitor.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-03-01 - 1.1.1 - fix(package)
 | 
					## 2025-03-01 - 1.1.1 - fix(package)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update dependencies and pnpm configuration
 | 
					Update dependencies and pnpm configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated @types/node to 22.13.8
 | 
					- Updated @types/node to 22.13.8
 | 
				
			||||||
- Updated pnpm configuration to include onlyBuiltDependencies with esbuild, mongodb-memory-server, and puppeteer
 | 
					- Updated pnpm configuration to include onlyBuiltDependencies with esbuild, mongodb-memory-server, and puppeteer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-03-01 - 1.1.0 - feat(core)
 | 
					## 2025-03-01 - 1.1.0 - feat(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Introduce ProcessMonitor class and integrate native and external plugins
 | 
					Introduce ProcessMonitor class and integrate native and external plugins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added a new ProcessMonitor class to manage and monitor child processes with memory constraints.
 | 
					- Added a new ProcessMonitor class to manage and monitor child processes with memory constraints.
 | 
				
			||||||
@@ -58,14 +79,16 @@ Introduce ProcessMonitor class and integrate native and external plugins
 | 
				
			|||||||
- Adjusted index and related files for improved modular structure.
 | 
					- Adjusted index and related files for improved modular structure.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-02-24 - 1.0.3 - fix(core)
 | 
					## 2025-02-24 - 1.0.3 - fix(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Corrected description in package.json and readme.md from 'task manager' to 'process manager'.
 | 
					Corrected description in package.json and readme.md from 'task manager' to 'process manager'.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated the project description in package.json.
 | 
					- Updated the project description in package.json.
 | 
				
			||||||
- Aligned the description in readme.md with package.json.
 | 
					- Aligned the description in readme.md with package.json.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-02-24 - 1.0.2 - fix(core)
 | 
					## 2025-02-24 - 1.0.2 - fix(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Internal changes with no functional impact.
 | 
					Internal changes with no functional impact.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
## 2025-02-24 - 1.0.1 - initial release
 | 
					## 2025-02-24 - 1.0.1 - initial release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Initial release with baseline functionality.
 | 
					Initial release with baseline functionality.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								package.json
									
									
									
									
									
								
							@@ -18,20 +18,21 @@
 | 
				
			|||||||
    "tspm": "./cli.js"
 | 
					    "tspm": "./cli.js"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@git.zone/tsbuild": "^2.2.6",
 | 
					    "@git.zone/tsbuild": "^2.6.7",
 | 
				
			||||||
    "@git.zone/tsbundle": "^2.0.5",
 | 
					    "@git.zone/tsbundle": "^2.5.1",
 | 
				
			||||||
    "@git.zone/tsrun": "^1.2.46",
 | 
					    "@git.zone/tsrun": "^1.2.46",
 | 
				
			||||||
    "@git.zone/tstest": "^1.0.44",
 | 
					    "@git.zone/tstest": "^2.3.5",
 | 
				
			||||||
    "@push.rocks/tapbundle": "^5.5.9",
 | 
					    "@push.rocks/tapbundle": "^6.0.3",
 | 
				
			||||||
    "@types/node": "^22.13.10"
 | 
					    "@types/node": "^22.13.10"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@push.rocks/npmextra": "^5.1.2",
 | 
					    "@push.rocks/npmextra": "^5.3.3",
 | 
				
			||||||
    "@push.rocks/projectinfo": "^5.0.2",
 | 
					    "@push.rocks/projectinfo": "^5.0.2",
 | 
				
			||||||
    "@push.rocks/smartcli": "^4.0.11",
 | 
					    "@push.rocks/smartcli": "^4.0.11",
 | 
				
			||||||
    "@push.rocks/smartdaemon": "^2.0.8",
 | 
					    "@push.rocks/smartdaemon": "^2.0.8",
 | 
				
			||||||
    "@push.rocks/smartpath": "^5.0.18",
 | 
					    "@push.rocks/smartipc": "^2.0.3",
 | 
				
			||||||
    "pidusage": "^4.0.0",
 | 
					    "@push.rocks/smartpath": "^6.0.0",
 | 
				
			||||||
 | 
					    "pidusage": "^4.0.1",
 | 
				
			||||||
    "ps-tree": "^1.2.0"
 | 
					    "ps-tree": "^1.2.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "repository": {
 | 
					  "repository": {
 | 
				
			||||||
@@ -61,5 +62,6 @@
 | 
				
			|||||||
      "mongodb-memory-server",
 | 
					      "mongodb-memory-server",
 | 
				
			||||||
      "puppeteer"
 | 
					      "puppeteer"
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  }
 | 
					  },
 | 
				
			||||||
 | 
					  "packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5443
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5443
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										209
									
								
								readme.plan.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								readme.plan.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,209 @@
 | 
				
			|||||||
 | 
					# TSPM Refactoring Plan: Central Daemon Architecture
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Problem Analysis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Currently, each `startAsDaemon` creates an isolated tspm instance with no coordination:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Multiple daemons reading/writing same config file
 | 
				
			||||||
 | 
					- No communication between instances
 | 
				
			||||||
 | 
					- Inconsistent process management
 | 
				
			||||||
 | 
					- `tspm list` shows all processes but each daemon only manages its own
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Proposed Architecture
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 1. Central Daemon Manager (`ts/classes.daemon.ts`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Single daemon instance managing ALL processes
 | 
				
			||||||
 | 
					- Runs continuously in background
 | 
				
			||||||
 | 
					- Uses Unix socket for IPC at `~/.tspm/tspm.sock`
 | 
				
			||||||
 | 
					- Maintains single source of truth for process state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 2. IPC Communication Layer (`ts/classes.ipc.ts`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **Framework**: Use `@push.rocks/smartipc` v2.0.1
 | 
				
			||||||
 | 
					- **Server**: SmartIpc server in daemon using Unix Domain Socket
 | 
				
			||||||
 | 
					- **Client**: SmartIpc client in CLI for all operations
 | 
				
			||||||
 | 
					- **Socket Path**: `~/.tspm/tspm.sock` (Unix) or named pipe (Windows)
 | 
				
			||||||
 | 
					- **Protocol**: Type-safe request/response with SmartIpc's built-in patterns
 | 
				
			||||||
 | 
					- **Features**:
 | 
				
			||||||
 | 
					  - Automatic reconnection with exponential backoff
 | 
				
			||||||
 | 
					  - Heartbeat monitoring for daemon health
 | 
				
			||||||
 | 
					  - Type-safe message contracts
 | 
				
			||||||
 | 
					- **Auto-start**: CLI starts daemon if connection fails
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 3. New CLI Commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `tspm enable` - Start central daemon using systemd/launchd
 | 
				
			||||||
 | 
					- `tspm disable` - Stop and disable central daemon
 | 
				
			||||||
 | 
					- `tspm status` - Show daemon status
 | 
				
			||||||
 | 
					- Remove `startAsDaemon` (replaced by daemon + `tspm start`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 4. Refactored CLI (`ts/cli.ts`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					All commands become daemon clients:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```typescript
 | 
				
			||||||
 | 
					// Before: Direct process management
 | 
				
			||||||
 | 
					await tspm.start(config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// After: Send to daemon
 | 
				
			||||||
 | 
					await ipcClient.request('start', config);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 5. File Structure Changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					ts/
 | 
				
			||||||
 | 
					├── classes.daemon.ts       # New: Central daemon server
 | 
				
			||||||
 | 
					├── classes.ipc.ts          # New: IPC client/server
 | 
				
			||||||
 | 
					├── classes.tspm.ts         # Modified: Used by daemon only
 | 
				
			||||||
 | 
					├── cli.ts                  # Modified: Becomes thin client
 | 
				
			||||||
 | 
					└── classes.daemonmanager.ts # New: Systemd/launchd integration
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Implementation Steps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Phase 1: Core Infrastructure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [ ] Add `@push.rocks/smartipc` dependency (v2.0.1)
 | 
				
			||||||
 | 
					- [ ] Create IPC message type definitions for all operations
 | 
				
			||||||
 | 
					- [ ] Implement daemon server with SmartIpc server
 | 
				
			||||||
 | 
					- [ ] Create IPC client wrapper for CLI
 | 
				
			||||||
 | 
					- [ ] Add daemon lifecycle management (enable/disable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Phase 2: CLI Refactoring
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [ ] Convert all CLI commands to SmartIpc client requests
 | 
				
			||||||
 | 
					- [ ] Add daemon auto-start logic with connection monitoring
 | 
				
			||||||
 | 
					- [ ] Leverage SmartIpc's built-in reconnection and error handling
 | 
				
			||||||
 | 
					- [ ] Implement type-safe message contracts for all commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Phase 3: Migration & Cleanup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [ ] Migrate existing config to daemon-compatible format
 | 
				
			||||||
 | 
					- [ ] Remove `startAsDaemon` command
 | 
				
			||||||
 | 
					- [ ] Add migration guide for users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Technical Details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### IPC Implementation with SmartIpc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```typescript
 | 
				
			||||||
 | 
					// Daemon server setup
 | 
				
			||||||
 | 
					import { SmartIpc } from '@push.rocks/smartipc';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ipcServer = SmartIpc.createServer({
 | 
				
			||||||
 | 
					  id: 'tspm-daemon',
 | 
				
			||||||
 | 
					  socketPath: '~/.tspm/tspm.sock', // Unix socket
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Message handlers with type safety
 | 
				
			||||||
 | 
					ipcServer.onMessage<StartRequest, StartResponse>(
 | 
				
			||||||
 | 
					  'start',
 | 
				
			||||||
 | 
					  async (data, clientId) => {
 | 
				
			||||||
 | 
					    const result = await tspmManager.start(data.config);
 | 
				
			||||||
 | 
					    return { success: true, processId: result.pid };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CLI client setup
 | 
				
			||||||
 | 
					const ipcClient = SmartIpc.createClient({
 | 
				
			||||||
 | 
					  id: 'tspm-daemon',
 | 
				
			||||||
 | 
					  socketPath: '~/.tspm/tspm.sock',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Type-safe requests
 | 
				
			||||||
 | 
					const response = await ipcClient.request<StartRequest, StartResponse>('start', {
 | 
				
			||||||
 | 
					  config: processConfig,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Message Types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```typescript
 | 
				
			||||||
 | 
					interface StartRequest {
 | 
				
			||||||
 | 
					  config: ProcessConfig;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface StartResponse {
 | 
				
			||||||
 | 
					  success: boolean;
 | 
				
			||||||
 | 
					  processId?: number;
 | 
				
			||||||
 | 
					  error?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Daemon State File
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`~/.tspm/daemon.state` - PID, socket path, version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Process Management
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Daemon maintains all ProcessMonitor instances internally, CLI never directly manages processes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Key Benefits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Architecture Benefits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Single daemon manages all processes
 | 
				
			||||||
 | 
					- Consistent state management
 | 
				
			||||||
 | 
					- Efficient resource usage
 | 
				
			||||||
 | 
					- Better process coordination
 | 
				
			||||||
 | 
					- Proper service integration with OS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### SmartIpc Advantages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **Cross-platform**: Unix sockets on Linux/macOS, named pipes on Windows
 | 
				
			||||||
 | 
					- **Type-safe**: Full TypeScript support with generic message types
 | 
				
			||||||
 | 
					- **Resilient**: Automatic reconnection with exponential backoff
 | 
				
			||||||
 | 
					- **Observable**: Built-in metrics and heartbeat monitoring
 | 
				
			||||||
 | 
					- **Performant**: Low-latency messaging with zero external dependencies
 | 
				
			||||||
 | 
					- **Secure**: Connection limits and message size restrictions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Backwards Compatibility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Keep existing config format
 | 
				
			||||||
 | 
					- Auto-migrate on first run
 | 
				
			||||||
 | 
					- Provide clear upgrade instructions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Architecture Diagram
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					┌─────────────┐     IPC      ┌──────────────┐
 | 
				
			||||||
 | 
					│   CLI       │◄────────────►│    Daemon    │
 | 
				
			||||||
 | 
					│ (thin client)│    Socket    │   (server)   │
 | 
				
			||||||
 | 
					└─────────────┘              └──────────────┘
 | 
				
			||||||
 | 
					      │                              │
 | 
				
			||||||
 | 
					      │                              ▼
 | 
				
			||||||
 | 
					      │                       ┌──────────────┐
 | 
				
			||||||
 | 
					      │                       │     Tspm     │
 | 
				
			||||||
 | 
					      │                       │   Manager    │
 | 
				
			||||||
 | 
					      │                       └──────────────┘
 | 
				
			||||||
 | 
					      │                              │
 | 
				
			||||||
 | 
					      ▼                              ▼
 | 
				
			||||||
 | 
					┌─────────────┐              ┌──────────────┐
 | 
				
			||||||
 | 
					│   User      │              │ProcessMonitor│
 | 
				
			||||||
 | 
					│  Commands   │              │  Instances   │
 | 
				
			||||||
 | 
					└─────────────┘              └──────────────┘
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Migration Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **Version 2.0.0-alpha**: Implement daemon with backwards compatibility
 | 
				
			||||||
 | 
					2. **Version 2.0.0-beta**: Deprecate `startAsDaemon`, encourage daemon mode
 | 
				
			||||||
 | 
					3. **Version 2.0.0**: Remove legacy code, daemon-only operation
 | 
				
			||||||
 | 
					4. **Documentation**: Update all examples and guides
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Security Considerations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Unix socket permissions (user-only access)
 | 
				
			||||||
 | 
					- Validate all IPC messages
 | 
				
			||||||
 | 
					- Rate limiting for IPC requests
 | 
				
			||||||
 | 
					- Secure daemon shutdown mechanism
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Testing Requirements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Unit tests for IPC layer
 | 
				
			||||||
 | 
					- Integration tests for daemon lifecycle
 | 
				
			||||||
 | 
					- Migration tests from current architecture
 | 
				
			||||||
 | 
					- Performance tests for multiple processes
 | 
				
			||||||
 | 
					- Stress tests for IPC communication
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { expect, expectAsync, tap } from '@push.rocks/tapbundle';
 | 
					import { expect, tap } from '@git.zone/tstest/tapbundle';
 | 
				
			||||||
import * as tspm from '../ts/index.js';
 | 
					import * as tspm from '../ts/index.js';
 | 
				
			||||||
import { join } from 'path';
 | 
					import { join } from 'path';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,6 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const commitinfo = {
 | 
					export const commitinfo = {
 | 
				
			||||||
  name: '@git.zone/tspm',
 | 
					  name: '@git.zone/tspm',
 | 
				
			||||||
  version: '1.5.1',
 | 
					  version: '1.6.0',
 | 
				
			||||||
  description: 'a no fuzz process manager'
 | 
					  description: 'a no fuzz process manager'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ export class TspmConfig {
 | 
				
			|||||||
  public npmextraInstance = new plugins.npmextra.KeyValueStore({
 | 
					  public npmextraInstance = new plugins.npmextra.KeyValueStore({
 | 
				
			||||||
    identityArg: '@git.zone__tspm',
 | 
					    identityArg: '@git.zone__tspm',
 | 
				
			||||||
    typeArg: 'userHomeDir',
 | 
					    typeArg: 'userHomeDir',
 | 
				
			||||||
  })
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async readKey(keyArg: string): Promise<string> {
 | 
					  public async readKey(keyArg: string): Promise<string> {
 | 
				
			||||||
    return await this.npmextraInstance.readKey(keyArg);
 | 
					    return await this.npmextraInstance.readKey(keyArg);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										415
									
								
								ts/classes.daemon.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										415
									
								
								ts/classes.daemon.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,415 @@
 | 
				
			|||||||
 | 
					import * as plugins from './plugins.js';
 | 
				
			||||||
 | 
					import * as paths from './paths.js';
 | 
				
			||||||
 | 
					import { Tspm } from './classes.tspm.js';
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  IpcMethodMap,
 | 
				
			||||||
 | 
					  RequestForMethod,
 | 
				
			||||||
 | 
					  ResponseForMethod,
 | 
				
			||||||
 | 
					  DaemonStatusResponse,
 | 
				
			||||||
 | 
					  HeartbeatResponse,
 | 
				
			||||||
 | 
					} from './ipc.types.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Central daemon server that manages all TSPM processes
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class TspmDaemon {
 | 
				
			||||||
 | 
					  private tspmInstance: Tspm;
 | 
				
			||||||
 | 
					  private ipcServer: plugins.smartipc.IpcServer;
 | 
				
			||||||
 | 
					  private startTime: number;
 | 
				
			||||||
 | 
					  private isShuttingDown: boolean = false;
 | 
				
			||||||
 | 
					  private socketPath: string;
 | 
				
			||||||
 | 
					  private heartbeatInterval: NodeJS.Timeout | null = null;
 | 
				
			||||||
 | 
					  private daemonPidFile: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    this.tspmInstance = new Tspm();
 | 
				
			||||||
 | 
					    this.socketPath = plugins.path.join(paths.tspmDir, 'tspm.sock');
 | 
				
			||||||
 | 
					    this.daemonPidFile = plugins.path.join(paths.tspmDir, 'daemon.pid');
 | 
				
			||||||
 | 
					    this.startTime = Date.now();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Start the daemon server
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async start(): Promise<void> {
 | 
				
			||||||
 | 
					    console.log('Starting TSPM daemon...');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check if another daemon is already running
 | 
				
			||||||
 | 
					    if (await this.isDaemonRunning()) {
 | 
				
			||||||
 | 
					      throw new Error('Another TSPM daemon instance is already running');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Initialize IPC server
 | 
				
			||||||
 | 
					    this.ipcServer = new plugins.smartipc.IpcServer({
 | 
				
			||||||
 | 
					      id: 'tspm-daemon',
 | 
				
			||||||
 | 
					      socketPath: this.socketPath,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Register message handlers
 | 
				
			||||||
 | 
					    this.registerHandlers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Start the IPC server
 | 
				
			||||||
 | 
					    await this.ipcServer.start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Write PID file
 | 
				
			||||||
 | 
					    await this.writePidFile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Start heartbeat monitoring
 | 
				
			||||||
 | 
					    this.startHeartbeatMonitoring();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Load existing process configurations
 | 
				
			||||||
 | 
					    await this.tspmInstance.loadProcessConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set up graceful shutdown handlers
 | 
				
			||||||
 | 
					    this.setupShutdownHandlers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(`TSPM daemon started successfully on ${this.socketPath}`);
 | 
				
			||||||
 | 
					    console.log(`PID: ${process.pid}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Register all IPC message handlers
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private registerHandlers(): void {
 | 
				
			||||||
 | 
					    // Process management handlers
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'start'>>(
 | 
				
			||||||
 | 
					      'start',
 | 
				
			||||||
 | 
					      async (request) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          await this.tspmInstance.start(request.config);
 | 
				
			||||||
 | 
					          const processInfo = this.tspmInstance.processInfo.get(
 | 
				
			||||||
 | 
					            request.config.id,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            processId: request.config.id,
 | 
				
			||||||
 | 
					            pid: processInfo?.pid,
 | 
				
			||||||
 | 
					            status: processInfo?.status || 'stopped',
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          throw new Error(`Failed to start process: ${error.message}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'stop'>>(
 | 
				
			||||||
 | 
					      'stop',
 | 
				
			||||||
 | 
					      async (request) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          await this.tspmInstance.stop(request.id);
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            success: true,
 | 
				
			||||||
 | 
					            message: `Process ${request.id} stopped successfully`,
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          throw new Error(`Failed to stop process: ${error.message}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'restart'>>('restart', async (request) => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await this.tspmInstance.restart(request.id);
 | 
				
			||||||
 | 
					        const processInfo = this.tspmInstance.processInfo.get(request.id);
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          processId: request.id,
 | 
				
			||||||
 | 
					          pid: processInfo?.pid,
 | 
				
			||||||
 | 
					          status: processInfo?.status || 'stopped',
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        throw new Error(`Failed to restart process: ${error.message}`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'delete'>>(
 | 
				
			||||||
 | 
					      'delete',
 | 
				
			||||||
 | 
					      async (request) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          await this.tspmInstance.delete(request.id);
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            success: true,
 | 
				
			||||||
 | 
					            message: `Process ${request.id} deleted successfully`,
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          throw new Error(`Failed to delete process: ${error.message}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Query handlers
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'list'>>(
 | 
				
			||||||
 | 
					      'list',
 | 
				
			||||||
 | 
					      async () => {
 | 
				
			||||||
 | 
					        const processes = await this.tspmInstance.list();
 | 
				
			||||||
 | 
					        return { processes };
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'describe'>>('describe', async (request) => {
 | 
				
			||||||
 | 
					      const processInfo = await this.tspmInstance.describe(request.id);
 | 
				
			||||||
 | 
					      const config = this.tspmInstance.processConfigs.get(request.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!processInfo || !config) {
 | 
				
			||||||
 | 
					        throw new Error(`Process ${request.id} not found`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        processInfo,
 | 
				
			||||||
 | 
					        config,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'getLogs'>>('getLogs', async (request) => {
 | 
				
			||||||
 | 
					      const logs = await this.tspmInstance.getLogs(request.id);
 | 
				
			||||||
 | 
					      return { logs };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Batch operations handlers
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'startAll'>>('startAll', async () => {
 | 
				
			||||||
 | 
					      const started: string[] = [];
 | 
				
			||||||
 | 
					      const failed: Array<{ id: string; error: string }> = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await this.tspmInstance.startAll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Get status of all processes
 | 
				
			||||||
 | 
					      for (const [id, processInfo] of this.tspmInstance.processInfo) {
 | 
				
			||||||
 | 
					        if (processInfo.status === 'online') {
 | 
				
			||||||
 | 
					          started.push(id);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          failed.push({ id, error: 'Failed to start' });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return { started, failed };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'stopAll'>>('stopAll', async () => {
 | 
				
			||||||
 | 
					      const stopped: string[] = [];
 | 
				
			||||||
 | 
					      const failed: Array<{ id: string; error: string }> = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await this.tspmInstance.stopAll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Get status of all processes
 | 
				
			||||||
 | 
					      for (const [id, processInfo] of this.tspmInstance.processInfo) {
 | 
				
			||||||
 | 
					        if (processInfo.status === 'stopped') {
 | 
				
			||||||
 | 
					          stopped.push(id);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          failed.push({ id, error: 'Failed to stop' });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return { stopped, failed };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'restartAll'>>('restartAll', async () => {
 | 
				
			||||||
 | 
					      const restarted: string[] = [];
 | 
				
			||||||
 | 
					      const failed: Array<{ id: string; error: string }> = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await this.tspmInstance.restartAll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Get status of all processes
 | 
				
			||||||
 | 
					      for (const [id, processInfo] of this.tspmInstance.processInfo) {
 | 
				
			||||||
 | 
					        if (processInfo.status === 'online') {
 | 
				
			||||||
 | 
					          restarted.push(id);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          failed.push({ id, error: 'Failed to restart' });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return { restarted, failed };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Daemon management handlers
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'daemon:status'>>('daemon:status', async () => {
 | 
				
			||||||
 | 
					      const memUsage = process.memoryUsage();
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        status: 'running',
 | 
				
			||||||
 | 
					        pid: process.pid,
 | 
				
			||||||
 | 
					        uptime: Date.now() - this.startTime,
 | 
				
			||||||
 | 
					        processCount: this.tspmInstance.processes.size,
 | 
				
			||||||
 | 
					        memoryUsage: memUsage.heapUsed,
 | 
				
			||||||
 | 
					        cpuUsage: process.cpuUsage().user / 1000000, // Convert to seconds
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'daemon:shutdown'>>('daemon:shutdown', async (request) => {
 | 
				
			||||||
 | 
					      if (this.isShuttingDown) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          success: false,
 | 
				
			||||||
 | 
					          message: 'Daemon is already shutting down',
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Schedule shutdown
 | 
				
			||||||
 | 
					      const graceful = request.graceful !== false;
 | 
				
			||||||
 | 
					      const timeout = request.timeout || 10000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (graceful) {
 | 
				
			||||||
 | 
					        setTimeout(() => this.shutdown(true), 100);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        setTimeout(() => this.shutdown(false), 100);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        success: true,
 | 
				
			||||||
 | 
					        message: `Daemon will shutdown ${graceful ? 'gracefully' : 'immediately'} in ${timeout}ms`,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Heartbeat handler
 | 
				
			||||||
 | 
					    this.ipcServer.on<RequestForMethod<'heartbeat'>>('heartbeat', async () => {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        timestamp: Date.now(),
 | 
				
			||||||
 | 
					        status: this.isShuttingDown ? 'degraded' : 'healthy',
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Start heartbeat monitoring
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private startHeartbeatMonitoring(): void {
 | 
				
			||||||
 | 
					    // Send heartbeat every 30 seconds
 | 
				
			||||||
 | 
					    this.heartbeatInterval = setInterval(() => {
 | 
				
			||||||
 | 
					      // This is where we could implement health checks
 | 
				
			||||||
 | 
					      // For now, just log that the daemon is alive
 | 
				
			||||||
 | 
					      const uptime = Math.floor((Date.now() - this.startTime) / 1000);
 | 
				
			||||||
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `[Heartbeat] Daemon alive - Uptime: ${uptime}s, Processes: ${this.tspmInstance.processes.size}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }, 30000);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Set up graceful shutdown handlers
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private setupShutdownHandlers(): void {
 | 
				
			||||||
 | 
					    const shutdownHandler = async (signal: string) => {
 | 
				
			||||||
 | 
					      console.log(`\nReceived ${signal}, initiating graceful shutdown...`);
 | 
				
			||||||
 | 
					      await this.shutdown(true);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    process.on('SIGTERM', () => shutdownHandler('SIGTERM'));
 | 
				
			||||||
 | 
					    process.on('SIGINT', () => shutdownHandler('SIGINT'));
 | 
				
			||||||
 | 
					    process.on('SIGHUP', () => shutdownHandler('SIGHUP'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Handle uncaught errors
 | 
				
			||||||
 | 
					    process.on('uncaughtException', (error) => {
 | 
				
			||||||
 | 
					      console.error('Uncaught exception:', error);
 | 
				
			||||||
 | 
					      this.shutdown(false);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    process.on('unhandledRejection', (reason, promise) => {
 | 
				
			||||||
 | 
					      console.error('Unhandled rejection at:', promise, 'reason:', reason);
 | 
				
			||||||
 | 
					      // Don't exit on unhandled rejection, just log it
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Shutdown the daemon
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async shutdown(graceful: boolean = true): Promise<void> {
 | 
				
			||||||
 | 
					    if (this.isShuttingDown) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.isShuttingDown = true;
 | 
				
			||||||
 | 
					    console.log('Shutting down TSPM daemon...');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Clear heartbeat interval
 | 
				
			||||||
 | 
					    if (this.heartbeatInterval) {
 | 
				
			||||||
 | 
					      clearInterval(this.heartbeatInterval);
 | 
				
			||||||
 | 
					      this.heartbeatInterval = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (graceful) {
 | 
				
			||||||
 | 
					      // Stop all processes gracefully
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        console.log('Stopping all managed processes...');
 | 
				
			||||||
 | 
					        await this.tspmInstance.stopAll();
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error stopping processes:', error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Stop IPC server
 | 
				
			||||||
 | 
					    if (this.ipcServer) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await this.ipcServer.stop();
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error stopping IPC server:', error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove PID file
 | 
				
			||||||
 | 
					    await this.removePidFile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove socket file if it exists
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const fs = await import('fs');
 | 
				
			||||||
 | 
					      await fs.promises.unlink(this.socketPath).catch(() => {});
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      // Ignore errors
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log('TSPM daemon shutdown complete');
 | 
				
			||||||
 | 
					    process.exit(0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Check if another daemon instance is running
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async isDaemonRunning(): Promise<boolean> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const fs = await import('fs');
 | 
				
			||||||
 | 
					      const pidContent = await fs.promises.readFile(
 | 
				
			||||||
 | 
					        this.daemonPidFile,
 | 
				
			||||||
 | 
					        'utf-8',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      const pid = parseInt(pidContent.trim(), 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check if process is running
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        process.kill(pid, 0);
 | 
				
			||||||
 | 
					        return true; // Process exists
 | 
				
			||||||
 | 
					      } catch {
 | 
				
			||||||
 | 
					        // Process doesn't exist, clean up stale PID file
 | 
				
			||||||
 | 
					        await this.removePidFile();
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      // PID file doesn't exist
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Write the daemon PID to a file
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async writePidFile(): Promise<void> {
 | 
				
			||||||
 | 
					    const fs = await import('fs');
 | 
				
			||||||
 | 
					    await fs.promises.writeFile(this.daemonPidFile, process.pid.toString());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Remove the daemon PID file
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async removePidFile(): Promise<void> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const fs = await import('fs');
 | 
				
			||||||
 | 
					      await fs.promises.unlink(this.daemonPidFile);
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      // Ignore if file doesn't exist
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Main entry point for the daemon
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const startDaemon = async (): Promise<void> => {
 | 
				
			||||||
 | 
					  const daemon = new TspmDaemon();
 | 
				
			||||||
 | 
					  await daemon.start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Keep the process alive
 | 
				
			||||||
 | 
					  await new Promise(() => {});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										263
									
								
								ts/classes.ipcclient.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								ts/classes.ipcclient.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,263 @@
 | 
				
			|||||||
 | 
					import * as plugins from './plugins.js';
 | 
				
			||||||
 | 
					import * as paths from './paths.js';
 | 
				
			||||||
 | 
					import { spawn } from 'child_process';
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  IpcMethodMap,
 | 
				
			||||||
 | 
					  RequestForMethod,
 | 
				
			||||||
 | 
					  ResponseForMethod,
 | 
				
			||||||
 | 
					} from './ipc.types.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * IPC client for communicating with the TSPM daemon
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class TspmIpcClient {
 | 
				
			||||||
 | 
					  private ipcClient: plugins.smartipc.IpcClient | null = null;
 | 
				
			||||||
 | 
					  private socketPath: string;
 | 
				
			||||||
 | 
					  private daemonPidFile: string;
 | 
				
			||||||
 | 
					  private isConnected: boolean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    this.socketPath = plugins.path.join(paths.tspmDir, 'tspm.sock');
 | 
				
			||||||
 | 
					    this.daemonPidFile = plugins.path.join(paths.tspmDir, 'daemon.pid');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Connect to the daemon, starting it if necessary
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async connect(): Promise<void> {
 | 
				
			||||||
 | 
					    // Check if already connected
 | 
				
			||||||
 | 
					    if (this.isConnected && this.ipcClient) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check if daemon is running
 | 
				
			||||||
 | 
					    const daemonRunning = await this.isDaemonRunning();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!daemonRunning) {
 | 
				
			||||||
 | 
					      console.log('Daemon not running, starting it...');
 | 
				
			||||||
 | 
					      await this.startDaemon();
 | 
				
			||||||
 | 
					      // Wait a bit for daemon to initialize
 | 
				
			||||||
 | 
					      await new Promise((resolve) => setTimeout(resolve, 1000));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create IPC client
 | 
				
			||||||
 | 
					    this.ipcClient = new plugins.smartipc.IpcClient({
 | 
				
			||||||
 | 
					      id: 'tspm-cli',
 | 
				
			||||||
 | 
					      socketPath: this.socketPath,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Connect to the daemon
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await this.ipcClient.connect();
 | 
				
			||||||
 | 
					      this.isConnected = true;
 | 
				
			||||||
 | 
					      console.log('Connected to TSPM daemon');
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.error('Failed to connect to daemon:', error);
 | 
				
			||||||
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        'Could not connect to TSPM daemon. Please try running "tspm daemon start" manually.',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Disconnect from the daemon
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async disconnect(): Promise<void> {
 | 
				
			||||||
 | 
					    if (this.ipcClient) {
 | 
				
			||||||
 | 
					      await this.ipcClient.disconnect();
 | 
				
			||||||
 | 
					      this.ipcClient = null;
 | 
				
			||||||
 | 
					      this.isConnected = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Send a request to the daemon
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async request<M extends keyof IpcMethodMap>(
 | 
				
			||||||
 | 
					    method: M,
 | 
				
			||||||
 | 
					    params: RequestForMethod<M>,
 | 
				
			||||||
 | 
					  ): Promise<ResponseForMethod<M>> {
 | 
				
			||||||
 | 
					    if (!this.isConnected || !this.ipcClient) {
 | 
				
			||||||
 | 
					      await this.connect();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const response = await this.ipcClient!.request<
 | 
				
			||||||
 | 
					        RequestForMethod<M>,
 | 
				
			||||||
 | 
					        ResponseForMethod<M>
 | 
				
			||||||
 | 
					      >(method, params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return response;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      // Handle connection errors by trying to reconnect once
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        error.message?.includes('ECONNREFUSED') ||
 | 
				
			||||||
 | 
					        error.message?.includes('ENOENT')
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        console.log('Connection lost, attempting to reconnect...');
 | 
				
			||||||
 | 
					        this.isConnected = false;
 | 
				
			||||||
 | 
					        await this.connect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Retry the request
 | 
				
			||||||
 | 
					        return await this.ipcClient!.request<
 | 
				
			||||||
 | 
					          RequestForMethod<M>,
 | 
				
			||||||
 | 
					          ResponseForMethod<M>
 | 
				
			||||||
 | 
					        >(method, params);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      throw error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Check if the daemon is running
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async isDaemonRunning(): Promise<boolean> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const fs = await import('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check if PID file exists
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const pidContent = await fs.promises.readFile(
 | 
				
			||||||
 | 
					          this.daemonPidFile,
 | 
				
			||||||
 | 
					          'utf-8',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        const pid = parseInt(pidContent.trim(), 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check if process is running
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          process.kill(pid, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Also check if socket exists and is accessible
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					            await fs.promises.access(this.socketPath);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					          } catch {
 | 
				
			||||||
 | 
					            // Socket doesn't exist, daemon might be starting
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } catch {
 | 
				
			||||||
 | 
					          // Process doesn't exist, clean up stale PID file
 | 
				
			||||||
 | 
					          await fs.promises.unlink(this.daemonPidFile).catch(() => {});
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch {
 | 
				
			||||||
 | 
					        // PID file doesn't exist
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Start the daemon process
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async startDaemon(): Promise<void> {
 | 
				
			||||||
 | 
					    const daemonScript = plugins.path.join(
 | 
				
			||||||
 | 
					      paths.packageDir,
 | 
				
			||||||
 | 
					      'dist_ts',
 | 
				
			||||||
 | 
					      'daemon.js',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Spawn the daemon as a detached process
 | 
				
			||||||
 | 
					    const daemonProcess = spawn(process.execPath, [daemonScript], {
 | 
				
			||||||
 | 
					      detached: true,
 | 
				
			||||||
 | 
					      stdio: ['ignore', 'ignore', 'ignore'],
 | 
				
			||||||
 | 
					      env: {
 | 
				
			||||||
 | 
					        ...process.env,
 | 
				
			||||||
 | 
					        TSPM_DAEMON_MODE: 'true',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Unref the process so the parent can exit
 | 
				
			||||||
 | 
					    daemonProcess.unref();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(`Started daemon process with PID: ${daemonProcess.pid}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Wait for daemon to be ready (check for socket file)
 | 
				
			||||||
 | 
					    const maxWaitTime = 10000; // 10 seconds
 | 
				
			||||||
 | 
					    const startTime = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (Date.now() - startTime < maxWaitTime) {
 | 
				
			||||||
 | 
					      if (await this.isDaemonRunning()) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      await new Promise((resolve) => setTimeout(resolve, 500));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw new Error('Daemon failed to start within timeout period');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Stop the daemon
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async stopDaemon(graceful: boolean = true): Promise<void> {
 | 
				
			||||||
 | 
					    if (!(await this.isDaemonRunning())) {
 | 
				
			||||||
 | 
					      console.log('Daemon is not running');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await this.connect();
 | 
				
			||||||
 | 
					      await this.request('daemon:shutdown', {
 | 
				
			||||||
 | 
					        graceful,
 | 
				
			||||||
 | 
					        timeout: 10000,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      console.log('Daemon shutdown initiated');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Wait for daemon to actually stop
 | 
				
			||||||
 | 
					      const maxWaitTime = 15000; // 15 seconds
 | 
				
			||||||
 | 
					      const startTime = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      while (Date.now() - startTime < maxWaitTime) {
 | 
				
			||||||
 | 
					        if (!(await this.isDaemonRunning())) {
 | 
				
			||||||
 | 
					          console.log('Daemon stopped successfully');
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await new Promise((resolve) => setTimeout(resolve, 500));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      console.warn(
 | 
				
			||||||
 | 
					        'Daemon did not stop within timeout, it may still be running',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.error('Error stopping daemon:', error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Try to kill the process directly if graceful shutdown failed
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const fs = await import('fs');
 | 
				
			||||||
 | 
					        const pidContent = await fs.promises.readFile(
 | 
				
			||||||
 | 
					          this.daemonPidFile,
 | 
				
			||||||
 | 
					          'utf-8',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        const pid = parseInt(pidContent.trim(), 10);
 | 
				
			||||||
 | 
					        process.kill(pid, 'SIGKILL');
 | 
				
			||||||
 | 
					        console.log('Force killed daemon process');
 | 
				
			||||||
 | 
					      } catch {
 | 
				
			||||||
 | 
					        console.error('Could not force kill daemon');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get daemon status
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async getDaemonStatus(): Promise<ResponseForMethod<'daemon:status'> | null> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (!(await this.isDaemonRunning())) {
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await this.connect();
 | 
				
			||||||
 | 
					      return await this.request('daemon:status', {});
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.error('Error getting daemon status:', error);
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Singleton instance
 | 
				
			||||||
 | 
					export const tspmIpcClient = new TspmIpcClient();
 | 
				
			||||||
@@ -36,7 +36,10 @@ export class ProcessMonitor {
 | 
				
			|||||||
    const interval = this.config.monitorIntervalMs || 5000;
 | 
					    const interval = this.config.monitorIntervalMs || 5000;
 | 
				
			||||||
    this.intervalId = setInterval((): void => {
 | 
					    this.intervalId = setInterval((): void => {
 | 
				
			||||||
      if (this.processWrapper && this.processWrapper.getPid()) {
 | 
					      if (this.processWrapper && this.processWrapper.getPid()) {
 | 
				
			||||||
        this.monitorProcessGroup(this.processWrapper.getPid()!, this.config.memoryLimitBytes);
 | 
					        this.monitorProcessGroup(
 | 
				
			||||||
 | 
					          this.processWrapper.getPid()!,
 | 
				
			||||||
 | 
					          this.config.memoryLimitBytes,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }, interval);
 | 
					    }, interval);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -69,7 +72,9 @@ export class ProcessMonitor {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.processWrapper.on('exit', (code: number | null, signal: string | null): void => {
 | 
					    this.processWrapper.on(
 | 
				
			||||||
 | 
					      'exit',
 | 
				
			||||||
 | 
					      (code: number | null, signal: string | null): void => {
 | 
				
			||||||
        const exitMsg = `Process exited with code ${code}, signal ${signal}.`;
 | 
					        const exitMsg = `Process exited with code ${code}, signal ${signal}.`;
 | 
				
			||||||
        this.logger.info(exitMsg);
 | 
					        this.logger.info(exitMsg);
 | 
				
			||||||
        this.log(exitMsg);
 | 
					        this.log(exitMsg);
 | 
				
			||||||
@@ -80,12 +85,16 @@ export class ProcessMonitor {
 | 
				
			|||||||
          this.restartCount++;
 | 
					          this.restartCount++;
 | 
				
			||||||
          this.spawnProcess();
 | 
					          this.spawnProcess();
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
        this.logger.debug('Not restarting process because monitor is stopped');
 | 
					          this.logger.debug(
 | 
				
			||||||
 | 
					            'Not restarting process because monitor is stopped',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.processWrapper.on('error', (error: Error | ProcessError): void => {
 | 
					    this.processWrapper.on('error', (error: Error | ProcessError): void => {
 | 
				
			||||||
      const errorMsg = error instanceof ProcessError 
 | 
					      const errorMsg =
 | 
				
			||||||
 | 
					        error instanceof ProcessError
 | 
				
			||||||
          ? `Process error: ${error.toString()}`
 | 
					          ? `Process error: ${error.toString()}`
 | 
				
			||||||
          : `Process error: ${error.message}`;
 | 
					          : `Process error: ${error.message}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,7 +117,9 @@ export class ProcessMonitor {
 | 
				
			|||||||
    } catch (error: Error | unknown) {
 | 
					    } catch (error: Error | unknown) {
 | 
				
			||||||
      // The process wrapper will handle logging the error
 | 
					      // The process wrapper will handle logging the error
 | 
				
			||||||
      // Just prevent it from bubbling up further
 | 
					      // Just prevent it from bubbling up further
 | 
				
			||||||
      this.logger.error(`Failed to start process: ${error instanceof Error ? error.message : String(error)}`);
 | 
					      this.logger.error(
 | 
				
			||||||
 | 
					        `Failed to start process: ${error instanceof Error ? error.message : String(error)}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -116,24 +127,27 @@ export class ProcessMonitor {
 | 
				
			|||||||
   * Monitor the process group's memory usage. If the total memory exceeds the limit,
 | 
					   * Monitor the process group's memory usage. If the total memory exceeds the limit,
 | 
				
			||||||
   * kill the process group so that the 'exit' handler can restart it.
 | 
					   * kill the process group so that the 'exit' handler can restart it.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  private async monitorProcessGroup(pid: number, memoryLimit: number): Promise<void> {
 | 
					  private async monitorProcessGroup(
 | 
				
			||||||
 | 
					    pid: number,
 | 
				
			||||||
 | 
					    memoryLimit: number,
 | 
				
			||||||
 | 
					  ): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const memoryUsage = await this.getProcessGroupMemory(pid);
 | 
					      const memoryUsage = await this.getProcessGroupMemory(pid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.logger.debug(
 | 
					      this.logger.debug(
 | 
				
			||||||
        `Memory usage for PID ${pid}: ${this.humanReadableBytes(memoryUsage)} (${memoryUsage} bytes)`
 | 
					        `Memory usage for PID ${pid}: ${this.humanReadableBytes(memoryUsage)} (${memoryUsage} bytes)`,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Only log to the process log at longer intervals to avoid spamming
 | 
					      // Only log to the process log at longer intervals to avoid spamming
 | 
				
			||||||
      this.log(
 | 
					      this.log(
 | 
				
			||||||
        `Current memory usage for process group (PID ${pid}): ${this.humanReadableBytes(
 | 
					        `Current memory usage for process group (PID ${pid}): ${this.humanReadableBytes(
 | 
				
			||||||
          memoryUsage
 | 
					          memoryUsage,
 | 
				
			||||||
        )} (${memoryUsage} bytes)`
 | 
					        )} (${memoryUsage} bytes)`,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (memoryUsage > memoryLimit) {
 | 
					      if (memoryUsage > memoryLimit) {
 | 
				
			||||||
        const memoryLimitMsg = `Memory usage ${this.humanReadableBytes(
 | 
					        const memoryLimitMsg = `Memory usage ${this.humanReadableBytes(
 | 
				
			||||||
          memoryUsage
 | 
					          memoryUsage,
 | 
				
			||||||
        )} exceeds limit of ${this.humanReadableBytes(memoryLimit)}. Restarting process.`;
 | 
					        )} exceeds limit of ${this.humanReadableBytes(memoryLimit)}. Restarting process.`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.logger.warn(memoryLimitMsg);
 | 
					        this.logger.warn(memoryLimitMsg);
 | 
				
			||||||
@@ -148,7 +162,7 @@ export class ProcessMonitor {
 | 
				
			|||||||
      const processError = new ProcessError(
 | 
					      const processError = new ProcessError(
 | 
				
			||||||
        error instanceof Error ? error.message : String(error),
 | 
					        error instanceof Error ? error.message : String(error),
 | 
				
			||||||
        'ERR_MEMORY_MONITORING_FAILED',
 | 
					        'ERR_MEMORY_MONITORING_FAILED',
 | 
				
			||||||
        { pid }
 | 
					        { pid },
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.logger.error(processError);
 | 
					      this.logger.error(processError);
 | 
				
			||||||
@@ -161,29 +175,40 @@ export class ProcessMonitor {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  private getProcessGroupMemory(pid: number): Promise<number> {
 | 
					  private getProcessGroupMemory(pid: number): Promise<number> {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
      this.logger.debug(`Getting memory usage for process group with PID ${pid}`);
 | 
					      this.logger.debug(
 | 
				
			||||||
 | 
					        `Getting memory usage for process group with PID ${pid}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      plugins.psTree(pid, (err: Error | null, children: Array<{ PID: string }>) => {
 | 
					      plugins.psTree(
 | 
				
			||||||
 | 
					        pid,
 | 
				
			||||||
 | 
					        (err: Error | null, children: Array<{ PID: string }>) => {
 | 
				
			||||||
          if (err) {
 | 
					          if (err) {
 | 
				
			||||||
            const processError = new ProcessError(
 | 
					            const processError = new ProcessError(
 | 
				
			||||||
              `Failed to get process tree: ${err.message}`,
 | 
					              `Failed to get process tree: ${err.message}`,
 | 
				
			||||||
              'ERR_PSTREE_FAILED',
 | 
					              'ERR_PSTREE_FAILED',
 | 
				
			||||||
            { pid }
 | 
					              { pid },
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            this.logger.debug(`psTree error: ${err.message}`);
 | 
					            this.logger.debug(`psTree error: ${err.message}`);
 | 
				
			||||||
            return reject(processError);
 | 
					            return reject(processError);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // Include the main process and its children.
 | 
					          // Include the main process and its children.
 | 
				
			||||||
        const pids: number[] = [pid, ...children.map(child => Number(child.PID))];
 | 
					          const pids: number[] = [
 | 
				
			||||||
        this.logger.debug(`Found ${pids.length} processes in group with parent PID ${pid}`);
 | 
					            pid,
 | 
				
			||||||
 | 
					            ...children.map((child) => Number(child.PID)),
 | 
				
			||||||
 | 
					          ];
 | 
				
			||||||
 | 
					          this.logger.debug(
 | 
				
			||||||
 | 
					            `Found ${pids.length} processes in group with parent PID ${pid}`,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        plugins.pidusage(pids, (err: Error | null, stats: Record<string, { memory: number }>) => {
 | 
					          plugins.pidusage(
 | 
				
			||||||
 | 
					            pids,
 | 
				
			||||||
 | 
					            (err: Error | null, stats: Record<string, { memory: number }>) => {
 | 
				
			||||||
              if (err) {
 | 
					              if (err) {
 | 
				
			||||||
                const processError = new ProcessError(
 | 
					                const processError = new ProcessError(
 | 
				
			||||||
                  `Failed to get process usage stats: ${err.message}`,
 | 
					                  `Failed to get process usage stats: ${err.message}`,
 | 
				
			||||||
                  'ERR_PIDUSAGE_FAILED',
 | 
					                  'ERR_PIDUSAGE_FAILED',
 | 
				
			||||||
              { pids }
 | 
					                  { pids },
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                this.logger.debug(`pidusage error: ${err.message}`);
 | 
					                this.logger.debug(`pidusage error: ${err.message}`);
 | 
				
			||||||
                return reject(processError);
 | 
					                return reject(processError);
 | 
				
			||||||
@@ -194,10 +219,14 @@ export class ProcessMonitor {
 | 
				
			|||||||
                totalMemory += stats[key].memory;
 | 
					                totalMemory += stats[key].memory;
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          this.logger.debug(`Total memory for process group: ${this.humanReadableBytes(totalMemory)}`);
 | 
					              this.logger.debug(
 | 
				
			||||||
 | 
					                `Total memory for process group: ${this.humanReadableBytes(totalMemory)}`,
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
              resolve(totalMemory);
 | 
					              resolve(totalMemory);
 | 
				
			||||||
        });
 | 
					            },
 | 
				
			||||||
      });
 | 
					          );
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,11 +42,15 @@ export class ProcessWrapper extends EventEmitter {
 | 
				
			|||||||
      this.logger.debug(`Starting process: ${this.options.command}`);
 | 
					      this.logger.debug(`Starting process: ${this.options.command}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.options.args && this.options.args.length > 0) {
 | 
					      if (this.options.args && this.options.args.length > 0) {
 | 
				
			||||||
        this.process = plugins.childProcess.spawn(this.options.command, this.options.args, {
 | 
					        this.process = plugins.childProcess.spawn(
 | 
				
			||||||
 | 
					          this.options.command,
 | 
				
			||||||
 | 
					          this.options.args,
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
            cwd: this.options.cwd,
 | 
					            cwd: this.options.cwd,
 | 
				
			||||||
            env: this.options.env || process.env,
 | 
					            env: this.options.env || process.env,
 | 
				
			||||||
            stdio: ['ignore', 'pipe', 'pipe'], // We need to pipe stdout and stderr
 | 
					            stdio: ['ignore', 'pipe', 'pipe'], // We need to pipe stdout and stderr
 | 
				
			||||||
        });
 | 
					          },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // Use shell mode to allow a full command string
 | 
					        // Use shell mode to allow a full command string
 | 
				
			||||||
        this.process = plugins.childProcess.spawn(this.options.command, {
 | 
					        this.process = plugins.childProcess.spawn(this.options.command, {
 | 
				
			||||||
@@ -72,7 +76,7 @@ export class ProcessWrapper extends EventEmitter {
 | 
				
			|||||||
        const processError = new ProcessError(
 | 
					        const processError = new ProcessError(
 | 
				
			||||||
          error.message,
 | 
					          error.message,
 | 
				
			||||||
          'ERR_PROCESS_EXECUTION',
 | 
					          'ERR_PROCESS_EXECUTION',
 | 
				
			||||||
          { command: this.options.command, pid: this.process?.pid }
 | 
					          { command: this.options.command, pid: this.process?.pid },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        this.logger.error(processError);
 | 
					        this.logger.error(processError);
 | 
				
			||||||
        this.addSystemLog(`Process error: ${processError.toString()}`);
 | 
					        this.addSystemLog(`Process error: ${processError.toString()}`);
 | 
				
			||||||
@@ -106,14 +110,14 @@ export class ProcessWrapper extends EventEmitter {
 | 
				
			|||||||
      this.addSystemLog(`Process started with PID ${this.process.pid}`);
 | 
					      this.addSystemLog(`Process started with PID ${this.process.pid}`);
 | 
				
			||||||
      this.logger.info(`Process started with PID ${this.process.pid}`);
 | 
					      this.logger.info(`Process started with PID ${this.process.pid}`);
 | 
				
			||||||
      this.emit('start', this.process.pid);
 | 
					      this.emit('start', this.process.pid);
 | 
				
			||||||
      
 | 
					 | 
				
			||||||
    } catch (error: Error | unknown) {
 | 
					    } catch (error: Error | unknown) {
 | 
				
			||||||
      const processError = error instanceof ProcessError 
 | 
					      const processError =
 | 
				
			||||||
 | 
					        error instanceof ProcessError
 | 
				
			||||||
          ? error
 | 
					          ? error
 | 
				
			||||||
          : new ProcessError(
 | 
					          : new ProcessError(
 | 
				
			||||||
              error instanceof Error ? error.message : String(error),
 | 
					              error instanceof Error ? error.message : String(error),
 | 
				
			||||||
              'ERR_PROCESS_START_FAILED',
 | 
					              'ERR_PROCESS_START_FAILED',
 | 
				
			||||||
            { command: this.options.command }
 | 
					              { command: this.options.command },
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.logger.error(processError);
 | 
					      this.logger.error(processError);
 | 
				
			||||||
@@ -145,15 +149,21 @@ export class ProcessWrapper extends EventEmitter {
 | 
				
			|||||||
        // Give it 5 seconds to shut down gracefully
 | 
					        // Give it 5 seconds to shut down gracefully
 | 
				
			||||||
        setTimeout((): void => {
 | 
					        setTimeout((): void => {
 | 
				
			||||||
          if (this.process && this.process.pid) {
 | 
					          if (this.process && this.process.pid) {
 | 
				
			||||||
            this.logger.warn(`Process ${this.process.pid} did not exit gracefully, force killing...`);
 | 
					            this.logger.warn(
 | 
				
			||||||
            this.addSystemLog('Process did not exit gracefully, force killing...');
 | 
					              `Process ${this.process.pid} did not exit gracefully, force killing...`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            this.addSystemLog(
 | 
				
			||||||
 | 
					              'Process did not exit gracefully, force killing...',
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
              process.kill(this.process.pid, 'SIGKILL');
 | 
					              process.kill(this.process.pid, 'SIGKILL');
 | 
				
			||||||
            } catch (error: Error | unknown) {
 | 
					            } catch (error: Error | unknown) {
 | 
				
			||||||
              // Process might have exited between checks
 | 
					              // Process might have exited between checks
 | 
				
			||||||
              this.logger.debug(`Failed to send SIGKILL, process probably already exited: ${
 | 
					              this.logger.debug(
 | 
				
			||||||
 | 
					                `Failed to send SIGKILL, process probably already exited: ${
 | 
				
			||||||
                  error instanceof Error ? error.message : String(error)
 | 
					                  error instanceof Error ? error.message : String(error)
 | 
				
			||||||
              }`);
 | 
					                }`,
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }, 5000);
 | 
					        }, 5000);
 | 
				
			||||||
@@ -161,7 +171,7 @@ export class ProcessWrapper extends EventEmitter {
 | 
				
			|||||||
        const processError = new ProcessError(
 | 
					        const processError = new ProcessError(
 | 
				
			||||||
          error instanceof Error ? error.message : String(error),
 | 
					          error instanceof Error ? error.message : String(error),
 | 
				
			||||||
          'ERR_PROCESS_STOP_FAILED',
 | 
					          'ERR_PROCESS_STOP_FAILED',
 | 
				
			||||||
          { pid: this.process.pid }
 | 
					          { pid: this.process.pid },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        this.logger.error(processError);
 | 
					        this.logger.error(processError);
 | 
				
			||||||
        this.addSystemLog(`Error stopping process: ${processError.toString()}`);
 | 
					        this.addSystemLog(`Error stopping process: ${processError.toString()}`);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,16 @@
 | 
				
			|||||||
import * as plugins from './plugins.js';
 | 
					import * as plugins from './plugins.js';
 | 
				
			||||||
import * as paths from './paths.js';
 | 
					import * as paths from './paths.js';
 | 
				
			||||||
import { ProcessMonitor, type IMonitorConfig } from './classes.processmonitor.js';
 | 
					import {
 | 
				
			||||||
 | 
					  ProcessMonitor,
 | 
				
			||||||
 | 
					  type IMonitorConfig,
 | 
				
			||||||
 | 
					} from './classes.processmonitor.js';
 | 
				
			||||||
import { TspmConfig } from './classes.config.js';
 | 
					import { TspmConfig } from './classes.config.js';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Logger,
 | 
					  Logger,
 | 
				
			||||||
  ProcessError,
 | 
					  ProcessError,
 | 
				
			||||||
  ConfigError,
 | 
					  ConfigError,
 | 
				
			||||||
  ValidationError,
 | 
					  ValidationError,
 | 
				
			||||||
  handleError 
 | 
					  handleError,
 | 
				
			||||||
} from './utils.errorhandler.js';
 | 
					} from './utils.errorhandler.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IProcessConfig extends IMonitorConfig {
 | 
					export interface IProcessConfig extends IMonitorConfig {
 | 
				
			||||||
@@ -34,9 +37,9 @@ export interface IProcessLog {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Tspm {
 | 
					export class Tspm {
 | 
				
			||||||
  private processes: Map<string, ProcessMonitor> = new Map();
 | 
					  public processes: Map<string, ProcessMonitor> = new Map();
 | 
				
			||||||
  private processConfigs: Map<string, IProcessConfig> = new Map();
 | 
					  public processConfigs: Map<string, IProcessConfig> = new Map();
 | 
				
			||||||
  private processInfo: Map<string, IProcessInfo> = new Map();
 | 
					  public processInfo: Map<string, IProcessInfo> = new Map();
 | 
				
			||||||
  private config: TspmConfig;
 | 
					  private config: TspmConfig;
 | 
				
			||||||
  private configStorageKey = 'processes';
 | 
					  private configStorageKey = 'processes';
 | 
				
			||||||
  private logger: Logger;
 | 
					  private logger: Logger;
 | 
				
			||||||
@@ -58,7 +61,7 @@ export class Tspm {
 | 
				
			|||||||
      throw new ValidationError(
 | 
					      throw new ValidationError(
 | 
				
			||||||
        'Invalid process configuration: missing required fields',
 | 
					        'Invalid process configuration: missing required fields',
 | 
				
			||||||
        'ERR_INVALID_CONFIG',
 | 
					        'ERR_INVALID_CONFIG',
 | 
				
			||||||
        { config }
 | 
					        { config },
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -66,7 +69,7 @@ export class Tspm {
 | 
				
			|||||||
    if (this.processes.has(config.id)) {
 | 
					    if (this.processes.has(config.id)) {
 | 
				
			||||||
      throw new ValidationError(
 | 
					      throw new ValidationError(
 | 
				
			||||||
        `Process with id '${config.id}' already exists`,
 | 
					        `Process with id '${config.id}' already exists`,
 | 
				
			||||||
        'ERR_DUPLICATE_PROCESS'
 | 
					        'ERR_DUPLICATE_PROCESS',
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -79,7 +82,7 @@ export class Tspm {
 | 
				
			|||||||
        id: config.id,
 | 
					        id: config.id,
 | 
				
			||||||
        status: 'stopped',
 | 
					        status: 'stopped',
 | 
				
			||||||
        memory: 0,
 | 
					        memory: 0,
 | 
				
			||||||
        restarts: 0
 | 
					        restarts: 0,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Create and start process monitor
 | 
					      // Create and start process monitor
 | 
				
			||||||
@@ -91,7 +94,7 @@ export class Tspm {
 | 
				
			|||||||
        memoryLimitBytes: config.memoryLimitBytes,
 | 
					        memoryLimitBytes: config.memoryLimitBytes,
 | 
				
			||||||
        monitorIntervalMs: config.monitorIntervalMs,
 | 
					        monitorIntervalMs: config.monitorIntervalMs,
 | 
				
			||||||
        env: config.env,
 | 
					        env: config.env,
 | 
				
			||||||
        logBufferSize: config.logBufferSize
 | 
					        logBufferSize: config.logBufferSize,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.processes.set(config.id, monitor);
 | 
					      this.processes.set(config.id, monitor);
 | 
				
			||||||
@@ -115,13 +118,13 @@ export class Tspm {
 | 
				
			|||||||
        throw new ProcessError(
 | 
					        throw new ProcessError(
 | 
				
			||||||
          `Failed to start process: ${error.message}`,
 | 
					          `Failed to start process: ${error.message}`,
 | 
				
			||||||
          'ERR_PROCESS_START_FAILED',
 | 
					          'ERR_PROCESS_START_FAILED',
 | 
				
			||||||
          { id: config.id, command: config.command }
 | 
					          { id: config.id, command: config.command },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        const genericError = new ProcessError(
 | 
					        const genericError = new ProcessError(
 | 
				
			||||||
          `Failed to start process: ${String(error)}`,
 | 
					          `Failed to start process: ${String(error)}`,
 | 
				
			||||||
          'ERR_PROCESS_START_FAILED',
 | 
					          'ERR_PROCESS_START_FAILED',
 | 
				
			||||||
          { id: config.id }
 | 
					          { id: config.id },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        this.logger.error(genericError);
 | 
					        this.logger.error(genericError);
 | 
				
			||||||
        throw genericError;
 | 
					        throw genericError;
 | 
				
			||||||
@@ -139,7 +142,7 @@ export class Tspm {
 | 
				
			|||||||
    if (!monitor) {
 | 
					    if (!monitor) {
 | 
				
			||||||
      const error = new ValidationError(
 | 
					      const error = new ValidationError(
 | 
				
			||||||
        `Process with id '${id}' not found`,
 | 
					        `Process with id '${id}' not found`,
 | 
				
			||||||
        'ERR_PROCESS_NOT_FOUND'
 | 
					        'ERR_PROCESS_NOT_FOUND',
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.logger.error(error);
 | 
					      this.logger.error(error);
 | 
				
			||||||
      throw error;
 | 
					      throw error;
 | 
				
			||||||
@@ -153,7 +156,7 @@ export class Tspm {
 | 
				
			|||||||
      const processError = new ProcessError(
 | 
					      const processError = new ProcessError(
 | 
				
			||||||
        `Failed to stop process: ${error instanceof Error ? error.message : String(error)}`,
 | 
					        `Failed to stop process: ${error instanceof Error ? error.message : String(error)}`,
 | 
				
			||||||
        'ERR_PROCESS_STOP_FAILED',
 | 
					        'ERR_PROCESS_STOP_FAILED',
 | 
				
			||||||
        { id }
 | 
					        { id },
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.logger.error(processError);
 | 
					      this.logger.error(processError);
 | 
				
			||||||
      throw processError;
 | 
					      throw processError;
 | 
				
			||||||
@@ -175,7 +178,7 @@ export class Tspm {
 | 
				
			|||||||
    if (!monitor || !config) {
 | 
					    if (!monitor || !config) {
 | 
				
			||||||
      const error = new ValidationError(
 | 
					      const error = new ValidationError(
 | 
				
			||||||
        `Process with id '${id}' not found`,
 | 
					        `Process with id '${id}' not found`,
 | 
				
			||||||
        'ERR_PROCESS_NOT_FOUND'
 | 
					        'ERR_PROCESS_NOT_FOUND',
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.logger.error(error);
 | 
					      this.logger.error(error);
 | 
				
			||||||
      throw error;
 | 
					      throw error;
 | 
				
			||||||
@@ -194,7 +197,7 @@ export class Tspm {
 | 
				
			|||||||
        memoryLimitBytes: config.memoryLimitBytes,
 | 
					        memoryLimitBytes: config.memoryLimitBytes,
 | 
				
			||||||
        monitorIntervalMs: config.monitorIntervalMs,
 | 
					        monitorIntervalMs: config.monitorIntervalMs,
 | 
				
			||||||
        env: config.env,
 | 
					        env: config.env,
 | 
				
			||||||
        logBufferSize: config.logBufferSize
 | 
					        logBufferSize: config.logBufferSize,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.processes.set(id, newMonitor);
 | 
					      this.processes.set(id, newMonitor);
 | 
				
			||||||
@@ -205,7 +208,7 @@ export class Tspm {
 | 
				
			|||||||
      if (info) {
 | 
					      if (info) {
 | 
				
			||||||
        this.updateProcessInfo(id, {
 | 
					        this.updateProcessInfo(id, {
 | 
				
			||||||
          status: 'online',
 | 
					          status: 'online',
 | 
				
			||||||
          restarts: info.restarts + 1
 | 
					          restarts: info.restarts + 1,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -214,7 +217,7 @@ export class Tspm {
 | 
				
			|||||||
      const processError = new ProcessError(
 | 
					      const processError = new ProcessError(
 | 
				
			||||||
        `Failed to restart process: ${error instanceof Error ? error.message : String(error)}`,
 | 
					        `Failed to restart process: ${error instanceof Error ? error.message : String(error)}`,
 | 
				
			||||||
        'ERR_PROCESS_RESTART_FAILED',
 | 
					        'ERR_PROCESS_RESTART_FAILED',
 | 
				
			||||||
        { id }
 | 
					        { id },
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.logger.error(processError);
 | 
					      this.logger.error(processError);
 | 
				
			||||||
      throw processError;
 | 
					      throw processError;
 | 
				
			||||||
@@ -231,7 +234,7 @@ export class Tspm {
 | 
				
			|||||||
    if (!this.processConfigs.has(id)) {
 | 
					    if (!this.processConfigs.has(id)) {
 | 
				
			||||||
      const error = new ValidationError(
 | 
					      const error = new ValidationError(
 | 
				
			||||||
        `Process with id '${id}' not found`,
 | 
					        `Process with id '${id}' not found`,
 | 
				
			||||||
        'ERR_PROCESS_NOT_FOUND'
 | 
					        'ERR_PROCESS_NOT_FOUND',
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.logger.error(error);
 | 
					      this.logger.error(error);
 | 
				
			||||||
      throw error;
 | 
					      throw error;
 | 
				
			||||||
@@ -260,12 +263,14 @@ export class Tspm {
 | 
				
			|||||||
        this.processInfo.delete(id);
 | 
					        this.processInfo.delete(id);
 | 
				
			||||||
        await this.saveProcessConfigs();
 | 
					        await this.saveProcessConfigs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.logger.info(`Successfully deleted process with id '${id}' after stopping failure`);
 | 
					        this.logger.info(
 | 
				
			||||||
 | 
					          `Successfully deleted process with id '${id}' after stopping failure`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      } catch (deleteError: Error | unknown) {
 | 
					      } catch (deleteError: Error | unknown) {
 | 
				
			||||||
        const configError = new ConfigError(
 | 
					        const configError = new ConfigError(
 | 
				
			||||||
          `Failed to delete process configuration: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`,
 | 
					          `Failed to delete process configuration: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`,
 | 
				
			||||||
          'ERR_CONFIG_DELETE_FAILED',
 | 
					          'ERR_CONFIG_DELETE_FAILED',
 | 
				
			||||||
          { id }
 | 
					          { id },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        this.logger.error(configError);
 | 
					        this.logger.error(configError);
 | 
				
			||||||
        throw configError;
 | 
					        throw configError;
 | 
				
			||||||
@@ -283,7 +288,9 @@ export class Tspm {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get detailed info for a specific process
 | 
					   * Get detailed info for a specific process
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public describe(id: string): { config: IProcessConfig; info: IProcessInfo } | null {
 | 
					  public describe(
 | 
				
			||||||
 | 
					    id: string,
 | 
				
			||||||
 | 
					  ): { config: IProcessConfig; info: IProcessInfo } | null {
 | 
				
			||||||
    const config = this.processConfigs.get(id);
 | 
					    const config = this.processConfigs.get(id);
 | 
				
			||||||
    const info = this.processInfo.get(id);
 | 
					    const info = this.processInfo.get(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -353,12 +360,15 @@ export class Tspm {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const configs = Array.from(this.processConfigs.values());
 | 
					      const configs = Array.from(this.processConfigs.values());
 | 
				
			||||||
      await this.config.writeKey(this.configStorageKey, JSON.stringify(configs));
 | 
					      await this.config.writeKey(
 | 
				
			||||||
 | 
					        this.configStorageKey,
 | 
				
			||||||
 | 
					        JSON.stringify(configs),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      this.logger.debug(`Saved ${configs.length} process configurations`);
 | 
					      this.logger.debug(`Saved ${configs.length} process configurations`);
 | 
				
			||||||
    } catch (error: Error | unknown) {
 | 
					    } catch (error: Error | unknown) {
 | 
				
			||||||
      const configError = new ConfigError(
 | 
					      const configError = new ConfigError(
 | 
				
			||||||
        `Failed to save process configurations: ${error instanceof Error ? error.message : String(error)}`,
 | 
					        `Failed to save process configurations: ${error instanceof Error ? error.message : String(error)}`,
 | 
				
			||||||
        'ERR_CONFIG_SAVE_FAILED'
 | 
					        'ERR_CONFIG_SAVE_FAILED',
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      this.logger.error(configError);
 | 
					      this.logger.error(configError);
 | 
				
			||||||
      throw configError;
 | 
					      throw configError;
 | 
				
			||||||
@@ -368,7 +378,7 @@ export class Tspm {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Load process configurations from config storage
 | 
					   * Load process configurations from config storage
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  private async loadProcessConfigs(): Promise<void> {
 | 
					  public async loadProcessConfigs(): Promise<void> {
 | 
				
			||||||
    this.logger.debug('Loading process configurations from storage');
 | 
					    this.logger.debug('Loading process configurations from storage');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
@@ -381,7 +391,9 @@ export class Tspm {
 | 
				
			|||||||
          for (const config of configs) {
 | 
					          for (const config of configs) {
 | 
				
			||||||
            // Validate config
 | 
					            // Validate config
 | 
				
			||||||
            if (!config.id || !config.command || !config.projectDir) {
 | 
					            if (!config.id || !config.command || !config.projectDir) {
 | 
				
			||||||
              this.logger.warn(`Skipping invalid process config for id '${config.id || 'unknown'}'`);
 | 
					              this.logger.warn(
 | 
				
			||||||
 | 
					                `Skipping invalid process config for id '${config.id || 'unknown'}'`,
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
              continue;
 | 
					              continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -392,13 +404,13 @@ export class Tspm {
 | 
				
			|||||||
              id: config.id,
 | 
					              id: config.id,
 | 
				
			||||||
              status: 'stopped',
 | 
					              status: 'stopped',
 | 
				
			||||||
              memory: 0,
 | 
					              memory: 0,
 | 
				
			||||||
              restarts: 0
 | 
					              restarts: 0,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } catch (parseError: Error | unknown) {
 | 
					        } catch (parseError: Error | unknown) {
 | 
				
			||||||
          const configError = new ConfigError(
 | 
					          const configError = new ConfigError(
 | 
				
			||||||
            `Failed to parse process configurations: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
 | 
					            `Failed to parse process configurations: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
 | 
				
			||||||
            'ERR_CONFIG_PARSE_FAILED'
 | 
					            'ERR_CONFIG_PARSE_FAILED',
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
          this.logger.error(configError);
 | 
					          this.logger.error(configError);
 | 
				
			||||||
          throw configError;
 | 
					          throw configError;
 | 
				
			||||||
@@ -413,7 +425,9 @@ export class Tspm {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // If no configs found or error reading, just continue with empty configs
 | 
					      // 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.info(
 | 
				
			||||||
 | 
					        'No saved process configurations found or error reading them',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										747
									
								
								ts/cli.ts
									
									
									
									
									
								
							
							
						
						
									
										747
									
								
								ts/cli.ts
									
									
									
									
									
								
							@@ -1,26 +1,66 @@
 | 
				
			|||||||
import * as plugins from './plugins.js';
 | 
					import * as plugins from './plugins.js';
 | 
				
			||||||
import * as paths from './paths.js';
 | 
					import * as paths from './paths.js';
 | 
				
			||||||
import { Tspm, type IProcessConfig } from './classes.tspm.js';
 | 
					import { tspmIpcClient } from './classes.ipcclient.js';
 | 
				
			||||||
import { 
 | 
					import { Logger, LogLevel } from './utils.errorhandler.js';
 | 
				
			||||||
  Logger, 
 | 
					import type { IProcessConfig } from './classes.tspm.js';
 | 
				
			||||||
  LogLevel, 
 | 
					 | 
				
			||||||
  handleError, 
 | 
					 | 
				
			||||||
  TspmError,
 | 
					 | 
				
			||||||
  ProcessError, 
 | 
					 | 
				
			||||||
  ConfigError, 
 | 
					 | 
				
			||||||
  ValidationError 
 | 
					 | 
				
			||||||
} from './utils.errorhandler.js';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Define interface for CLI arguments
 | 
					export interface CliArguments {
 | 
				
			||||||
interface CliArguments {
 | 
					  verbose?: boolean;
 | 
				
			||||||
  _: (string | number)[];
 | 
					  watch?: boolean;
 | 
				
			||||||
 | 
					  memory?: string;
 | 
				
			||||||
 | 
					  cwd?: string;
 | 
				
			||||||
 | 
					  daemon?: boolean;
 | 
				
			||||||
 | 
					  test?: boolean;
 | 
				
			||||||
 | 
					  name?: string;
 | 
				
			||||||
 | 
					  autorestart?: boolean;
 | 
				
			||||||
 | 
					  watchPaths?: string[];
 | 
				
			||||||
  [key: string]: any;
 | 
					  [key: string]: any;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper function to parse memory strings (e.g., "512MB", "2GB")
 | 
				
			||||||
 | 
					function parseMemoryString(memStr: string): number {
 | 
				
			||||||
 | 
					  const units = {
 | 
				
			||||||
 | 
					    KB: 1024,
 | 
				
			||||||
 | 
					    MB: 1024 * 1024,
 | 
				
			||||||
 | 
					    GB: 1024 * 1024 * 1024,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const match = memStr.toUpperCase().match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)?$/);
 | 
				
			||||||
 | 
					  if (!match) {
 | 
				
			||||||
 | 
					    throw new Error(
 | 
				
			||||||
 | 
					      `Invalid memory format: ${memStr}. Use format like "512MB" or "2GB"`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const value = parseFloat(match[1]);
 | 
				
			||||||
 | 
					  const unit = (match[2] || 'MB') as keyof typeof units;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return Math.floor(value * units[unit]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper function to format memory for display
 | 
				
			||||||
 | 
					function formatMemory(bytes: number): string {
 | 
				
			||||||
 | 
					  if (bytes >= 1024 * 1024 * 1024) {
 | 
				
			||||||
 | 
					    return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
 | 
				
			||||||
 | 
					  } else if (bytes >= 1024 * 1024) {
 | 
				
			||||||
 | 
					    return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
 | 
				
			||||||
 | 
					  } else if (bytes >= 1024) {
 | 
				
			||||||
 | 
					    return `${(bytes / 1024).toFixed(1)} KB`;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return `${bytes} B`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper function for padding strings
 | 
				
			||||||
 | 
					function pad(str: string, length: number): string {
 | 
				
			||||||
 | 
					  return str.length > length
 | 
				
			||||||
 | 
					    ? str.substring(0, length - 3) + '...'
 | 
				
			||||||
 | 
					    : str.padEnd(length);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const run = async (): Promise<void> => {
 | 
					export const run = async (): Promise<void> => {
 | 
				
			||||||
  const cliLogger = new Logger('CLI');
 | 
					  const cliLogger = new Logger('CLI');
 | 
				
			||||||
  const tspmProjectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
 | 
					  const tspmProjectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
 | 
				
			||||||
  const tspm = new Tspm();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Check if debug mode is enabled
 | 
					  // Check if debug mode is enabled
 | 
				
			||||||
  const debugMode = process.env.TSPM_DEBUG === 'true';
 | 
					  const debugMode = process.env.TSPM_DEBUG === 'true';
 | 
				
			||||||
@@ -35,337 +75,564 @@ export const run = async (): Promise<void> => {
 | 
				
			|||||||
  // Default command - show help and list processes
 | 
					  // Default command - show help and list processes
 | 
				
			||||||
  smartcliInstance.standardCommand().subscribe({
 | 
					  smartcliInstance.standardCommand().subscribe({
 | 
				
			||||||
    next: async (argvArg: CliArguments) => {
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
      console.log(`TSPM - TypeScript Process Manager v${tspmProjectinfo.npm.version}`);
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `TSPM - TypeScript Process Manager v${tspmProjectinfo.npm.version}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      console.log('Usage: tspm [command] [options]');
 | 
					      console.log('Usage: tspm [command] [options]');
 | 
				
			||||||
      console.log('\nCommands:');
 | 
					      console.log('\nCommands:');
 | 
				
			||||||
      console.log('  start <script>           Start a process');
 | 
					      console.log('  start <script>           Start a process');
 | 
				
			||||||
      console.log('  startAsDaemon <script>   Start a process in daemon mode');
 | 
					 | 
				
			||||||
      console.log('  list                     List all processes');
 | 
					      console.log('  list                     List all processes');
 | 
				
			||||||
      console.log('  stop <id>                Stop a process');
 | 
					      console.log('  stop <id>                Stop a process');
 | 
				
			||||||
      console.log('  restart <id>             Restart a process');
 | 
					      console.log('  restart <id>             Restart a process');
 | 
				
			||||||
      console.log('  delete <id>              Delete a process');
 | 
					      console.log('  delete <id>              Delete a process');
 | 
				
			||||||
      console.log('  describe <id>            Show details for a process');
 | 
					      console.log('  describe <id>            Show details for a process');
 | 
				
			||||||
      console.log('\nUse tspm [command] --help for more information about a command.');
 | 
					      console.log('  logs <id>                Show logs for a process');
 | 
				
			||||||
 | 
					      console.log('  start-all                Start all saved processes');
 | 
				
			||||||
 | 
					      console.log('  stop-all                 Stop all processes');
 | 
				
			||||||
 | 
					      console.log('  restart-all              Restart all processes');
 | 
				
			||||||
 | 
					      console.log('\nDaemon Commands:');
 | 
				
			||||||
 | 
					      console.log('  daemon start             Start the TSPM daemon');
 | 
				
			||||||
 | 
					      console.log('  daemon stop              Stop the TSPM daemon');
 | 
				
			||||||
 | 
					      console.log('  daemon status            Show daemon status');
 | 
				
			||||||
 | 
					      console.log(
 | 
				
			||||||
 | 
					        '\nUse tspm [command] --help for more information about a command.',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Show current process list
 | 
					      // Show current process list
 | 
				
			||||||
      console.log('\nProcess List:');
 | 
					      console.log('\nProcess List:');
 | 
				
			||||||
      const processes = tspm.list();
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      if (processes.length === 0) {
 | 
					 | 
				
			||||||
        console.log('  No processes running. Use "tspm start" to start a process.');
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log('┌─────────┬─────────────┬───────────┬───────────┬──────────┐');
 | 
					 | 
				
			||||||
        console.log('│ ID      │ Name        │ Status    │ Memory    │ Restarts │');
 | 
					 | 
				
			||||||
        console.log('├─────────┼─────────────┼───────────┼───────────┼──────────┤');
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        for (const proc of processes) {
 | 
					 | 
				
			||||||
          console.log(`│ ${pad(proc.id, 8)} │ ${pad(proc.id, 12)} │ ${pad(proc.status, 10)} │ ${pad(formatMemory(proc.memory), 10)} │ ${pad(String(proc.restarts), 9)} │`);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        console.log('└─────────┴─────────────┴───────────┴───────────┴──────────┘');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Start command - start a new process
 | 
					 | 
				
			||||||
  smartcliInstance.addCommand('start').subscribe({
 | 
					 | 
				
			||||||
    next: async (argvArg: CliArguments) => {
 | 
					 | 
				
			||||||
      const script = argvArg._.length > 1 ? String(argvArg._[1]) : '';
 | 
					 | 
				
			||||||
      if (!script) {
 | 
					 | 
				
			||||||
        console.error('Error: Missing script argument. Usage: tspm start <script>');
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      // Parse additional options
 | 
					 | 
				
			||||||
      const name = argvArg.name || script;
 | 
					 | 
				
			||||||
      const cwd = argvArg.cwd || process.cwd();
 | 
					 | 
				
			||||||
      const memLimit = parseMemoryString(argvArg.memory || '500MB');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        cliLogger.debug(`Starting process with script: ${script}`);
 | 
					        const response = await tspmIpcClient.request('list', {});
 | 
				
			||||||
 | 
					        const processes = response.processes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const processConfig: IProcessConfig = {
 | 
					        if (processes.length === 0) {
 | 
				
			||||||
          id: argvArg.id || name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase(),
 | 
					          console.log(
 | 
				
			||||||
          name: name,
 | 
					            '  No processes running. Use "tspm start" to start a process.',
 | 
				
			||||||
          projectDir: cwd,
 | 
					          );
 | 
				
			||||||
          command: script,
 | 
					 | 
				
			||||||
          args: argvArg.args ? String(argvArg.args).split(' ') : undefined,
 | 
					 | 
				
			||||||
          memoryLimitBytes: memLimit,
 | 
					 | 
				
			||||||
          monitorIntervalMs: Number(argvArg.interval) || 5000,
 | 
					 | 
				
			||||||
          autorestart: argvArg.autorestart !== 'false',
 | 
					 | 
				
			||||||
          watch: Boolean(argvArg.watch)
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        cliLogger.debug(`Created process config: ${JSON.stringify(processConfig)}`);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        await tspm.start(processConfig);
 | 
					 | 
				
			||||||
        console.log(`Process ${processConfig.id} started successfully.`);
 | 
					 | 
				
			||||||
      } catch (error: Error | unknown) {
 | 
					 | 
				
			||||||
        const tspmError = handleError(error);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (tspmError instanceof ValidationError) {
 | 
					 | 
				
			||||||
          console.error(`Validation error: ${tspmError.message}`);
 | 
					 | 
				
			||||||
        } else if (tspmError instanceof ProcessError) {
 | 
					 | 
				
			||||||
          console.error(`Process error: ${tspmError.message}`);
 | 
					 | 
				
			||||||
          if (debugMode) {
 | 
					 | 
				
			||||||
            console.error(`Error details: ${JSON.stringify(tspmError.details)}`);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          console.error(`Error starting process: ${tspmError.message}`);
 | 
					          console.log(
 | 
				
			||||||
 | 
					            '┌─────────┬─────────────┬───────────┬───────────┬──────────┐',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          console.log(
 | 
				
			||||||
 | 
					            '│ ID      │ Name        │ Status    │ Memory    │ Restarts │',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          console.log(
 | 
				
			||||||
 | 
					            '├─────────┼─────────────┼───────────┼───────────┼──────────┤',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          for (const proc of processes) {
 | 
				
			||||||
 | 
					            const statusColor =
 | 
				
			||||||
 | 
					              proc.status === 'online'
 | 
				
			||||||
 | 
					                ? '\x1b[32m'
 | 
				
			||||||
 | 
					                : proc.status === 'errored'
 | 
				
			||||||
 | 
					                  ? '\x1b[31m'
 | 
				
			||||||
 | 
					                  : '\x1b[33m';
 | 
				
			||||||
 | 
					            const resetColor = '\x1b[0m';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            console.log(
 | 
				
			||||||
 | 
					              `│ ${pad(proc.id, 7)} │ ${pad(proc.id, 11)} │ ${statusColor}${pad(proc.status, 9)}${resetColor} │ ${pad(formatMemory(proc.memory), 9)} │ ${pad(proc.restarts.toString(), 8)} │`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cliLogger.error(tspmError);
 | 
					          console.log(
 | 
				
			||||||
 | 
					            '└─────────┴─────────────┴───────────┴───────────┴──────────┘',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error(
 | 
				
			||||||
 | 
					          'Error: Could not connect to TSPM daemon. Use "tspm daemon start" to start it.',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Start as daemon command
 | 
					  // Start command
 | 
				
			||||||
  smartcliInstance.addCommand('startAsDaemon').subscribe({
 | 
					  smartcliInstance.addCommand('start').subscribe({
 | 
				
			||||||
    next: async (argvArg: CliArguments) => {
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
      const script = argvArg._.length > 1 ? String(argvArg._[1]) : '';
 | 
					      try {
 | 
				
			||||||
 | 
					        const script = argvArg._[1];
 | 
				
			||||||
        if (!script) {
 | 
					        if (!script) {
 | 
				
			||||||
        console.error('Error: Missing script argument. Usage: tspm startAsDaemon <script>');
 | 
					          console.error('Error: Please provide a script to run');
 | 
				
			||||||
 | 
					          console.log('Usage: tspm start <script> [options]');
 | 
				
			||||||
 | 
					          console.log('\nOptions:');
 | 
				
			||||||
 | 
					          console.log('  --name <name>         Name for the process');
 | 
				
			||||||
 | 
					          console.log(
 | 
				
			||||||
 | 
					            '  --memory <size>       Memory limit (e.g., "512MB", "2GB")',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          console.log('  --cwd <path>          Working directory');
 | 
				
			||||||
 | 
					          console.log(
 | 
				
			||||||
 | 
					            '  --watch               Watch for file changes and restart',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          console.log('  --watch-paths <paths> Comma-separated paths to watch');
 | 
				
			||||||
 | 
					          console.log('  --autorestart         Auto-restart on crash');
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // For daemon mode, we'll detach from the console
 | 
					        const memoryLimit = argvArg.memory
 | 
				
			||||||
      const daemonProcess = plugins.childProcess.spawn(
 | 
					          ? parseMemoryString(argvArg.memory)
 | 
				
			||||||
        process.execPath,
 | 
					          : 512 * 1024 * 1024; // Default 512MB
 | 
				
			||||||
        [
 | 
					        const projectDir = argvArg.cwd || process.cwd();
 | 
				
			||||||
          ...process.execArgv,
 | 
					        const name = argvArg.name || script;
 | 
				
			||||||
          process.argv[1], // The tspm script path
 | 
					        const watch = argvArg.watch || false;
 | 
				
			||||||
          'start',
 | 
					        const autorestart = argvArg.autorestart !== false; // Default true
 | 
				
			||||||
          script,
 | 
					        const watchPaths = argvArg.watchPaths
 | 
				
			||||||
          ...process.argv.slice(3) // Pass other arguments
 | 
					          ? typeof argvArg.watchPaths === 'string'
 | 
				
			||||||
        ],
 | 
					            ? (argvArg.watchPaths as string).split(',')
 | 
				
			||||||
        {
 | 
					            : argvArg.watchPaths
 | 
				
			||||||
          detached: true,
 | 
					          : undefined;
 | 
				
			||||||
          stdio: 'ignore',
 | 
					 | 
				
			||||||
          cwd: process.cwd()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Unref to allow parent to exit
 | 
					        const processConfig: IProcessConfig = {
 | 
				
			||||||
      daemonProcess.unref();
 | 
					          id: name.replace(/[^a-zA-Z0-9-_]/g, '_'),
 | 
				
			||||||
 | 
					          name,
 | 
				
			||||||
 | 
					          command: script,
 | 
				
			||||||
 | 
					          projectDir,
 | 
				
			||||||
 | 
					          memoryLimitBytes: memoryLimit,
 | 
				
			||||||
 | 
					          autorestart,
 | 
				
			||||||
 | 
					          watch,
 | 
				
			||||||
 | 
					          watchPaths,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      console.log(`Started process ${script} as daemon.`);
 | 
					        console.log(`Starting process: ${name}`);
 | 
				
			||||||
 | 
					        console.log(`  Command: ${script}`);
 | 
				
			||||||
 | 
					        console.log(`  Directory: ${projectDir}`);
 | 
				
			||||||
 | 
					        console.log(`  Memory limit: ${formatMemory(memoryLimit)}`);
 | 
				
			||||||
 | 
					        console.log(`  Auto-restart: ${autorestart}`);
 | 
				
			||||||
 | 
					        if (watch) {
 | 
				
			||||||
 | 
					          console.log(`  Watch mode: enabled`);
 | 
				
			||||||
 | 
					          if (watchPaths) {
 | 
				
			||||||
 | 
					            console.log(`  Watch paths: ${watchPaths.join(', ')}`);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const response = await tspmIpcClient.request('start', {
 | 
				
			||||||
 | 
					          config: processConfig,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log(`✓ Process started successfully`);
 | 
				
			||||||
 | 
					        console.log(`  ID: ${response.processId}`);
 | 
				
			||||||
 | 
					        console.log(`  PID: ${response.pid || 'N/A'}`);
 | 
				
			||||||
 | 
					        console.log(`  Status: ${response.status}`);
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error starting process:', error.message);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Stop command
 | 
					  // Stop command
 | 
				
			||||||
  smartcliInstance.addCommand('stop').subscribe({
 | 
					  smartcliInstance.addCommand('stop').subscribe({
 | 
				
			||||||
    next: async (argvArg: CliArguments) => {
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
      const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
 | 
					      try {
 | 
				
			||||||
      
 | 
					        const id = argvArg._[1];
 | 
				
			||||||
        if (!id) {
 | 
					        if (!id) {
 | 
				
			||||||
        console.error('Error: Missing process ID. Usage: tspm stop <id>');
 | 
					          console.error('Error: Please provide a process ID');
 | 
				
			||||||
 | 
					          console.log('Usage: tspm stop <id>');
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					        console.log(`Stopping process: ${id}`);
 | 
				
			||||||
        cliLogger.debug(`Stopping process: ${id}`);
 | 
					        const response = await tspmIpcClient.request('stop', { id });
 | 
				
			||||||
        await tspm.stop(id);
 | 
					 | 
				
			||||||
        console.log(`Process ${id} stopped.`);
 | 
					 | 
				
			||||||
      } catch (error: Error | unknown) {
 | 
					 | 
				
			||||||
        const tspmError = handleError(error);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (tspmError instanceof ValidationError) {
 | 
					        if (response.success) {
 | 
				
			||||||
          console.error(`Validation error: ${tspmError.message}`);
 | 
					          console.log(`✓ ${response.message}`);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          console.error(`Error stopping process: ${tspmError.message}`);
 | 
					          console.error(`✗ Failed to stop process: ${response.message}`);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        cliLogger.error(tspmError);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error stopping process:', error.message);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Restart command
 | 
					  // Restart command
 | 
				
			||||||
  smartcliInstance.addCommand('restart').subscribe({
 | 
					  smartcliInstance.addCommand('restart').subscribe({
 | 
				
			||||||
    next: async (argvArg: CliArguments) => {
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
      const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
 | 
					      try {
 | 
				
			||||||
      
 | 
					        const id = argvArg._[1];
 | 
				
			||||||
        if (!id) {
 | 
					        if (!id) {
 | 
				
			||||||
        console.error('Error: Missing process ID. Usage: tspm restart <id>');
 | 
					          console.error('Error: Please provide a process ID');
 | 
				
			||||||
 | 
					          console.log('Usage: tspm restart <id>');
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					        console.log(`Restarting process: ${id}`);
 | 
				
			||||||
        cliLogger.debug(`Restarting process: ${id}`);
 | 
					        const response = await tspmIpcClient.request('restart', { id });
 | 
				
			||||||
        await tspm.restart(id);
 | 
					 | 
				
			||||||
        console.log(`Process ${id} restarted.`);
 | 
					 | 
				
			||||||
      } catch (error: Error | unknown) {
 | 
					 | 
				
			||||||
        const tspmError = handleError(error);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (tspmError instanceof ValidationError) {
 | 
					        console.log(`✓ Process restarted successfully`);
 | 
				
			||||||
          console.error(`Validation error: ${tspmError.message}`);
 | 
					        console.log(`  ID: ${response.processId}`);
 | 
				
			||||||
        } else if (tspmError instanceof ProcessError) {
 | 
					        console.log(`  PID: ${response.pid || 'N/A'}`);
 | 
				
			||||||
          console.error(`Process error: ${tspmError.message}`);
 | 
					        console.log(`  Status: ${response.status}`);
 | 
				
			||||||
        } else {
 | 
					      } catch (error) {
 | 
				
			||||||
          console.error(`Error restarting process: ${tspmError.message}`);
 | 
					        console.error('Error restarting process:', error.message);
 | 
				
			||||||
        }
 | 
					        process.exit(1);
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        cliLogger.error(tspmError);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Delete command
 | 
					  // Delete command
 | 
				
			||||||
  smartcliInstance.addCommand('delete').subscribe({
 | 
					  smartcliInstance.addCommand('delete').subscribe({
 | 
				
			||||||
    next: async (argvArg: CliArguments) => {
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
      const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
 | 
					      try {
 | 
				
			||||||
      
 | 
					        const id = argvArg._[1];
 | 
				
			||||||
        if (!id) {
 | 
					        if (!id) {
 | 
				
			||||||
        console.error('Error: Missing process ID. Usage: tspm delete <id>');
 | 
					          console.error('Error: Please provide a process ID');
 | 
				
			||||||
 | 
					          console.log('Usage: tspm delete <id>');
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					        console.log(`Deleting process: ${id}`);
 | 
				
			||||||
        cliLogger.debug(`Deleting process: ${id}`);
 | 
					        const response = await tspmIpcClient.request('delete', { id });
 | 
				
			||||||
        await tspm.delete(id);
 | 
					 | 
				
			||||||
        console.log(`Process ${id} deleted.`);
 | 
					 | 
				
			||||||
      } catch (error: Error | unknown) {
 | 
					 | 
				
			||||||
        const tspmError = handleError(error);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (tspmError instanceof ValidationError) {
 | 
					        if (response.success) {
 | 
				
			||||||
          console.error(`Validation error: ${tspmError.message}`);
 | 
					          console.log(`✓ ${response.message}`);
 | 
				
			||||||
        } else if (tspmError instanceof ConfigError) {
 | 
					 | 
				
			||||||
          console.error(`Configuration error: ${tspmError.message}`);
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          console.error(`Error deleting process: ${tspmError.message}`);
 | 
					          console.error(`✗ Failed to delete process: ${response.message}`);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        cliLogger.error(tspmError);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error deleting process:', error.message);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // List command
 | 
					  // List command
 | 
				
			||||||
  smartcliInstance.addCommand('list').subscribe({
 | 
					  smartcliInstance.addCommand('list').subscribe({
 | 
				
			||||||
    next: async (argvArg: CliArguments) => {
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
      const processes = tspm.list();
 | 
					      try {
 | 
				
			||||||
 | 
					        const response = await tspmIpcClient.request('list', {});
 | 
				
			||||||
 | 
					        const processes = response.processes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (processes.length === 0) {
 | 
					        if (processes.length === 0) {
 | 
				
			||||||
          console.log('No processes running.');
 | 
					          console.log('No processes running.');
 | 
				
			||||||
        return;
 | 
					        } else {
 | 
				
			||||||
      }
 | 
					          console.log('Process List:');
 | 
				
			||||||
      
 | 
					          console.log(
 | 
				
			||||||
      console.log('┌─────────┬─────────────┬───────────┬───────────┬──────────┐');
 | 
					            '┌─────────┬─────────────┬───────────┬───────────┬──────────┬──────────┐',
 | 
				
			||||||
      console.log('│ ID      │ Name        │ Status    │ Memory    │ Restarts │');
 | 
					          );
 | 
				
			||||||
      console.log('├─────────┼─────────────┼───────────┼───────────┼──────────┤');
 | 
					          console.log(
 | 
				
			||||||
 | 
					            '│ ID      │ Name        │ Status    │ PID       │ Memory   │ Restarts │',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          console.log(
 | 
				
			||||||
 | 
					            '├─────────┼─────────────┼───────────┼───────────┼──────────┼──────────┤',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          for (const proc of processes) {
 | 
					          for (const proc of processes) {
 | 
				
			||||||
        console.log(`│ ${pad(proc.id, 8)} │ ${pad(proc.id, 12)} │ ${pad(proc.status, 10)} │ ${pad(formatMemory(proc.memory), 10)} │ ${pad(String(proc.restarts), 9)} │`);
 | 
					            const statusColor =
 | 
				
			||||||
 | 
					              proc.status === 'online'
 | 
				
			||||||
 | 
					                ? '\x1b[32m'
 | 
				
			||||||
 | 
					                : proc.status === 'errored'
 | 
				
			||||||
 | 
					                  ? '\x1b[31m'
 | 
				
			||||||
 | 
					                  : '\x1b[33m';
 | 
				
			||||||
 | 
					            const resetColor = '\x1b[0m';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            console.log(
 | 
				
			||||||
 | 
					              `│ ${pad(proc.id, 7)} │ ${pad(proc.id, 11)} │ ${statusColor}${pad(proc.status, 9)}${resetColor} │ ${pad((proc.pid || '-').toString(), 9)} │ ${pad(formatMemory(proc.memory), 8)} │ ${pad(proc.restarts.toString(), 8)} │`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      console.log('└─────────┴─────────────┴───────────┴───────────┴──────────┘');
 | 
					          console.log(
 | 
				
			||||||
 | 
					            '└─────────┴─────────────┴───────────┴───────────┴──────────┴──────────┘',
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error listing processes:', error.message);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Describe command
 | 
					  // Describe command
 | 
				
			||||||
  smartcliInstance.addCommand('describe').subscribe({
 | 
					  smartcliInstance.addCommand('describe').subscribe({
 | 
				
			||||||
    next: async (argvArg: CliArguments) => {
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
      const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
 | 
					      try {
 | 
				
			||||||
      
 | 
					        const id = argvArg._[1];
 | 
				
			||||||
        if (!id) {
 | 
					        if (!id) {
 | 
				
			||||||
        console.error('Error: Missing process ID. Usage: tspm describe <id>');
 | 
					          console.error('Error: Please provide a process ID');
 | 
				
			||||||
 | 
					          console.log('Usage: tspm describe <id>');
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const details = tspm.describe(id);
 | 
					        const response = await tspmIpcClient.request('describe', { id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!details) {
 | 
					        console.log(`Process Details: ${id}`);
 | 
				
			||||||
        console.error(`Process with ID '${id}' not found.`);
 | 
					        console.log('─'.repeat(40));
 | 
				
			||||||
        return;
 | 
					        console.log(`Status:      ${response.processInfo.status}`);
 | 
				
			||||||
      }
 | 
					        console.log(`PID:         ${response.processInfo.pid || 'N/A'}`);
 | 
				
			||||||
      
 | 
					        console.log(
 | 
				
			||||||
      console.log(`Details for process '${id}':`);
 | 
					          `Memory:      ${formatMemory(response.processInfo.memory)}`,
 | 
				
			||||||
      console.log(`  Status: ${details.info.status}`);
 | 
					        );
 | 
				
			||||||
      console.log(`  Memory: ${formatMemory(details.info.memory)}`);
 | 
					        console.log(
 | 
				
			||||||
      console.log(`  Restarts: ${details.info.restarts}`);
 | 
					          `CPU:         ${response.processInfo.cpu ? response.processInfo.cpu.toFixed(1) + '%' : 'N/A'}`,
 | 
				
			||||||
      console.log(`  Command: ${details.config.command}`);
 | 
					        );
 | 
				
			||||||
      console.log(`  Directory: ${details.config.projectDir}`);
 | 
					        console.log(
 | 
				
			||||||
      console.log(`  Memory limit: ${formatMemory(details.config.memoryLimitBytes)}`);
 | 
					          `Uptime:      ${response.processInfo.uptime ? Math.floor(response.processInfo.uptime / 1000) + 's' : 'N/A'}`,
 | 
				
			||||||
      
 | 
					        );
 | 
				
			||||||
      if (details.config.args && details.config.args.length > 0) {
 | 
					        console.log(`Restarts:    ${response.processInfo.restarts}`);
 | 
				
			||||||
        console.log(`  Arguments: ${details.config.args.join(' ')}`);
 | 
					        console.log('\nConfiguration:');
 | 
				
			||||||
 | 
					        console.log(`Command:     ${response.config.command}`);
 | 
				
			||||||
 | 
					        console.log(`Directory:   ${response.config.projectDir}`);
 | 
				
			||||||
 | 
					        console.log(
 | 
				
			||||||
 | 
					          `Memory Limit: ${formatMemory(response.config.memoryLimitBytes)}`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        console.log(`Auto-restart: ${response.config.autorestart}`);
 | 
				
			||||||
 | 
					        if (response.config.watch) {
 | 
				
			||||||
 | 
					          console.log(`Watch:       enabled`);
 | 
				
			||||||
 | 
					          if (response.config.watchPaths) {
 | 
				
			||||||
 | 
					            console.log(
 | 
				
			||||||
 | 
					              `Watch Paths: ${response.config.watchPaths.join(', ')}`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error describing process:', error.message);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Logs command
 | 
					  // Logs command
 | 
				
			||||||
  smartcliInstance.addCommand('logs').subscribe({
 | 
					  smartcliInstance.addCommand('logs').subscribe({
 | 
				
			||||||
    next: async (argvArg: CliArguments) => {
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
      const id = argvArg._.length > 1 ? String(argvArg._[1]) : '';
 | 
					      try {
 | 
				
			||||||
      
 | 
					        const id = argvArg._[1];
 | 
				
			||||||
        if (!id) {
 | 
					        if (!id) {
 | 
				
			||||||
        console.error('Error: Missing process ID. Usage: tspm logs <id>');
 | 
					          console.error('Error: Please provide a process ID');
 | 
				
			||||||
 | 
					          console.log('Usage: tspm logs <id>');
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const lines = Number(argvArg.lines || argvArg.n) || 20;
 | 
					        const lines = argvArg.lines || 50;
 | 
				
			||||||
      const logs = tspm.getLogs(id, lines);
 | 
					        const response = await tspmIpcClient.request('getLogs', { id, lines });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (logs.length === 0) {
 | 
					        console.log(`Logs for process: ${id} (last ${lines} lines)`);
 | 
				
			||||||
        console.log(`No logs found for process '${id}'.`);
 | 
					        console.log('─'.repeat(60));
 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Display logs with colors for different log types
 | 
					        for (const log of response.logs) {
 | 
				
			||||||
      for (const log of logs) {
 | 
					          const timestamp = new Date(log.timestamp).toLocaleTimeString();
 | 
				
			||||||
        const timestamp = log.timestamp.toISOString();
 | 
					          const prefix = log.type === 'stdout' ? '[OUT]' : '[ERR]';
 | 
				
			||||||
        const prefix = `[${timestamp}] `;
 | 
					          console.log(`${timestamp} ${prefix} ${log.message}`);
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        switch (log.type) {
 | 
					 | 
				
			||||||
          case 'stdout':
 | 
					 | 
				
			||||||
            console.log(`${prefix}${log.message}`);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
          case 'stderr':
 | 
					 | 
				
			||||||
            console.error(`${prefix}${log.message}`);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
          case 'system':
 | 
					 | 
				
			||||||
            console.log(`${prefix}[SYSTEM] ${log.message}`);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error getting logs:', error.message);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Start parsing
 | 
					  // Start-all command
 | 
				
			||||||
 | 
					  smartcliInstance.addCommand('start-all').subscribe({
 | 
				
			||||||
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        console.log('Starting all processes...');
 | 
				
			||||||
 | 
					        const response = await tspmIpcClient.request('startAll', {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (response.started.length > 0) {
 | 
				
			||||||
 | 
					          console.log(`✓ Started ${response.started.length} processes:`);
 | 
				
			||||||
 | 
					          for (const id of response.started) {
 | 
				
			||||||
 | 
					            console.log(`  - ${id}`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (response.failed.length > 0) {
 | 
				
			||||||
 | 
					          console.log(`✗ Failed to start ${response.failed.length} processes:`);
 | 
				
			||||||
 | 
					          for (const failure of response.failed) {
 | 
				
			||||||
 | 
					            console.log(`  - ${failure.id}: ${failure.error}`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error starting all processes:', error.message);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Stop-all command
 | 
				
			||||||
 | 
					  smartcliInstance.addCommand('stop-all').subscribe({
 | 
				
			||||||
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        console.log('Stopping all processes...');
 | 
				
			||||||
 | 
					        const response = await tspmIpcClient.request('stopAll', {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (response.stopped.length > 0) {
 | 
				
			||||||
 | 
					          console.log(`✓ Stopped ${response.stopped.length} processes:`);
 | 
				
			||||||
 | 
					          for (const id of response.stopped) {
 | 
				
			||||||
 | 
					            console.log(`  - ${id}`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (response.failed.length > 0) {
 | 
				
			||||||
 | 
					          console.log(`✗ Failed to stop ${response.failed.length} processes:`);
 | 
				
			||||||
 | 
					          for (const failure of response.failed) {
 | 
				
			||||||
 | 
					            console.log(`  - ${failure.id}: ${failure.error}`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error stopping all processes:', error.message);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Restart-all command
 | 
				
			||||||
 | 
					  smartcliInstance.addCommand('restart-all').subscribe({
 | 
				
			||||||
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        console.log('Restarting all processes...');
 | 
				
			||||||
 | 
					        const response = await tspmIpcClient.request('restartAll', {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (response.restarted.length > 0) {
 | 
				
			||||||
 | 
					          console.log(`✓ Restarted ${response.restarted.length} processes:`);
 | 
				
			||||||
 | 
					          for (const id of response.restarted) {
 | 
				
			||||||
 | 
					            console.log(`  - ${id}`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (response.failed.length > 0) {
 | 
				
			||||||
 | 
					          console.log(
 | 
				
			||||||
 | 
					            `✗ Failed to restart ${response.failed.length} processes:`,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          for (const failure of response.failed) {
 | 
				
			||||||
 | 
					            console.log(`  - ${failure.id}: ${failure.error}`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error('Error restarting all processes:', error.message);
 | 
				
			||||||
 | 
					        process.exit(1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Daemon commands
 | 
				
			||||||
 | 
					  smartcliInstance.addCommand('daemon').subscribe({
 | 
				
			||||||
 | 
					    next: async (argvArg: CliArguments) => {
 | 
				
			||||||
 | 
					      const subCommand = argvArg._[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      switch (subCommand) {
 | 
				
			||||||
 | 
					        case 'start':
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					            const status = await tspmIpcClient.getDaemonStatus();
 | 
				
			||||||
 | 
					            if (status) {
 | 
				
			||||||
 | 
					              console.log('TSPM daemon is already running');
 | 
				
			||||||
 | 
					              console.log(`  PID: ${status.pid}`);
 | 
				
			||||||
 | 
					              console.log(
 | 
				
			||||||
 | 
					                `  Uptime: ${Math.floor((status.uptime || 0) / 1000)}s`,
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					              console.log(`  Processes: ${status.processCount}`);
 | 
				
			||||||
 | 
					              return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            console.log('Starting TSPM daemon...');
 | 
				
			||||||
 | 
					            await tspmIpcClient.connect();
 | 
				
			||||||
 | 
					            console.log('✓ TSPM daemon started successfully');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const newStatus = await tspmIpcClient.getDaemonStatus();
 | 
				
			||||||
 | 
					            if (newStatus) {
 | 
				
			||||||
 | 
					              console.log(`  PID: ${newStatus.pid}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } catch (error) {
 | 
				
			||||||
 | 
					            console.error('Error starting daemon:', error.message);
 | 
				
			||||||
 | 
					            process.exit(1);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case 'stop':
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					            console.log('Stopping TSPM daemon...');
 | 
				
			||||||
 | 
					            await tspmIpcClient.stopDaemon(true);
 | 
				
			||||||
 | 
					            console.log('✓ TSPM daemon stopped successfully');
 | 
				
			||||||
 | 
					          } catch (error) {
 | 
				
			||||||
 | 
					            console.error('Error stopping daemon:', error.message);
 | 
				
			||||||
 | 
					            process.exit(1);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case 'status':
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					            const status = await tspmIpcClient.getDaemonStatus();
 | 
				
			||||||
 | 
					            if (!status) {
 | 
				
			||||||
 | 
					              console.log('TSPM daemon is not running');
 | 
				
			||||||
 | 
					              console.log('Use "tspm daemon start" to start it');
 | 
				
			||||||
 | 
					              return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            console.log('TSPM Daemon Status:');
 | 
				
			||||||
 | 
					            console.log('─'.repeat(40));
 | 
				
			||||||
 | 
					            console.log(`Status:      ${status.status}`);
 | 
				
			||||||
 | 
					            console.log(`PID:         ${status.pid}`);
 | 
				
			||||||
 | 
					            console.log(
 | 
				
			||||||
 | 
					              `Uptime:      ${Math.floor((status.uptime || 0) / 1000)}s`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            console.log(`Processes:   ${status.processCount}`);
 | 
				
			||||||
 | 
					            console.log(
 | 
				
			||||||
 | 
					              `Memory:      ${formatMemory(status.memoryUsage || 0)}`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            console.log(`CPU:         ${status.cpuUsage?.toFixed(1) || 0}s`);
 | 
				
			||||||
 | 
					          } catch (error) {
 | 
				
			||||||
 | 
					            console.error('Error getting daemon status:', error.message);
 | 
				
			||||||
 | 
					            process.exit(1);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          console.log('Usage: tspm daemon <command>');
 | 
				
			||||||
 | 
					          console.log('\nCommands:');
 | 
				
			||||||
 | 
					          console.log('  start   Start the TSPM daemon');
 | 
				
			||||||
 | 
					          console.log('  stop    Stop the TSPM daemon');
 | 
				
			||||||
 | 
					          console.log('  status  Show daemon status');
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    error: (err) => {
 | 
				
			||||||
 | 
					      cliLogger.error(err);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    complete: () => {},
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Start parsing commands
 | 
				
			||||||
  smartcliInstance.startParse();
 | 
					  smartcliInstance.startParse();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Helper function to format memory usage
 | 
					 | 
				
			||||||
function formatMemory(bytes: number): string {
 | 
					 | 
				
			||||||
  if (bytes === 0) return '0 B';
 | 
					 | 
				
			||||||
  const k = 1024;
 | 
					 | 
				
			||||||
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
 | 
					 | 
				
			||||||
  const i = Math.floor(Math.log(bytes) / Math.log(k));
 | 
					 | 
				
			||||||
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Helper function to parse memory strings like "500MB"
 | 
					 | 
				
			||||||
function parseMemoryString(memString: string): number {
 | 
					 | 
				
			||||||
  const units = {
 | 
					 | 
				
			||||||
    'B': 1,
 | 
					 | 
				
			||||||
    'KB': 1024,
 | 
					 | 
				
			||||||
    'MB': 1024 * 1024,
 | 
					 | 
				
			||||||
    'GB': 1024 * 1024 * 1024,
 | 
					 | 
				
			||||||
    'TB': 1024 * 1024 * 1024 * 1024
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  const match = memString.match(/^(\d+(?:\.\d+)?)\s*([KMGT]?B)$/i);
 | 
					 | 
				
			||||||
  if (!match) {
 | 
					 | 
				
			||||||
    throw new Error(`Invalid memory format: ${memString}. Use format like 500MB`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  const value = parseFloat(match[1]);
 | 
					 | 
				
			||||||
  const unit = match[2].toUpperCase();
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  return value * units[unit];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Helper function to pad strings for table display
 | 
					 | 
				
			||||||
function pad(str: string, length: number): string {
 | 
					 | 
				
			||||||
  return str.padEnd(length);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										9
									
								
								ts/daemon.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								ts/daemon.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { startDaemon } from './classes.daemon.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Start the daemon
 | 
				
			||||||
 | 
					startDaemon().catch((error) => {
 | 
				
			||||||
 | 
					  console.error('Failed to start daemon:', error);
 | 
				
			||||||
 | 
					  process.exit(1);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -1,5 +1,8 @@
 | 
				
			|||||||
export * from './classes.tspm.js';
 | 
					export * from './classes.tspm.js';
 | 
				
			||||||
export * from './classes.processmonitor.js';
 | 
					export * from './classes.processmonitor.js';
 | 
				
			||||||
 | 
					export * from './classes.daemon.js';
 | 
				
			||||||
 | 
					export * from './classes.ipcclient.js';
 | 
				
			||||||
 | 
					export * from './ipc.types.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as cli from './cli.js';
 | 
					import * as cli from './cli.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,4 +11,4 @@ import * as cli from './cli.js';
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const runCli = async () => {
 | 
					export const runCli = async () => {
 | 
				
			||||||
  await cli.run();
 | 
					  await cli.run();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										201
									
								
								ts/ipc.types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								ts/ipc.types.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
				
			|||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  IProcessConfig,
 | 
				
			||||||
 | 
					  IProcessInfo,
 | 
				
			||||||
 | 
					  IProcessLog,
 | 
				
			||||||
 | 
					} from './classes.tspm.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Base message types
 | 
				
			||||||
 | 
					export interface IpcRequest<T = any> {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  method: string;
 | 
				
			||||||
 | 
					  params: T;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IpcResponse<T = any> {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  success: boolean;
 | 
				
			||||||
 | 
					  result?: T;
 | 
				
			||||||
 | 
					  error?: {
 | 
				
			||||||
 | 
					    code: number;
 | 
				
			||||||
 | 
					    message: string;
 | 
				
			||||||
 | 
					    data?: any;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Request/Response pairs for each operation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Start command
 | 
				
			||||||
 | 
					export interface StartRequest {
 | 
				
			||||||
 | 
					  config: IProcessConfig;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface StartResponse {
 | 
				
			||||||
 | 
					  processId: string;
 | 
				
			||||||
 | 
					  pid?: number;
 | 
				
			||||||
 | 
					  status: 'online' | 'stopped' | 'errored';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Stop command
 | 
				
			||||||
 | 
					export interface StopRequest {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface StopResponse {
 | 
				
			||||||
 | 
					  success: boolean;
 | 
				
			||||||
 | 
					  message?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Restart command
 | 
				
			||||||
 | 
					export interface RestartRequest {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface RestartResponse {
 | 
				
			||||||
 | 
					  processId: string;
 | 
				
			||||||
 | 
					  pid?: number;
 | 
				
			||||||
 | 
					  status: 'online' | 'stopped' | 'errored';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Delete command
 | 
				
			||||||
 | 
					export interface DeleteRequest {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DeleteResponse {
 | 
				
			||||||
 | 
					  success: boolean;
 | 
				
			||||||
 | 
					  message?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// List command
 | 
				
			||||||
 | 
					export interface ListRequest {
 | 
				
			||||||
 | 
					  // No parameters needed
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ListResponse {
 | 
				
			||||||
 | 
					  processes: IProcessInfo[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Describe command
 | 
				
			||||||
 | 
					export interface DescribeRequest {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DescribeResponse {
 | 
				
			||||||
 | 
					  processInfo: IProcessInfo;
 | 
				
			||||||
 | 
					  config: IProcessConfig;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get logs command
 | 
				
			||||||
 | 
					export interface GetLogsRequest {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  lines?: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface GetLogsResponse {
 | 
				
			||||||
 | 
					  logs: IProcessLog[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Start all command
 | 
				
			||||||
 | 
					export interface StartAllRequest {
 | 
				
			||||||
 | 
					  // No parameters needed
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface StartAllResponse {
 | 
				
			||||||
 | 
					  started: string[];
 | 
				
			||||||
 | 
					  failed: Array<{
 | 
				
			||||||
 | 
					    id: string;
 | 
				
			||||||
 | 
					    error: string;
 | 
				
			||||||
 | 
					  }>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Stop all command
 | 
				
			||||||
 | 
					export interface StopAllRequest {
 | 
				
			||||||
 | 
					  // No parameters needed
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface StopAllResponse {
 | 
				
			||||||
 | 
					  stopped: string[];
 | 
				
			||||||
 | 
					  failed: Array<{
 | 
				
			||||||
 | 
					    id: string;
 | 
				
			||||||
 | 
					    error: string;
 | 
				
			||||||
 | 
					  }>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Restart all command
 | 
				
			||||||
 | 
					export interface RestartAllRequest {
 | 
				
			||||||
 | 
					  // No parameters needed
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface RestartAllResponse {
 | 
				
			||||||
 | 
					  restarted: string[];
 | 
				
			||||||
 | 
					  failed: Array<{
 | 
				
			||||||
 | 
					    id: string;
 | 
				
			||||||
 | 
					    error: string;
 | 
				
			||||||
 | 
					  }>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Daemon status command
 | 
				
			||||||
 | 
					export interface DaemonStatusRequest {
 | 
				
			||||||
 | 
					  // No parameters needed
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DaemonStatusResponse {
 | 
				
			||||||
 | 
					  status: 'running' | 'stopped';
 | 
				
			||||||
 | 
					  pid?: number;
 | 
				
			||||||
 | 
					  uptime?: number;
 | 
				
			||||||
 | 
					  processCount: number;
 | 
				
			||||||
 | 
					  memoryUsage?: number;
 | 
				
			||||||
 | 
					  cpuUsage?: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Daemon shutdown command
 | 
				
			||||||
 | 
					export interface DaemonShutdownRequest {
 | 
				
			||||||
 | 
					  graceful?: boolean;
 | 
				
			||||||
 | 
					  timeout?: number; // milliseconds
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DaemonShutdownResponse {
 | 
				
			||||||
 | 
					  success: boolean;
 | 
				
			||||||
 | 
					  message?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Heartbeat command
 | 
				
			||||||
 | 
					export interface HeartbeatRequest {
 | 
				
			||||||
 | 
					  // No parameters needed
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface HeartbeatResponse {
 | 
				
			||||||
 | 
					  timestamp: number;
 | 
				
			||||||
 | 
					  status: 'healthy' | 'degraded';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Type mappings for methods
 | 
				
			||||||
 | 
					export type IpcMethodMap = {
 | 
				
			||||||
 | 
					  start: { request: StartRequest; response: StartResponse };
 | 
				
			||||||
 | 
					  stop: { request: StopRequest; response: StopResponse };
 | 
				
			||||||
 | 
					  restart: { request: RestartRequest; response: RestartResponse };
 | 
				
			||||||
 | 
					  delete: { request: DeleteRequest; response: DeleteResponse };
 | 
				
			||||||
 | 
					  list: { request: ListRequest; response: ListResponse };
 | 
				
			||||||
 | 
					  describe: { request: DescribeRequest; response: DescribeResponse };
 | 
				
			||||||
 | 
					  getLogs: { request: GetLogsRequest; response: GetLogsResponse };
 | 
				
			||||||
 | 
					  startAll: { request: StartAllRequest; response: StartAllResponse };
 | 
				
			||||||
 | 
					  stopAll: { request: StopAllRequest; response: StopAllResponse };
 | 
				
			||||||
 | 
					  restartAll: { request: RestartAllRequest; response: RestartAllResponse };
 | 
				
			||||||
 | 
					  'daemon:status': {
 | 
				
			||||||
 | 
					    request: DaemonStatusRequest;
 | 
				
			||||||
 | 
					    response: DaemonStatusResponse;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  'daemon:shutdown': {
 | 
				
			||||||
 | 
					    request: DaemonShutdownRequest;
 | 
				
			||||||
 | 
					    response: DaemonShutdownResponse;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  heartbeat: { request: HeartbeatRequest; response: HeartbeatResponse };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper type to extract request type for a method
 | 
				
			||||||
 | 
					export type RequestForMethod<M extends keyof IpcMethodMap> =
 | 
				
			||||||
 | 
					  IpcMethodMap[M]['request'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper type to extract response type for a method
 | 
				
			||||||
 | 
					export type ResponseForMethod<M extends keyof IpcMethodMap> =
 | 
				
			||||||
 | 
					  IpcMethodMap[M]['response'];
 | 
				
			||||||
@@ -1,4 +1,10 @@
 | 
				
			|||||||
import * as plugins from './plugins.js';
 | 
					import * as plugins from './plugins.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const packageDir: string = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '..');
 | 
					export const packageDir: string = plugins.path.join(
 | 
				
			||||||
 | 
					  plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
 | 
				
			||||||
 | 
					  '..',
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
export const cwd: string = process.cwd();
 | 
					export const cwd: string = process.cwd();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as os from 'os';
 | 
				
			||||||
 | 
					export const tspmDir: string = plugins.path.join(os.homedir(), '.tspm');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,33 +3,22 @@ import * as childProcess from 'child_process';
 | 
				
			|||||||
import * as path from 'node:path';
 | 
					import * as path from 'node:path';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Export with explicit module types
 | 
					// Export with explicit module types
 | 
				
			||||||
export {
 | 
					export { childProcess, path };
 | 
				
			||||||
  childProcess,
 | 
					 | 
				
			||||||
  path,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// @push.rocks scope
 | 
					// @push.rocks scope
 | 
				
			||||||
import * as npmextra from '@push.rocks/npmextra';
 | 
					import * as npmextra from '@push.rocks/npmextra';
 | 
				
			||||||
import * as projectinfo from '@push.rocks/projectinfo';
 | 
					import * as projectinfo from '@push.rocks/projectinfo';
 | 
				
			||||||
import * as smartcli from '@push.rocks/smartcli';
 | 
					import * as smartcli from '@push.rocks/smartcli';
 | 
				
			||||||
import * as smartdaemon from '@push.rocks/smartdaemon';
 | 
					import * as smartdaemon from '@push.rocks/smartdaemon';
 | 
				
			||||||
 | 
					import * as smartipc from '@push.rocks/smartipc';
 | 
				
			||||||
import * as smartpath from '@push.rocks/smartpath';
 | 
					import * as smartpath from '@push.rocks/smartpath';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Export with explicit module types
 | 
					// Export with explicit module types
 | 
				
			||||||
export {
 | 
					export { npmextra, projectinfo, smartcli, smartdaemon, smartipc, smartpath };
 | 
				
			||||||
  npmextra,
 | 
					 | 
				
			||||||
  projectinfo,
 | 
					 | 
				
			||||||
  smartcli,
 | 
					 | 
				
			||||||
  smartdaemon,
 | 
					 | 
				
			||||||
  smartpath,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// third-party scope
 | 
					// third-party scope
 | 
				
			||||||
import psTree from 'ps-tree';
 | 
					import psTree from 'ps-tree';
 | 
				
			||||||
import pidusage from 'pidusage';
 | 
					import pidusage from 'pidusage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add explicit types for third-party exports
 | 
					// Add explicit types for third-party exports
 | 
				
			||||||
export {
 | 
					export { psTree, pidusage };
 | 
				
			||||||
  psTree,
 | 
					 | 
				
			||||||
  pidusage,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ export enum ErrorType {
 | 
				
			|||||||
  PROCESS = 'ProcessError',
 | 
					  PROCESS = 'ProcessError',
 | 
				
			||||||
  RUNTIME = 'RuntimeError',
 | 
					  RUNTIME = 'RuntimeError',
 | 
				
			||||||
  VALIDATION = 'ValidationError',
 | 
					  VALIDATION = 'ValidationError',
 | 
				
			||||||
  UNKNOWN = 'UnknownError'
 | 
					  UNKNOWN = 'UnknownError',
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Base error class with type and code support
 | 
					// Base error class with type and code support
 | 
				
			||||||
@@ -21,7 +21,7 @@ export class TspmError extends Error {
 | 
				
			|||||||
    message: string,
 | 
					    message: string,
 | 
				
			||||||
    type: ErrorType = ErrorType.UNKNOWN,
 | 
					    type: ErrorType = ErrorType.UNKNOWN,
 | 
				
			||||||
    code: string = 'ERR_UNKNOWN',
 | 
					    code: string = 'ERR_UNKNOWN',
 | 
				
			||||||
    details?: Record<string, any>
 | 
					    details?: Record<string, any>,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    super(message);
 | 
					    super(message);
 | 
				
			||||||
    this.name = type;
 | 
					    this.name = type;
 | 
				
			||||||
@@ -42,19 +42,31 @@ export class TspmError extends Error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Specific error classes
 | 
					// Specific error classes
 | 
				
			||||||
export class ConfigError extends TspmError {
 | 
					export class ConfigError extends TspmError {
 | 
				
			||||||
  constructor(message: string, code: string = 'ERR_CONFIG', details?: Record<string, any>) {
 | 
					  constructor(
 | 
				
			||||||
 | 
					    message: string,
 | 
				
			||||||
 | 
					    code: string = 'ERR_CONFIG',
 | 
				
			||||||
 | 
					    details?: Record<string, any>,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    super(message, ErrorType.CONFIG, code, details);
 | 
					    super(message, ErrorType.CONFIG, code, details);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ProcessError extends TspmError {
 | 
					export class ProcessError extends TspmError {
 | 
				
			||||||
  constructor(message: string, code: string = 'ERR_PROCESS', details?: Record<string, any>) {
 | 
					  constructor(
 | 
				
			||||||
 | 
					    message: string,
 | 
				
			||||||
 | 
					    code: string = 'ERR_PROCESS',
 | 
				
			||||||
 | 
					    details?: Record<string, any>,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    super(message, ErrorType.PROCESS, code, details);
 | 
					    super(message, ErrorType.PROCESS, code, details);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ValidationError extends TspmError {
 | 
					export class ValidationError extends TspmError {
 | 
				
			||||||
  constructor(message: string, code: string = 'ERR_VALIDATION', details?: Record<string, any>) {
 | 
					  constructor(
 | 
				
			||||||
 | 
					    message: string,
 | 
				
			||||||
 | 
					    code: string = 'ERR_VALIDATION',
 | 
				
			||||||
 | 
					    details?: Record<string, any>,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    super(message, ErrorType.VALIDATION, code, details);
 | 
					    super(message, ErrorType.VALIDATION, code, details);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -66,7 +78,9 @@ export const handleError = (error: Error | unknown): TspmError => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (error instanceof Error) {
 | 
					  if (error instanceof Error) {
 | 
				
			||||||
    return new TspmError(error.message, ErrorType.UNKNOWN, 'ERR_UNKNOWN', { originalError: error });
 | 
					    return new TspmError(error.message, ErrorType.UNKNOWN, 'ERR_UNKNOWN', {
 | 
				
			||||||
 | 
					      originalError: error,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return new TspmError(String(error), ErrorType.UNKNOWN, 'ERR_UNKNOWN');
 | 
					  return new TspmError(String(error), ErrorType.UNKNOWN, 'ERR_UNKNOWN');
 | 
				
			||||||
@@ -78,7 +92,7 @@ export enum LogLevel {
 | 
				
			|||||||
  INFO = 1,
 | 
					  INFO = 1,
 | 
				
			||||||
  WARN = 2,
 | 
					  WARN = 2,
 | 
				
			||||||
  ERROR = 3,
 | 
					  ERROR = 3,
 | 
				
			||||||
  NONE = 4
 | 
					  NONE = 4,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Logger {
 | 
					export class Logger {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,5 @@
 | 
				
			|||||||
    "baseUrl": ".",
 | 
					    "baseUrl": ".",
 | 
				
			||||||
    "paths": {}
 | 
					    "paths": {}
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "exclude": [
 | 
					  "exclude": ["dist_*/**/*.d.ts"]
 | 
				
			||||||
    "dist_*/**/*.d.ts"
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user