@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 +134 -8
- package/dist/index.js +76 -28
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
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
|
|
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 {
|
|
7
|
-
import { FennecServer, SessionStore } from "@plumpslabs/fennec-core";
|
|
6
|
+
import { createInterface } from "readline";
|
|
8
7
|
import { execSync } from "child_process";
|
|
9
|
-
import {
|
|
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
|
|
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
|
-
|
|
217
|
-
const mcpClient = await
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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
|
-
|
|
289
|
+
console.log("Setup complete! Run 'fennec start' to begin.\n");
|
|
243
290
|
}
|
|
244
291
|
async function installBrowsersCommand() {
|
|
245
|
-
|
|
246
|
-
const s =
|
|
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
|
-
|
|
304
|
+
console.log("\nBrowser installation complete.\n");
|
|
259
305
|
}
|
|
260
306
|
async function initCommand() {
|
|
261
|
-
|
|
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
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
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.
|
|
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
|
-
"@
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
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": "
|
|
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",
|