Compare commits

..

6 Commits

Author SHA1 Message Date
1c2310c185 2.0.0
Some checks failed
Default (tags) / security (push) Successful in 1m2s
Default (tags) / test (push) Failing after 1m31s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-28 10:39:35 +00:00
d33a001edc BREAKING CHANGE(daemon): Refactor daemon lifecycle and service management: remove IPC auto-spawn, add TspmServiceManager and CLI enable/disable 2025-08-28 10:39:35 +00:00
35b6a6a8d0 1.8.0
Some checks failed
Default (tags) / security (push) Failing after 10m29s
Default (tags) / test (push) Has been cancelled
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-08-26 15:00:15 +00:00
50c5fdb0ea feat(daemon): Add real-time log streaming and pub/sub: daemon publishes per-process logs, IPC client subscribe/unsubscribe, CLI --follow streaming, and sequencing for logs 2025-08-26 15:00:15 +00:00
4e0944034b 1.7.0
Some checks failed
Default (tags) / security (push) Successful in 49s
Default (tags) / test (push) Failing after 4m6s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-25 12:33:57 +00:00
ca0dfa6432 feat(readme): Add comprehensive README with detailed usage, command reference, daemon management, architecture and development instructions 2025-08-25 12:33:57 +00:00
16 changed files with 819 additions and 323 deletions

View File

@@ -1,5 +1,36 @@
# Changelog # Changelog
## 2025-08-28 - 2.0.0 - BREAKING CHANGE(daemon)
Refactor daemon lifecycle and service management: remove IPC auto-spawn, add TspmServiceManager and CLI enable/disable
- Do not auto-spawn the daemon from the IPC client anymore — attempts to connect will now error with instructions to start the daemon manually or enable the system service (breaking change).
- Add TspmServiceManager to manage the daemon as a systemd service via smartdaemon (enable/disable/reload/status helpers).
- CLI: add 'enable' and 'disable' commands to install/uninstall the daemon as a system service and add 'daemon start-service' entrypoint used by systemd.
- CLI: improve error handling and user hints when the daemon is not running (suggests `tspm daemon start` or `tspm enable`).
- IPC client: removed startDaemon() and related auto-reconnect/start logic; request() no longer auto-reconnects or implicitly start the daemon.
- Export TspmServiceManager from the package index so service management is part of the public API.
- Updated development plan/readme (readme.plan.md) to reflect the refactor toward proper SmartDaemon integration and migration notes.
## 2025-08-26 - 1.8.0 - feat(daemon)
Add real-time log streaming and pub/sub: daemon publishes per-process logs, IPC client subscribe/unsubscribe, CLI --follow streaming, and sequencing for logs
- Upgrade @push.rocks/smartipc dependency to ^2.1.2
- Daemon: initialize SmartIpc server with heartbeat and publish process logs to topic `logs.<processId>`; write PID file and start heartbeat monitoring
- Tspm: re-emit monitor log events as 'process:log' so daemon can broadcast logs
- ProcessWrapper: include seq and runId on IProcessLog entries and maintain nextSeq/runId (adds sequencing to logs); default log buffer size applied
- TspmIpcClient: improved connect options (retries, timeouts, heartbeat handling), add subscribe/unsubscribe for real-time logs, and use SmartIpc.waitForServer when starting daemon
- CLI: add --follow flag to `logs` command to stream live logs, detect sequence gaps/duplicates, and handle graceful cleanup on Ctrl+C
- ProcessMonitor: now extends EventEmitter and re-emits process logs for upstream consumption
- Standardized heartbeat and IPC timing defaults (heartbeatInterval: 5000ms, heartbeatTimeout: 20000ms, heartbeatInitialGracePeriodMs: 10000ms)
## 2025-08-25 - 1.7.0 - feat(readme)
Add comprehensive README with detailed usage, command reference, daemon management, architecture and development instructions
- Expanded README from a short placeholder to a full documentation covering: Quick Start, Installation, Command Reference, Daemon Management, Monitoring & Information, Batch Operations, Architecture, Programmatic Usage, Advanced Features, Development, Debugging, Performance, and Legal information
- Included usage examples and CLI command reference for start/stop/restart/delete/list/describe/logs and batch/daemon commands
- Added human-friendly memory formatting and examples, process and daemon status outputs, and programmatic TypeScript usage snippet
- Improved onboarding instructions: cloning, installing, testing, building, and running the project
## 2025-08-25 - 1.6.1 - fix(daemon) ## 2025-08-25 - 1.6.1 - fix(daemon)
Fix smartipc integration and add daemon/ipc integration tests Fix smartipc integration and add daemon/ipc integration tests

0
cli.js Normal file → Executable file
View File

View File

@@ -1,6 +1,6 @@
{ {
"name": "@git.zone/tspm", "name": "@git.zone/tspm",
"version": "1.6.1", "version": "2.0.0",
"private": false, "private": false,
"description": "a no fuzz process manager", "description": "a no fuzz process manager",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
@@ -30,7 +30,7 @@
"@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/smartipc": "^2.0.3", "@push.rocks/smartipc": "^2.1.2",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"pidusage": "^4.0.1", "pidusage": "^4.0.1",
"ps-tree": "^1.2.0" "ps-tree": "^1.2.0"

10
pnpm-lock.yaml generated
View File

@@ -21,8 +21,8 @@ importers:
specifier: ^2.0.8 specifier: ^2.0.8
version: 2.0.8 version: 2.0.8
'@push.rocks/smartipc': '@push.rocks/smartipc':
specifier: ^2.0.3 specifier: ^2.1.2
version: 2.0.3 version: 2.1.2
'@push.rocks/smartpath': '@push.rocks/smartpath':
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0 version: 6.0.0
@@ -950,8 +950,8 @@ packages:
'@push.rocks/smarthash@3.2.3': '@push.rocks/smarthash@3.2.3':
resolution: {integrity: sha512-fBPQCGYtOlfLORm9tI3MyoJVT8bixs3MNTAfDDGBw91UKfOVOrPk5jBU+PwVnqZl7IE5mc9b+4wqAJn3giqEpw==} resolution: {integrity: sha512-fBPQCGYtOlfLORm9tI3MyoJVT8bixs3MNTAfDDGBw91UKfOVOrPk5jBU+PwVnqZl7IE5mc9b+4wqAJn3giqEpw==}
'@push.rocks/smartipc@2.0.3': '@push.rocks/smartipc@2.1.2':
resolution: {integrity: sha512-Yty+craFj9lYp6dL1dxHwrF1ykeu02o78D9kNGb5XR+4c53Cci7puqgK9+zbSakaHlNMqKHUWICi50ziGuq5xQ==} resolution: {integrity: sha512-QyFrohq9jq4ISl6DUyeS1uuWgKxQiTrWZAzIqsGZW/BT36FGoqMpGufgjjkVuBvZtYW8e3hl+lcmT+DHfVMfmg==}
'@push.rocks/smartjson@5.0.20': '@push.rocks/smartjson@5.0.20':
resolution: {integrity: sha512-ogGBLyOTluphZVwBYNyjhm5sziPGuiAwWihW07OSRxD4HQUyqj9Ek6r1pqH07JUG5EbtRYivM1Yt1cCwnu3JVQ==} resolution: {integrity: sha512-ogGBLyOTluphZVwBYNyjhm5sziPGuiAwWihW07OSRxD4HQUyqj9Ek6r1pqH07JUG5EbtRYivM1Yt1cCwnu3JVQ==}
@@ -6425,7 +6425,7 @@ snapshots:
'@types/through2': 2.0.41 '@types/through2': 2.0.41
through2: 4.0.2 through2: 4.0.2
'@push.rocks/smartipc@2.0.3': '@push.rocks/smartipc@2.1.2':
dependencies: dependencies:
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartrx': 3.0.10 '@push.rocks/smartrx': 3.0.10

341
readme.md
View File

@@ -1,7 +1,340 @@
# @git.zone/tspm # @git.zone/tspm 🚀
a no fuzz process manager **TypeScript Process Manager** - A robust, no-fuss process manager designed specifically for TypeScript and Node.js applications. Built for developers who need reliable process management without the complexity.
## How to create the docs ## 🎯 What TSPM Does
To create docs run gitzone aidoc. TSPM is your production-ready process manager that handles the hard parts of running Node.js applications:
- **Automatic Memory Management** - Set memory limits and let TSPM handle the rest
- **Smart Auto-Restart** - Crashed processes come back automatically (when you want them to)
- **File Watching** - Auto-restart on file changes during development
- **Process Groups** - Track parent and child processes together
- **Daemon Architecture** - Survives terminal sessions with a persistent background daemon
- **Beautiful CLI** - Clean, informative terminal output with real-time status
- **Structured Logging** - Capture and manage stdout/stderr with intelligent buffering
- **Zero Config** - Works out of the box, customize when you need to
## 📦 Installation
```bash
# Install globally
npm install -g @git.zone/tspm
# Or with pnpm (recommended)
pnpm add -g @git.zone/tspm
# Or use in your project
npm install --save-dev @git.zone/tspm
```
## 🚀 Quick Start
```bash
# Start the daemon (happens automatically on first use)
tspm daemon start
# Start a process
tspm start server.js --name my-server
# Start with memory limit
tspm start app.js --memory 512MB --name my-app
# Start with file watching (great for development)
tspm start dev.js --watch --name dev-server
# List all processes
tspm list
# Check process details
tspm describe my-server
# View logs
tspm logs my-server --lines 100
# Stop a process
tspm stop my-server
# Restart a process
tspm restart my-server
```
## 📋 Command Reference
### Process Management
#### `tspm start <script> [options]`
Start a new process with automatic monitoring and management.
**Options:**
- `--name <name>` - Custom name for the process (default: script name)
- `--memory <size>` - Memory limit (e.g., "512MB", "2GB", default: 512MB)
- `--cwd <path>` - Working directory (default: current directory)
- `--watch` - Enable file watching for auto-restart
- `--watch-paths <paths>` - Comma-separated paths to watch (with --watch)
- `--autorestart` - Auto-restart on crash (default: true)
**Examples:**
```bash
# Simple start
tspm start server.js
# Production setup with 2GB memory
tspm start app.js --name production-api --memory 2GB
# Development with watching
tspm start dev-server.js --watch --watch-paths "src,config" --name dev
# Custom working directory
tspm start ../other-project/index.js --cwd ../other-project --name other
```
#### `tspm stop <id>`
Gracefully stop a running process (SIGTERM → SIGKILL after timeout).
```bash
tspm stop my-server
```
#### `tspm restart <id>`
Stop and restart a process with the same configuration.
```bash
tspm restart my-server
```
#### `tspm delete <id>`
Stop and remove a process from TSPM management.
```bash
tspm delete old-server
```
### Monitoring & Information
#### `tspm list`
Display all managed processes in a beautiful table.
```bash
tspm list
# Output:
┌─────────┬─────────────┬───────────┬───────────┬──────────┐
│ ID │ Name │ Status │ Memory │ Restarts │
├─────────┼─────────────┼───────────┼───────────┼──────────┤
│ my-app │ my-app │ online │ 245.3 MB │ 0
│ worker │ worker │ online │ 128.7 MB │ 2
└─────────┴─────────────┴───────────┴───────────┴──────────┘
```
#### `tspm describe <id>`
Get detailed information about a specific process.
```bash
tspm describe my-server
# Output:
Process Details: my-server
────────────────────────────────────────
Status: online
PID: 45123
Memory: 245.3 MB
CPU: 2.3%
Uptime: 3600s
Restarts: 0
Configuration:
Command: server.js
Directory: /home/user/project
Memory Limit: 2 GB
Auto-restart: true
Watch: enabled
Watch Paths: src, config
```
#### `tspm logs <id> [options]`
View process logs (stdout and stderr).
**Options:**
- `--lines <n>` - Number of lines to display (default: 50)
```bash
tspm logs my-server --lines 100
```
### Batch Operations
#### `tspm start-all`
Start all saved processes at once.
```bash
tspm start-all
```
#### `tspm stop-all`
Stop all running processes.
```bash
tspm stop-all
```
#### `tspm restart-all`
Restart all running processes.
```bash
tspm restart-all
```
### Daemon Management
#### `tspm daemon start`
Start the TSPM daemon (happens automatically on first command).
```bash
tspm daemon start
```
#### `tspm daemon stop`
Stop the TSPM daemon and all managed processes.
```bash
tspm daemon stop
```
#### `tspm daemon status`
Check daemon health and statistics.
```bash
tspm daemon status
# Output:
TSPM Daemon Status:
────────────────────────────────────────
Status: running
PID: 12345
Uptime: 86400s
Processes: 5
Memory: 45.2 MB
CPU: 0.1%
```
## 🏗️ Architecture
TSPM uses a three-tier architecture for maximum reliability:
1. **ProcessWrapper** - Low-level process management with stream handling
2. **ProcessMonitor** - Adds monitoring, memory limits, and auto-restart logic
3. **Tspm Core** - High-level orchestration with configuration persistence
The daemon architecture ensures your processes keep running even after you close your terminal. All process communication happens through a robust IPC (Inter-Process Communication) system.
## 🎮 Programmatic Usage
TSPM can also be used as a library in your Node.js applications:
```typescript
import { Tspm } from '@git.zone/tspm';
const manager = new Tspm();
// Start a process
const processId = await manager.start({
id: 'worker',
name: 'Background Worker',
command: 'node worker.js',
projectDir: process.cwd(),
memoryLimitBytes: 512 * 1024 * 1024, // 512MB
autorestart: true,
watch: false
});
// Monitor process
const info = await manager.getProcessInfo(processId);
console.log(`Process ${info.id} is ${info.status}`);
// Stop process
await manager.stop(processId);
```
## 🔧 Advanced Features
### Memory Limit Enforcement
TSPM tracks memory usage including all child processes spawned by your application. When a process exceeds its memory limit, it's gracefully restarted.
### Process Group Tracking
Using `ps-tree`, TSPM monitors not just your main process but all child processes it spawns, ensuring complete cleanup on stop/restart.
### Intelligent Logging
Logs are buffered and managed efficiently, preventing memory issues from excessive output while ensuring you don't lose important information.
### Graceful Shutdown
Processes receive SIGTERM first, allowing them to clean up. After a timeout, SIGKILL ensures termination.
### Configuration Persistence
Process configurations are saved, allowing you to restart all processes after a system reboot with a single command.
## 🛠️ Development
```bash
# Clone the repository
git clone https://code.foss.global/git.zone/tspm.git
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build the project
pnpm build
# Start development
pnpm start
```
## 🐛 Debugging
Enable debug mode for verbose logging:
```bash
export TSPM_DEBUG=true
tspm list
```
## 📊 Performance
TSPM is designed to be lightweight and efficient:
- Minimal CPU overhead (typically < 0.5%)
- Small memory footprint (~30-50MB for the daemon)
- Fast process startup and shutdown
- Efficient log buffering and rotation
## 🤝 Why TSPM?
Unlike general-purpose process managers, TSPM is built specifically for the TypeScript/Node.js ecosystem:
- **TypeScript First** - Written in TypeScript, for TypeScript projects
- **ESM Native** - Full support for ES modules
- **Developer Friendly** - Beautiful CLI output and helpful error messages
- **Production Ready** - Battle-tested memory management and error handling
- **No Configuration Required** - Sensible defaults that just work
- **Modern Architecture** - Async/await throughout, no callback hell
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

View File

@@ -1,209 +1,48 @@
# TSPM Refactoring Plan: Central Daemon Architecture # TSPM SmartDaemon Service Management Refactor
## Problem Analysis ## Problem
Currently TSPM auto-spawns the daemon as a detached child process, which is improper daemon management. It should use smartdaemon for all lifecycle management and never spawn processes directly.
Currently, each `startAsDaemon` creates an isolated tspm instance with no coordination: ## Solution
Refactor to use SmartDaemon for proper systemd service integration.
- Multiple daemons reading/writing same config file ## Implementation Tasks
- No communication between instances
- Inconsistent process management
- `tspm list` shows all processes but each daemon only manages its own
## Proposed Architecture ### Phase 1: Remove Auto-Spawn Behavior
- [x] Remove spawn import from ts/classes.ipcclient.ts
- [x] Delete startDaemon() method from IpcClient
- [x] Update connect() to throw error when daemon not running
- [x] Remove auto-reconnect logic from request() method
### 1. Central Daemon Manager (`ts/classes.daemon.ts`) ### Phase 2: Create Service Manager
- [x] Create new file ts/classes.servicemanager.ts
- [x] Implement TspmServiceManager class
- [x] Add getOrCreateService() method
- [x] Add enableService() method
- [x] Add disableService() method
- [x] Add getServiceStatus() method
- Single daemon instance managing ALL processes ### Phase 3: Update CLI Commands
- Runs continuously in background - [x] Add 'enable' command to CLI
- Uses Unix socket for IPC at `~/.tspm/tspm.sock` - [x] Add 'disable' command to CLI
- Maintains single source of truth for process state - [x] Update 'daemon start' to work without systemd
- [x] Add 'daemon start-service' internal command for systemd
- [x] Update all commands to handle missing daemon gracefully
- [x] Add proper error messages with hints
### 2. IPC Communication Layer (`ts/classes.ipc.ts`) ### Phase 4: Update Documentation
- [x] Update help text in CLI
- [ ] Update command descriptions
- [x] Add service management section
- **Framework**: Use `@push.rocks/smartipc` v2.0.1 ### Phase 5: Testing
- **Server**: SmartIpc server in daemon using Unix Domain Socket - [x] Test enable command
- **Client**: SmartIpc client in CLI for all operations - [x] Test disable command
- **Socket Path**: `~/.tspm/tspm.sock` (Unix) or named pipe (Windows) - [x] Test daemon commands
- **Protocol**: Type-safe request/response with SmartIpc's built-in patterns - [x] Test error handling when daemon not running
- **Features**: - [x] Build and verify TypeScript compilation
- 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 ## Migration Notes
- Users will need to run `tspm enable` once after update
- `tspm enable` - Start central daemon using systemd/launchd - Existing daemon instances will stop working
- `tspm disable` - Stop and disable central daemon - Documentation needs updating to explain new behavior
- `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

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tspm', name: '@git.zone/tspm',
version: '1.6.1', version: '2.0.0',
description: 'a no fuzz process manager' description: 'a no fuzz process manager'
} }

View File

@@ -43,14 +43,19 @@ export class TspmDaemon {
this.ipcServer = plugins.smartipc.SmartIpc.createServer({ this.ipcServer = plugins.smartipc.SmartIpc.createServer({
id: 'tspm-daemon', id: 'tspm-daemon',
socketPath: this.socketPath, socketPath: this.socketPath,
heartbeat: false, // Disable heartbeat for now autoCleanupSocketFile: true, // Clean up stale sockets
socketMode: 0o600, // Set proper permissions
heartbeat: true,
heartbeatInterval: 5000,
heartbeatTimeout: 20000,
heartbeatInitialGracePeriodMs: 10000 // Grace period for startup
}); });
// Register message handlers // Register message handlers
this.registerHandlers(); this.registerHandlers();
// Start the IPC server // Start the IPC server and wait until ready to accept connections
await this.ipcServer.start(); await this.ipcServer.start({ readyWhen: 'accepting' });
// Write PID file // Write PID file
await this.writePidFile(); await this.writePidFile();
@@ -60,6 +65,16 @@ export class TspmDaemon {
// Load existing process configurations // Load existing process configurations
await this.tspmInstance.loadProcessConfigs(); await this.tspmInstance.loadProcessConfigs();
// Set up log publishing
this.tspmInstance.on('process:log', ({ processId, log }) => {
// Publish to topic for this process
const topic = `logs.${processId}`;
// Broadcast to all connected clients subscribed to this topic
if (this.ipcServer) {
this.ipcServer.broadcast(`topic:${topic}`, log);
}
});
// Set up graceful shutdown handlers // Set up graceful shutdown handlers
this.setupShutdownHandlers(); this.setupShutdownHandlers();

View File

@@ -1,6 +1,6 @@
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 { spawn } from 'child_process';
import type { import type {
IpcMethodMap, IpcMethodMap,
RequestForMethod, RequestForMethod,
@@ -34,28 +34,50 @@ export class TspmIpcClient {
const daemonRunning = await this.isDaemonRunning(); const daemonRunning = await this.isDaemonRunning();
if (!daemonRunning) { if (!daemonRunning) {
console.log('Daemon not running, starting it...'); throw new Error(
await this.startDaemon(); 'TSPM daemon is not running.\n\n' +
// Wait a bit for daemon to initialize 'To start the daemon, run one of:\n' +
await new Promise((resolve) => setTimeout(resolve, 1000)); ' tspm daemon start - Start daemon for this session\n' +
' tspm enable - Enable daemon as system service (recommended)\n'
);
} }
// Create IPC client // Create IPC client
this.ipcClient = plugins.smartipc.SmartIpc.createClient({ this.ipcClient = plugins.smartipc.SmartIpc.createClient({
id: 'tspm-cli', id: 'tspm-cli',
socketPath: this.socketPath, socketPath: this.socketPath,
heartbeat: false, // Disable heartbeat for now clientId: `cli-${process.pid}`,
connectRetry: {
enabled: true,
initialDelay: 100,
maxDelay: 2000,
maxAttempts: 30,
totalTimeout: 15000,
},
registerTimeoutMs: 8000,
heartbeat: true,
heartbeatInterval: 5000,
heartbeatTimeout: 20000,
heartbeatInitialGracePeriodMs: 10000,
heartbeatThrowOnTimeout: false // Don't throw, emit events instead
}); });
// Connect to the daemon // Connect to the daemon
try { try {
await this.ipcClient.connect(); await this.ipcClient.connect({ waitForReady: true });
this.isConnected = true; this.isConnected = true;
// Handle heartbeat timeouts gracefully
this.ipcClient.on('heartbeatTimeout', () => {
console.warn('Heartbeat timeout detected, connection may be degraded');
this.isConnected = false;
});
console.log('Connected to TSPM daemon'); console.log('Connected to TSPM daemon');
} catch (error) { } catch (error) {
console.error('Failed to connect to daemon:', error); console.error('Failed to connect to daemon:', error);
throw new Error( throw new Error(
'Could not connect to TSPM daemon. Please try running "tspm daemon start" manually.', 'Could not connect to TSPM daemon. Please try running "tspm daemon start" or "tspm enable".',
); );
} }
} }
@@ -79,7 +101,10 @@ export class TspmIpcClient {
params: RequestForMethod<M>, params: RequestForMethod<M>,
): Promise<ResponseForMethod<M>> { ): Promise<ResponseForMethod<M>> {
if (!this.isConnected || !this.ipcClient) { if (!this.isConnected || !this.ipcClient) {
await this.connect(); throw new Error(
'Not connected to TSPM daemon.\n' +
'Run "tspm daemon start" or "tspm enable" first.'
);
} }
try { try {
@@ -90,25 +115,34 @@ export class TspmIpcClient {
return response; return response;
} catch (error) { } catch (error) {
// Handle connection errors by trying to reconnect once // Don't try to auto-reconnect, just throw the error
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; throw error;
} }
} }
/**
* Subscribe to log updates for a specific process
*/
public async subscribe(processId: string, handler: (log: any) => void): Promise<void> {
if (!this.ipcClient || !this.isConnected) {
throw new Error('Not connected to daemon');
}
const topic = `logs.${processId}`;
await this.ipcClient.subscribe(`topic:${topic}`, handler);
}
/**
* Unsubscribe from log updates for a specific process
*/
public async unsubscribe(processId: string): Promise<void> {
if (!this.ipcClient || !this.isConnected) {
throw new Error('Not connected to daemon');
}
const topic = `logs.${processId}`;
await this.ipcClient.unsubscribe(`topic:${topic}`);
}
/** /**
* Check if the daemon is running * Check if the daemon is running
@@ -151,44 +185,7 @@ export class TspmIpcClient {
} }
} }
/**
* 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 * Stop the daemon

View File

@@ -1,4 +1,5 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import { EventEmitter } from 'events';
import { ProcessWrapper, type IProcessLog } from './classes.processwrapper.js'; import { ProcessWrapper, type IProcessLog } from './classes.processwrapper.js';
import { Logger, ProcessError, handleError } from './utils.errorhandler.js'; import { Logger, ProcessError, handleError } from './utils.errorhandler.js';
@@ -13,7 +14,7 @@ export interface IMonitorConfig {
logBufferSize?: number; // Optional: number of log lines to keep (default: 100) logBufferSize?: number; // Optional: number of log lines to keep (default: 100)
} }
export class ProcessMonitor { export class ProcessMonitor extends EventEmitter {
private processWrapper: ProcessWrapper | null = null; private processWrapper: ProcessWrapper | null = null;
private config: IMonitorConfig; private config: IMonitorConfig;
private intervalId: NodeJS.Timeout | null = null; private intervalId: NodeJS.Timeout | null = null;
@@ -22,6 +23,7 @@ export class ProcessMonitor {
private logger: Logger; private logger: Logger;
constructor(config: IMonitorConfig) { constructor(config: IMonitorConfig) {
super();
this.config = config; this.config = config;
this.logger = new Logger(`ProcessMonitor:${config.name || 'unnamed'}`); this.logger = new Logger(`ProcessMonitor:${config.name || 'unnamed'}`);
} }
@@ -65,8 +67,10 @@ export class ProcessMonitor {
// Set up event handlers // Set up event handlers
this.processWrapper.on('log', (log: IProcessLog): void => { this.processWrapper.on('log', (log: IProcessLog): void => {
// Here we could add handlers to send logs somewhere // Re-emit the log event for upstream handlers
// For now, we just log system messages to the console this.emit('log', log);
// Log system messages to the console
if (log.type === 'system') { if (log.type === 'system') {
this.log(log.message); this.log(log.message);
} }

View File

@@ -15,6 +15,8 @@ export interface IProcessLog {
timestamp: Date; timestamp: Date;
type: 'stdout' | 'stderr' | 'system'; type: 'stdout' | 'stderr' | 'system';
message: string; message: string;
seq: number;
runId: string;
} }
export class ProcessWrapper extends EventEmitter { export class ProcessWrapper extends EventEmitter {
@@ -24,12 +26,15 @@ export class ProcessWrapper extends EventEmitter {
private logBufferSize: number; private logBufferSize: number;
private startTime: Date | null = null; private startTime: Date | null = null;
private logger: Logger; private logger: Logger;
private nextSeq: number = 0;
private runId: string = '';
constructor(options: IProcessWrapperOptions) { constructor(options: IProcessWrapperOptions) {
super(); super();
this.options = options; this.options = options;
this.logBufferSize = options.logBuffer || 100; this.logBufferSize = options.logBuffer || 100;
this.logger = new Logger(`ProcessWrapper:${options.name}`); this.logger = new Logger(`ProcessWrapper:${options.name}`);
this.runId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
} }
/** /**
@@ -217,6 +222,8 @@ export class ProcessWrapper extends EventEmitter {
timestamp: new Date(), timestamp: new Date(),
type, type,
message, message,
seq: this.nextSeq++,
runId: this.runId,
}; };
this.logs.push(log); this.logs.push(log);
@@ -238,6 +245,8 @@ export class ProcessWrapper extends EventEmitter {
timestamp: new Date(), timestamp: new Date(),
type: 'system', type: 'system',
message, message,
seq: this.nextSeq++,
runId: this.runId,
}; };
this.logs.push(log); this.logs.push(log);

View File

@@ -0,0 +1,103 @@
import * as plugins from './plugins.js';
import * as paths from './paths.js';
/**
* Manages TSPM daemon as a systemd service via smartdaemon
*/
export class TspmServiceManager {
private smartDaemon: plugins.smartdaemon.SmartDaemon;
private service: any = null; // SmartDaemonService type is not exported
constructor() {
this.smartDaemon = new plugins.smartdaemon.SmartDaemon();
}
/**
* Get or create the TSPM daemon service configuration
*/
private async getOrCreateService(): Promise<any> {
if (!this.service) {
const cliPath = plugins.path.join(paths.packageDir, 'cli.js');
// Create service configuration
this.service = await this.smartDaemon.addService({
name: 'tspm-daemon',
description: 'TSPM Process Manager Daemon',
command: `${process.execPath} ${cliPath} daemon start-service`,
workingDir: process.env.HOME || process.cwd(),
version: '1.0.0'
});
}
return this.service;
}
/**
* Enable the TSPM daemon as a system service
*/
public async enableService(): Promise<void> {
const service = await this.getOrCreateService();
// Save service configuration
await service.save();
// Enable service to start on boot
await service.enable();
// Start the service immediately
await service.start();
}
/**
* Disable the TSPM daemon service
*/
public async disableService(): Promise<void> {
const service = await this.getOrCreateService();
// Stop the service if running
try {
await service.stop();
} catch (error) {
// Service might not be running
console.log('Service was not running');
}
// Disable service from starting on boot
await service.disable();
}
/**
* Get the current status of the systemd service
*/
public async getServiceStatus(): Promise<{
enabled: boolean;
running: boolean;
status: string;
}> {
try {
await this.getOrCreateService();
// Note: SmartDaemon doesn't provide direct status methods,
// so we'll need to check via systemctl commands
// This is a simplified implementation
return {
enabled: true, // Would need to check systemctl is-enabled
running: true, // Would need to check systemctl is-active
status: 'active'
};
} catch (error) {
return {
enabled: false,
running: false,
status: 'inactive'
};
}
}
/**
* Reload the systemd service configuration
*/
public async reloadService(): Promise<void> {
const service = await this.getOrCreateService();
await service.reload();
}
}

View File

@@ -1,9 +1,11 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import { EventEmitter } from 'events';
import * as paths from './paths.js'; import * as paths from './paths.js';
import { import {
ProcessMonitor, ProcessMonitor,
type IMonitorConfig, type IMonitorConfig,
} from './classes.processmonitor.js'; } from './classes.processmonitor.js';
import { type IProcessLog } from './classes.processwrapper.js';
import { TspmConfig } from './classes.config.js'; import { TspmConfig } from './classes.config.js';
import { import {
Logger, Logger,
@@ -30,13 +32,9 @@ export interface IProcessInfo {
restarts: number; restarts: number;
} }
export interface IProcessLog {
timestamp: Date;
type: 'stdout' | 'stderr' | 'system';
message: string;
}
export class Tspm {
export class Tspm extends EventEmitter {
public processes: Map<string, ProcessMonitor> = new Map(); public processes: Map<string, ProcessMonitor> = new Map();
public processConfigs: Map<string, IProcessConfig> = new Map(); public processConfigs: Map<string, IProcessConfig> = new Map();
public processInfo: Map<string, IProcessInfo> = new Map(); public processInfo: Map<string, IProcessInfo> = new Map();
@@ -45,6 +43,7 @@ export class Tspm {
private logger: Logger; private logger: Logger;
constructor() { constructor() {
super();
this.logger = new Logger('Tspm'); this.logger = new Logger('Tspm');
this.config = new TspmConfig(); this.config = new TspmConfig();
this.loadProcessConfigs(); this.loadProcessConfigs();
@@ -98,6 +97,12 @@ export class Tspm {
}); });
this.processes.set(config.id, monitor); this.processes.set(config.id, monitor);
// Set up log event handler to re-emit for pub/sub
monitor.on('log', (log: IProcessLog) => {
this.emit('process:log', { processId: config.id, log });
});
monitor.start(); monitor.start();
// Update process info // Update process info

231
ts/cli.ts
View File

@@ -1,6 +1,7 @@
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 { tspmIpcClient } from './classes.ipcclient.js'; import { tspmIpcClient } from './classes.ipcclient.js';
import { TspmServiceManager } from './classes.servicemanager.js';
import { Logger, LogLevel } from './utils.errorhandler.js'; import { Logger, LogLevel } from './utils.errorhandler.js';
import type { IProcessConfig } from './classes.tspm.js'; import type { IProcessConfig } from './classes.tspm.js';
@@ -51,6 +52,21 @@ function formatMemory(bytes: number): string {
} }
} }
// Helper function to handle daemon connection errors
function handleDaemonError(error: any, action: string): void {
if (error.message?.includes('daemon is not running') ||
error.message?.includes('Not connected') ||
error.message?.includes('ECONNREFUSED')) {
console.error(`Error: Cannot ${action} - TSPM daemon is not running.`);
console.log('\nTo start the daemon, run one of:');
console.log(' tspm daemon start - Start for this session only');
console.log(' tspm enable - Enable as system service (recommended)');
} else {
console.error(`Error ${action}:`, error.message);
}
process.exit(1);
}
// Helper function for padding strings // Helper function for padding strings
function pad(str: string, length: number): string { function pad(str: string, length: number): string {
return str.length > length return str.length > length
@@ -79,7 +95,10 @@ export const run = async (): Promise<void> => {
`TSPM - TypeScript Process Manager v${tspmProjectinfo.npm.version}`, `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('\nService Management:');
console.log(' enable Enable TSPM as system service (systemd)');
console.log(' disable Disable TSPM system service');
console.log('\nProcess Commands:');
console.log(' start <script> Start a process'); console.log(' start <script> Start a process');
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');
@@ -91,8 +110,8 @@ export const run = async (): Promise<void> => {
console.log(' stop-all Stop all processes'); console.log(' stop-all Stop all processes');
console.log(' restart-all Restart all processes'); console.log(' restart-all Restart all processes');
console.log('\nDaemon Commands:'); console.log('\nDaemon Commands:');
console.log(' daemon start Start the TSPM daemon'); console.log(' daemon start Start daemon manually (current session)');
console.log(' daemon stop Stop the TSPM daemon'); console.log(' daemon stop Stop the daemon');
console.log(' daemon status Show daemon status'); console.log(' daemon status Show daemon status');
console.log( console.log(
'\nUse tspm [command] --help for more information about a command.', '\nUse tspm [command] --help for more information about a command.',
@@ -139,9 +158,10 @@ export const run = async (): Promise<void> => {
); );
} }
} catch (error) { } catch (error) {
console.error( console.error('Error: TSPM daemon is not running.');
'Error: Could not connect to TSPM daemon. Use "tspm daemon start" to start it.', console.log('\nTo start the daemon, run one of:');
); console.log(' tspm daemon start - Start for this session only');
console.log(' tspm enable - Enable as system service (recommended)');
} }
}, },
error: (err) => { error: (err) => {
@@ -217,8 +237,7 @@ export const run = async (): Promise<void> => {
console.log(` PID: ${response.pid || 'N/A'}`); console.log(` PID: ${response.pid || 'N/A'}`);
console.log(` Status: ${response.status}`); console.log(` Status: ${response.status}`);
} catch (error) { } catch (error) {
console.error('Error starting process:', error.message); handleDaemonError(error, 'start process');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -247,8 +266,7 @@ export const run = async (): Promise<void> => {
console.error(`✗ Failed to stop process: ${response.message}`); console.error(`✗ Failed to stop process: ${response.message}`);
} }
} catch (error) { } catch (error) {
console.error('Error stopping process:', error.message); handleDaemonError(error, 'stop process');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -276,8 +294,7 @@ export const run = async (): Promise<void> => {
console.log(` PID: ${response.pid || 'N/A'}`); console.log(` PID: ${response.pid || 'N/A'}`);
console.log(` Status: ${response.status}`); console.log(` Status: ${response.status}`);
} catch (error) { } catch (error) {
console.error('Error restarting process:', error.message); handleDaemonError(error, 'restart process');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -306,8 +323,7 @@ export const run = async (): Promise<void> => {
console.error(`✗ Failed to delete process: ${response.message}`); console.error(`✗ Failed to delete process: ${response.message}`);
} }
} catch (error) { } catch (error) {
console.error('Error deleting process:', error.message); handleDaemonError(error, 'delete process');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -356,8 +372,7 @@ export const run = async (): Promise<void> => {
); );
} }
} catch (error) { } catch (error) {
console.error('Error listing processes:', error.message); handleDaemonError(error, 'list processes');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -409,8 +424,7 @@ export const run = async (): Promise<void> => {
} }
} }
} catch (error) { } catch (error) {
console.error('Error describing process:', error.message); handleDaemonError(error, 'describe process');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -426,24 +440,87 @@ export const run = async (): Promise<void> => {
const id = argvArg._[1]; const id = argvArg._[1];
if (!id) { if (!id) {
console.error('Error: Please provide a process ID'); console.error('Error: Please provide a process ID');
console.log('Usage: tspm logs <id>'); console.log('Usage: tspm logs <id> [options]');
console.log('\nOptions:');
console.log(' --lines <n> Number of lines to show (default: 50)');
console.log(' --follow Stream logs in real-time (like tail -f)');
return; return;
} }
const lines = argvArg.lines || 50; const lines = argvArg.lines || 50;
const follow = argvArg.follow || argvArg.f || false;
// Get initial logs
const response = await tspmIpcClient.request('getLogs', { id, lines }); const response = await tspmIpcClient.request('getLogs', { id, lines });
console.log(`Logs for process: ${id} (last ${lines} lines)`); if (!follow) {
console.log('─'.repeat(60)); // Static log output
console.log(`Logs for process: ${id} (last ${lines} lines)`);
console.log('─'.repeat(60));
for (const log of response.logs) { for (const log of response.logs) {
const timestamp = new Date(log.timestamp).toLocaleTimeString(); const timestamp = new Date(log.timestamp).toLocaleTimeString();
const prefix = log.type === 'stdout' ? '[OUT]' : '[ERR]'; const prefix = log.type === 'stdout' ? '[OUT]' : log.type === 'stderr' ? '[ERR]' : '[SYS]';
console.log(`${timestamp} ${prefix} ${log.message}`); console.log(`${timestamp} ${prefix} ${log.message}`);
}
} else {
// Streaming log output
console.log(`Logs for process: ${id} (streaming...)`);
console.log('─'.repeat(60));
// Display initial logs
let lastSeq = 0;
for (const log of response.logs) {
const timestamp = new Date(log.timestamp).toLocaleTimeString();
const prefix = log.type === 'stdout' ? '[OUT]' : log.type === 'stderr' ? '[ERR]' : '[SYS]';
console.log(`${timestamp} ${prefix} ${log.message}`);
if (log.seq !== undefined) {
lastSeq = Math.max(lastSeq, log.seq);
}
}
// Subscribe to real-time updates
await tspmIpcClient.subscribe(id, (log: any) => {
// Check for sequence gap or duplicate
if (log.seq !== undefined && log.seq <= lastSeq) {
return; // Skip duplicate
}
if (log.seq !== undefined && log.seq > lastSeq + 1) {
console.log(`[WARNING] Log gap detected: expected seq ${lastSeq + 1}, got ${log.seq}`);
}
const timestamp = new Date(log.timestamp).toLocaleTimeString();
const prefix = log.type === 'stdout' ? '[OUT]' : log.type === 'stderr' ? '[ERR]' : '[SYS]';
console.log(`${timestamp} ${prefix} ${log.message}`);
if (log.seq !== undefined) {
lastSeq = log.seq;
}
});
// Handle Ctrl+C gracefully
let isCleaningUp = false;
const cleanup = async () => {
if (isCleaningUp) return;
isCleaningUp = true;
console.log('\n\nStopping log stream...');
try {
await tspmIpcClient.unsubscribe(id);
await tspmIpcClient.disconnect();
} catch (err) {
// Ignore cleanup errors
}
process.exit(0);
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
// Keep the process alive
await new Promise(() => {}); // Block forever until interrupted
} }
} catch (error) { } catch (error) {
console.error('Error getting logs:', error.message); handleDaemonError(error, 'get logs');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -473,8 +550,7 @@ export const run = async (): Promise<void> => {
} }
} }
} catch (error) { } catch (error) {
console.error('Error starting all processes:', error.message); handleDaemonError(error, 'start all processes');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -504,8 +580,7 @@ export const run = async (): Promise<void> => {
} }
} }
} catch (error) { } catch (error) {
console.error('Error stopping all processes:', error.message); handleDaemonError(error, 'stop all processes');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -537,8 +612,7 @@ export const run = async (): Promise<void> => {
} }
} }
} catch (error) { } catch (error) {
console.error('Error restarting all processes:', error.message); handleDaemonError(error, 'restart all processes');
process.exit(1);
} }
}, },
error: (err) => { error: (err) => {
@@ -566,19 +640,49 @@ export const run = async (): Promise<void> => {
return; return;
} }
console.log('Starting TSPM daemon...'); console.log('Starting TSPM daemon manually...');
await tspmIpcClient.connect();
console.log('✓ TSPM daemon started successfully'); // Import spawn to start daemon process
const { spawn } = await import('child_process');
const daemonScript = plugins.path.join(
paths.packageDir,
'dist_ts',
'daemon.js',
);
// Start daemon as a regular background process (not detached)
const daemonProcess = spawn(process.execPath, [daemonScript], {
stdio: 'ignore',
env: {
...process.env,
TSPM_DAEMON_MODE: 'true',
},
});
console.log(`Started daemon with PID: ${daemonProcess.pid}`);
// Wait for daemon to be ready
await new Promise(resolve => setTimeout(resolve, 2000));
const newStatus = await tspmIpcClient.getDaemonStatus(); const newStatus = await tspmIpcClient.getDaemonStatus();
if (newStatus) { if (newStatus) {
console.log('✓ TSPM daemon started successfully');
console.log(` PID: ${newStatus.pid}`); console.log(` PID: ${newStatus.pid}`);
console.log('\nNote: This daemon will run until you stop it or logout.');
console.log('For automatic startup, use "tspm enable" instead.');
} }
} catch (error) { } catch (error) {
console.error('Error starting daemon:', error.message); console.error('Error starting daemon:', error.message);
process.exit(1); process.exit(1);
} }
break; break;
case 'start-service':
// This is called by systemd - start the daemon directly
console.log('Starting TSPM daemon for systemd service...');
const { startDaemon } = await import('./classes.daemon.js');
await startDaemon();
break;
case 'stop': case 'stop':
try { try {
@@ -612,6 +716,9 @@ export const run = async (): Promise<void> => {
`Memory: ${formatMemory(status.memoryUsage || 0)}`, `Memory: ${formatMemory(status.memoryUsage || 0)}`,
); );
console.log(`CPU: ${status.cpuUsage?.toFixed(1) || 0}s`); console.log(`CPU: ${status.cpuUsage?.toFixed(1) || 0}s`);
// Disconnect from daemon after getting status
await tspmIpcClient.disconnect();
} catch (error) { } catch (error) {
console.error('Error getting daemon status:', error.message); console.error('Error getting daemon status:', error.message);
process.exit(1); process.exit(1);
@@ -633,6 +740,58 @@ export const run = async (): Promise<void> => {
complete: () => {}, complete: () => {},
}); });
// Enable command - Enable TSPM daemon as systemd service
smartcliInstance.addCommand('enable').subscribe({
next: async (argvArg: CliArguments) => {
try {
const serviceManager = new TspmServiceManager();
console.log('Enabling TSPM daemon as system service...');
await serviceManager.enableService();
console.log('✓ TSPM daemon enabled and started as system service');
console.log(' The daemon will now start automatically on system boot');
console.log(' Use "tspm disable" to remove the service');
} catch (error) {
console.error('Error enabling service:', error.message);
if (error.message.includes('permission') || error.message.includes('denied')) {
console.log('\nNote: You may need to run this command with sudo');
}
process.exit(1);
}
},
error: (err) => {
cliLogger.error(err);
},
complete: () => {},
});
// Disable command - Disable TSPM daemon systemd service
smartcliInstance.addCommand('disable').subscribe({
next: async (argvArg: CliArguments) => {
try {
const serviceManager = new TspmServiceManager();
console.log('Disabling TSPM daemon service...');
await serviceManager.disableService();
console.log('✓ TSPM daemon service disabled');
console.log(' The daemon will no longer start on system boot');
console.log(' Use "tspm enable" to re-enable the service');
} catch (error) {
console.error('Error disabling service:', error.message);
if (error.message.includes('permission') || error.message.includes('denied')) {
console.log('\nNote: You may need to run this command with sudo');
}
process.exit(1);
}
},
error: (err) => {
cliLogger.error(err);
},
complete: () => {},
});
// Start parsing commands // Start parsing commands
smartcliInstance.startParse(); smartcliInstance.startParse();
}; };

View File

@@ -2,6 +2,7 @@ export * from './classes.tspm.js';
export * from './classes.processmonitor.js'; export * from './classes.processmonitor.js';
export * from './classes.daemon.js'; export * from './classes.daemon.js';
export * from './classes.ipcclient.js'; export * from './classes.ipcclient.js';
export * from './classes.servicemanager.js';
export * from './ipc.types.js'; export * from './ipc.types.js';
import * as cli from './cli.js'; import * as cli from './cli.js';

View File

@@ -1,8 +1,8 @@
import type { import type {
IProcessConfig, IProcessConfig,
IProcessInfo, IProcessInfo,
IProcessLog,
} from './classes.tspm.js'; } from './classes.tspm.js';
import type { IProcessLog } from './classes.processwrapper.js';
// Base message types // Base message types
export interface IpcRequest<T = any> { export interface IpcRequest<T = any> {