@plumpslabs/fennec-cli 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,13 +7,14 @@
7
7
  <p align="center">
8
8
  <strong>Ears everywhere in your stack.</strong>
9
9
  <br />
10
- <em>AI-native developer observability — browser, terminal, and processes, all in one MCP server.</em>
10
+ <em>AI-native developer observability MCP server — browser, terminal, and processes, all in one.</em>
11
11
  </p>
12
12
 
13
13
  <p align="center">
14
14
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
15
15
  <a href="https://www.npmjs.com/package/@plumpslabs/fennec-cli"><img src="https://img.shields.io/npm/v/@plumpslabs/fennec-cli" alt="npm version" /></a>
16
16
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node.js-20%2B-339933?logo=node.js" alt="Node.js" /></a>
17
+ <a href="https://chromium.org"><img src="https://img.shields.io/badge/Browser-Chromium%20%7C%20Firefox%20%7C%20WebKit-blue?logo=googlechrome" alt="Cross-browser" /></a>
17
18
  </p>
18
19
 
19
20
  ---
@@ -22,24 +23,44 @@
22
23
 
23
24
  **Fennec** is an MCP (Model Context Protocol) server that bridges the gap between AI agents and your development environment. It gives your AI full-stack visibility:
24
25
 
25
- - 🔍 **Observe** browser console logs, network requests, and performance metrics
26
- - 🖥️ **Watch** terminal output and server logs
27
- - 🎮 **Control** browser sessions — navigate, click, type, screenshot
26
+ - 🔍 **Observe** browser console logs, network requests, and performance metrics in real-time
27
+ - 🖥️ **Watch** terminal output and server logs without changing your workflow
28
+ - 🎮 **Control** browser sessions — navigate, click, type, screenshot, annotate, diff
28
29
  - 🔐 **Persist** authentication sessions across conversations
29
30
  - 🔗 **Correlate** events across layers to identify root causes automatically
31
+ - 🌐 **Cross-browser** support: Chromium, Firefox, WebKit
30
32
 
31
33
  ## Installation
32
34
 
35
+ ### Global Install (Recommended)
36
+
33
37
  ```bash
34
38
  npm install -g @plumpslabs/fennec-cli
35
39
  ```
36
40
 
37
- Then install browser engines:
41
+ Then (optional) install browser engines if you need browser automation:
38
42
 
39
43
  ```bash
40
44
  fennec install-browsers
41
45
  ```
42
46
 
47
+ > **Note:** Playwright is an **optional peer dependency**. Fennec works for terminal/process monitoring without it. Only install browser engines if you need browser automation features.
48
+
49
+ ### From Source
50
+
51
+ ```bash
52
+ git clone https://github.com/plumpslabs/fennec.git
53
+ cd fennec
54
+ pnpm install
55
+ pnpm build
56
+ ```
57
+
58
+ Browser engines (optional for source install):
59
+ ```bash
60
+ pnpm add playwright
61
+ npx playwright install chromium
62
+ ```
63
+
43
64
  ## Quick Start
44
65
 
45
66
  ### 1. Start the MCP server
@@ -63,11 +84,22 @@ Add this to your MCP client's config file:
63
84
  }
64
85
  ```
65
86
 
87
+ ### 3. Pipe your server output (optional)
88
+
89
+ ```bash
90
+ npm run dev 2>&1 | fennec pipe --name "dev-server"
91
+ ```
92
+
93
+ ### 4. Ask your AI to diagnose issues
94
+
95
+ > *"Why is my app broken?"* — AI uses Fennec to check browser console, network, and server logs simultaneously.
96
+
66
97
  ## CLI Commands
67
98
 
68
99
  | Command | Description |
69
- |---|---|
70
- | `fennec start` | Start the Fennec MCP server |
100
+ |---------|-------------|
101
+ | `fennec start` | Start the Fennec MCP server (stdio transport) |
102
+ | `fennec start --transport sse` | Start with SSE transport (experimental) |
71
103
  | `fennec pipe --name <name>` | Pipe process output to Fennec for log watching |
72
104
  | `fennec attach-pid <pid>` | Attach to a running process by PID |
73
105
  | `fennec attach-port <port>` | Discover and attach to a process by port |
@@ -78,12 +110,106 @@ Add this to your MCP client's config file:
78
110
  | `fennec install-browsers` | Install Playwright browser engines |
79
111
  | `fennec help` | Show help information |
80
112
 
113
+ ## Usage Examples
114
+
115
+ ### Pipe Mode (Recommended for Observing Server Logs)
116
+
117
+ ```bash
118
+ # Start your dev server with Fennec watching
119
+ npm run dev 2>&1 | fennec pipe --name "my-app"
120
+ ```
121
+
122
+ ### Attach by PID
123
+
124
+ ```bash
125
+ # Find your process PID
126
+ ps aux | grep "node"
127
+
128
+ # Attach Fennec to it
129
+ fennec attach-pid 12345 --name "my-app"
130
+ ```
131
+
132
+ ### Attach by Port
133
+
134
+ ```bash
135
+ # Auto-detect process listening on port 3000
136
+ fennec attach-port 3000 --name "my-app"
137
+ ```
138
+
139
+ ### Multiple Sessions for Parallel Testing
140
+
141
+ ```bash
142
+ # Terminal 1: Run admin user session
143
+ npm run dev 2>&1 | fennec pipe --name "admin-test"
144
+
145
+ # Terminal 2: Run regular user session (same Fennec server)
146
+ fennec start # In another MCP client config
147
+ ```
148
+
149
+ ## Configuration
150
+
151
+ Fennec works with zero config, but supports customization:
152
+
153
+ ```bash
154
+ fennec init # Creates fennec.config.yaml
155
+ ```
156
+
157
+ Key configuration options (see [full reference](docs/configuration.md)):
158
+
159
+ ```yaml
160
+ browser:
161
+ type: chromium # chromium, firefox, or webkit
162
+ headless: true
163
+ viewport:
164
+ width: 1280
165
+ height: 720
166
+
167
+ security:
168
+ sandbox: true
169
+ allowProcessSpawn: true
170
+ allowJSEvaluation: true
171
+
172
+ correlation:
173
+ windowMs: 500
174
+ enableRootCauseInference: true
175
+ minConfidence: 0.7
176
+ ```
177
+
178
+ Environment variables also supported: `FENNEC_BROWSER_TYPE`, `FENNEC_HEADLESS`, `FENNEC_PORT`, etc.
179
+
180
+ ## Cross-Browser Support
181
+
182
+ Fennec supports all three major browser engines via Playwright:
183
+
184
+ ```bash
185
+ # Set via config
186
+ fennec init # then edit browser.type
187
+
188
+ # Or via env var
189
+ FENNEC_BROWSER_TYPE=firefox fennec start
190
+ FENNEC_BROWSER_TYPE=webkit fennec start
191
+ ```
192
+
193
+ ## Security
194
+
195
+ Fennec ships with **sandbox mode enabled by default**. Key security features:
196
+
197
+ - 🔒 Process spawn allowlist (only npm, node, pnpm, etc. allowed by default)
198
+ - 🔒 Domain allowlist/blocklist for browser navigation
199
+ - 🔒 Per-tool permissions (eval, kill, spawn)
200
+ - 🔒 Audit logging of all tool calls
201
+ - 🔒 Session data export path confinement
202
+
203
+ See [Security Model](docs/security-model.md) for details.
204
+
81
205
  ## Documentation
82
206
 
83
207
  - [Getting Started Guide](https://github.com/plumpslabs/fennec/blob/main/docs/getting-started.md)
84
- - [Full Tool Reference](https://github.com/plumpslabs/fennec/blob/main/docs/tools/README.md)
208
+ - [Full Tool Reference](https://github.com/plumpslabs/fennec/blob/main/docs/tools/README.md) — 123 tools across 16 categories (including Mobile)
85
209
  - [Configuration Reference](https://github.com/plumpslabs/fennec/blob/main/docs/configuration.md)
86
210
  - [Security Model](https://github.com/plumpslabs/fennec/blob/main/docs/security-model.md)
211
+ - [Auth Flows Guide](https://github.com/plumpslabs/fennec/blob/main/docs/guides/auth-flows.md)
212
+ - [Full-Stack Debugging Guide](https://github.com/plumpslabs/fennec/blob/main/docs/guides/fullstack-debugging.md)
87
213
  - [GitHub Repository](https://github.com/plumpslabs/fennec)
88
214
 
89
215
  ## License
package/dist/index.js CHANGED
@@ -1,18 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { existsSync as existsSync2 } from "fs";
4
+ import { existsSync as existsSync2, writeFileSync } from "fs";
5
5
  import { resolve as resolve2 } from "path";
6
- import { intro, outro, spinner, confirm, select, isCancel } from "@clack/prompts";
7
- import { FennecServer, SessionStore } from "@plumpslabs/fennec-core";
6
+ import { createInterface } from "readline";
8
7
  import { execSync } from "child_process";
9
- import { writeFileSync } from "fs";
8
+ import { FennecServer, SessionStore } from "@plumpslabs/fennec-core";
10
9
 
11
10
  // src/utils/banner.ts
12
11
  function printBanner() {
13
12
  console.error(`
14
13
  /\\ /\\
15
- ( o o ) fennec v0.1.0
14
+ ( o o ) fennec v1.9.0
16
15
  =( Y )= ears everywhere in your stack.
17
16
  ) (
18
17
  `);
@@ -162,6 +161,57 @@ async function watchCommand(args2) {
162
161
 
163
162
  // src/index.ts
164
163
  var [, , command, ...args] = process.argv;
164
+ function rlQuestion(query) {
165
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
166
+ return new Promise((resolve3) => {
167
+ rl.question(query, (answer) => {
168
+ rl.close();
169
+ resolve3(answer.trim());
170
+ });
171
+ });
172
+ }
173
+ async function confirmPrompt(message, defaultValue = false) {
174
+ const hint = defaultValue ? "Y/n" : "y/N";
175
+ const answer = await rlQuestion(`
176
+ ${message} (${hint}): `);
177
+ if (!answer) return defaultValue;
178
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
179
+ }
180
+ async function selectPrompt(message, options) {
181
+ console.log(`
182
+ ${message}
183
+ `);
184
+ options.forEach((opt, i) => {
185
+ console.log(` ${i + 1}) ${opt.label}`);
186
+ });
187
+ console.log(` 0) Cancel`);
188
+ const answer = await rlQuestion(`
189
+ Enter number (0-${options.length}): `);
190
+ const num = parseInt(answer, 10);
191
+ if (isNaN(num) || num === 0) return /* @__PURE__ */ Symbol("cancel");
192
+ const selected = options[num - 1];
193
+ if (!selected) {
194
+ console.log(" Invalid selection. Cancelled.");
195
+ return /* @__PURE__ */ Symbol("cancel");
196
+ }
197
+ return selected.value;
198
+ }
199
+ function simpleSpinner(text) {
200
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
201
+ let i = 0;
202
+ const interval = setInterval(() => {
203
+ process.stdout.write(`\r${frames[i]} ${text}`);
204
+ i = (i + 1) % frames.length;
205
+ }, 80);
206
+ return {
207
+ stop(message) {
208
+ clearInterval(interval);
209
+ process.stdout.write(`\r${message ? "\u2713" : " "} ${message ?? text}
210
+ `);
211
+ }
212
+ };
213
+ }
214
+ var isCancel = (v) => v === /* @__PURE__ */ Symbol("cancel");
165
215
  async function main() {
166
216
  if (!command || command === "start") {
167
217
  await startServer(args);
@@ -213,16 +263,13 @@ async function sessionsCommand() {
213
263
  Total: ${sessions.length} session(s)`);
214
264
  }
215
265
  async function setupCommand() {
216
- intro("Fennec Setup");
217
- const mcpClient = await select({
218
- message: "Which MCP client are you using?",
219
- options: [
220
- { value: "claude", label: "Claude Desktop" },
221
- { value: "other", label: "Other MCP client" }
222
- ]
223
- });
266
+ console.log("\n Fennec Setup\n");
267
+ const mcpClient = await selectPrompt("Which MCP client are you using?", [
268
+ { value: "claude", label: "Claude Desktop" },
269
+ { value: "other", label: "Other MCP client" }
270
+ ]);
224
271
  if (isCancel(mcpClient)) {
225
- outro("Setup cancelled");
272
+ console.log("Setup cancelled");
226
273
  return;
227
274
  }
228
275
  if (mcpClient === "claude") {
@@ -239,12 +286,11 @@ To configure Claude Desktop for Fennec, add to your config:
239
286
  }
240
287
  `);
241
288
  }
242
- outro("Setup complete! Run 'fennec start' to begin.");
289
+ console.log("Setup complete! Run 'fennec start' to begin.\n");
243
290
  }
244
291
  async function installBrowsersCommand() {
245
- intro("Installing browser engines");
246
- const s = spinner();
247
- s.start("Installing Chromium...");
292
+ console.log("\n Installing browser engines\n");
293
+ const s = simpleSpinner("Installing Chromium...");
248
294
  try {
249
295
  execSync("npx playwright install chromium", {
250
296
  stdio: "inherit",
@@ -255,17 +301,18 @@ async function installBrowsersCommand() {
255
301
  s.stop("Failed to install Chromium");
256
302
  console.error("Try running manually: npx playwright install chromium");
257
303
  }
258
- outro("Browser installation complete.");
304
+ console.log("\nBrowser installation complete.\n");
259
305
  }
260
306
  async function initCommand() {
261
- intro("Initialize Fennec Configuration");
307
+ console.log("\n Initialize Fennec Configuration\n");
262
308
  const configFile = resolve2("./fennec.config.yaml");
263
309
  if (existsSync2(configFile)) {
264
- const overwrite = await confirm({
265
- message: "fennec.config.yaml already exists. Overwrite?"
266
- });
267
- if (isCancel(overwrite) || !overwrite) {
268
- outro("Cancelled");
310
+ const overwrite = await confirmPrompt(
311
+ "fennec.config.yaml already exists. Overwrite?",
312
+ false
313
+ );
314
+ if (!overwrite) {
315
+ console.log("Cancelled");
269
316
  return;
270
317
  }
271
318
  }
@@ -283,7 +330,7 @@ browser:
283
330
  session:
284
331
  maxSessions: 10
285
332
  idleTimeoutSecs: 1800
286
- persistPath: "./.fennec/sessions"
333
+ persistPath: ".fennec/sessions"
287
334
 
288
335
  process:
289
336
  maxProcesses: 10
@@ -329,7 +376,7 @@ security:
329
376
  blockedDomains: []
330
377
  allowFileProtocol: false
331
378
  allowCDPRawAccess: false
332
- exportPath: "./.fennec/exports"
379
+ exportPath: ".fennec/exports"
333
380
  maxExportSizeMB: 10
334
381
 
335
382
  transport:
@@ -343,7 +390,8 @@ logging:
343
390
  file: null
344
391
  `;
345
392
  writeFileSync(configFile, config, "utf-8");
346
- outro(`Configuration written to ${configFile}`);
393
+ console.log(`Configuration written to ${configFile}
394
+ `);
347
395
  }
348
396
  main().catch((error) => {
349
397
  console.error("Fatal error:", error);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils/banner.ts","../src/utils/help.ts","../src/commands/pipe.ts","../src/commands/attach-pid.ts","../src/commands/attach-port.ts","../src/commands/watch.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { intro, outro, spinner, confirm, select, isCancel } from \"@clack/prompts\";\nimport { FennecServer, SessionStore } from \"@plumpslabs/fennec-core\";\nimport { execSync } from \"node:child_process\";\nimport { writeFileSync } from \"node:fs\";\nimport { printBanner } from \"./utils/banner.js\";\nimport { showHelp } from \"./utils/help.js\";\nimport { pipeCommand } from \"./commands/pipe.js\";\nimport { attachPidCommand } from \"./commands/attach-pid.js\";\nimport { attachPortCommand } from \"./commands/attach-port.js\";\nimport { watchCommand } from \"./commands/watch.js\";\n\nconst [, , command, ...args] = process.argv;\n\nasync function main(): Promise<void> {\n if (!command || command === \"start\") {\n await startServer(args);\n } else if (command === \"pipe\") {\n await pipeCommand(args);\n } else if (command === \"attach-pid\") {\n await attachPidCommand(args);\n } else if (command === \"attach-port\") {\n await attachPortCommand(args);\n } else if (command === \"watch\") {\n await watchCommand(args);\n } else if (command === \"sessions\") {\n await sessionsCommand();\n } else if (command === \"setup\") {\n await setupCommand();\n } else if (command === \"install-browsers\") {\n printBanner();\n await installBrowsersCommand();\n } else if (command === \"init\") {\n printBanner();\n await initCommand();\n } else if (command === \"help\" || command === \"--help\" || command === \"-h\") {\n printBanner();\n showHelp();\n } else {\n console.error(`Unknown command: ${command}`);\n console.error(\"Run 'fennec help' for usage information\");\n process.exit(1);\n }\n}\n\nasync function startServer(args: string[]): Promise<void> {\n const configIndex = args.indexOf(\"--config\");\n const configPath = configIndex !== -1 ? args[configIndex + 1] : undefined;\n const server = new FennecServer(configPath);\n await server.start();\n}\n\nasync function sessionsCommand(): Promise<void> {\n const store = new SessionStore(\"./.fennec/sessions\");\n const sessions = store.list();\n\n if (sessions.length === 0) {\n console.log(\"No saved sessions found.\");\n return;\n }\n\n console.log(\"Saved sessions:\");\n for (const s of sessions) {\n console.log(` ${s.name} at ${s.origin} (saved: ${new Date(s.savedAt).toLocaleString()})`);\n }\n console.log(`\\nTotal: ${sessions.length} session(s)`);\n}\n\nasync function setupCommand(): Promise<void> {\n intro(\"Fennec Setup\");\n\n const mcpClient = await select({\n message: \"Which MCP client are you using?\",\n options: [\n { value: \"claude\", label: \"Claude Desktop\" },\n { value: \"other\", label: \"Other MCP client\" },\n ],\n });\n\n if (isCancel(mcpClient)) {\n outro(\"Setup cancelled\");\n return;\n }\n\n if (mcpClient === \"claude\") {\n console.log(`\nTo configure Claude Desktop for Fennec, add to your config:\n\n{\n \"mcpServers\": {\n \"fennec\": {\n \"command\": \"fennec\",\n \"args\": [\"start\"]\n }\n }\n}\n `);\n }\n\n outro(\"Setup complete! Run 'fennec start' to begin.\");\n}\n\nasync function installBrowsersCommand(): Promise<void> {\n intro(\"Installing browser engines\");\n\n const s = spinner();\n s.start(\"Installing Chromium...\");\n\n try {\n execSync(\"npx playwright install chromium\", {\n stdio: \"inherit\",\n timeout: 120000,\n });\n s.stop(\"Chromium installed successfully\");\n } catch {\n s.stop(\"Failed to install Chromium\");\n console.error(\"Try running manually: npx playwright install chromium\");\n }\n\n outro(\"Browser installation complete.\");\n}\n\nasync function initCommand(): Promise<void> {\n intro(\"Initialize Fennec Configuration\");\n\n const configFile = resolve(\"./fennec.config.yaml\");\n\n if (existsSync(configFile)) {\n const overwrite = await confirm({\n message: \"fennec.config.yaml already exists. Overwrite?\",\n });\n\n if (isCancel(overwrite) || !overwrite) {\n outro(\"Cancelled\");\n return;\n }\n }\n\n const config = `# Fennec Configuration\n# Generated by 'fennec init'\n\nbrowser:\n type: chromium\n headless: true\n defaultTimeout: 30000\n viewport:\n width: 1280\n height: 720\n\nsession:\n maxSessions: 10\n idleTimeoutSecs: 1800\n persistPath: \"./.fennec/sessions\"\n\nprocess:\n maxProcesses: 10\n logBufferLines: 2000\n spawnAllowlist:\n - \"npm\"\n - \"node\"\n - \"pnpm\"\n - \"yarn\"\n - \"bun\"\n - \"python\"\n - \"python3\"\n\nterminal:\n logBufferLines: 2000\n watchDebounceMs: 50\n\nnetwork:\n bufferSize: 1000\n captureRequestBody: true\n captureResponseBody: true\n slowRequestThresholdMs: 1000\n\nconsole:\n bufferSize: 500\n levels:\n - log\n - info\n - warn\n - error\n - debug\n\ncorrelation:\n windowMs: 500\n enableRootCauseInference: true\n minConfidence: 0.7\n\nsecurity:\n sandbox: true\n allowProcessSpawn: true\n allowProcessKill: false\n allowedDomains: []\n blockedDomains: []\n allowFileProtocol: false\n allowCDPRawAccess: false\n exportPath: \"./.fennec/exports\"\n maxExportSizeMB: 10\n\ntransport:\n type: stdio\n port: 3333\n host: \"127.0.0.1\"\n\nlogging:\n level: info\n format: pretty\n file: null\n`;\n\n writeFileSync(configFile, config, \"utf-8\");\n outro(`Configuration written to ${configFile}`);\n}\n\nmain().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n});\n","export function printBanner(): void {\n console.error(`\n /\\\\ /\\\\\n ( o o ) fennec v0.1.0\n =( Y )= ears everywhere in your stack.\n ) (\n `);\n}\n","export function showHelp(): void {\n console.error(`\nUsage: fennec <command> [options]\n\nCommands:\n start Start the MCP server (default)\n --transport Transport type (stdio | sse) [default: stdio]\n --port SSE port [default: 3333]\n --config Path to config file\n\n pipe Pipe stdin to log watcher\n --name Watcher name (required)\n\n attach-pid Attach to a running process by PID\n <pid> Process ID (required)\n --name Name for the process\n\n attach-port Attach to a process by port\n <port> Port number (required)\n --name Name for the process\n\n watch Watch a log file\n --file File path (required)\n --name Watcher name\n\n sessions List saved auth sessions\n setup Configure MCP client (Claude Desktop)\n install-browsers Install Playwright browser engines\n init Generate fennec.config.yaml\n\n help Show this help message\n `);\n}\n","export async function pipeCommand(args: string[]): Promise<void> {\n const nameIndex = args.indexOf(\"--name\");\n const name = nameIndex !== -1 ? args[nameIndex + 1] : \"pipe\";\n\n if (!name) {\n console.error(\"Error: --name is required for pipe command\");\n process.exit(1);\n }\n\n const { PipeWatcher } = await import(\"@plumpslabs/fennec-core\");\n const watcher = new PipeWatcher();\n const { write } = watcher.createPipe(name);\n\n console.error(`Pipe watcher '${name}' active. Forwarding stdin...`);\n\n const onDrain = () => {\n process.stdin.resume();\n };\n process.stdout.on(\"drain\", onDrain);\n\n process.stdin.setEncoding(\"utf-8\");\n process.stdin.on(\"data\", (data: string) => {\n try {\n write(data);\n const canContinue = process.stdout.write(data);\n if (!canContinue) {\n process.stdin.pause();\n }\n } catch (error) {\n console.error(\"Pipe error:\", error);\n }\n });\n\n process.stdin.on(\"error\", (error) => {\n console.error(\"Pipe stdin error:\", error);\n });\n\n process.stdin.on(\"end\", () => {\n console.error(`Pipe watcher '${name}' ended.`);\n watcher.cleanup();\n });\n\n const shutdown = () => {\n watcher.cleanup();\n process.stdout.removeListener(\"drain\", onDrain);\n process.stdin.removeAllListeners();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","export async function attachPidCommand(args: string[]): Promise<void> {\n const pid = parseInt(args[0]!, 10);\n if (isNaN(pid)) {\n console.error(\"Error: valid PID is required\");\n process.exit(1);\n }\n const { PortDetector } = await import(\"@plumpslabs/fennec-core\");\n const detector = new PortDetector();\n const info = detector.detectByPid(pid);\n if (info) {\n console.log(`Attached to PID ${pid}${info.command ? ` (${info.command})` : \"\"}`);\n if (info.port) console.log(` Port: ${info.port}`);\n } else {\n console.error(`Could not find process with PID ${pid}`);\n process.exit(1);\n }\n}\n","export async function attachPortCommand(args: string[]): Promise<void> {\n const port = parseInt(args[0]!, 10);\n if (isNaN(port)) {\n console.error(\"Error: valid port number is required\");\n process.exit(1);\n }\n const { PortDetector } = await import(\"@plumpslabs/fennec-core\");\n const detector = new PortDetector();\n const info = detector.detectByPort(port);\n if (info) {\n console.log(`Found process on port ${port}: PID ${info.pid}${info.command ? ` (${info.command})` : \"\"}`);\n } else {\n console.error(`No process found listening on port ${port}`);\n process.exit(1);\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport async function watchCommand(args: string[]): Promise<void> {\n const fileIndex = args.indexOf(\"--file\");\n const filePath = fileIndex !== -1 ? args[fileIndex + 1] : undefined;\n if (!filePath) {\n console.error(\"Error: --file is required for watch command\");\n process.exit(1);\n }\n const nameIndex = args.indexOf(\"--name\");\n const name = nameIndex !== -1 ? args[nameIndex + 1] : undefined;\n const resolvedPath = resolve(filePath);\n if (!existsSync(resolvedPath)) {\n console.error(`Error: File not found: ${resolvedPath}`);\n process.exit(1);\n }\n const { LogWatcher } = await import(\"@plumpslabs/fennec-core\");\n const watcher = new LogWatcher();\n const watcherId = watcher.watchFile(resolvedPath, name);\n console.log(`Watching file: ${resolvedPath}`);\n console.log(`Watcher ID: ${watcherId}`);\n process.stdin.resume();\n}\n"],"mappings":"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","names":["existsSync","resolve","args","args","args","args","args","resolve","existsSync"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/banner.ts","../src/utils/help.ts","../src/commands/pipe.ts","../src/commands/attach-pid.ts","../src/commands/attach-port.ts","../src/commands/watch.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { execSync } from \"node:child_process\";\nimport { FennecServer, SessionStore } from \"@plumpslabs/fennec-core\";\nimport { printBanner } from \"./utils/banner.js\";\nimport { showHelp } from \"./utils/help.js\";\nimport { pipeCommand } from \"./commands/pipe.js\";\nimport { attachPidCommand } from \"./commands/attach-pid.js\";\nimport { attachPortCommand } from \"./commands/attach-port.js\";\nimport { watchCommand } from \"./commands/watch.js\";\n\nconst [, , command, ...args] = process.argv;\n\n// ─── Minimal readline-based prompt utilities ─────────────────────\n\nfunction rlQuestion(query: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(query, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nasync function confirmPrompt(message: string, defaultValue = false): Promise<boolean> {\n const hint = defaultValue ? \"Y/n\" : \"y/N\";\n const answer = await rlQuestion(`\\n${message} (${hint}): `);\n if (!answer) return defaultValue;\n return answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\";\n}\n\nasync function selectPrompt<T extends string>(\n message: string,\n options: { value: T; label: string }[],\n): Promise<T | symbol> {\n console.log(`\\n${message}\\n`);\n options.forEach((opt, i) => {\n console.log(` ${i + 1}) ${opt.label}`);\n });\n console.log(` 0) Cancel`);\n const answer = await rlQuestion(`\\nEnter number (0-${options.length}): `);\n const num = parseInt(answer, 10);\n if (isNaN(num) || num === 0) return Symbol(\"cancel\");\n const selected = options[num - 1];\n if (!selected) {\n console.log(\" Invalid selection. Cancelled.\");\n return Symbol(\"cancel\");\n }\n return selected.value;\n}\n\nfunction simpleSpinner(text: string) {\n const frames = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n let i = 0;\n const interval = setInterval(() => {\n process.stdout.write(`\\r${frames[i]} ${text}`);\n i = (i + 1) % frames.length;\n }, 80);\n return {\n stop(message?: string) {\n clearInterval(interval);\n process.stdout.write(`\\r${message ? \"✓\" : \" \"} ${message ?? text}\\n`);\n },\n };\n}\n\nconst isCancel = (v: unknown): boolean => v === Symbol(\"cancel\");\n\n// ─── Main ────────────────────────────────────────────────────────\n\nasync function main(): Promise<void> {\n if (!command || command === \"start\") {\n await startServer(args);\n } else if (command === \"pipe\") {\n await pipeCommand(args);\n } else if (command === \"attach-pid\") {\n await attachPidCommand(args);\n } else if (command === \"attach-port\") {\n await attachPortCommand(args);\n } else if (command === \"watch\") {\n await watchCommand(args);\n } else if (command === \"sessions\") {\n await sessionsCommand();\n } else if (command === \"setup\") {\n await setupCommand();\n } else if (command === \"install-browsers\") {\n printBanner();\n await installBrowsersCommand();\n } else if (command === \"init\") {\n printBanner();\n await initCommand();\n } else if (command === \"help\" || command === \"--help\" || command === \"-h\") {\n printBanner();\n showHelp();\n } else {\n console.error(`Unknown command: ${command}`);\n console.error(\"Run 'fennec help' for usage information\");\n process.exit(1);\n }\n}\n\nasync function startServer(args: string[]): Promise<void> {\n const configIndex = args.indexOf(\"--config\");\n const configPath = configIndex !== -1 ? args[configIndex + 1] : undefined;\n const server = new FennecServer(configPath);\n await server.start();\n}\n\nasync function sessionsCommand(): Promise<void> {\n const store = new SessionStore(\"./.fennec/sessions\");\n const sessions = store.list();\n\n if (sessions.length === 0) {\n console.log(\"No saved sessions found.\");\n return;\n }\n\n console.log(\"Saved sessions:\");\n for (const s of sessions) {\n console.log(` ${s.name} at ${s.origin} (saved: ${new Date(s.savedAt).toLocaleString()})`);\n }\n console.log(`\\nTotal: ${sessions.length} session(s)`);\n}\n\nasync function setupCommand(): Promise<void> {\n console.log(\"\\n Fennec Setup\\n\");\n\n const mcpClient = await selectPrompt(\"Which MCP client are you using?\", [\n { value: \"claude\", label: \"Claude Desktop\" },\n { value: \"other\", label: \"Other MCP client\" },\n ]);\n\n if (isCancel(mcpClient)) {\n console.log(\"Setup cancelled\");\n return;\n }\n\n if (mcpClient === \"claude\") {\n console.log(`\nTo configure Claude Desktop for Fennec, add to your config:\n\n{\n \"mcpServers\": {\n \"fennec\": {\n \"command\": \"fennec\",\n \"args\": [\"start\"]\n }\n }\n}\n `);\n }\n\n console.log(\"Setup complete! Run 'fennec start' to begin.\\n\");\n}\n\nasync function installBrowsersCommand(): Promise<void> {\n console.log(\"\\n Installing browser engines\\n\");\n\n const s = simpleSpinner(\"Installing Chromium...\");\n\n try {\n execSync(\"npx playwright install chromium\", {\n stdio: \"inherit\",\n timeout: 120000,\n });\n s.stop(\"Chromium installed successfully\");\n } catch {\n s.stop(\"Failed to install Chromium\");\n console.error(\"Try running manually: npx playwright install chromium\");\n }\n\n console.log(\"\\nBrowser installation complete.\\n\");\n}\n\nasync function initCommand(): Promise<void> {\n console.log(\"\\n Initialize Fennec Configuration\\n\");\n\n const configFile = resolve(\"./fennec.config.yaml\");\n\n if (existsSync(configFile)) {\n const overwrite = await confirmPrompt(\n \"fennec.config.yaml already exists. Overwrite?\",\n false,\n );\n\n if (!overwrite) {\n console.log(\"Cancelled\");\n return;\n }\n }\n\n const config = `# Fennec Configuration\n# Generated by 'fennec init'\n\nbrowser:\n type: chromium\n headless: true\n defaultTimeout: 30000\n viewport:\n width: 1280\n height: 720\n\nsession:\n maxSessions: 10\n idleTimeoutSecs: 1800\n persistPath: \".fennec/sessions\"\n\nprocess:\n maxProcesses: 10\n logBufferLines: 2000\n spawnAllowlist:\n - \"npm\"\n - \"node\"\n - \"pnpm\"\n - \"yarn\"\n - \"bun\"\n - \"python\"\n - \"python3\"\n\nterminal:\n logBufferLines: 2000\n watchDebounceMs: 50\n\nnetwork:\n bufferSize: 1000\n captureRequestBody: true\n captureResponseBody: true\n slowRequestThresholdMs: 1000\n\nconsole:\n bufferSize: 500\n levels:\n - log\n - info\n - warn\n - error\n - debug\n\ncorrelation:\n windowMs: 500\n enableRootCauseInference: true\n minConfidence: 0.7\n\nsecurity:\n sandbox: true\n allowProcessSpawn: true\n allowProcessKill: false\n allowedDomains: []\n blockedDomains: []\n allowFileProtocol: false\n allowCDPRawAccess: false\n exportPath: \".fennec/exports\"\n maxExportSizeMB: 10\n\ntransport:\n type: stdio\n port: 3333\n host: \"127.0.0.1\"\n\nlogging:\n level: info\n format: pretty\n file: null\n`;\n\n writeFileSync(configFile, config, \"utf-8\");\n console.log(`Configuration written to ${configFile}\\n`);\n}\n\nmain().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n});\n","export function printBanner(): void {\n console.error(`\n /\\\\ /\\\\\n ( o o ) fennec v1.9.0\n =( Y )= ears everywhere in your stack.\n ) (\n `);\n}\n","export function showHelp(): void {\n console.error(`\nUsage: fennec <command> [options]\n\nCommands:\n start Start the MCP server (default)\n --transport Transport type (stdio | sse) [default: stdio]\n --port SSE port [default: 3333]\n --config Path to config file\n\n pipe Pipe stdin to log watcher\n --name Watcher name (required)\n\n attach-pid Attach to a running process by PID\n <pid> Process ID (required)\n --name Name for the process\n\n attach-port Attach to a process by port\n <port> Port number (required)\n --name Name for the process\n\n watch Watch a log file\n --file File path (required)\n --name Watcher name\n\n sessions List saved auth sessions\n setup Configure MCP client (Claude Desktop)\n install-browsers Install Playwright browser engines\n init Generate fennec.config.yaml\n\n help Show this help message\n `);\n}\n","export async function pipeCommand(args: string[]): Promise<void> {\n const nameIndex = args.indexOf(\"--name\");\n const name = nameIndex !== -1 ? args[nameIndex + 1] : \"pipe\";\n\n if (!name) {\n console.error(\"Error: --name is required for pipe command\");\n process.exit(1);\n }\n\n const { PipeWatcher } = await import(\"@plumpslabs/fennec-core\");\n const watcher = new PipeWatcher();\n const { write } = watcher.createPipe(name);\n\n console.error(`Pipe watcher '${name}' active. Forwarding stdin...`);\n\n const onDrain = () => {\n process.stdin.resume();\n };\n process.stdout.on(\"drain\", onDrain);\n\n process.stdin.setEncoding(\"utf-8\");\n process.stdin.on(\"data\", (data: string) => {\n try {\n write(data);\n const canContinue = process.stdout.write(data);\n if (!canContinue) {\n process.stdin.pause();\n }\n } catch (error) {\n console.error(\"Pipe error:\", error);\n }\n });\n\n process.stdin.on(\"error\", (error) => {\n console.error(\"Pipe stdin error:\", error);\n });\n\n process.stdin.on(\"end\", () => {\n console.error(`Pipe watcher '${name}' ended.`);\n watcher.cleanup();\n });\n\n const shutdown = () => {\n watcher.cleanup();\n process.stdout.removeListener(\"drain\", onDrain);\n process.stdin.removeAllListeners();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n","export async function attachPidCommand(args: string[]): Promise<void> {\n const pid = parseInt(args[0]!, 10);\n if (isNaN(pid)) {\n console.error(\"Error: valid PID is required\");\n process.exit(1);\n }\n const { PortDetector } = await import(\"@plumpslabs/fennec-core\");\n const detector = new PortDetector();\n const info = detector.detectByPid(pid);\n if (info) {\n console.log(`Attached to PID ${pid}${info.command ? ` (${info.command})` : \"\"}`);\n if (info.port) console.log(` Port: ${info.port}`);\n } else {\n console.error(`Could not find process with PID ${pid}`);\n process.exit(1);\n }\n}\n","export async function attachPortCommand(args: string[]): Promise<void> {\n const port = parseInt(args[0]!, 10);\n if (isNaN(port)) {\n console.error(\"Error: valid port number is required\");\n process.exit(1);\n }\n const { PortDetector } = await import(\"@plumpslabs/fennec-core\");\n const detector = new PortDetector();\n const info = detector.detectByPort(port);\n if (info) {\n console.log(`Found process on port ${port}: PID ${info.pid}${info.command ? ` (${info.command})` : \"\"}`);\n } else {\n console.error(`No process found listening on port ${port}`);\n process.exit(1);\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport async function watchCommand(args: string[]): Promise<void> {\n const fileIndex = args.indexOf(\"--file\");\n const filePath = fileIndex !== -1 ? args[fileIndex + 1] : undefined;\n if (!filePath) {\n console.error(\"Error: --file is required for watch command\");\n process.exit(1);\n }\n const nameIndex = args.indexOf(\"--name\");\n const name = nameIndex !== -1 ? args[nameIndex + 1] : undefined;\n const resolvedPath = resolve(filePath);\n if (!existsSync(resolvedPath)) {\n console.error(`Error: File not found: ${resolvedPath}`);\n process.exit(1);\n }\n const { LogWatcher } = await import(\"@plumpslabs/fennec-core\");\n const watcher = new LogWatcher();\n const watcherId = watcher.watchFile(resolvedPath, name);\n console.log(`Watching file: ${resolvedPath}`);\n console.log(`Watcher ID: ${watcherId}`);\n process.stdin.resume();\n}\n"],"mappings":"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","names":["existsSync","resolve","args","args","args","args","resolve","args","existsSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumpslabs/fennec-cli",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Fennec CLI — pipe, attach, watch, and start the Fennec MCP server",
5
5
  "keywords": [
6
6
  "mcp",
@@ -38,10 +38,15 @@
38
38
  "LICENSE"
39
39
  ],
40
40
  "dependencies": {
41
- "@clack/prompts": "^0.7.0",
42
- "pino": "^8.19.0",
43
- "playwright": "^1.42.0",
44
- "@plumpslabs/fennec-core": "1.8.0"
41
+ "@plumpslabs/fennec-core": "1.10.0"
42
+ },
43
+ "peerDependencies": {
44
+ "playwright": "^1.42.0"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "playwright": {
48
+ "optional": true
49
+ }
45
50
  },
46
51
  "devDependencies": {
47
52
  "@types/node": "^20.11.0",
@@ -58,7 +63,7 @@
58
63
  "scripts": {
59
64
  "build": "tsup",
60
65
  "dev": "tsup --watch",
61
- "clean": "rm -rf dist",
66
+ "clean": "node -e \"const{rmSync,existsSync}=require('fs');const d='dist';existsSync(d)&&(console.log('removing',d),rmSync(d,{recursive:true,force:true}))\"",
62
67
  "typecheck": "tsc --noEmit",
63
68
  "lint": "echo 'lint: ok'",
64
69
  "test": "vitest run",