@plumpslabs/fennec-cli 1.12.2 → 1.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2088,7 +2088,7 @@ function hexColor(color) {
2088
2088
  }
2089
2089
  var fennecOrange2 = hexColor("#FF6432");
2090
2090
  var fennecGold = hexColor("#FFB347");
2091
- var VERSION = "1.12.2";
2091
+ var VERSION = "1.12.3";
2092
2092
  var cachedBanner = null;
2093
2093
  var cachedCompact = null;
2094
2094
  function generateBanner() {
@@ -2220,6 +2220,7 @@ function showHelp() {
2220
2220
  }
2221
2221
 
2222
2222
  // src/commands/pipe.ts
2223
+ import { PipeWatcher } from "@plumpslabs/fennec-core";
2223
2224
  async function pipeCommand(args2) {
2224
2225
  const nameIndex = args2.indexOf("--name");
2225
2226
  const name = nameIndex !== -1 ? args2[nameIndex + 1] : "pipe";
@@ -2227,7 +2228,6 @@ async function pipeCommand(args2) {
2227
2228
  console.error("Error: --name is required for pipe command");
2228
2229
  process.exit(1);
2229
2230
  }
2230
- const { PipeWatcher } = await import("@plumpslabs/fennec-core");
2231
2231
  const watcher = new PipeWatcher();
2232
2232
  const { write } = watcher.createPipe(name);
2233
2233
  console.error(`Pipe watcher '${name}' active. Forwarding stdin...`);
@@ -2265,13 +2265,13 @@ async function pipeCommand(args2) {
2265
2265
  }
2266
2266
 
2267
2267
  // src/commands/attach-pid.ts
2268
+ import { PortDetector } from "@plumpslabs/fennec-core";
2268
2269
  async function attachPidCommand(args2) {
2269
2270
  const pid = parseInt(args2[0], 10);
2270
2271
  if (isNaN(pid)) {
2271
2272
  console.error("Error: valid PID is required");
2272
2273
  process.exit(1);
2273
2274
  }
2274
- const { PortDetector } = await import("@plumpslabs/fennec-core");
2275
2275
  const detector = new PortDetector();
2276
2276
  const info = detector.detectByPid(pid);
2277
2277
  if (info) {
@@ -2284,14 +2284,14 @@ async function attachPidCommand(args2) {
2284
2284
  }
2285
2285
 
2286
2286
  // src/commands/attach-port.ts
2287
+ import { PortDetector as PortDetector2 } from "@plumpslabs/fennec-core";
2287
2288
  async function attachPortCommand(args2) {
2288
2289
  const port = parseInt(args2[0], 10);
2289
2290
  if (isNaN(port)) {
2290
2291
  console.error("Error: valid port number is required");
2291
2292
  process.exit(1);
2292
2293
  }
2293
- const { PortDetector } = await import("@plumpslabs/fennec-core");
2294
- const detector = new PortDetector();
2294
+ const detector = new PortDetector2();
2295
2295
  const info = detector.detectByPort(port);
2296
2296
  if (info) {
2297
2297
  console.log(`Found process on port ${port}: PID ${info.pid}${info.command ? ` (${info.command})` : ""}`);
@@ -2304,6 +2304,7 @@ async function attachPortCommand(args2) {
2304
2304
  // src/commands/watch.ts
2305
2305
  import { existsSync } from "fs";
2306
2306
  import { resolve } from "path";
2307
+ import { LogWatcher } from "@plumpslabs/fennec-core";
2307
2308
  async function watchCommand(args2) {
2308
2309
  const fileIndex = args2.indexOf("--file");
2309
2310
  const filePath = fileIndex !== -1 ? args2[fileIndex + 1] : void 0;
@@ -2318,7 +2319,6 @@ async function watchCommand(args2) {
2318
2319
  console.error(`Error: File not found: ${resolvedPath}`);
2319
2320
  process.exit(1);
2320
2321
  }
2321
- const { LogWatcher } = await import("@plumpslabs/fennec-core");
2322
2322
  const watcher = new LogWatcher();
2323
2323
  const watcherId = watcher.watchFile(resolvedPath, name);
2324
2324
  console.log(`Watching file: ${resolvedPath}`);
@@ -2327,12 +2327,12 @@ async function watchCommand(args2) {
2327
2327
  }
2328
2328
 
2329
2329
  // src/commands/start.ts
2330
- import { createWriteStream, readFileSync as readFileSync3, mkdirSync as mkdirSync2 } from "fs";
2330
+ import { createWriteStream, mkdirSync as mkdirSync2 } from "fs";
2331
2331
  import { resolve as resolve3 } from "path";
2332
2332
  import { homedir as homedir2 } from "os";
2333
2333
  import { spawn } from "child_process";
2334
2334
  import { FennecServer } from "@plumpslabs/fennec-core";
2335
- import pc4 from "picocolors";
2335
+ import pc5 from "picocolors";
2336
2336
 
2337
2337
  // src/commands/tracker.ts
2338
2338
  import { existsSync as existsSync2, writeFileSync, readFileSync, mkdirSync, renameSync, statSync, rmSync } from "fs";
@@ -2676,10 +2676,174 @@ function formatProcessState(state) {
2676
2676
  }
2677
2677
  }
2678
2678
 
2679
+ // src/commands/ps.ts
2680
+ import pc4 from "picocolors";
2681
+ async function psCommand(args2) {
2682
+ const watchFlag = args2.includes("-w") || args2.includes("--watch");
2683
+ const systemFlag = args2.includes("--system") || args2.includes("-a") || args2.includes("--all");
2684
+ const nameFilter = args2.includes("--name") ? args2[args2.indexOf("--name") + 1] : void 0;
2685
+ const sortBy = args2.includes("--sort") ? args2[args2.indexOf("--sort") + 1] : "name";
2686
+ if (watchFlag && systemFlag) {
2687
+ await watchSystemProcesses(sortBy, 15);
2688
+ return;
2689
+ }
2690
+ if (systemFlag) {
2691
+ const spinner = createSpinner("Scanning system processes...");
2692
+ try {
2693
+ const processes = getSystemProcesses({ name: nameFilter, userOnly: true, sortBy, limit: 30 });
2694
+ spinner.stop();
2695
+ process.stdout.write("\r\x1B[K");
2696
+ if (processes.length === 0) {
2697
+ console.error(`
2698
+ ${pc4.dim("No system processes found.")}
2699
+ `);
2700
+ return;
2701
+ }
2702
+ const columns2 = [
2703
+ { key: "pid", label: "PID", align: "right", format: (v) => pc4.dim(String(v).padStart(6)) },
2704
+ { key: "name", label: "Name", format: (v) => pc4.bold(String(v)) },
2705
+ { key: "cpu", label: "CPU%", align: "right", format: (v) => {
2706
+ const n = v;
2707
+ return n > 10 ? pc4.red(String(n)) : n > 5 ? pc4.yellow(String(n)) : pc4.dim(String(n));
2708
+ } },
2709
+ { key: "mem", label: "MEM%", align: "right", format: (v) => {
2710
+ const n = v;
2711
+ return n > 10 ? pc4.red(String(n)) : n > 5 ? pc4.yellow(String(n)) : pc4.dim(String(n));
2712
+ } },
2713
+ { key: "state", label: "State", format: (v) => {
2714
+ const s = String(v);
2715
+ if (s === "R" || s === "Running") return pc4.green(s);
2716
+ if (s === "Z" || s === "Zombie") return pc4.red(s);
2717
+ if (s === "S" || s === "Sleeping") return pc4.cyan(s);
2718
+ return pc4.dim(s);
2719
+ } }
2720
+ ];
2721
+ const rows2 = processes.map((p) => ({ pid: p.pid, name: p.name, cpu: p.cpuPercent, mem: p.memPercent, state: formatProcessState(p.state) }));
2722
+ console.error(`
2723
+ ${symbols.fox} ${pc4.bold("System Processes")} ${pc4.dim(`(top ${processes.length} by ${sortBy})`)}
2724
+ `);
2725
+ console.error(renderTable(columns2, rows2));
2726
+ console.error();
2727
+ } catch (error) {
2728
+ spinner.fail("Failed to scan processes");
2729
+ console.error(renderError("Process scan failed", String(error)));
2730
+ }
2731
+ return;
2732
+ }
2733
+ const tracked = readTracked();
2734
+ if (tracked.length === 0) {
2735
+ console.error(`
2736
+ ${pc4.dim("No tracked processes.")}`);
2737
+ console.error(` ${pc4.dim("Start an app with:")} ${pc4.cyan("fennec start <command> --name <name>")}
2738
+ `);
2739
+ return;
2740
+ }
2741
+ const columns = [
2742
+ { key: "name", label: "App", format: (v) => pc4.bold(String(v)) },
2743
+ { key: "pid", label: "PID", align: "right" },
2744
+ { key: "status", label: "Status", format: (v) => {
2745
+ const s = v;
2746
+ return s === "running" ? pc4.green("\u25CF running") : pc4.red("\u25CB stopped");
2747
+ } },
2748
+ { key: "port", label: "Port", format: (v) => {
2749
+ const p = v;
2750
+ return p ? pc4.yellow(`:${p}`) : pc4.dim("-");
2751
+ } },
2752
+ { key: "command", label: "Command", format: (v) => {
2753
+ const c = String(v);
2754
+ return c.length > 50 ? c.slice(0, 50) + "\u2026" : c;
2755
+ } },
2756
+ { key: "uptime", label: "Uptime", format: (v) => pc4.dim(String(v)) }
2757
+ ];
2758
+ const rows = tracked.map((t) => {
2759
+ const running = isProcessRunning(t.pid);
2760
+ const uptime = running ? formatUptime(Math.floor((Date.now() - new Date(t.startedAt).getTime()) / 1e3)) : "-";
2761
+ return { name: t.name, pid: running ? String(t.pid) : pc4.dim(String(t.pid)), status: running ? "running" : "stopped", port: t.port ?? null, command: t.command, uptime };
2762
+ });
2763
+ const runningCount = tracked.filter((t) => isProcessRunning(t.pid)).length;
2764
+ console.error(`
2765
+ ${symbols.fox} ${pc4.bold("Fennec Apps")} ${pc4.dim(`(${runningCount}/${tracked.length} running)`)}
2766
+ `);
2767
+ console.error(renderTable(columns, rows));
2768
+ console.error(` ${pc4.dim("Use")} ${pc4.cyan("fennec start <command> --name <name> --port <port>")} ${pc4.dim("to add more apps.")}`);
2769
+ console.error(` ${pc4.dim("Use")} ${pc4.cyan("fennec log <name>")} ${pc4.dim("to view logs.")}`);
2770
+ console.error(` ${pc4.dim("Use")} ${pc4.cyan("fennec kill <name>")} ${pc4.dim("to stop an app.")}`);
2771
+ console.error();
2772
+ }
2773
+ async function statusCommand(_args) {
2774
+ const watchFlag = _args.includes("-w") || _args.includes("--watch");
2775
+ const tracked = readTracked();
2776
+ const topSystem = getSystemProcesses({ userOnly: true, sortBy: "cpu", limit: 5 });
2777
+ const totalUserProcs = getSystemProcesses({ userOnly: true }).length;
2778
+ console.error(`
2779
+ ${symbols.fox} ${pc4.bold("Fennec Status")}
2780
+ `);
2781
+ if (tracked.length > 0) {
2782
+ const runningCount = tracked.filter((t) => isProcessRunning(t.pid)).length;
2783
+ console.error(` ${pc4.bold("Managed Apps")} ${pc4.dim(`(${runningCount}/${tracked.length} running)`)}
2784
+ `);
2785
+ for (const t of tracked) {
2786
+ const running = isProcessRunning(t.pid);
2787
+ const statusIcon = running ? pc4.green("\u25CF") : pc4.red("\u25CB");
2788
+ const portStr = t.port ? ` ${pc4.yellow(`:${t.port}`)}` : "";
2789
+ const uptime = running ? pc4.dim(formatUptime(Math.floor((Date.now() - new Date(t.startedAt).getTime()) / 1e3))) : pc4.red("stopped");
2790
+ console.error(` ${statusIcon} ${pc4.bold(t.name)}${portStr} ${pc4.dim(`(PID ${t.pid})`)} \u2014 ${uptime}`);
2791
+ }
2792
+ console.error();
2793
+ } else {
2794
+ console.error(` ${pc4.dim("No managed apps.")} ${pc4.cyan("fennec start <command> --name <name>")}
2795
+ `);
2796
+ }
2797
+ console.error(` ${pc4.bold("System")} ${pc4.dim(`(${totalUserProcs} user processes)`)}`);
2798
+ for (const p of topSystem) {
2799
+ const cpuStr = p.cpuPercent > 10 ? pc4.red(`${p.cpuPercent}%`) : p.cpuPercent > 5 ? pc4.yellow(`${p.cpuPercent}%`) : pc4.dim(`${p.cpuPercent}%`);
2800
+ const memStr = p.memPercent > 10 ? pc4.red(`${p.memPercent}%`) : p.memPercent > 5 ? pc4.yellow(`${p.memPercent}%`) : pc4.dim(`${p.memPercent}%`);
2801
+ console.error(` ${pc4.dim(`PID ${p.pid}`)} ${pc4.bold(p.name)} \u2014 CPU: ${cpuStr} MEM: ${memStr}`);
2802
+ }
2803
+ if (watchFlag) await watchSystemProcesses("cpu", 15);
2804
+ console.error();
2805
+ }
2806
+ async function watchSystemProcesses(sortBy, limit) {
2807
+ console.error(`
2808
+ ${pc4.bold("Watching system processes")} ${pc4.dim("(Ctrl+C to stop, refreshes every 3s)")}
2809
+ `);
2810
+ const render = () => {
2811
+ const processes = getSystemProcesses({ userOnly: true, sortBy, limit });
2812
+ const columns = [
2813
+ { key: "pid", label: "PID", align: "right", format: (v) => pc4.dim(String(v).padStart(6)) },
2814
+ { key: "name", label: "Name", format: (v) => pc4.bold(String(v)) },
2815
+ { key: "cpu", label: "CPU%", align: "right", format: (v) => {
2816
+ const n = v;
2817
+ return n > 10 ? pc4.red(String(n)) : n > 5 ? pc4.yellow(String(n)) : pc4.dim(String(n));
2818
+ } },
2819
+ { key: "mem", label: "MEM%", align: "right", format: (v) => {
2820
+ const n = v;
2821
+ return n > 10 ? pc4.red(String(n)) : n > 5 ? pc4.yellow(String(n)) : pc4.dim(String(n));
2822
+ } },
2823
+ { key: "state", label: "S", align: "center" }
2824
+ ];
2825
+ const rows = processes.map((p) => ({ pid: p.pid, name: p.name, cpu: p.cpuPercent, mem: p.memPercent, state: p.state }));
2826
+ return ` ${timestamp()} ${pc4.dim(`${processes.length} processes`)}
2827
+ ${renderTable(columns, rows, { compact: true })}`;
2828
+ };
2829
+ console.error(render());
2830
+ const interval = setInterval(() => {
2831
+ process.stdout.write("\x1B[J");
2832
+ console.error(render());
2833
+ }, 3e3);
2834
+ await new Promise((resolve8) => {
2835
+ process.once("SIGINT", () => {
2836
+ clearInterval(interval);
2837
+ process.stdout.write("\x1B[J");
2838
+ resolve8();
2839
+ });
2840
+ });
2841
+ }
2842
+
2679
2843
  // src/commands/start.ts
2680
2844
  async function startServer(args2) {
2681
2845
  printBanner();
2682
- console.error(` ${pc4.dim("Starting Fennec MCP server...")}
2846
+ console.error(` ${pc5.dim("Starting Fennec MCP server...")}
2683
2847
  `);
2684
2848
  const configIndex = args2.indexOf("--config");
2685
2849
  const configPath = configIndex !== -1 ? args2[configIndex + 1] : void 0;
@@ -2688,11 +2852,11 @@ async function startServer(args2) {
2688
2852
  await resurrectTracked();
2689
2853
  await server.start();
2690
2854
  console.error(`
2691
- ${pc4.green("\u2713")} ${pc4.bold("Fennec server is running")}`);
2855
+ ${pc5.green("\u2713")} ${pc5.bold("Fennec server is running")}`);
2692
2856
  console.error(` ${renderKV("Transport", "stdio")}`);
2693
2857
  console.error(` ${renderKV("AI Agent", "Connect via MCP protocol")}`);
2694
2858
  console.error(`
2695
- ${pc4.dim("Press Ctrl+C to stop")}
2859
+ ${pc5.dim("Press Ctrl+C to stop")}
2696
2860
  `);
2697
2861
  } catch (error) {
2698
2862
  console.error(renderError("Failed to start server", String(error)));
@@ -2701,7 +2865,7 @@ async function startServer(args2) {
2701
2865
  }
2702
2866
  async function startCommand(args2) {
2703
2867
  printBanner();
2704
- console.error(` ${pc4.dim("Starting app...")}
2868
+ console.error(` ${pc5.dim("Starting app...")}
2705
2869
  `);
2706
2870
  await runCommand(args2);
2707
2871
  }
@@ -2725,9 +2889,9 @@ async function runCommand(args2) {
2725
2889
  const existing = tracked.find((t) => t.name === appName);
2726
2890
  if (existing && isProcessRunning(existing.pid)) {
2727
2891
  console.error();
2728
- console.error(` ${pc4.yellow("\u26A0")} ${pc4.bold(appName)} ${pc4.dim(`is already running (PID: ${existing.pid})`)}`);
2729
- console.error(` ${renderKV("Logs", pc4.cyan(`fennec log ${appName}`))}`);
2730
- console.error(` ${renderKV("Stop", pc4.cyan(`fennec kill ${appName}`))}`);
2892
+ console.error(` ${pc5.yellow("\u26A0")} ${pc5.bold(appName)} ${pc5.dim(`is already running (PID: ${existing.pid})`)}`);
2893
+ console.error(` ${renderKV("Logs", pc5.cyan(`fennec log ${appName}`))}`);
2894
+ console.error(` ${renderKV("Stop", pc5.cyan(`fennec kill ${appName}`))}`);
2731
2895
  console.error();
2732
2896
  process.exit(0);
2733
2897
  }
@@ -2735,7 +2899,7 @@ async function runCommand(args2) {
2735
2899
  mkdirSync2(logDir, { recursive: true });
2736
2900
  const logFilePath = resolve3(logDir, `${appName}.log`);
2737
2901
  console.error(`
2738
- ${symbols.fox} ${pc4.bold("Starting")} ${renderAppName(appName)} ${pc4.dim("(daemon)")}
2902
+ ${symbols.fox} ${pc5.bold("Starting")} ${renderAppName(appName)} ${pc5.dim("(daemon)")}
2739
2903
  `);
2740
2904
  console.error(` ${renderKV("Command", cmd)}`);
2741
2905
  if (port) console.error(` ${renderKV("Port", String(port))}`);
@@ -2762,32 +2926,8 @@ async function runCommand(args2) {
2762
2926
  cwd,
2763
2927
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
2764
2928
  });
2765
- console.error(`
2766
- ${pc4.green("\u25CF")} ${pc4.bold(appName)} ${pc4.dim(`started (PID: ${pid})`)}${port ? pc4.dim(` :${port}`) : ""}
2767
- `);
2768
- await new Promise((r) => setTimeout(r, 1500));
2769
- try {
2770
- const initialLogs = readFileSync3(logFilePath, "utf-8").split("\n").filter(Boolean).slice(-8);
2771
- if (initialLogs.length > 0) {
2772
- for (const line of initialLogs) {
2773
- const truncated = line.length > 150 ? line.slice(0, 150) + "\u2026" : line;
2774
- if (line.toLowerCase().includes("error")) {
2775
- console.error(` ${pc4.dim("\u2502")} ${pc4.red(truncated)}`);
2776
- } else if (line.toLowerCase().includes("warn")) {
2777
- console.error(` ${pc4.dim("\u2502")} ${pc4.yellow(truncated)}`);
2778
- } else {
2779
- console.error(` ${pc4.dim("\u2502")} ${truncated}`);
2780
- }
2781
- }
2782
- }
2783
- } catch {
2784
- }
2785
- console.error();
2786
- console.error(` ${pc4.green("\u2713")} ${pc4.bold(appName)} running in background`);
2787
- console.error(` ${renderKV("Logs", pc4.cyan(`fennec log ${appName}`))}`);
2788
- console.error(` ${renderKV("Status", pc4.cyan("fennec ps"))}`);
2789
- console.error(` ${renderKV("Stop", pc4.cyan(`fennec kill ${appName}`))}`);
2790
- console.error();
2929
+ console.error(` ${pc5.green("\u2713")} ${pc5.bold(appName)} ${pc5.dim(`started (PID: ${pid})`)}`);
2930
+ await psCommand([]);
2791
2931
  } catch (error) {
2792
2932
  console.error(renderError(`Failed to start ${appName}`, String(error)));
2793
2933
  process.exit(1);
@@ -2838,177 +2978,13 @@ async function resurrectTracked() {
2838
2978
  spinner.stop();
2839
2979
  process.stdout.write("\r\x1B[K");
2840
2980
  if (resurrected > 0) {
2841
- console.error(` ${pc4.green("\u25CF")} ${pc4.bold(`Resurrected ${resurrected} process(es)`)}`);
2981
+ console.error(` ${pc5.green("\u25CF")} ${pc5.bold(`Resurrected ${resurrected} process(es)`)}`);
2842
2982
  }
2843
2983
  if (failed > 0) {
2844
- console.error(` ${pc4.red("\u25CF")} ${pc4.bold(`${failed} process(es) failed to resurrect`)}`);
2984
+ console.error(` ${pc5.red("\u25CF")} ${pc5.bold(`${failed} process(es) failed to resurrect`)}`);
2845
2985
  }
2846
2986
  }
2847
2987
 
2848
- // src/commands/ps.ts
2849
- import pc5 from "picocolors";
2850
- async function psCommand(args2) {
2851
- const watchFlag = args2.includes("-w") || args2.includes("--watch");
2852
- const systemFlag = args2.includes("--system") || args2.includes("-a") || args2.includes("--all");
2853
- const nameFilter = args2.includes("--name") ? args2[args2.indexOf("--name") + 1] : void 0;
2854
- const sortBy = args2.includes("--sort") ? args2[args2.indexOf("--sort") + 1] : "name";
2855
- if (watchFlag && systemFlag) {
2856
- await watchSystemProcesses(sortBy, 15);
2857
- return;
2858
- }
2859
- if (systemFlag) {
2860
- const spinner = createSpinner("Scanning system processes...");
2861
- try {
2862
- const processes = getSystemProcesses({ name: nameFilter, userOnly: true, sortBy, limit: 30 });
2863
- spinner.stop();
2864
- process.stdout.write("\r\x1B[K");
2865
- if (processes.length === 0) {
2866
- console.error(`
2867
- ${pc5.dim("No system processes found.")}
2868
- `);
2869
- return;
2870
- }
2871
- const columns2 = [
2872
- { key: "pid", label: "PID", align: "right", format: (v) => pc5.dim(String(v).padStart(6)) },
2873
- { key: "name", label: "Name", format: (v) => pc5.bold(String(v)) },
2874
- { key: "cpu", label: "CPU%", align: "right", format: (v) => {
2875
- const n = v;
2876
- return n > 10 ? pc5.red(String(n)) : n > 5 ? pc5.yellow(String(n)) : pc5.dim(String(n));
2877
- } },
2878
- { key: "mem", label: "MEM%", align: "right", format: (v) => {
2879
- const n = v;
2880
- return n > 10 ? pc5.red(String(n)) : n > 5 ? pc5.yellow(String(n)) : pc5.dim(String(n));
2881
- } },
2882
- { key: "state", label: "State", format: (v) => {
2883
- const s = String(v);
2884
- if (s === "R" || s === "Running") return pc5.green(s);
2885
- if (s === "Z" || s === "Zombie") return pc5.red(s);
2886
- if (s === "S" || s === "Sleeping") return pc5.cyan(s);
2887
- return pc5.dim(s);
2888
- } }
2889
- ];
2890
- const rows2 = processes.map((p) => ({ pid: p.pid, name: p.name, cpu: p.cpuPercent, mem: p.memPercent, state: formatProcessState(p.state) }));
2891
- console.error(`
2892
- ${symbols.fox} ${pc5.bold("System Processes")} ${pc5.dim(`(top ${processes.length} by ${sortBy})`)}
2893
- `);
2894
- console.error(renderTable(columns2, rows2));
2895
- console.error();
2896
- } catch (error) {
2897
- spinner.fail("Failed to scan processes");
2898
- console.error(renderError("Process scan failed", String(error)));
2899
- }
2900
- return;
2901
- }
2902
- const tracked = readTracked();
2903
- if (tracked.length === 0) {
2904
- console.error(`
2905
- ${pc5.dim("No tracked processes.")}`);
2906
- console.error(` ${pc5.dim("Start an app with:")} ${pc5.cyan("fennec start <command> --name <name>")}
2907
- `);
2908
- return;
2909
- }
2910
- const columns = [
2911
- { key: "name", label: "App", format: (v) => pc5.bold(String(v)) },
2912
- { key: "pid", label: "PID", align: "right" },
2913
- { key: "status", label: "Status", format: (v) => {
2914
- const s = v;
2915
- return s === "running" ? pc5.green("\u25CF running") : pc5.red("\u25CB stopped");
2916
- } },
2917
- { key: "port", label: "Port", format: (v) => {
2918
- const p = v;
2919
- return p ? pc5.yellow(`:${p}`) : pc5.dim("-");
2920
- } },
2921
- { key: "command", label: "Command", format: (v) => {
2922
- const c = String(v);
2923
- return c.length > 50 ? c.slice(0, 50) + "\u2026" : c;
2924
- } },
2925
- { key: "uptime", label: "Uptime", format: (v) => pc5.dim(String(v)) }
2926
- ];
2927
- const rows = tracked.map((t) => {
2928
- const running = isProcessRunning(t.pid);
2929
- const uptime = running ? formatUptime(Math.floor((Date.now() - new Date(t.startedAt).getTime()) / 1e3)) : "-";
2930
- return { name: t.name, pid: running ? String(t.pid) : pc5.dim(String(t.pid)), status: running ? "running" : "stopped", port: t.port ?? null, command: t.command, uptime };
2931
- });
2932
- const runningCount = tracked.filter((t) => isProcessRunning(t.pid)).length;
2933
- console.error(`
2934
- ${symbols.fox} ${pc5.bold("Fennec Apps")} ${pc5.dim(`(${runningCount}/${tracked.length} running)`)}
2935
- `);
2936
- console.error(renderTable(columns, rows));
2937
- console.error(` ${pc5.dim("Use")} ${pc5.cyan("fennec start <command> --name <name> --port <port>")} ${pc5.dim("to add more apps.")}`);
2938
- console.error(` ${pc5.dim("Use")} ${pc5.cyan("fennec log <name>")} ${pc5.dim("to view logs.")}`);
2939
- console.error(` ${pc5.dim("Use")} ${pc5.cyan("fennec kill <name>")} ${pc5.dim("to stop an app.")}`);
2940
- console.error();
2941
- }
2942
- async function statusCommand(_args) {
2943
- const watchFlag = _args.includes("-w") || _args.includes("--watch");
2944
- const tracked = readTracked();
2945
- const topSystem = getSystemProcesses({ userOnly: true, sortBy: "cpu", limit: 5 });
2946
- const totalUserProcs = getSystemProcesses({ userOnly: true }).length;
2947
- console.error(`
2948
- ${symbols.fox} ${pc5.bold("Fennec Status")}
2949
- `);
2950
- if (tracked.length > 0) {
2951
- const runningCount = tracked.filter((t) => isProcessRunning(t.pid)).length;
2952
- console.error(` ${pc5.bold("Managed Apps")} ${pc5.dim(`(${runningCount}/${tracked.length} running)`)}
2953
- `);
2954
- for (const t of tracked) {
2955
- const running = isProcessRunning(t.pid);
2956
- const statusIcon = running ? pc5.green("\u25CF") : pc5.red("\u25CB");
2957
- const portStr = t.port ? ` ${pc5.yellow(`:${t.port}`)}` : "";
2958
- const uptime = running ? pc5.dim(formatUptime(Math.floor((Date.now() - new Date(t.startedAt).getTime()) / 1e3))) : pc5.red("stopped");
2959
- console.error(` ${statusIcon} ${pc5.bold(t.name)}${portStr} ${pc5.dim(`(PID ${t.pid})`)} \u2014 ${uptime}`);
2960
- }
2961
- console.error();
2962
- } else {
2963
- console.error(` ${pc5.dim("No managed apps.")} ${pc5.cyan("fennec start <command> --name <name>")}
2964
- `);
2965
- }
2966
- console.error(` ${pc5.bold("System")} ${pc5.dim(`(${totalUserProcs} user processes)`)}`);
2967
- for (const p of topSystem) {
2968
- const cpuStr = p.cpuPercent > 10 ? pc5.red(`${p.cpuPercent}%`) : p.cpuPercent > 5 ? pc5.yellow(`${p.cpuPercent}%`) : pc5.dim(`${p.cpuPercent}%`);
2969
- const memStr = p.memPercent > 10 ? pc5.red(`${p.memPercent}%`) : p.memPercent > 5 ? pc5.yellow(`${p.memPercent}%`) : pc5.dim(`${p.memPercent}%`);
2970
- console.error(` ${pc5.dim(`PID ${p.pid}`)} ${pc5.bold(p.name)} \u2014 CPU: ${cpuStr} MEM: ${memStr}`);
2971
- }
2972
- if (watchFlag) await watchSystemProcesses("cpu", 15);
2973
- console.error();
2974
- }
2975
- async function watchSystemProcesses(sortBy, limit) {
2976
- console.error(`
2977
- ${pc5.bold("Watching system processes")} ${pc5.dim("(Ctrl+C to stop, refreshes every 3s)")}
2978
- `);
2979
- const render = () => {
2980
- const processes = getSystemProcesses({ userOnly: true, sortBy, limit });
2981
- const columns = [
2982
- { key: "pid", label: "PID", align: "right", format: (v) => pc5.dim(String(v).padStart(6)) },
2983
- { key: "name", label: "Name", format: (v) => pc5.bold(String(v)) },
2984
- { key: "cpu", label: "CPU%", align: "right", format: (v) => {
2985
- const n = v;
2986
- return n > 10 ? pc5.red(String(n)) : n > 5 ? pc5.yellow(String(n)) : pc5.dim(String(n));
2987
- } },
2988
- { key: "mem", label: "MEM%", align: "right", format: (v) => {
2989
- const n = v;
2990
- return n > 10 ? pc5.red(String(n)) : n > 5 ? pc5.yellow(String(n)) : pc5.dim(String(n));
2991
- } },
2992
- { key: "state", label: "S", align: "center" }
2993
- ];
2994
- const rows = processes.map((p) => ({ pid: p.pid, name: p.name, cpu: p.cpuPercent, mem: p.memPercent, state: p.state }));
2995
- return ` ${timestamp()} ${pc5.dim(`${processes.length} processes`)}
2996
- ${renderTable(columns, rows, { compact: true })}`;
2997
- };
2998
- console.error(render());
2999
- const interval = setInterval(() => {
3000
- process.stdout.write("\x1B[J");
3001
- console.error(render());
3002
- }, 3e3);
3003
- await new Promise((resolve8) => {
3004
- process.once("SIGINT", () => {
3005
- clearInterval(interval);
3006
- process.stdout.write("\x1B[J");
3007
- resolve8();
3008
- });
3009
- });
3010
- }
3011
-
3012
2988
  // src/commands/kill.ts
3013
2989
  import pc6 from "picocolors";
3014
2990
  async function killCommand(args2) {
@@ -3035,25 +3011,32 @@ async function killCommand(args2) {
3035
3011
  targetPid = pid;
3036
3012
  displayName = `PID ${pid}`;
3037
3013
  } else {
3038
- const matches = getSystemProcesses({ name: rawTarget, userOnly: true, sortBy: "cpu" });
3039
- if (matches.length === 0) {
3040
- console.error(renderError("Process not found", `No running process matching "${rawTarget}"`));
3041
- process.exit(1);
3042
- }
3043
- if (matches.length > 1) {
3044
- console.error(`
3014
+ const tracked = readTracked();
3015
+ const trackedMatch = tracked.find((t) => t.name === rawTarget);
3016
+ if (trackedMatch && isProcessRunning(trackedMatch.pid)) {
3017
+ targetPid = trackedMatch.pid;
3018
+ displayName = `${trackedMatch.name} (PID ${targetPid})`;
3019
+ } else {
3020
+ const matches = getSystemProcesses({ name: rawTarget, userOnly: true, sortBy: "cpu" });
3021
+ if (matches.length === 0) {
3022
+ console.error(renderError("Process not found", `No running process matching "${rawTarget}"`));
3023
+ process.exit(1);
3024
+ }
3025
+ if (matches.length > 1) {
3026
+ console.error(`
3045
3027
  ${pc6.bold(`Multiple processes match "${rawTarget}":`)}`);
3046
- const selected = await selectPrompt("Select which to kill:", matches.map((p, i) => ({ value: String(i), label: `${p.name} (PID ${p.pid})`, description: `${p.command.slice(0, 80)} \u2014 CPU: ${p.cpuPercent}% MEM: ${p.memPercent}%` })));
3047
- if (selected === null) {
3048
- console.error(` ${pc6.dim("Cancelled")}`);
3049
- return;
3028
+ const selected = await selectPrompt("Select which to kill:", matches.map((p, i) => ({ value: String(i), label: `${p.name} (PID ${p.pid})`, description: `${p.command.slice(0, 80)} \u2014 CPU: ${p.cpuPercent}% MEM: ${p.memPercent}%` })));
3029
+ if (selected === null) {
3030
+ console.error(` ${pc6.dim("Cancelled")}`);
3031
+ return;
3032
+ }
3033
+ const idx = parseInt(selected, 10);
3034
+ targetPid = matches[idx].pid;
3035
+ displayName = `${matches[idx].name} (PID ${targetPid})`;
3036
+ } else {
3037
+ targetPid = matches[0].pid;
3038
+ displayName = `${matches[0].name} (PID ${targetPid})`;
3050
3039
  }
3051
- const idx = parseInt(selected, 10);
3052
- targetPid = matches[idx].pid;
3053
- displayName = `${matches[idx].name} (PID ${targetPid})`;
3054
- } else {
3055
- targetPid = matches[0].pid;
3056
- displayName = `${matches[0].name} (PID ${targetPid})`;
3057
3040
  }
3058
3041
  }
3059
3042
  const confirmed = await confirmPrompt(`Kill ${pc6.bold(displayName)} with ${pc6.yellow(signal)}?`, false);
@@ -3206,7 +3189,7 @@ async function restartCommand(args2) {
3206
3189
  }
3207
3190
 
3208
3191
  // src/commands/log.ts
3209
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
3192
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
3210
3193
  import { resolve as resolve5 } from "path";
3211
3194
  import { homedir as homedir4 } from "os";
3212
3195
  import { spawn as spawn3, execSync as execSync2 } from "child_process";
@@ -3238,7 +3221,7 @@ async function logCommand(args2) {
3238
3221
  try {
3239
3222
  let logLines = [];
3240
3223
  if (logFilePath && existsSync4(logFilePath)) {
3241
- const content = readFileSync4(logFilePath, "utf-8");
3224
+ const content = readFileSync3(logFilePath, "utf-8");
3242
3225
  logLines = content.split("\n").filter(Boolean);
3243
3226
  } else {
3244
3227
  const pid = trackedMatch?.pid ?? parseInt(target, 10);
@@ -3271,8 +3254,12 @@ async function logCommand(args2) {
3271
3254
  ${pc8.dim("Following... (Ctrl+C to stop)")}
3272
3255
  `);
3273
3256
  const tail = spawn3("tail", ["-n", "0", "-f", logFilePath], { stdio: "inherit" });
3274
- tail.on("exit", () => process.exit(0));
3275
- await new Promise(() => {
3257
+ await new Promise((resolve8) => {
3258
+ tail.on("exit", () => resolve8());
3259
+ process.once("SIGINT", () => {
3260
+ tail.kill("SIGTERM");
3261
+ resolve8();
3262
+ });
3276
3263
  });
3277
3264
  }
3278
3265
  console.error();
@@ -3283,6 +3270,7 @@ async function logCommand(args2) {
3283
3270
  }
3284
3271
 
3285
3272
  // src/commands/attach.ts
3273
+ import { PortDetector as PortDetector3 } from "@plumpslabs/fennec-core";
3286
3274
  import pc9 from "picocolors";
3287
3275
  async function attachCommand(args2) {
3288
3276
  const port = parseInt(args2[0], 10);
@@ -3295,8 +3283,7 @@ async function attachCommand(args2) {
3295
3283
  const name = rawName ?? `port-${port}`;
3296
3284
  const spinner = createSpinner(`Attaching to :${port}...`);
3297
3285
  try {
3298
- const { PortDetector } = await import("@plumpslabs/fennec-core");
3299
- const detector = new PortDetector();
3286
+ const detector = new PortDetector3();
3300
3287
  const info = detector.detectByPort(port);
3301
3288
  if (info) {
3302
3289
  spinner.succeed(`Attached to :${port}`);