From 9c1327c9be1f42c65e4b4af56b0a0d3f206cff02 Mon Sep 17 00:00:00 2001
From: Philipp Kunz <code@philkunz.com>
Date: Mon, 3 Mar 2025 05:21:52 +0000
Subject: [PATCH] feat(core): Introduced process management features using
 ProcessWrapper and enhanced configuration.

---
 changelog.md                 |   8 ++
 package.json                 |   1 +
 pnpm-lock.yaml               |  18 +++
 ts/00_commitinfo_data.ts     |   2 +-
 ts/classes.config.ts         |  20 +++
 ts/classes.processmonitor.ts | 133 ++++++++++++------
 ts/classes.processwrapper.ts | 207 ++++++++++++++++++++++++++++
 ts/classes.tspm.ts           | 255 ++++++++++++++++++++++++++++++++++-
 ts/cli.ts                    |   8 ++
 ts/plugins.ts                |   2 +
 10 files changed, 610 insertions(+), 44 deletions(-)
 create mode 100644 ts/classes.config.ts
 create mode 100644 ts/classes.processwrapper.ts

diff --git a/changelog.md b/changelog.md
index 8d3c765..1b3fcd8 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## 2025-03-03 - 1.4.0 - feat(core)
+Introduced process management features using ProcessWrapper and enhanced configuration.
+
+- Added ProcessWrapper for wrapping and managing child processes.
+- Refactored process monitoring logic using ProcessWrapper.
+- Introduced TspmConfig for configuration handling.
+- Enhanced CLI to support new process management commands like 'startAsDaemon'.
+
 ## 2025-03-01 - 1.3.1 - fix(test)
 Update test script to fix type references and remove private method call
 
diff --git a/package.json b/package.json
index 5d0526f..c8fa616 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
     "@types/node": "^22.13.8"
   },
   "dependencies": {
+    "@push.rocks/npmextra": "^5.1.2",
     "@push.rocks/projectinfo": "^5.0.2",
     "@push.rocks/smartcli": "^4.0.11",
     "@push.rocks/smartpath": "^5.0.18",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1f37ec3..1bfdc73 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
 
   .:
     dependencies:
+      '@push.rocks/npmextra':
+        specifier: ^5.1.2
+        version: 5.1.2
       '@push.rocks/projectinfo':
         specifier: ^5.0.2
         version: 5.0.2
@@ -713,6 +716,9 @@ packages:
   '@push.rocks/mongodump@1.0.8':
     resolution: {integrity: sha512-oDufyjNBg8I50OaJvbHhc0RnRpJQ544dr9her0G6sA8JmI3hD2/amTdcPLVIX1kzYf5GsTUKeWuRaZgdNqz3ew==}
 
+  '@push.rocks/npmextra@5.1.2':
+    resolution: {integrity: sha512-0utZEsQSUDgFG6nGcm66Dh4DgPwqpUQcEAOtJKvubXIFRaOzQ3Yp6M8GKeL5VwxgFxWWtqp9xP8NxLEtHN9UcA==}
+
   '@push.rocks/projectinfo@5.0.2':
     resolution: {integrity: sha512-zzieCal6jwR++o+fDl8gMpWkNV2cGEsbT96vCNZu/H9kr0iqRmapOiA4DFadkhOnhlDqvRr6TPaXESu2YUbI8Q==}
 
@@ -5418,6 +5424,18 @@ snapshots:
     transitivePeerDependencies:
       - aws-crt
 
+  '@push.rocks/npmextra@5.1.2':
+    dependencies:
+      '@push.rocks/qenv': 6.1.0
+      '@push.rocks/smartfile': 11.2.0
+      '@push.rocks/smartjson': 5.0.20
+      '@push.rocks/smartlog': 3.0.7
+      '@push.rocks/smartpath': 5.0.18
+      '@push.rocks/smartpromise': 4.2.3
+      '@push.rocks/smartrx': 3.0.7
+      '@push.rocks/taskbuffer': 3.1.7
+      '@tsclass/tsclass': 4.4.0
+
   '@push.rocks/projectinfo@5.0.2':
     dependencies:
       '@push.rocks/smartfile': 10.0.41
diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts
index 38ee4e6..f58e36b 100644
--- a/ts/00_commitinfo_data.ts
+++ b/ts/00_commitinfo_data.ts
@@ -3,6 +3,6 @@
  */
 export const commitinfo = {
   name: '@git.zone/tspm',
-  version: '1.3.1',
+  version: '1.4.0',
   description: 'a no fuzz process manager'
 }
diff --git a/ts/classes.config.ts b/ts/classes.config.ts
new file mode 100644
index 0000000..085822e
--- /dev/null
+++ b/ts/classes.config.ts
@@ -0,0 +1,20 @@
+import * as plugins from './plugins.js';
+
+export class TspmConfig {
+  public npmextraInstance = new plugins.npmextra.KeyValueStore({
+    identityArg: '@git.zone__tspm',
+    typeArg: 'userHomeDir',
+  })
+
+  public async readKey(keyArg: string): Promise<string> {
+    return await this.npmextraInstance.readKey(keyArg);
+  }
+
+  public async writeKey(keyArg: string, value: string): Promise<void> {
+    return await this.npmextraInstance.writeKey(keyArg, value);
+  }
+
+  public async deleteKey(keyArg: string): Promise<void> {
+    return await this.npmextraInstance.deleteKey(keyArg);
+  }
+}
\ No newline at end of file
diff --git a/ts/classes.processmonitor.ts b/ts/classes.processmonitor.ts
index d6ca44d..c8af963 100644
--- a/ts/classes.processmonitor.ts
+++ b/ts/classes.processmonitor.ts
@@ -1,4 +1,5 @@
 import * as plugins from './plugins.js';
+import { ProcessWrapper } from './classes.processwrapper.js';
 
 export interface IMonitorConfig {
   name?: string;                 // Optional name to identify the instance
@@ -7,13 +8,16 @@ export interface IMonitorConfig {
   args?: string[];               // Optional: arguments for the command
   memoryLimitBytes: number;      // Maximum allowed memory (in bytes) for the process group
   monitorIntervalMs?: number;    // Interval (in ms) at which memory is checked (default: 5000)
+  env?: NodeJS.ProcessEnv;       // Optional: custom environment variables
+  logBufferSize?: number;        // Optional: number of log lines to keep (default: 100)
 }
 
 export class ProcessMonitor {
-  private child: plugins.childProcess.ChildProcess | null = null;
+  private processWrapper: ProcessWrapper | null = null;
   private config: IMonitorConfig;
   private intervalId: NodeJS.Timeout | null = null;
   private stopped: boolean = true; // Initially stopped until start() is called
+  private restartCount: number = 0;
 
   constructor(config: IMonitorConfig) {
     this.config = config;
@@ -23,59 +27,64 @@ export class ProcessMonitor {
     // Reset the stopped flag so that new processes can spawn.
     this.stopped = false;
     this.log(`Starting process monitor.`);
-    this.spawnChild();
+    this.spawnProcess();
 
     // Set the monitoring interval.
     const interval = this.config.monitorIntervalMs || 5000;
     this.intervalId = setInterval(() => {
-      if (this.child && this.child.pid) {
-        this.monitorProcessGroup(this.child.pid, this.config.memoryLimitBytes);
+      if (this.processWrapper && this.processWrapper.getPid()) {
+        this.monitorProcessGroup(this.processWrapper.getPid()!, this.config.memoryLimitBytes);
       }
     }, interval);
   }
 
-  private spawnChild(): void {
+  private spawnProcess(): void {
     // Don't spawn if the monitor has been stopped.
     if (this.stopped) return;
 
-    if (this.config.args && this.config.args.length > 0) {
-      this.log(
-        `Spawning command "${this.config.command}" with args [${this.config.args.join(
-          ', '
-        )}] in directory: ${this.config.projectDir}`
-      );
-      this.child = plugins.childProcess.spawn(this.config.command, this.config.args, {
-        cwd: this.config.projectDir,
-        detached: true,
-        stdio: 'inherit',
-      });
-    } else {
-      this.log(
-        `Spawning command "${this.config.command}" in directory: ${this.config.projectDir}`
-      );
-      // Use shell mode to allow a full command string.
-      this.child = plugins.childProcess.spawn(this.config.command, {
-        cwd: this.config.projectDir,
-        detached: true,
-        stdio: 'inherit',
-        shell: true,
-      });
-    }
+    // Create a new process wrapper
+    this.processWrapper = new ProcessWrapper({
+      name: this.config.name || 'unnamed-process',
+      command: this.config.command,
+      args: this.config.args,
+      cwd: this.config.projectDir,
+      env: this.config.env,
+      logBuffer: this.config.logBufferSize,
+    });
 
-    this.log(`Spawned process with PID ${this.child.pid}`);
-
-    // When the child process exits, restart it if the monitor isn't stopped.
-    this.child.on('exit', (code, signal) => {
-      this.log(`Child process exited with code ${code}, signal ${signal}.`);
-      if (!this.stopped) {
-        this.log('Restarting process...');
-        this.spawnChild();
+    // Set up event handlers
+    this.processWrapper.on('log', (log) => {
+      // Here we could add handlers to send logs somewhere
+      // For now, we just log system messages to the console
+      if (log.type === 'system') {
+        this.log(log.message);
       }
     });
+
+    this.processWrapper.on('exit', (code, signal) => {
+      this.log(`Process exited with code ${code}, signal ${signal}.`);
+      if (!this.stopped) {
+        this.log('Restarting process...');
+        this.restartCount++;
+        this.spawnProcess();
+      }
+    });
+
+    this.processWrapper.on('error', (error) => {
+      this.log(`Process error: ${error.message}`);
+      if (!this.stopped) {
+        this.log('Restarting process due to error...');
+        this.restartCount++;
+        this.spawnProcess();
+      }
+    });
+
+    // Start the process
+    this.processWrapper.start();
   }
 
   /**
-   * 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.
    */
   private async monitorProcessGroup(pid: number, memoryLimit: number): Promise<void> {
@@ -92,8 +101,10 @@ export class ProcessMonitor {
             memoryUsage
           )} exceeds limit of ${this.humanReadableBytes(memoryLimit)}. Restarting process.`
         );
-        // Kill the entire process group by sending a signal to -PID.
-        process.kill(-pid, 'SIGKILL');
+        // Stop the process wrapper, which will trigger the exit handler and restart
+        if (this.processWrapper) {
+          this.processWrapper.stop();
+        }
       }
     } catch (error) {
       this.log('Error monitoring process group: ' + error);
@@ -142,10 +153,48 @@ export class ProcessMonitor {
     if (this.intervalId) {
       clearInterval(this.intervalId);
     }
-    if (this.child && this.child.pid) {
-      process.kill(-this.child.pid, 'SIGKILL');
+    if (this.processWrapper) {
+      this.processWrapper.stop();
     }
   }
+  
+  /**
+   * Get the current logs from the process
+   */
+  public getLogs(limit?: number): Array<{ timestamp: Date, type: string, message: string }> {
+    if (!this.processWrapper) {
+      return [];
+    }
+    return this.processWrapper.getLogs(limit);
+  }
+  
+  /**
+   * Get the number of times the process has been restarted
+   */
+  public getRestartCount(): number {
+    return this.restartCount;
+  }
+  
+  /**
+   * Get the process ID if running
+   */
+  public getPid(): number | null {
+    return this.processWrapper?.getPid() || null;
+  }
+  
+  /**
+   * Get process uptime in milliseconds
+   */
+  public getUptime(): number {
+    return this.processWrapper?.getUptime() || 0;
+  }
+  
+  /**
+   * Check if the process is currently running
+   */
+  public isRunning(): boolean {
+    return this.processWrapper?.isRunning() || false;
+  }
 
   /**
    * Helper method for logging messages with the instance name.
@@ -154,4 +203,4 @@ export class ProcessMonitor {
     const prefix = this.config.name ? `[${this.config.name}] ` : '';
     console.log(prefix + message);
   }
-}
+}
\ No newline at end of file
diff --git a/ts/classes.processwrapper.ts b/ts/classes.processwrapper.ts
new file mode 100644
index 0000000..859d7d3
--- /dev/null
+++ b/ts/classes.processwrapper.ts
@@ -0,0 +1,207 @@
+import * as plugins from './plugins.js';
+import { EventEmitter } from 'events';
+
+export interface IProcessWrapperOptions {
+  command: string;
+  args?: string[];
+  cwd: string;
+  env?: NodeJS.ProcessEnv;
+  name: string;
+  logBuffer?: number; // Number of log lines to keep in memory (default: 100)
+}
+
+export interface IProcessLog {
+  timestamp: Date;
+  type: 'stdout' | 'stderr' | 'system';
+  message: string;
+}
+
+export class ProcessWrapper extends EventEmitter {
+  private process: plugins.childProcess.ChildProcess | null = null;
+  private options: IProcessWrapperOptions;
+  private logs: IProcessLog[] = [];
+  private logBufferSize: number;
+  private startTime: Date | null = null;
+  
+  constructor(options: IProcessWrapperOptions) {
+    super();
+    this.options = options;
+    this.logBufferSize = options.logBuffer || 100;
+  }
+  
+  /**
+   * Start the wrapped process
+   */
+  public start(): void {
+    this.addSystemLog('Starting process...');
+    
+    try {
+      if (this.options.args && this.options.args.length > 0) {
+        this.process = plugins.childProcess.spawn(this.options.command, this.options.args, {
+          cwd: this.options.cwd,
+          env: this.options.env || process.env,
+          stdio: ['ignore', 'pipe', 'pipe'], // We need to pipe stdout and stderr
+        });
+      } else {
+        // Use shell mode to allow a full command string
+        this.process = plugins.childProcess.spawn(this.options.command, {
+          cwd: this.options.cwd,
+          env: this.options.env || process.env,
+          stdio: ['ignore', 'pipe', 'pipe'], // We need to pipe stdout and stderr
+          shell: true,
+        });
+      }
+      
+      this.startTime = new Date();
+      
+      // Handle process exit
+      this.process.on('exit', (code, signal) => {
+        this.addSystemLog(`Process exited with code ${code}, signal ${signal}`);
+        this.emit('exit', code, signal);
+      });
+      
+      // Handle errors
+      this.process.on('error', (error) => {
+        this.addSystemLog(`Process error: ${error.message}`);
+        this.emit('error', error);
+      });
+      
+      // Capture stdout
+      if (this.process.stdout) {
+        this.process.stdout.on('data', (data) => {
+          const lines = data.toString().split('\n');
+          for (const line of lines) {
+            if (line.trim()) {
+              this.addLog('stdout', line);
+            }
+          }
+        });
+      }
+      
+      // Capture stderr
+      if (this.process.stderr) {
+        this.process.stderr.on('data', (data) => {
+          const lines = data.toString().split('\n');
+          for (const line of lines) {
+            if (line.trim()) {
+              this.addLog('stderr', line);
+            }
+          }
+        });
+      }
+      
+      this.addSystemLog(`Process started with PID ${this.process.pid}`);
+      this.emit('start', this.process.pid);
+      
+    } catch (error) {
+      this.addSystemLog(`Failed to start process: ${error.message}`);
+      this.emit('error', error);
+      throw error;
+    }
+  }
+  
+  /**
+   * Stop the wrapped process
+   */
+  public stop(): void {
+    if (!this.process) {
+      this.addSystemLog('No process running');
+      return;
+    }
+    
+    this.addSystemLog('Stopping process...');
+    
+    // First try SIGTERM for graceful shutdown
+    if (this.process.pid) {
+      try {
+        process.kill(this.process.pid, 'SIGTERM');
+        
+        // Give it 5 seconds to shut down gracefully
+        setTimeout(() => {
+          if (this.process && this.process.pid) {
+            this.addSystemLog('Process did not exit gracefully, force killing...');
+            try {
+              process.kill(this.process.pid, 'SIGKILL');
+            } catch (error) {
+              // Process might have exited between checks
+            }
+          }
+        }, 5000);
+      } catch (error) {
+        this.addSystemLog(`Error stopping process: ${error.message}`);
+      }
+    }
+  }
+  
+  /**
+   * Get the process ID if running
+   */
+  public getPid(): number | null {
+    return this.process?.pid || null;
+  }
+  
+  /**
+   * Get the current logs
+   */
+  public getLogs(limit: number = this.logBufferSize): IProcessLog[] {
+    // Return the most recent logs up to the limit
+    return this.logs.slice(-limit);
+  }
+  
+  /**
+   * Get uptime in milliseconds
+   */
+  public getUptime(): number {
+    if (!this.startTime) return 0;
+    return Date.now() - this.startTime.getTime();
+  }
+  
+  /**
+   * Check if the process is currently running
+   */
+  public isRunning(): boolean {
+    return this.process !== null && typeof this.process.exitCode !== 'number';
+  }
+  
+  /**
+   * Add a log entry from stdout or stderr
+   */
+  private addLog(type: 'stdout' | 'stderr', message: string): void {
+    const log: IProcessLog = {
+      timestamp: new Date(),
+      type,
+      message,
+    };
+    
+    this.logs.push(log);
+    
+    // Trim logs if they exceed buffer size
+    if (this.logs.length > this.logBufferSize) {
+      this.logs = this.logs.slice(-this.logBufferSize);
+    }
+    
+    // Emit log event for potential handlers
+    this.emit('log', log);
+  }
+  
+  /**
+   * Add a system log entry (not from the process itself)
+   */
+  private addSystemLog(message: string): void {
+    const log: IProcessLog = {
+      timestamp: new Date(),
+      type: 'system',
+      message,
+    };
+    
+    this.logs.push(log);
+    
+    // Trim logs if they exceed buffer size
+    if (this.logs.length > this.logBufferSize) {
+      this.logs = this.logs.slice(-this.logBufferSize);
+    }
+    
+    // Emit log event for potential handlers
+    this.emit('log', log);
+  }
+}
\ No newline at end of file
diff --git a/ts/classes.tspm.ts b/ts/classes.tspm.ts
index 3a591d5..82fb275 100644
--- a/ts/classes.tspm.ts
+++ b/ts/classes.tspm.ts
@@ -1,6 +1,259 @@
 import * as plugins from './plugins.js';
 import * as paths from './paths.js';
+import { ProcessMonitor, type IMonitorConfig } from './classes.processmonitor.js';
+import { TspmConfig } from './classes.config.js';
+
+export interface IProcessConfig extends IMonitorConfig {
+  id: string;         // Unique identifier for the process
+  autorestart: boolean; // Whether to restart the process automatically on crash
+  watch?: boolean;    // Whether to watch for file changes and restart
+  watchPaths?: string[]; // Paths to watch for changes
+}
+
+export interface IProcessInfo {
+  id: string;
+  pid?: number;
+  status: 'online' | 'stopped' | 'errored';
+  memory: number;
+  cpu?: number;
+  uptime?: number;
+  restarts: number;
+}
+
+export interface IProcessLog {
+  timestamp: Date;
+  type: 'stdout' | 'stderr' | 'system';
+  message: string;
+}
 
 export class Tspm {
-   
+  private processes: Map<string, ProcessMonitor> = new Map();
+  private processConfigs: Map<string, IProcessConfig> = new Map();
+  private processInfo: Map<string, IProcessInfo> = new Map();
+  private config: TspmConfig;
+  private configStorageKey = 'processes';
+  
+  constructor() {
+    this.config = new TspmConfig();
+    this.loadProcessConfigs();
+  }
+  
+  /**
+   * Start a new process with the given configuration
+   */
+  public async start(config: IProcessConfig): Promise<void> {
+    // Check if process with this id already exists
+    if (this.processes.has(config.id)) {
+      throw new Error(`Process with id '${config.id}' already exists`);
+    }
+    
+    // Create and store process config
+    this.processConfigs.set(config.id, config);
+    
+    // Initialize process info
+    this.processInfo.set(config.id, {
+      id: config.id,
+      status: 'stopped',
+      memory: 0,
+      restarts: 0
+    });
+    
+    // Create and start process monitor
+    const monitor = new ProcessMonitor({
+      name: config.name || config.id,
+      projectDir: config.projectDir,
+      command: config.command,
+      args: config.args,
+      memoryLimitBytes: config.memoryLimitBytes,
+      monitorIntervalMs: config.monitorIntervalMs
+    });
+    
+    this.processes.set(config.id, monitor);
+    monitor.start();
+    
+    // Update process info
+    this.updateProcessInfo(config.id, { status: 'online' });
+    
+    // Save updated configs
+    await this.saveProcessConfigs();
+  }
+  
+  /**
+   * Stop a process by id
+   */
+  public async stop(id: string): Promise<void> {
+    const monitor = this.processes.get(id);
+    if (!monitor) {
+      throw new Error(`Process with id '${id}' not found`);
+    }
+    
+    monitor.stop();
+    this.updateProcessInfo(id, { status: 'stopped' });
+    
+    // Don't remove from the maps, just mark as stopped
+    // This allows it to be restarted later
+  }
+  
+  /**
+   * Restart a process by id
+   */
+  public async restart(id: string): Promise<void> {
+    const monitor = this.processes.get(id);
+    const config = this.processConfigs.get(id);
+    
+    if (!monitor || !config) {
+      throw new Error(`Process with id '${id}' not found`);
+    }
+    
+    // Stop and then start the process
+    monitor.stop();
+    
+    // Create a new monitor instance
+    const newMonitor = new ProcessMonitor({
+      name: config.name || config.id,
+      projectDir: config.projectDir,
+      command: config.command,
+      args: config.args,
+      memoryLimitBytes: config.memoryLimitBytes,
+      monitorIntervalMs: config.monitorIntervalMs
+    });
+    
+    this.processes.set(id, newMonitor);
+    newMonitor.start();
+    
+    // Update restart count
+    const info = this.processInfo.get(id);
+    if (info) {
+      this.updateProcessInfo(id, { 
+        status: 'online',
+        restarts: info.restarts + 1
+      });
+    }
+  }
+  
+  /**
+   * Delete a process by id
+   */
+  public async delete(id: string): Promise<void> {
+    // Stop the process if it's running
+    try {
+      await this.stop(id);
+    } catch (error) {
+      // Ignore errors if the process is not running
+    }
+    
+    // Remove from all maps
+    this.processes.delete(id);
+    this.processConfigs.delete(id);
+    this.processInfo.delete(id);
+    
+    // Save updated configs
+    await this.saveProcessConfigs();
+  }
+  
+  /**
+   * Get a list of all process infos
+   */
+  public list(): IProcessInfo[] {
+    return Array.from(this.processInfo.values());
+  }
+  
+  /**
+   * Get detailed info for a specific process
+   */
+  public describe(id: string): { config: IProcessConfig; info: IProcessInfo } | null {
+    const config = this.processConfigs.get(id);
+    const info = this.processInfo.get(id);
+    
+    if (!config || !info) {
+      return null;
+    }
+    
+    return { config, info };
+  }
+  
+  /**
+   * Get process logs
+   */
+  public getLogs(id: string, limit?: number): IProcessLog[] {
+    const monitor = this.processes.get(id);
+    if (!monitor) {
+      return [];
+    }
+    
+    return monitor.getLogs(limit);
+  }
+  
+  /**
+   * Start all saved processes
+   */
+  public async startAll(): Promise<void> {
+    for (const [id, config] of this.processConfigs.entries()) {
+      if (!this.processes.has(id)) {
+        await this.start(config);
+      }
+    }
+  }
+  
+  /**
+   * Stop all running processes
+   */
+  public async stopAll(): Promise<void> {
+    for (const id of this.processes.keys()) {
+      await this.stop(id);
+    }
+  }
+  
+  /**
+   * Restart all processes
+   */
+  public async restartAll(): Promise<void> {
+    for (const id of this.processes.keys()) {
+      await this.restart(id);
+    }
+  }
+  
+  /**
+   * Update the info for a process
+   */
+  private updateProcessInfo(id: string, update: Partial<IProcessInfo>): void {
+    const info = this.processInfo.get(id);
+    if (info) {
+      this.processInfo.set(id, { ...info, ...update });
+    }
+  }
+  
+  /**
+   * Save all process configurations to config storage
+   */
+  private async saveProcessConfigs(): Promise<void> {
+    const configs = Array.from(this.processConfigs.values());
+    await this.config.writeKey(this.configStorageKey, JSON.stringify(configs));
+  }
+  
+  /**
+   * Load process configurations from config storage
+   */
+  private async loadProcessConfigs(): Promise<void> {
+    try {
+      const configsJson = await this.config.readKey(this.configStorageKey);
+      if (configsJson) {
+        const configs = JSON.parse(configsJson) as IProcessConfig[];
+        for (const config of configs) {
+          this.processConfigs.set(config.id, config);
+          
+          // Initialize process info
+          this.processInfo.set(config.id, {
+            id: config.id,
+            status: 'stopped',
+            memory: 0,
+            restarts: 0
+          });
+        }
+      }
+    } catch (error) {
+      // If no configs found or error reading, just continue with empty configs
+      console.log('No saved process configurations found');
+    }
+  }
 }
\ No newline at end of file
diff --git a/ts/cli.ts b/ts/cli.ts
index 4dcd933..409e375 100644
--- a/ts/cli.ts
+++ b/ts/cli.ts
@@ -17,5 +17,13 @@ export const run = async () => {
 
   })
 
+  smartcliInstance.addCommand('startAsDaemon').subscribe({
+
+  })
+
+  smartcliInstance.addCommand('stop').subscribe({
+
+  })
+
   smartcliInstance.startParse();
 }
\ No newline at end of file
diff --git a/ts/plugins.ts b/ts/plugins.ts
index afad6f7..8a7ddbf 100644
--- a/ts/plugins.ts
+++ b/ts/plugins.ts
@@ -8,11 +8,13 @@ export {
 }
 
 // @push.rocks scope
+import * as npmextra from '@push.rocks/npmextra';
 import * as projectinfo from '@push.rocks/projectinfo';
 import * as smartpath from '@push.rocks/smartpath';
 import * as smartcli from '@push.rocks/smartcli';
 
 export {
+  npmextra,
   projectinfo,
   smartpath,
   smartcli,