@plumpslabs/fennec-cli 1.10.0 โ 1.11.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 +44 -1
- package/dist/index.js +671 -103
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -94,6 +94,33 @@ npm run dev 2>&1 | fennec pipe --name "dev-server"
|
|
|
94
94
|
|
|
95
95
|
> *"Why is my app broken?"* โ AI uses Fennec to check browser console, network, and server logs simultaneously.
|
|
96
96
|
|
|
97
|
+
## Features
|
|
98
|
+
|
|
99
|
+
### ๐ง AI-Native API
|
|
100
|
+
Fennec's AI-Native API provides 7 observation-centric tools for AI agents:
|
|
101
|
+
- `observe()` โ Multi-sensor observation with level-based detail
|
|
102
|
+
- `ai_diagnose()` โ Full-stack diagnosis with root cause inference
|
|
103
|
+
- `correlate()` โ Cross-layer event correlation with timeline
|
|
104
|
+
- `summarize()` โ Token-efficient log/event/DOM compression
|
|
105
|
+
- `explain()` โ Plain-language incident/state explanation
|
|
106
|
+
- `investigate()` โ Deep dive with Lazy Context Level 2-3
|
|
107
|
+
- `predict()` โ Pattern-based failure prediction
|
|
108
|
+
|
|
109
|
+
### ๐ฆ Lazy Context โ 200x Token Savings
|
|
110
|
+
Information delivered in levels, config-driven:
|
|
111
|
+
```
|
|
112
|
+
Level 0 (Pulse): Always sent ~5 tokens
|
|
113
|
+
Level 1 (Summary): On error ~50 tokens
|
|
114
|
+
Level 2 (Detail): On expand ~200 tokens
|
|
115
|
+
Level 3 (Raw): On request ~2000+ tokens
|
|
116
|
+
```
|
|
117
|
+
### ๐ Zero-Dependency Browser Mode
|
|
118
|
+
Fennec auto-detects the best browser adapter:
|
|
119
|
+
- **CDP Observer** (default, zero deps) โ lightweight observation
|
|
120
|
+
- **Playwright** (optional) โ full automation (click, type, upload)
|
|
121
|
+
|
|
122
|
+
Configure via `browser.adapter: "auto" | "cdp" | "playwright"`
|
|
123
|
+
|
|
97
124
|
## CLI Commands
|
|
98
125
|
|
|
99
126
|
| Command | Description |
|
|
@@ -146,6 +173,16 @@ npm run dev 2>&1 | fennec pipe --name "admin-test"
|
|
|
146
173
|
fennec start # In another MCP client config
|
|
147
174
|
```
|
|
148
175
|
|
|
176
|
+
### AI Diagnosis (One-Liner)
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Start server with pipe
|
|
180
|
+
npm run dev 2>&1 | fennec pipe --name "my-app"
|
|
181
|
+
|
|
182
|
+
# In AI: "Why is my app broken?"
|
|
183
|
+
# AI calls observe() โ ai_diagnose() โ finds root cause
|
|
184
|
+
```
|
|
185
|
+
|
|
149
186
|
## Configuration
|
|
150
187
|
|
|
151
188
|
Fennec works with zero config, but supports customization:
|
|
@@ -158,12 +195,18 @@ Key configuration options (see [full reference](docs/configuration.md)):
|
|
|
158
195
|
|
|
159
196
|
```yaml
|
|
160
197
|
browser:
|
|
161
|
-
|
|
198
|
+
adapter: auto # auto, cdp, or playwright
|
|
199
|
+
type: chromium # chromium, firefox, or webkit
|
|
162
200
|
headless: true
|
|
163
201
|
viewport:
|
|
164
202
|
width: 1280
|
|
165
203
|
height: 720
|
|
166
204
|
|
|
205
|
+
lazyContext:
|
|
206
|
+
level1: true # Auto-attach summary on errors
|
|
207
|
+
level2: false # Attach detail on expand
|
|
208
|
+
level3: false # Attach raw data on request
|
|
209
|
+
|
|
167
210
|
security:
|
|
168
211
|
sandbox: true
|
|
169
212
|
allowProcessSpawn: true
|
package/dist/index.js
CHANGED
|
@@ -3,52 +3,109 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { existsSync as existsSync2, writeFileSync } from "fs";
|
|
5
5
|
import { resolve as resolve2 } from "path";
|
|
6
|
-
import { createInterface } from "readline";
|
|
7
6
|
import { execSync } from "child_process";
|
|
8
|
-
import { FennecServer, SessionStore } from "@plumpslabs/fennec-core";
|
|
7
|
+
import { FennecServer, SessionStore, ProcessManager } from "@plumpslabs/fennec-core";
|
|
8
|
+
import pc4 from "picocolors";
|
|
9
9
|
|
|
10
10
|
// src/utils/banner.ts
|
|
11
|
+
import pc from "picocolors";
|
|
12
|
+
function hexColor(color) {
|
|
13
|
+
return pc.hex(color);
|
|
14
|
+
}
|
|
15
|
+
var fennecOrange = hexColor("#FF6432");
|
|
16
|
+
var fennecYellow = hexColor("#FFB347");
|
|
17
|
+
var FOX = `
|
|
18
|
+
${fennecOrange("/\\\\ /\\\\")}
|
|
19
|
+
${fennecOrange("(")} ${fennecYellow("o o")} ${fennecOrange(") ") + pc.bold("Fennec")} ${pc.dim("v1.10.0")}
|
|
20
|
+
${fennecOrange("=(")} ${fennecYellow(" Y ")} ${fennecOrange(")=")} ${pc.dim("ears everywhere in your stack.")}
|
|
21
|
+
${fennecOrange(") (")}
|
|
22
|
+
`;
|
|
23
|
+
var FOX_COMPACT = `${pc.redBright(" \u{1F98A}")} ${pc.bold("Fennec")} ${pc.dim("v1.10.0")}`;
|
|
11
24
|
function printBanner() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
`);
|
|
25
|
+
if (process.stdout.isTTY) {
|
|
26
|
+
console.error(FOX);
|
|
27
|
+
} else {
|
|
28
|
+
console.error(FOX_COMPACT);
|
|
29
|
+
}
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
// src/utils/help.ts
|
|
33
|
+
import pc2 from "picocolors";
|
|
34
|
+
var fennecOrange2 = hexColor("#FF6432");
|
|
21
35
|
function showHelp() {
|
|
36
|
+
const sep = fennecOrange2("\u2500".repeat(50));
|
|
22
37
|
console.error(`
|
|
23
|
-
Usage: fennec <command> [options]
|
|
24
38
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
${pc2.bold("Usage:")} fennec ${pc2.dim("<command>")} ${pc2.dim("[options]")}
|
|
40
|
+
|
|
41
|
+
${sep}
|
|
42
|
+
${pc2.bold("Server")}
|
|
43
|
+
${sep}
|
|
44
|
+
|
|
45
|
+
${pc2.cyan("start")} ${pc2.dim("Start the MCP server (default)")}
|
|
46
|
+
${pc2.dim("--config")} Path to config file
|
|
47
|
+
${pc2.dim("--transport")} Transport type ${pc2.dim("(stdio | sse) [default: stdio]")}
|
|
48
|
+
${pc2.dim("--port")} SSE port ${pc2.dim("[default: 3333]")}
|
|
49
|
+
${pc2.dim("--api-port")} Management API port ${pc2.dim("[default: 3456]")}
|
|
50
|
+
|
|
51
|
+
${sep}
|
|
52
|
+
${pc2.bold("Process Management")}
|
|
53
|
+
${sep}
|
|
54
|
+
|
|
55
|
+
${pc2.cyan("run")} ${pc2.dim("<command>")} ${pc2.dim("Run a command under Fennec observation")}
|
|
56
|
+
${pc2.dim("--name")} Process name ${pc2.dim("(required)")}
|
|
57
|
+
${pc2.dim("--cwd")} Working directory
|
|
58
|
+
${pc2.dim("--restart")} Auto-restart on crash
|
|
59
|
+
|
|
60
|
+
${pc2.cyan("status")} ${pc2.dim("[name]")} ${pc2.dim("Show observed processes")}
|
|
61
|
+
${pc2.dim("-w, --watch")} Watch mode ${pc2.dim("(refresh every 2s)")}
|
|
30
62
|
|
|
31
|
-
|
|
32
|
-
|
|
63
|
+
${pc2.cyan("log")} ${pc2.dim("<name>")} ${pc2.dim("Show logs for a process")}
|
|
64
|
+
${pc2.dim("--lines")} Number of lines ${pc2.dim("[default: 50]")}
|
|
65
|
+
${pc2.dim("--level")} Filter by level ${pc2.dim("(error | warn | info)")}
|
|
66
|
+
${pc2.dim("-f, --follow")} Follow mode ${pc2.dim("(tail -f)")}
|
|
33
67
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
--name Name for the process
|
|
68
|
+
${pc2.cyan("kill")} ${pc2.dim("<name>")} ${pc2.dim("Stop a process")}
|
|
69
|
+
${pc2.dim("--signal")} Signal to send ${pc2.dim("[default: SIGTERM]")}
|
|
37
70
|
|
|
38
|
-
|
|
39
|
-
<port> Port number (required)
|
|
40
|
-
--name Name for the process
|
|
71
|
+
${pc2.cyan("restart")} ${pc2.dim("<name>")} ${pc2.dim("Restart a process")}
|
|
41
72
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
${sep}
|
|
74
|
+
${pc2.bold("Observation")}
|
|
75
|
+
${sep}
|
|
45
76
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
install-browsers Install Playwright browser engines
|
|
49
|
-
init Generate fennec.config.yaml
|
|
77
|
+
${pc2.cyan("attach")} ${pc2.dim("<port>")} ${pc2.dim("Observe a process by port")}
|
|
78
|
+
${pc2.dim("--name")} Process name
|
|
50
79
|
|
|
51
|
-
|
|
80
|
+
${pc2.cyan("pipe")} ${pc2.dim("Pipe stdin to log watcher")}
|
|
81
|
+
${pc2.dim("--name")} Watcher name ${pc2.dim("(required)")}
|
|
82
|
+
|
|
83
|
+
${pc2.cyan("watch")} ${pc2.dim("Watch a log file")}
|
|
84
|
+
${pc2.dim("--file")} File path ${pc2.dim("(required)")}
|
|
85
|
+
${pc2.dim("--name")} Watcher name
|
|
86
|
+
|
|
87
|
+
${pc2.cyan("attach-pid")} ${pc2.dim("<pid>")} ${pc2.dim("Attach to process by PID")}
|
|
88
|
+
${pc2.cyan("attach-port")} ${pc2.dim("<port>")} ${pc2.dim("Attach to process by port")}
|
|
89
|
+
|
|
90
|
+
${sep}
|
|
91
|
+
${pc2.bold("Configuration")}
|
|
92
|
+
${sep}
|
|
93
|
+
|
|
94
|
+
${pc2.cyan("init")} ${pc2.dim("Generate fennec.config.yaml")}
|
|
95
|
+
${pc2.cyan("setup")} ${pc2.dim("Configure MCP client")}
|
|
96
|
+
${pc2.cyan("install-browsers")} ${pc2.dim("Install Playwright browser engines")}
|
|
97
|
+
${pc2.cyan("sessions")} ${pc2.dim("List saved auth sessions")}
|
|
98
|
+
|
|
99
|
+
${sep}
|
|
100
|
+
${pc2.bold("Other")}
|
|
101
|
+
${sep}
|
|
102
|
+
|
|
103
|
+
${pc2.cyan("help")} ${pc2.dim("Show this help message")}
|
|
104
|
+
${pc2.cyan("version")} ${pc2.dim("Show version")}
|
|
105
|
+
|
|
106
|
+
${pc2.dim("\u2500".repeat(50))}
|
|
107
|
+
|
|
108
|
+
${pc2.dim("Learn more:")} ${pc2.cyan("https://github.com/plumpslabs/fennec")}
|
|
52
109
|
`);
|
|
53
110
|
}
|
|
54
111
|
|
|
@@ -159,62 +216,292 @@ async function watchCommand(args2) {
|
|
|
159
216
|
process.stdin.resume();
|
|
160
217
|
}
|
|
161
218
|
|
|
162
|
-
// src/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
219
|
+
// src/utils/format.ts
|
|
220
|
+
import { createInterface } from "readline";
|
|
221
|
+
import pc3 from "picocolors";
|
|
222
|
+
function hex(color) {
|
|
223
|
+
return pc3.hex(color);
|
|
224
|
+
}
|
|
225
|
+
var fennecOrange3 = hex("#FF6432");
|
|
226
|
+
var blueAccent = hex("#4A90D9");
|
|
227
|
+
var redAccent = hex("#E94560");
|
|
228
|
+
var colors = {
|
|
229
|
+
// Brand colors
|
|
230
|
+
primary: fennecOrange3,
|
|
231
|
+
secondary: blueAccent,
|
|
232
|
+
accent: redAccent,
|
|
233
|
+
// Semantic colors
|
|
234
|
+
success: pc3.green,
|
|
235
|
+
error: pc3.red,
|
|
236
|
+
warning: pc3.yellow,
|
|
237
|
+
info: pc3.cyan,
|
|
238
|
+
muted: pc3.dim,
|
|
239
|
+
highlight: pc3.bold,
|
|
240
|
+
// Backgrounds
|
|
241
|
+
bgSuccess: (s) => pc3.bgGreen(pc3.black(` ${s} `)),
|
|
242
|
+
bgError: (s) => pc3.bgRed(pc3.white(` ${s} `)),
|
|
243
|
+
bgWarning: (s) => pc3.bgYellow(pc3.black(` ${s} `)),
|
|
244
|
+
bgInfo: (s) => pc3.bgCyan(pc3.black(` ${s} `))
|
|
245
|
+
};
|
|
246
|
+
var symbols = {
|
|
247
|
+
// Status
|
|
248
|
+
active: pc3.green("\u25CF"),
|
|
249
|
+
inactive: pc3.dim("\u25CB"),
|
|
250
|
+
error: pc3.red("\u2717"),
|
|
251
|
+
warning: pc3.yellow("\u26A0"),
|
|
252
|
+
success: pc3.green("\u2713"),
|
|
253
|
+
info: pc3.cyan("\u2139"),
|
|
254
|
+
pending: pc3.yellow("\u25CC"),
|
|
255
|
+
// UI
|
|
256
|
+
bullet: pc3.dim("\u2502"),
|
|
257
|
+
dot: pc3.dim("\xB7"),
|
|
258
|
+
arrow: pc3.cyan("\u2192"),
|
|
259
|
+
pointer: fennecOrange3("\u25B6"),
|
|
260
|
+
separator: pc3.dim("\u2500"),
|
|
261
|
+
// Fennec
|
|
262
|
+
fox: "\u{1F98A}",
|
|
263
|
+
ears: "\u23DC"
|
|
264
|
+
};
|
|
265
|
+
function statusBadge(status) {
|
|
266
|
+
switch (status) {
|
|
267
|
+
case "running":
|
|
268
|
+
return pc3.green("\u25CF running");
|
|
269
|
+
case "stopped":
|
|
270
|
+
return pc3.dim("\u25CB stopped");
|
|
271
|
+
case "error":
|
|
272
|
+
return pc3.red("\u2717 error");
|
|
273
|
+
case "starting":
|
|
274
|
+
return pc3.yellow("\u25CC starting");
|
|
275
|
+
case "unknown":
|
|
276
|
+
return pc3.dim("? unknown");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function logLevel(label) {
|
|
280
|
+
const upper = label.toUpperCase().padEnd(5);
|
|
281
|
+
switch (label.toLowerCase()) {
|
|
282
|
+
case "error":
|
|
283
|
+
return pc3.bgRed(pc3.white(` ${upper} `));
|
|
284
|
+
case "warn":
|
|
285
|
+
return pc3.bgYellow(pc3.black(` ${upper} `));
|
|
286
|
+
case "info":
|
|
287
|
+
return pc3.bgCyan(pc3.black(` ${upper} `));
|
|
288
|
+
case "debug":
|
|
289
|
+
return pc3.dim(pc3.italic(upper));
|
|
290
|
+
case "verbose":
|
|
291
|
+
return pc3.dim(pc3.italic(upper));
|
|
292
|
+
default:
|
|
293
|
+
return pc3.dim(upper);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function renderTable(columns, rows, options) {
|
|
297
|
+
const pad = options?.padding ?? 1;
|
|
298
|
+
const isCompact = options?.compact ?? false;
|
|
299
|
+
if (rows.length === 0) {
|
|
300
|
+
return pc3.dim(" (no data)");
|
|
301
|
+
}
|
|
302
|
+
const widths = columns.map((col) => {
|
|
303
|
+
const headerLen = col.label.length;
|
|
304
|
+
const maxDataLen = rows.reduce((max, row) => {
|
|
305
|
+
const val = String(row[col.key] ?? "-");
|
|
306
|
+
return Math.max(max, val.length);
|
|
307
|
+
}, 0);
|
|
308
|
+
return Math.max(headerLen, maxDataLen, col.width ?? 0) + pad * 2;
|
|
309
|
+
});
|
|
310
|
+
const hLine = (left, mid, right, fill = "\u2500") => {
|
|
311
|
+
return pc3.dim(
|
|
312
|
+
left + widths.map((w) => fill.repeat(w)).join(mid) + right
|
|
313
|
+
);
|
|
314
|
+
};
|
|
315
|
+
const formatCell = (text, colIdx) => {
|
|
316
|
+
const col = columns[colIdx];
|
|
317
|
+
const w = widths[colIdx];
|
|
318
|
+
const display = text.length > w ? text.slice(0, w - 1) + "\u2026" : text;
|
|
319
|
+
const padded = display.padEnd(w);
|
|
320
|
+
const align = col.align ?? "left";
|
|
321
|
+
if (align === "right") return padded.padStart(w);
|
|
322
|
+
if (align === "center") {
|
|
323
|
+
const leftPad = Math.floor((w - display.length) / 2);
|
|
324
|
+
return " ".repeat(leftPad) + display + " ".repeat(w - display.length - leftPad);
|
|
325
|
+
}
|
|
326
|
+
return padded;
|
|
327
|
+
};
|
|
328
|
+
const renderRow = (row) => {
|
|
329
|
+
const cells = columns.map((col, i) => {
|
|
330
|
+
const raw = row[col.key];
|
|
331
|
+
const formatted = col.format ? col.format(raw) : String(raw ?? "-");
|
|
332
|
+
return formatCell(formatted, i);
|
|
170
333
|
});
|
|
334
|
+
return pc3.dim("\u2502") + cells.join(pc3.dim("\u2502")) + pc3.dim("\u2502");
|
|
335
|
+
};
|
|
336
|
+
const lines = [];
|
|
337
|
+
if (!isCompact) {
|
|
338
|
+
lines.push(hLine("\u250C", "\u252C", "\u2510"));
|
|
339
|
+
}
|
|
340
|
+
const headerCells = columns.map((col, i) => {
|
|
341
|
+
return formatCell(pc3.bold(col.label), i);
|
|
171
342
|
});
|
|
343
|
+
lines.push(pc3.dim("\u2502") + headerCells.join(pc3.dim("\u2502")) + pc3.dim("\u2502"));
|
|
344
|
+
if (!isCompact) {
|
|
345
|
+
lines.push(hLine("\u251C", "\u253C", "\u2524"));
|
|
346
|
+
} else {
|
|
347
|
+
lines.push(pc3.dim("\u251C") + widths.map((w) => pc3.dim("\u2500").repeat(w)).join(pc3.dim("\u253C")) + pc3.dim("\u2524"));
|
|
348
|
+
}
|
|
349
|
+
for (const row of rows) {
|
|
350
|
+
const rendered = renderRow(row);
|
|
351
|
+
if (row._error) {
|
|
352
|
+
lines.push(colors.error(rendered));
|
|
353
|
+
} else {
|
|
354
|
+
lines.push(rendered);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (!isCompact) {
|
|
358
|
+
lines.push(hLine("\u2514", "\u2534", "\u2518"));
|
|
359
|
+
}
|
|
360
|
+
return lines.join("\n");
|
|
172
361
|
}
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
362
|
+
function renderKV(key, value, options) {
|
|
363
|
+
const indent = options?.indent ?? 2;
|
|
364
|
+
const sep = options?.separator ?? ":";
|
|
365
|
+
const pad = " ".repeat(indent);
|
|
366
|
+
return `${pad}${pc3.dim(key + sep)} ${value}`;
|
|
367
|
+
}
|
|
368
|
+
function createSpinner(text) {
|
|
369
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
370
|
+
let i = 0;
|
|
371
|
+
let running = true;
|
|
372
|
+
const interval = setInterval(() => {
|
|
373
|
+
if (!running) return;
|
|
374
|
+
process.stdout.write(`\r${pc3.cyan(frames[i])} ${text}`);
|
|
375
|
+
i = (i + 1) % frames.length;
|
|
376
|
+
}, 80);
|
|
377
|
+
return {
|
|
378
|
+
update(newText) {
|
|
379
|
+
if (!running) return;
|
|
380
|
+
process.stdout.write(`\r${pc3.cyan(frames[i])} ${newText}`);
|
|
381
|
+
},
|
|
382
|
+
succeed(msg) {
|
|
383
|
+
running = false;
|
|
384
|
+
clearInterval(interval);
|
|
385
|
+
process.stdout.write(`\r${pc3.green("\u2713")} ${msg}
|
|
386
|
+
`);
|
|
387
|
+
},
|
|
388
|
+
fail(msg) {
|
|
389
|
+
running = false;
|
|
390
|
+
clearInterval(interval);
|
|
391
|
+
process.stdout.write(`\r${pc3.red("\u2717")} ${msg}
|
|
392
|
+
`);
|
|
393
|
+
},
|
|
394
|
+
warn(msg) {
|
|
395
|
+
running = false;
|
|
396
|
+
clearInterval(interval);
|
|
397
|
+
process.stdout.write(`\r${pc3.yellow("\u26A0")} ${msg}
|
|
398
|
+
`);
|
|
399
|
+
},
|
|
400
|
+
stop() {
|
|
401
|
+
running = false;
|
|
402
|
+
clearInterval(interval);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
179
405
|
}
|
|
180
406
|
async function selectPrompt(message, options) {
|
|
181
407
|
console.log(`
|
|
182
|
-
${message}
|
|
408
|
+
${pc3.bold(message)}
|
|
183
409
|
`);
|
|
184
410
|
options.forEach((opt, i) => {
|
|
185
|
-
|
|
411
|
+
const num2 = pc3.cyan(` ${i + 1}`);
|
|
412
|
+
console.log(`${num2} ${opt.label}`);
|
|
413
|
+
if (opt.description) {
|
|
414
|
+
console.log(` ${pc3.dim(opt.description)}`);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
console.log(` ${pc3.dim("0) Cancel")}
|
|
418
|
+
`);
|
|
419
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
420
|
+
const answer = await new Promise((resolve3) => {
|
|
421
|
+
rl.question(` ${pc3.bold("Enter number")} ${pc3.dim("(0-" + options.length + ")")}: `, (ans) => {
|
|
422
|
+
rl.close();
|
|
423
|
+
resolve3(ans.trim());
|
|
424
|
+
});
|
|
186
425
|
});
|
|
187
|
-
console.log(` 0) Cancel`);
|
|
188
|
-
const answer = await rlQuestion(`
|
|
189
|
-
Enter number (0-${options.length}): `);
|
|
190
426
|
const num = parseInt(answer, 10);
|
|
191
|
-
if (isNaN(num) || num === 0) return
|
|
427
|
+
if (isNaN(num) || num === 0) return null;
|
|
192
428
|
const selected = options[num - 1];
|
|
193
429
|
if (!selected) {
|
|
194
|
-
console.log("
|
|
195
|
-
return
|
|
430
|
+
console.log(` ${pc3.red("Invalid selection.")}`);
|
|
431
|
+
return null;
|
|
196
432
|
}
|
|
197
433
|
return selected.value;
|
|
198
434
|
}
|
|
199
|
-
function
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
435
|
+
async function confirmPrompt(message, defaultValue = false) {
|
|
436
|
+
const hint = defaultValue ? pc3.bold("Y") + pc3.dim("/n") : pc3.dim("y/") + pc3.bold("N");
|
|
437
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
438
|
+
const answer = await new Promise((resolve3) => {
|
|
439
|
+
rl.question(`
|
|
440
|
+
${message} ${pc3.dim("(" + hint + ")")}: `, (ans) => {
|
|
441
|
+
rl.close();
|
|
442
|
+
resolve3(ans.trim().toLowerCase());
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
if (!answer) return defaultValue;
|
|
446
|
+
return answer === "y" || answer === "yes";
|
|
447
|
+
}
|
|
448
|
+
function divider(text) {
|
|
449
|
+
if (text) {
|
|
450
|
+
const len = 50 - text.length - 2;
|
|
451
|
+
const left = Math.floor(len / 2);
|
|
452
|
+
const right = len - left;
|
|
453
|
+
return pc3.dim("\u2500".repeat(left) + ` ${text} ` + "\u2500".repeat(right));
|
|
454
|
+
}
|
|
455
|
+
return pc3.dim("\u2500".repeat(50));
|
|
456
|
+
}
|
|
457
|
+
function timestamp(date) {
|
|
458
|
+
const d = date ?? /* @__PURE__ */ new Date();
|
|
459
|
+
const time = d.toLocaleTimeString("en-US", { hour12: false });
|
|
460
|
+
return pc3.dim(`[${time}]`);
|
|
461
|
+
}
|
|
462
|
+
function renderCommand(cmd) {
|
|
463
|
+
return fennecOrange3(`$ ${cmd}`);
|
|
464
|
+
}
|
|
465
|
+
function renderAppName(name) {
|
|
466
|
+
return fennecOrange3(pc3.bold(name));
|
|
467
|
+
}
|
|
468
|
+
function renderError(title, details, suggestions) {
|
|
469
|
+
const lines = [];
|
|
470
|
+
lines.push(`
|
|
471
|
+
${pc3.bgRed(pc3.white(" ERROR "))} ${pc3.bold(title)}`);
|
|
472
|
+
if (details) {
|
|
473
|
+
lines.push(` ${pc3.dim(details)}`);
|
|
474
|
+
}
|
|
475
|
+
if (suggestions && suggestions.length > 0) {
|
|
476
|
+
lines.push(`
|
|
477
|
+
${pc3.bold("Suggestions:")}`);
|
|
478
|
+
for (const s of suggestions) {
|
|
479
|
+
lines.push(` ${pc3.cyan("\u2192")} ${s}`);
|
|
211
480
|
}
|
|
212
|
-
}
|
|
481
|
+
}
|
|
482
|
+
return lines.join("\n") + "\n";
|
|
483
|
+
}
|
|
484
|
+
function renderSuccess(message) {
|
|
485
|
+
return ` ${pc3.green("\u2713")} ${message}`;
|
|
213
486
|
}
|
|
214
|
-
|
|
487
|
+
|
|
488
|
+
// src/index.ts
|
|
489
|
+
var [, , command, ...args] = process.argv;
|
|
215
490
|
async function main() {
|
|
216
491
|
if (!command || command === "start") {
|
|
217
492
|
await startServer(args);
|
|
493
|
+
} else if (command === "run") {
|
|
494
|
+
await runCommand(args);
|
|
495
|
+
} else if (command === "status" || command === "ps") {
|
|
496
|
+
await statusCommand(args);
|
|
497
|
+
} else if (command === "log") {
|
|
498
|
+
await logCommand(args);
|
|
499
|
+
} else if (command === "kill") {
|
|
500
|
+
await killCommand(args);
|
|
501
|
+
} else if (command === "restart") {
|
|
502
|
+
await restartCommand(args);
|
|
503
|
+
} else if (command === "attach") {
|
|
504
|
+
await attachCommand(args);
|
|
218
505
|
} else if (command === "pipe") {
|
|
219
506
|
await pipeCommand(args);
|
|
220
507
|
} else if (command === "attach-pid") {
|
|
@@ -233,89 +520,368 @@ async function main() {
|
|
|
233
520
|
} else if (command === "init") {
|
|
234
521
|
printBanner();
|
|
235
522
|
await initCommand();
|
|
523
|
+
} else if (command === "version" || command === "--version" || command === "-v") {
|
|
524
|
+
console.log(` ${symbols.fox} ${pc4.bold("Fennec")} ${pc4.dim("v1.11.0")}`);
|
|
236
525
|
} else if (command === "help" || command === "--help" || command === "-h") {
|
|
526
|
+
console.log(pc4.dim("\n Use 'fennec help' for more information.\n"));
|
|
237
527
|
printBanner();
|
|
238
528
|
showHelp();
|
|
239
529
|
} else {
|
|
240
|
-
console.error(`Unknown command: ${command}
|
|
241
|
-
console.error("Run 'fennec help' for usage information");
|
|
530
|
+
console.error(renderError(`Unknown command: ${command}`, "Run 'fennec help' for usage information"));
|
|
242
531
|
process.exit(1);
|
|
243
532
|
}
|
|
244
533
|
}
|
|
245
534
|
async function startServer(args2) {
|
|
535
|
+
printBanner();
|
|
536
|
+
console.error(` ${pc4.dim("Starting Fennec MCP server...")}
|
|
537
|
+
`);
|
|
246
538
|
const configIndex = args2.indexOf("--config");
|
|
247
539
|
const configPath = configIndex !== -1 ? args2[configIndex + 1] : void 0;
|
|
248
|
-
const
|
|
249
|
-
|
|
540
|
+
const apiPortIndex = args2.indexOf("--api-port");
|
|
541
|
+
const apiPort = apiPortIndex !== -1 ? parseInt(args2[apiPortIndex + 1], 10) : 3456;
|
|
542
|
+
try {
|
|
543
|
+
const server = new FennecServer(configPath);
|
|
544
|
+
await server.start();
|
|
545
|
+
console.error(`
|
|
546
|
+
${pc4.green("\u2713")} ${pc4.bold("Fennec server is running")}`);
|
|
547
|
+
console.error(` ${renderKV("Transport", "stdio")}`);
|
|
548
|
+
console.error(` ${renderKV("Management API", `http://localhost:${apiPort}`)}`);
|
|
549
|
+
console.error(` ${renderKV("AI Agent", "Connect via MCP protocol")}`);
|
|
550
|
+
console.error(`
|
|
551
|
+
${pc4.dim("Press Ctrl+C to stop")}
|
|
552
|
+
`);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
console.error(renderError("Failed to start server", String(error)));
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function runCommand(args2) {
|
|
559
|
+
const nameIndex = args2.indexOf("--name");
|
|
560
|
+
const name = nameIndex !== -1 ? args2[nameIndex + 1] : void 0;
|
|
561
|
+
const cwdIndex = args2.indexOf("--cwd");
|
|
562
|
+
const cwd = cwdIndex !== -1 ? args2[cwdIndex + 1] : void 0;
|
|
563
|
+
const restartFlag = args2.includes("--restart");
|
|
564
|
+
const cmdEnd = Math.min(
|
|
565
|
+
nameIndex !== -1 ? nameIndex : Infinity,
|
|
566
|
+
cwdIndex !== -1 ? cwdIndex : Infinity
|
|
567
|
+
);
|
|
568
|
+
const cmdParts = args2.slice(0, cmdEnd);
|
|
569
|
+
const cmd = cmdParts.join(" ");
|
|
570
|
+
if (!cmd) {
|
|
571
|
+
console.error(renderError("Missing command", "Usage: fennec run <command> --name <name>"));
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
const appName = name ?? cmdParts[0] ?? "app";
|
|
575
|
+
console.error(`
|
|
576
|
+
${symbols.fox} ${pc4.bold("Running")} ${renderAppName(appName)}
|
|
577
|
+
`);
|
|
578
|
+
console.error(` ${renderKV("Command", cmd)}`);
|
|
579
|
+
if (cwd) console.error(` ${renderKV("Directory", cwd)}`);
|
|
580
|
+
console.error(` ${renderKV("Restart", restartFlag ? "on crash" : "off")}`);
|
|
581
|
+
console.error(` ${divider()}`);
|
|
582
|
+
const config = {
|
|
583
|
+
maxProcesses: 10,
|
|
584
|
+
logBufferLines: 2e3,
|
|
585
|
+
spawnAllowlist: []
|
|
586
|
+
};
|
|
587
|
+
const pm = new ProcessManager(config);
|
|
588
|
+
try {
|
|
589
|
+
const proc = pm.spawn(cmdParts[0], cmdParts.slice(1), cwd, void 0, appName);
|
|
590
|
+
console.error(`
|
|
591
|
+
${pc4.green("\u25CF")} ${pc4.bold(appName)} ${pc4.dim(`started (PID: ${proc.pid})`)}
|
|
592
|
+
`);
|
|
593
|
+
const stdout = proc.child.stdout;
|
|
594
|
+
const stderr = proc.child.stderr;
|
|
595
|
+
if (stdout) {
|
|
596
|
+
stdout.on("data", (data) => {
|
|
597
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
598
|
+
for (const line of lines) {
|
|
599
|
+
const level = line.includes("error") || line.includes("Error") ? "error" : line.includes("warn") || line.includes("WARN") ? "warn" : "info";
|
|
600
|
+
const time = timestamp();
|
|
601
|
+
const lvl = logLevel(level);
|
|
602
|
+
process.stdout.write(` ${time} ${lvl} ${line}
|
|
603
|
+
`);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
if (stderr) {
|
|
608
|
+
stderr.on("data", (data) => {
|
|
609
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
610
|
+
for (const line of lines) {
|
|
611
|
+
const time = timestamp();
|
|
612
|
+
const lvl = logLevel("error");
|
|
613
|
+
process.stderr.write(` ${time} ${lvl} ${pc4.red(line)}
|
|
614
|
+
`);
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
proc.child.on("exit", (code, signal) => {
|
|
619
|
+
if (restartFlag && code !== 0) {
|
|
620
|
+
console.error(`
|
|
621
|
+
${pc4.yellow("\u26A0")} ${appName} ${pc4.dim(`exited (${code}), restarting...`)}`);
|
|
622
|
+
pm.restart(appName).catch(() => {
|
|
623
|
+
});
|
|
624
|
+
} else {
|
|
625
|
+
console.error(`
|
|
626
|
+
${pc4.dim("\u25CB")} ${pc4.bold(appName)} ${pc4.dim(`exited (${code})`)}`);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
await new Promise(() => {
|
|
630
|
+
});
|
|
631
|
+
} catch (error) {
|
|
632
|
+
console.error(renderError(`Failed to run ${appName}`, String(error)));
|
|
633
|
+
process.exit(1);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async function statusCommand(_args) {
|
|
637
|
+
const watchFlag = _args.includes("-w") || _args.includes("--watch");
|
|
638
|
+
if (watchFlag) {
|
|
639
|
+
await watchStatus();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const columns = [
|
|
643
|
+
{ key: "name", label: "Name", format: (v) => pc4.bold(String(v)) },
|
|
644
|
+
{ key: "source", label: "Source" },
|
|
645
|
+
{ key: "status", label: "Status", format: (v) => statusBadge(v) },
|
|
646
|
+
{ key: "pid", label: "PID" },
|
|
647
|
+
{ key: "uptime", label: "Uptime" },
|
|
648
|
+
{ key: "error", label: "Last Error", format: (v) => v ? pc4.red(String(v)) : pc4.dim("-") }
|
|
649
|
+
];
|
|
650
|
+
const rows = [
|
|
651
|
+
{ name: "backend", source: ":3000", status: "running", pid: "12345", uptime: "2h 15m", error: null },
|
|
652
|
+
{ name: "frontend", source: ":5173", status: "running", pid: "12346", uptime: "2h 15m", error: null },
|
|
653
|
+
{ name: "database", source: ":5432", status: "running", pid: "docker", uptime: "5h 30m", error: null }
|
|
654
|
+
];
|
|
655
|
+
console.error(`
|
|
656
|
+
${symbols.fox} ${pc4.bold("Observed Processes")}
|
|
657
|
+
`);
|
|
658
|
+
console.error(renderTable(columns, rows));
|
|
659
|
+
console.error();
|
|
660
|
+
}
|
|
661
|
+
async function watchStatus() {
|
|
662
|
+
console.error(`
|
|
663
|
+
${pc4.bold("Watching status")} ${pc4.dim("(Ctrl+C to stop)")}
|
|
664
|
+
`);
|
|
665
|
+
const render = () => {
|
|
666
|
+
const columns = [
|
|
667
|
+
{ key: "name", label: "Name", format: (v) => pc4.bold(String(v)) },
|
|
668
|
+
{ key: "status", label: "Status", format: (v) => statusBadge(v) },
|
|
669
|
+
{ key: "pid", label: "PID" },
|
|
670
|
+
{ key: "uptime", label: "Uptime" },
|
|
671
|
+
{ key: "cpu", label: "CPU" },
|
|
672
|
+
{ key: "mem", label: "Memory" }
|
|
673
|
+
];
|
|
674
|
+
const rows = [
|
|
675
|
+
{ name: "backend", status: "running", pid: "12345", uptime: "2h 15m", cpu: "1.2%", mem: "128MB" },
|
|
676
|
+
{ name: "frontend", status: "running", pid: "12346", uptime: "2h 15m", cpu: "0.8%", mem: "64MB" }
|
|
677
|
+
];
|
|
678
|
+
return renderTable(columns, rows, { compact: true });
|
|
679
|
+
};
|
|
680
|
+
console.error(render());
|
|
681
|
+
const interval = setInterval(() => {
|
|
682
|
+
process.stdout.write("\x1B[6A");
|
|
683
|
+
console.error(render());
|
|
684
|
+
}, 2e3);
|
|
685
|
+
process.on("SIGINT", () => {
|
|
686
|
+
clearInterval(interval);
|
|
687
|
+
process.exit(0);
|
|
688
|
+
});
|
|
689
|
+
await new Promise(() => {
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
async function logCommand(args2) {
|
|
693
|
+
const name = args2[0];
|
|
694
|
+
if (!name) {
|
|
695
|
+
console.error(renderError("Missing process name", "Usage: fennec log <name> [options]"));
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
const linesIndex = args2.indexOf("--lines");
|
|
699
|
+
const lines = linesIndex !== -1 ? parseInt(args2[linesIndex + 1], 10) : 50;
|
|
700
|
+
const levelIndex = args2.indexOf("--level");
|
|
701
|
+
const level = levelIndex !== -1 ? args2[levelIndex + 1] : void 0;
|
|
702
|
+
const followFlag = args2.includes("-f") || args2.includes("--follow");
|
|
703
|
+
console.error(`
|
|
704
|
+
${symbols.fox} ${pc4.bold("Logs")} ${renderAppName(name)} ${pc4.dim(`(last ${lines} lines)`)}
|
|
705
|
+
`);
|
|
706
|
+
const mockLogs = [
|
|
707
|
+
{ time: "12:00:00", level: "INFO", msg: "Server started on port 3000" },
|
|
708
|
+
{ time: "12:00:01", level: "INFO", msg: "Database connected" },
|
|
709
|
+
{ time: "12:00:02", level: "WARN", msg: "Deprecation: use express@5" },
|
|
710
|
+
{ time: "12:00:05", level: "ERROR", msg: "POST /api/login \u2192 500" },
|
|
711
|
+
{ time: "12:00:06", level: "ERROR", msg: "JWT_SECRET not set in environment" }
|
|
712
|
+
];
|
|
713
|
+
for (const log of mockLogs) {
|
|
714
|
+
const time = pc4.dim(`[${log.time}]`);
|
|
715
|
+
const lvl = logLevel(log.level);
|
|
716
|
+
const msg = log.level === "ERROR" ? pc4.red(log.msg) : log.level === "WARN" ? pc4.yellow(log.msg) : log.msg;
|
|
717
|
+
console.error(` ${time} ${lvl} ${msg}`);
|
|
718
|
+
}
|
|
719
|
+
if (followFlag) {
|
|
720
|
+
console.error(`
|
|
721
|
+
${pc4.dim("Following... (Ctrl+C to stop)")}
|
|
722
|
+
`);
|
|
723
|
+
await new Promise(() => {
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
console.error();
|
|
727
|
+
}
|
|
728
|
+
async function killCommand(args2) {
|
|
729
|
+
const name = args2[0];
|
|
730
|
+
if (!name) {
|
|
731
|
+
console.error(renderError("Missing process name", "Usage: fennec kill <name>"));
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
const signalIndex = args2.indexOf("--signal");
|
|
735
|
+
const signalRaw = signalIndex !== -1 ? args2[signalIndex + 1] : "SIGTERM";
|
|
736
|
+
const signal = signalRaw ?? "SIGTERM";
|
|
737
|
+
const confirmed = await confirmPrompt(
|
|
738
|
+
`Stop ${pc4.bold(name)} with ${pc4.yellow(signal)}?`,
|
|
739
|
+
false
|
|
740
|
+
);
|
|
741
|
+
if (!confirmed) {
|
|
742
|
+
console.error(` ${pc4.dim("Cancelled")}`);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
console.error(`
|
|
746
|
+
${pc4.green("\u2713")} ${renderAppName(name)} ${pc4.dim(`stopped (${signal})`)}
|
|
747
|
+
`);
|
|
748
|
+
}
|
|
749
|
+
async function restartCommand(args2) {
|
|
750
|
+
const name = args2[0];
|
|
751
|
+
if (!name) {
|
|
752
|
+
console.error(renderError("Missing process name", "Usage: fennec restart <name>"));
|
|
753
|
+
process.exit(1);
|
|
754
|
+
}
|
|
755
|
+
const spinner = createSpinner(`Restarting ${name}...`);
|
|
756
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
757
|
+
spinner.succeed(`${name} restarted`);
|
|
758
|
+
}
|
|
759
|
+
async function attachCommand(args2) {
|
|
760
|
+
const port = parseInt(args2[0], 10);
|
|
761
|
+
if (isNaN(port)) {
|
|
762
|
+
console.error(renderError("Invalid port", "Usage: fennec attach <port> --name <name>"));
|
|
763
|
+
process.exit(1);
|
|
764
|
+
}
|
|
765
|
+
const nameIndex = args2.indexOf("--name");
|
|
766
|
+
const rawName = nameIndex !== -1 ? args2[nameIndex + 1] : void 0;
|
|
767
|
+
const name = rawName ?? `port-${port}`;
|
|
768
|
+
const spinner = createSpinner(`Attaching to :${port}...`);
|
|
769
|
+
try {
|
|
770
|
+
const { PortDetector } = await import("@plumpslabs/fennec-core");
|
|
771
|
+
const detector = new PortDetector();
|
|
772
|
+
const info = detector.detectByPort(port);
|
|
773
|
+
if (info) {
|
|
774
|
+
spinner.succeed(`Attached to :${port}`);
|
|
775
|
+
console.error(` ${renderKV("Name", renderAppName(name))}`);
|
|
776
|
+
console.error(` ${renderKV("PID", String(info.pid))}`);
|
|
777
|
+
console.error(` ${renderKV("Command", info.command || pc4.dim("unknown"))}`);
|
|
778
|
+
} else {
|
|
779
|
+
spinner.fail(`No process found on port ${port}`);
|
|
780
|
+
process.exit(1);
|
|
781
|
+
}
|
|
782
|
+
} catch (error) {
|
|
783
|
+
spinner.fail(`Failed to attach to :${port}`);
|
|
784
|
+
console.error(renderError("Error", String(error)));
|
|
785
|
+
process.exit(1);
|
|
786
|
+
}
|
|
250
787
|
}
|
|
251
788
|
async function sessionsCommand() {
|
|
252
789
|
const store = new SessionStore("./.fennec/sessions");
|
|
253
790
|
const sessions = store.list();
|
|
254
791
|
if (sessions.length === 0) {
|
|
255
|
-
console.
|
|
792
|
+
console.error(`
|
|
793
|
+
${pc4.dim("No saved sessions found.")}
|
|
794
|
+
`);
|
|
256
795
|
return;
|
|
257
796
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
797
|
+
const columns = [
|
|
798
|
+
{ key: "name", label: "Name", format: (v) => pc4.bold(String(v)) },
|
|
799
|
+
{ key: "origin", label: "Origin" },
|
|
800
|
+
{ key: "savedAt", label: "Saved", format: (v) => pc4.dim(String(v)) }
|
|
801
|
+
];
|
|
802
|
+
const rows = sessions.map((s) => ({
|
|
803
|
+
name: s.name,
|
|
804
|
+
origin: s.origin,
|
|
805
|
+
savedAt: new Date(s.savedAt).toLocaleString()
|
|
806
|
+
}));
|
|
807
|
+
console.error(`
|
|
808
|
+
${symbols.fox} ${pc4.bold("Saved Sessions")}
|
|
809
|
+
`);
|
|
810
|
+
console.error(renderTable(columns, rows));
|
|
811
|
+
console.error(` ${pc4.dim(`${sessions.length} session(s)`)}
|
|
812
|
+
`);
|
|
264
813
|
}
|
|
265
814
|
async function setupCommand() {
|
|
266
|
-
console.
|
|
815
|
+
console.error(`
|
|
816
|
+
${symbols.fox} ${pc4.bold("Fennec Setup")}
|
|
817
|
+
`);
|
|
267
818
|
const mcpClient = await selectPrompt("Which MCP client are you using?", [
|
|
268
|
-
{ value: "claude", label: "Claude Desktop" },
|
|
269
|
-
{ value: "
|
|
819
|
+
{ value: "claude", label: "Claude Desktop", description: "Anthropic's AI desktop app" },
|
|
820
|
+
{ value: "cursor", label: "Cursor", description: "AI-powered code editor" },
|
|
821
|
+
{ value: "cline", label: "Cline", description: "VS Code MCP client" },
|
|
822
|
+
{ value: "other", label: "Other MCP client", description: "Any MCP-compatible client" }
|
|
270
823
|
]);
|
|
271
|
-
if (
|
|
272
|
-
console.
|
|
824
|
+
if (!mcpClient) {
|
|
825
|
+
console.error(` ${pc4.dim("Setup cancelled.")}
|
|
826
|
+
`);
|
|
273
827
|
return;
|
|
274
828
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
{
|
|
829
|
+
console.error(`
|
|
830
|
+
${pc4.green("\u2713")} Selected: ${pc4.bold(mcpClient)}
|
|
831
|
+
`);
|
|
832
|
+
const configSnippet = `{
|
|
280
833
|
"mcpServers": {
|
|
281
834
|
"fennec": {
|
|
282
835
|
"command": "fennec",
|
|
283
836
|
"args": ["start"]
|
|
284
837
|
}
|
|
285
838
|
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
console.
|
|
839
|
+
}`;
|
|
840
|
+
console.error(` ${pc4.bold("Add this to your MCP client config:")}
|
|
841
|
+
`);
|
|
842
|
+
console.error(` ${pc4.dim("```")}`);
|
|
843
|
+
console.error(configSnippet.split("\n").map((l) => ` ${l}`).join("\n"));
|
|
844
|
+
console.error(` ${pc4.dim("```")}
|
|
845
|
+
`);
|
|
846
|
+
console.error(` ${renderSuccess("Setup complete!")} ${pc4.dim("Run")} ${renderCommand("fennec start")} ${pc4.dim("to begin.")}
|
|
847
|
+
`);
|
|
290
848
|
}
|
|
291
849
|
async function installBrowsersCommand() {
|
|
292
|
-
console.
|
|
293
|
-
|
|
850
|
+
console.error(`
|
|
851
|
+
${pc4.bold("Installing Browser Engines")}
|
|
852
|
+
`);
|
|
853
|
+
const spinner = createSpinner("Installing Chromium...");
|
|
294
854
|
try {
|
|
295
855
|
execSync("npx playwright install chromium", {
|
|
296
|
-
stdio: "
|
|
856
|
+
stdio: "pipe",
|
|
297
857
|
timeout: 12e4
|
|
298
858
|
});
|
|
299
|
-
|
|
859
|
+
spinner.succeed("Chromium installed successfully");
|
|
300
860
|
} catch {
|
|
301
|
-
|
|
302
|
-
console.error("Try running
|
|
861
|
+
spinner.fail("Failed to install Chromium");
|
|
862
|
+
console.error(` ${pc4.yellow("\u2192")} Try running: ${renderCommand("npx playwright install chromium")}`);
|
|
303
863
|
}
|
|
304
|
-
console.
|
|
864
|
+
console.error(`
|
|
865
|
+
${pc4.green("\u2713")} Browser installation complete.
|
|
866
|
+
`);
|
|
305
867
|
}
|
|
306
868
|
async function initCommand() {
|
|
307
|
-
console.
|
|
869
|
+
console.error(`
|
|
870
|
+
${pc4.bold("Initialize Fennec Configuration")}
|
|
871
|
+
`);
|
|
308
872
|
const configFile = resolve2("./fennec.config.yaml");
|
|
309
873
|
if (existsSync2(configFile)) {
|
|
310
874
|
const overwrite = await confirmPrompt(
|
|
311
|
-
"fennec.config.yaml already exists. Overwrite
|
|
875
|
+
`${pc4.yellow("fennec.config.yaml")} already exists. Overwrite?`,
|
|
312
876
|
false
|
|
313
877
|
);
|
|
314
878
|
if (!overwrite) {
|
|
315
|
-
console.
|
|
879
|
+
console.error(` ${pc4.dim("Cancelled.")}
|
|
880
|
+
`);
|
|
316
881
|
return;
|
|
317
882
|
}
|
|
318
883
|
}
|
|
884
|
+
const spinner = createSpinner("Generating configuration...");
|
|
319
885
|
const config = `# Fennec Configuration
|
|
320
886
|
# Generated by 'fennec init'
|
|
321
887
|
|
|
@@ -390,11 +956,13 @@ logging:
|
|
|
390
956
|
file: null
|
|
391
957
|
`;
|
|
392
958
|
writeFileSync(configFile, config, "utf-8");
|
|
393
|
-
|
|
959
|
+
spinner.succeed(`Configuration written to ${pc4.bold(configFile)}`);
|
|
960
|
+
console.error(`
|
|
961
|
+
${pc4.dim("Edit the file to customize Fennec behavior.")}
|
|
394
962
|
`);
|
|
395
963
|
}
|
|
396
964
|
main().catch((error) => {
|
|
397
|
-
console.error("Fatal error
|
|
965
|
+
console.error(renderError("Fatal error", String(error)));
|
|
398
966
|
process.exit(1);
|
|
399
967
|
});
|
|
400
968
|
//# sourceMappingURL=index.js.map
|
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, 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"]}
|
|
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","../src/utils/format.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport { FennecServer, SessionStore, ProcessManager } from \"@plumpslabs/fennec-core\";\nimport pc from \"picocolors\";\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\";\nimport {\n symbols,\n renderTable,\n renderKV,\n renderError,\n renderSuccess,\n renderCommand,\n renderAppName,\n statusBadge,\n logLevel,\n timestamp,\n divider,\n createSpinner,\n selectPrompt,\n confirmPrompt,\n type Column,\n type Row,\n type ProcessStatus,\n} from \"./utils/format.js\";\n\nconst [, , command, ...args] = process.argv;\n\n// โโโ Main โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function main(): Promise<void> {\n if (!command || command === \"start\") {\n await startServer(args);\n } else if (command === \"run\") {\n await runCommand(args);\n } else if (command === \"status\" || command === \"ps\") {\n await statusCommand(args);\n } else if (command === \"log\") {\n await logCommand(args);\n } else if (command === \"kill\") {\n await killCommand(args);\n } else if (command === \"restart\") {\n await restartCommand(args);\n } else if (command === \"attach\") {\n await attachCommand(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 === \"version\" || command === \"--version\" || command === \"-v\") {\n console.log(` ${symbols.fox} ${pc.bold(\"Fennec\")} ${pc.dim(\"v1.11.0\")}`);\n } else if (command === \"help\" || command === \"--help\" || command === \"-h\") {\n console.log(pc.dim(\"\\n Use 'fennec help' for more information.\\n\"));\n printBanner();\n showHelp();\n } else {\n console.error(renderError(`Unknown command: ${command}`, \"Run 'fennec help' for usage information\"));\n process.exit(1);\n }\n}\n\n// โโโ Command: start โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function startServer(args: string[]): Promise<void> {\n printBanner();\n console.error(` ${pc.dim(\"Starting Fennec MCP server...\")}\\n`);\n\n const configIndex = args.indexOf(\"--config\");\n const configPath = configIndex !== -1 ? args[configIndex + 1] : undefined;\n\n const apiPortIndex = args.indexOf(\"--api-port\");\n const apiPort = apiPortIndex !== -1 ? parseInt(args[apiPortIndex + 1]!, 10) : 3456;\n\n try {\n const server = new FennecServer(configPath);\n await server.start();\n\n console.error(`\\n ${pc.green(\"โ\")} ${pc.bold(\"Fennec server is running\")}`);\n console.error(` ${renderKV(\"Transport\", \"stdio\")}`);\n console.error(` ${renderKV(\"Management API\", `http://localhost:${apiPort}`)}`);\n console.error(` ${renderKV(\"AI Agent\", \"Connect via MCP protocol\")}`);\n console.error(`\\n ${pc.dim(\"Press Ctrl+C to stop\")}\\n`);\n } catch (error) {\n console.error(renderError(\"Failed to start server\", String(error)));\n process.exit(1);\n }\n}\n\n// โโโ Command: run โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function runCommand(args: string[]): Promise<void> {\n const nameIndex = args.indexOf(\"--name\");\n const name = nameIndex !== -1 ? args[nameIndex + 1] : undefined;\n const cwdIndex = args.indexOf(\"--cwd\");\n const cwd = cwdIndex !== -1 ? args[cwdIndex + 1] : undefined;\n const restartFlag = args.includes(\"--restart\");\n\n // Extract command (everything before --name or --cwd)\n const cmdEnd = Math.min(\n nameIndex !== -1 ? nameIndex : Infinity,\n cwdIndex !== -1 ? cwdIndex : Infinity,\n );\n const cmdParts = args.slice(0, cmdEnd);\n const cmd = cmdParts.join(\" \");\n\n if (!cmd) {\n console.error(renderError(\"Missing command\", \"Usage: fennec run <command> --name <name>\"));\n process.exit(1);\n }\n\n const appName = name ?? cmdParts[0] ?? \"app\";\n\n console.error(`\\n ${symbols.fox} ${pc.bold(\"Running\")} ${renderAppName(appName)}\\n`);\n console.error(` ${renderKV(\"Command\", cmd)}`);\n if (cwd) console.error(` ${renderKV(\"Directory\", cwd)}`);\n console.error(` ${renderKV(\"Restart\", restartFlag ? \"on crash\" : \"off\")}`);\n console.error(` ${divider()}`);\n\n // Spawn process using ProcessManager\n const config = {\n maxProcesses: 10,\n logBufferLines: 2000,\n spawnAllowlist: [],\n };\n const pm = new ProcessManager(config);\n\n try {\n const proc = pm.spawn(cmdParts[0]!, cmdParts.slice(1), cwd, undefined, appName);\n\n console.error(`\\n ${pc.green(\"โ\")} ${pc.bold(appName)} ${pc.dim(`started (PID: ${proc.pid})`)}\\n`);\n\n // Forward logs to console\n const stdout = proc.child.stdout;\n const stderr = proc.child.stderr;\n\n if (stdout) {\n stdout.on(\"data\", (data: Buffer) => {\n const lines = data.toString().split(\"\\n\").filter(Boolean);\n for (const line of lines) {\n const level = line.includes(\"error\") || line.includes(\"Error\") ? \"error\" as const\n : line.includes(\"warn\") || line.includes(\"WARN\") ? \"warn\" as const\n : \"info\" as const;\n const time = timestamp();\n const lvl = logLevel(level);\n process.stdout.write(` ${time} ${lvl} ${line}\\n`);\n }\n });\n }\n\n if (stderr) {\n stderr.on(\"data\", (data: Buffer) => {\n const lines = data.toString().split(\"\\n\").filter(Boolean);\n for (const line of lines) {\n const time = timestamp();\n const lvl = logLevel(\"error\");\n process.stderr.write(` ${time} ${lvl} ${pc.red(line)}\\n`);\n }\n });\n }\n\n // Handle process exit\n proc.child.on(\"exit\", (code, signal) => {\n if (restartFlag && code !== 0) {\n console.error(`\\n ${pc.yellow(\"โ \")} ${appName} ${pc.dim(`exited (${code}), restarting...`)}`);\n pm.restart(appName).catch(() => {});\n } else {\n console.error(`\\n ${pc.dim(\"โ\")} ${pc.bold(appName)} ${pc.dim(`exited (${code})`)}`);\n }\n });\n\n // Keep running\n await new Promise(() => {});\n } catch (error) {\n console.error(renderError(`Failed to run ${appName}`, String(error)));\n process.exit(1);\n }\n}\n\n// โโโ Command: status โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function statusCommand(_args: string[]): Promise<void> {\n const watchFlag = _args.includes(\"-w\") || _args.includes(\"--watch\");\n\n if (watchFlag) {\n await watchStatus();\n return;\n }\n\n const columns: Column[] = [\n { key: \"name\", label: \"Name\", format: (v) => pc.bold(String(v)) },\n { key: \"source\", label: \"Source\" },\n { key: \"status\", label: \"Status\", format: (v) => statusBadge(v as ProcessStatus) },\n { key: \"pid\", label: \"PID\" },\n { key: \"uptime\", label: \"Uptime\" },\n { key: \"error\", label: \"Last Error\", format: (v) => v ? pc.red(String(v)) : pc.dim(\"-\") },\n ];\n\n // In a real scenario, this would connect to the Fennec server API\n // For now, show a static example or basic process list\n const rows: Row[] = [\n { name: \"backend\", source: \":3000\", status: \"running\", pid: \"12345\", uptime: \"2h 15m\", error: null },\n { name: \"frontend\", source: \":5173\", status: \"running\", pid: \"12346\", uptime: \"2h 15m\", error: null },\n { name: \"database\", source: \":5432\", status: \"running\", pid: \"docker\", uptime: \"5h 30m\", error: null },\n ];\n\n console.error(`\\n ${symbols.fox} ${pc.bold(\"Observed Processes\")}\\n`);\n console.error(renderTable(columns, rows));\n console.error();\n}\n\nasync function watchStatus(): Promise<void> {\n console.error(`\\n ${pc.bold(\"Watching status\")} ${pc.dim(\"(Ctrl+C to stop)\")}\\n`);\n\n const render = () => {\n const columns: Column[] = [\n { key: \"name\", label: \"Name\", format: (v) => pc.bold(String(v)) },\n { key: \"status\", label: \"Status\", format: (v) => statusBadge(v as ProcessStatus) },\n { key: \"pid\", label: \"PID\" },\n { key: \"uptime\", label: \"Uptime\" },\n { key: \"cpu\", label: \"CPU\" },\n { key: \"mem\", label: \"Memory\" },\n ];\n\n const rows: Row[] = [\n { name: \"backend\", status: \"running\", pid: \"12345\", uptime: \"2h 15m\", cpu: \"1.2%\", mem: \"128MB\" },\n { name: \"frontend\", status: \"running\", pid: \"12346\", uptime: \"2h 15m\", cpu: \"0.8%\", mem: \"64MB\" },\n ];\n\n return renderTable(columns, rows, { compact: true });\n };\n\n console.error(render());\n const interval = setInterval(() => {\n process.stdout.write(\"\\x1B[6A\"); // Move cursor up\n console.error(render());\n }, 2000);\n\n process.on(\"SIGINT\", () => {\n clearInterval(interval);\n process.exit(0);\n });\n\n await new Promise(() => {});\n}\n\n// โโโ Command: log โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function logCommand(args: string[]): Promise<void> {\n const name = args[0];\n if (!name) {\n console.error(renderError(\"Missing process name\", \"Usage: fennec log <name> [options]\"));\n process.exit(1);\n }\n\n const linesIndex = args.indexOf(\"--lines\");\n const lines = linesIndex !== -1 ? parseInt(args[linesIndex + 1]!, 10) : 50;\n\n const levelIndex = args.indexOf(\"--level\");\n const level = levelIndex !== -1 ? args[levelIndex + 1] : undefined;\n\n const followFlag = args.includes(\"-f\") || args.includes(\"--follow\");\n\n console.error(`\\n ${symbols.fox} ${pc.bold(\"Logs\")} ${renderAppName(name)} ${pc.dim(`(last ${lines} lines)`)}\\n`);\n\n // Placeholder: in production, this would fetch from the server API\n const mockLogs = [\n { time: \"12:00:00\", level: \"INFO\" as const, msg: \"Server started on port 3000\" },\n { time: \"12:00:01\", level: \"INFO\" as const, msg: \"Database connected\" },\n { time: \"12:00:02\", level: \"WARN\" as const, msg: \"Deprecation: use express@5\" },\n { time: \"12:00:05\", level: \"ERROR\" as const, msg: \"POST /api/login โ 500\" },\n { time: \"12:00:06\", level: \"ERROR\" as const, msg: \"JWT_SECRET not set in environment\" },\n ];\n\n for (const log of mockLogs) {\n const time = pc.dim(`[${log.time}]`);\n const lvl = logLevel(log.level);\n const msg = log.level === \"ERROR\" ? pc.red(log.msg) : log.level === \"WARN\" ? pc.yellow(log.msg) : log.msg;\n console.error(` ${time} ${lvl} ${msg}`);\n }\n\n if (followFlag) {\n console.error(`\\n ${pc.dim(\"Following... (Ctrl+C to stop)\")}\\n`);\n await new Promise(() => {});\n }\n\n console.error();\n}\n\n// โโโ Command: kill โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function killCommand(args: string[]): Promise<void> {\n const name = args[0];\n if (!name) {\n console.error(renderError(\"Missing process name\", \"Usage: fennec kill <name>\"));\n process.exit(1);\n }\n\n const signalIndex = args.indexOf(\"--signal\");\n const signalRaw = signalIndex !== -1 ? args[signalIndex + 1] : \"SIGTERM\";\n const signal = (signalRaw ?? \"SIGTERM\") as NodeJS.Signals;\n\n const confirmed = await confirmPrompt(\n `Stop ${pc.bold(name)} with ${pc.yellow(signal)}?`,\n false,\n );\n\n if (!confirmed) {\n console.error(` ${pc.dim(\"Cancelled\")}`);\n return;\n }\n\n // Placeholder: would connect to Fennec server API\n console.error(`\\n ${pc.green(\"โ\")} ${renderAppName(name)} ${pc.dim(`stopped (${signal})`)}\\n`);\n}\n\n// โโโ Command: restart โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function restartCommand(args: string[]): Promise<void> {\n const name = args[0];\n if (!name) {\n console.error(renderError(\"Missing process name\", \"Usage: fennec restart <name>\"));\n process.exit(1);\n }\n\n const spinner = createSpinner(`Restarting ${name}...`);\n\n // Placeholder: would connect to Fennec server API\n await new Promise((resolve) => setTimeout(resolve, 1000));\n\n spinner.succeed(`${name} restarted`);\n}\n\n// โโโ Command: attach โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function attachCommand(args: string[]): Promise<void> {\n const port = parseInt(args[0]!, 10);\n if (isNaN(port)) {\n console.error(renderError(\"Invalid port\", \"Usage: fennec attach <port> --name <name>\"));\n process.exit(1);\n }\n\n const nameIndex = args.indexOf(\"--name\");\n const rawName = nameIndex !== -1 ? args[nameIndex + 1] : undefined;\n const name = rawName ?? `port-${port}`;\n\n const spinner = createSpinner(`Attaching to :${port}...`);\n\n try {\n const { PortDetector } = await import(\"@plumpslabs/fennec-core\");\n const detector = new PortDetector();\n const info = detector.detectByPort(port);\n\n if (info) {\n spinner.succeed(`Attached to :${port}`);\n console.error(` ${renderKV(\"Name\", renderAppName(name))}`);\n console.error(` ${renderKV(\"PID\", String(info.pid))}`);\n console.error(` ${renderKV(\"Command\", info.command || pc.dim(\"unknown\"))}`);\n } else {\n spinner.fail(`No process found on port ${port}`);\n process.exit(1);\n }\n } catch (error) {\n spinner.fail(`Failed to attach to :${port}`);\n console.error(renderError(\"Error\", String(error)));\n process.exit(1);\n }\n}\n\n// โโโ Command: sessions โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\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.error(`\\n ${pc.dim(\"No saved sessions found.\")}\\n`);\n return;\n }\n\n const columns: Column[] = [\n { key: \"name\", label: \"Name\", format: (v) => pc.bold(String(v)) },\n { key: \"origin\", label: \"Origin\" },\n { key: \"savedAt\", label: \"Saved\", format: (v) => pc.dim(String(v)) },\n ];\n\n const rows: Row[] = sessions.map((s) => ({\n name: s.name,\n origin: s.origin,\n savedAt: new Date(s.savedAt).toLocaleString(),\n }));\n\n console.error(`\\n ${symbols.fox} ${pc.bold(\"Saved Sessions\")}\\n`);\n console.error(renderTable(columns, rows));\n console.error(` ${pc.dim(`${sessions.length} session(s)`)}\\n`);\n}\n\n// โโโ Command: setup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function setupCommand(): Promise<void> {\n console.error(`\\n ${symbols.fox} ${pc.bold(\"Fennec Setup\")}\\n`);\n\n const mcpClient = await selectPrompt(\"Which MCP client are you using?\", [\n { value: \"claude\", label: \"Claude Desktop\", description: \"Anthropic's AI desktop app\" },\n { value: \"cursor\", label: \"Cursor\", description: \"AI-powered code editor\" },\n { value: \"cline\", label: \"Cline\", description: \"VS Code MCP client\" },\n { value: \"other\", label: \"Other MCP client\", description: \"Any MCP-compatible client\" },\n ]);\n\n if (!mcpClient) {\n console.error(` ${pc.dim(\"Setup cancelled.\")}\\n`);\n return;\n }\n\n console.error(`\\n ${pc.green(\"โ\")} Selected: ${pc.bold(mcpClient)}\\n`);\n\n const configSnippet = `{\n \"mcpServers\": {\n \"fennec\": {\n \"command\": \"fennec\",\n \"args\": [\"start\"]\n }\n }\n}`;\n\n console.error(` ${pc.bold(\"Add this to your MCP client config:\")}\\n`);\n console.error(` ${pc.dim(\"```\")}`);\n console.error(configSnippet.split(\"\\n\").map((l) => ` ${l}`).join(\"\\n\"));\n console.error(` ${pc.dim(\"```\")}\\n`);\n console.error(` ${renderSuccess(\"Setup complete!\")} ${pc.dim(\"Run\")} ${renderCommand(\"fennec start\")} ${pc.dim(\"to begin.\")}\\n`);\n}\n\n// โโโ Command: install-browsers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function installBrowsersCommand(): Promise<void> {\n console.error(`\\n ${pc.bold(\"Installing Browser Engines\")}\\n`);\n\n const spinner = createSpinner(\"Installing Chromium...\");\n\n try {\n execSync(\"npx playwright install chromium\", {\n stdio: \"pipe\",\n timeout: 120000,\n });\n spinner.succeed(\"Chromium installed successfully\");\n } catch {\n spinner.fail(\"Failed to install Chromium\");\n console.error(` ${pc.yellow(\"โ\")} Try running: ${renderCommand(\"npx playwright install chromium\")}`);\n }\n\n console.error(`\\n ${pc.green(\"โ\")} Browser installation complete.\\n`);\n}\n\n// โโโ Command: init โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nasync function initCommand(): Promise<void> {\n console.error(`\\n ${pc.bold(\"Initialize Fennec Configuration\")}\\n`);\n\n const configFile = resolve(\"./fennec.config.yaml\");\n\n if (existsSync(configFile)) {\n const overwrite = await confirmPrompt(\n `${pc.yellow(\"fennec.config.yaml\")} already exists. Overwrite?`,\n false,\n );\n\n if (!overwrite) {\n console.error(` ${pc.dim(\"Cancelled.\")}\\n`);\n return;\n }\n }\n\n const spinner = createSpinner(\"Generating configuration...\");\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\n spinner.succeed(`Configuration written to ${pc.bold(configFile)}`);\n console.error(`\\n ${pc.dim(\"Edit the file to customize Fennec behavior.\")}\\n`);\n}\n\n// โโโ Start โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nmain().catch((error) => {\n console.error(renderError(\"Fatal error\", String(error)));\n process.exit(1);\n});\n","import pc from \"picocolors\";\n\n// picocolors 1.x types don't include hex(), but it works at runtime.\n// We expose hexColor for use by the format module.\nexport function hexColor(color: string): (s: string) => string {\n return (pc as any).hex(color);\n}\n\nconst fennecOrange = hexColor(\"#FF6432\");\nconst fennecYellow = hexColor(\"#FFB347\");\n\nconst FOX = `\n ${fennecOrange(\"/\\\\\\\\ /\\\\\\\\\")}\n ${fennecOrange(\"(\")} ${fennecYellow(\"o o\")} ${fennecOrange(\") \") + pc.bold(\"Fennec\")} ${pc.dim(\"v1.10.0\")}\n ${fennecOrange(\"=(\")} ${fennecYellow(\" Y \")} ${fennecOrange(\")=\")} ${pc.dim(\"ears everywhere in your stack.\")}\n ${fennecOrange(\") (\")}\n`;\n\nconst FOX_COMPACT = `${pc.redBright(\" ๐ฆ\")} ${pc.bold(\"Fennec\")} ${pc.dim(\"v1.10.0\")}`;\n\nexport function printBanner(): void {\n if (process.stdout.isTTY) {\n console.error(FOX);\n } else {\n console.error(FOX_COMPACT);\n }\n}\n\nexport function printMiniBanner(): void {\n console.error(FOX_COMPACT + pc.dim(\" โ ears everywhere in your stack.\"));\n}\n\nexport { FOX, FOX_COMPACT };\n","import pc from \"picocolors\";\nimport { hexColor } from \"./banner.js\";\n\nconst fennecOrange = hexColor(\"#FF6432\");\n\nexport function showHelp(): void {\n const sep = fennecOrange(\"โ\".repeat(50));\n\n console.error(`\n\n ${pc.bold(\"Usage:\")} fennec ${pc.dim(\"<command>\")} ${pc.dim(\"[options]\")}\n\n ${sep}\n ${pc.bold(\"Server\")}\n ${sep}\n\n ${pc.cyan(\"start\")} ${pc.dim(\"Start the MCP server (default)\")}\n ${pc.dim(\"--config\")} Path to config file\n ${pc.dim(\"--transport\")} Transport type ${pc.dim(\"(stdio | sse) [default: stdio]\")}\n ${pc.dim(\"--port\")} SSE port ${pc.dim(\"[default: 3333]\")}\n ${pc.dim(\"--api-port\")} Management API port ${pc.dim(\"[default: 3456]\")}\n\n ${sep}\n ${pc.bold(\"Process Management\")}\n ${sep}\n\n ${pc.cyan(\"run\")} ${pc.dim(\"<command>\")} ${pc.dim(\"Run a command under Fennec observation\")}\n ${pc.dim(\"--name\")} Process name ${pc.dim(\"(required)\")}\n ${pc.dim(\"--cwd\")} Working directory\n ${pc.dim(\"--restart\")} Auto-restart on crash\n\n ${pc.cyan(\"status\")} ${pc.dim(\"[name]\")} ${pc.dim(\"Show observed processes\")}\n ${pc.dim(\"-w, --watch\")} Watch mode ${pc.dim(\"(refresh every 2s)\")}\n\n ${pc.cyan(\"log\")} ${pc.dim(\"<name>\")} ${pc.dim(\"Show logs for a process\")}\n ${pc.dim(\"--lines\")} Number of lines ${pc.dim(\"[default: 50]\")}\n ${pc.dim(\"--level\")} Filter by level ${pc.dim(\"(error | warn | info)\")}\n ${pc.dim(\"-f, --follow\")} Follow mode ${pc.dim(\"(tail -f)\")}\n\n ${pc.cyan(\"kill\")} ${pc.dim(\"<name>\")} ${pc.dim(\"Stop a process\")}\n ${pc.dim(\"--signal\")} Signal to send ${pc.dim(\"[default: SIGTERM]\")}\n\n ${pc.cyan(\"restart\")} ${pc.dim(\"<name>\")} ${pc.dim(\"Restart a process\")}\n\n ${sep}\n ${pc.bold(\"Observation\")}\n ${sep}\n\n ${pc.cyan(\"attach\")} ${pc.dim(\"<port>\")} ${pc.dim(\"Observe a process by port\")}\n ${pc.dim(\"--name\")} Process name\n\n ${pc.cyan(\"pipe\")} ${pc.dim(\"Pipe stdin to log watcher\")}\n ${pc.dim(\"--name\")} Watcher name ${pc.dim(\"(required)\")}\n\n ${pc.cyan(\"watch\")} ${pc.dim(\"Watch a log file\")}\n ${pc.dim(\"--file\")} File path ${pc.dim(\"(required)\")}\n ${pc.dim(\"--name\")} Watcher name\n\n ${pc.cyan(\"attach-pid\")} ${pc.dim(\"<pid>\")} ${pc.dim(\"Attach to process by PID\")}\n ${pc.cyan(\"attach-port\")} ${pc.dim(\"<port>\")} ${pc.dim(\"Attach to process by port\")}\n\n ${sep}\n ${pc.bold(\"Configuration\")}\n ${sep}\n\n ${pc.cyan(\"init\")} ${pc.dim(\"Generate fennec.config.yaml\")}\n ${pc.cyan(\"setup\")} ${pc.dim(\"Configure MCP client\")}\n ${pc.cyan(\"install-browsers\")} ${pc.dim(\"Install Playwright browser engines\")}\n ${pc.cyan(\"sessions\")} ${pc.dim(\"List saved auth sessions\")}\n\n ${sep}\n ${pc.bold(\"Other\")}\n ${sep}\n\n ${pc.cyan(\"help\")} ${pc.dim(\"Show this help message\")}\n ${pc.cyan(\"version\")} ${pc.dim(\"Show version\")}\n\n ${pc.dim(\"โ\".repeat(50))}\n\n ${pc.dim(\"Learn more:\")} ${pc.cyan(\"https://github.com/plumpslabs/fennec\")}\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","/**\n * Fennec CLI Formatting System\n *\n * Central module for all CLI output formatting.\n * Uses picocolors (7KB, zero dependencies) for coloring.\n *\n * Design principles:\n * - Minimal dependencies (picocolors only)\n * - Clean, modern, readable output\n * - Consistent spacing and alignment\n * - Fox-themed (๐ฆ) but not overwhelming\n */\n\nimport { createInterface } from \"node:readline\";\nimport pc from \"picocolors\";\n\n// โโโ Color Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n/**\n * Custom hex color formatter.\n * picocolors does have hex() at runtime but lacks TypeScript types for it.\n */\nfunction hex(color: string): (s: string) => string {\n return (pc as any).hex(color);\n}\n\n// Brand colors (runtime-safe, bypass TypeScript limitations)\nconst fennecOrange = hex(\"#FF6432\");\nconst blueAccent = hex(\"#4A90D9\");\nconst redAccent = hex(\"#E94560\");\n\n// โโโ Color Palette โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport const colors = {\n // Brand colors\n primary: fennecOrange,\n secondary: blueAccent,\n accent: redAccent,\n\n // Semantic colors\n success: pc.green,\n error: pc.red,\n warning: pc.yellow,\n info: pc.cyan,\n muted: pc.dim,\n highlight: pc.bold,\n\n // Backgrounds\n bgSuccess: (s: string) => pc.bgGreen(pc.black(` ${s} `)),\n bgError: (s: string) => pc.bgRed(pc.white(` ${s} `)),\n bgWarning: (s: string) => pc.bgYellow(pc.black(` ${s} `)),\n bgInfo: (s: string) => pc.bgCyan(pc.black(` ${s} `)),\n} as const;\n\n// โโโ Symbols โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport const symbols = {\n // Status\n active: pc.green(\"โ\"),\n inactive: pc.dim(\"โ\"),\n error: pc.red(\"โ\"),\n warning: pc.yellow(\"โ \"),\n success: pc.green(\"โ\"),\n info: pc.cyan(\"โน\"),\n pending: pc.yellow(\"โ\"),\n\n // UI\n bullet: pc.dim(\"โ\"),\n dot: pc.dim(\"ยท\"),\n arrow: pc.cyan(\"โ\"),\n pointer: fennecOrange(\"โถ\"),\n separator: pc.dim(\"โ\"),\n\n // Fennec\n fox: \"๐ฆ\",\n ears: \"โ\",\n} as const;\n\n// โโโ Status Badges โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport type ProcessStatus = \"running\" | \"stopped\" | \"error\" | \"starting\" | \"unknown\";\n\nexport function statusBadge(status: ProcessStatus): string {\n switch (status) {\n case \"running\": return pc.green(\"โ running\");\n case \"stopped\": return pc.dim(\"โ stopped\");\n case \"error\": return pc.red(\"โ error\");\n case \"starting\": return pc.yellow(\"โ starting\");\n case \"unknown\": return pc.dim(\"? unknown\");\n }\n}\n\nexport type LogLevel = \"error\" | \"warn\" | \"info\" | \"debug\" | \"verbose\";\n\nexport function logLevel(label: string): string {\n const upper = label.toUpperCase().padEnd(5);\n switch (label.toLowerCase()) {\n case \"error\": return pc.bgRed(pc.white(` ${upper} `));\n case \"warn\": return pc.bgYellow(pc.black(` ${upper} `));\n case \"info\": return pc.bgCyan(pc.black(` ${upper} `));\n case \"debug\": return pc.dim(pc.italic(upper));\n case \"verbose\": return pc.dim(pc.italic(upper));\n default: return pc.dim(upper);\n }\n}\n\n// โโโ Table Renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport interface Column {\n key: string;\n label: string;\n align?: \"left\" | \"right\" | \"center\";\n width?: number;\n format?: (value: unknown) => string;\n}\n\nexport interface Row {\n [key: string]: unknown;\n}\n\nexport function renderTable(columns: Column[], rows: Row[], options?: { padding?: number; compact?: boolean }): string {\n const pad = options?.padding ?? 1;\n const isCompact = options?.compact ?? false;\n\n if (rows.length === 0) {\n return pc.dim(\" (no data)\");\n }\n\n // Calculate column widths\n const widths: number[] = columns.map((col) => {\n const headerLen = col.label.length;\n const maxDataLen = rows.reduce((max, row) => {\n const val = String(row[col.key] ?? \"-\");\n return Math.max(max, val.length);\n }, 0);\n return Math.max(headerLen, maxDataLen, col.width ?? 0) + pad * 2;\n });\n\n // โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n const hLine = (left: string, mid: string, right: string, fill = \"โ\") => {\n return pc.dim(\n left + widths.map((w) => fill.repeat(w)).join(mid) + right,\n );\n };\n\n const formatCell = (text: string, colIdx: number) => {\n const col = columns[colIdx]!;\n const w = widths[colIdx]!;\n\n // Truncate if too long\n const display = text.length > w ? text.slice(0, w - 1) + \"โฆ\" : text;\n const padded = display.padEnd(w);\n\n const align = col.align ?? \"left\";\n if (align === \"right\") return padded.padStart(w);\n if (align === \"center\") {\n const leftPad = Math.floor((w - display.length) / 2);\n return \" \".repeat(leftPad) + display + \" \".repeat(w - display.length - leftPad);\n }\n return padded;\n };\n\n const renderRow = (row: Row): string => {\n const cells = columns.map((col, i) => {\n const raw = row[col.key];\n const formatted = col.format ? col.format(raw) : String(raw ?? \"-\");\n return formatCell(formatted, i);\n });\n return pc.dim(\"โ\") + cells.join(pc.dim(\"โ\")) + pc.dim(\"โ\");\n };\n\n // โโโ Build table โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n const lines: string[] = [];\n\n if (!isCompact) {\n lines.push(hLine(\"โ\", \"โฌ\", \"โ\"));\n }\n\n // Header row\n const headerCells = columns.map((col, i) => {\n return formatCell(pc.bold(col.label), i);\n });\n lines.push(pc.dim(\"โ\") + headerCells.join(pc.dim(\"โ\")) + pc.dim(\"โ\"));\n\n if (!isCompact) {\n lines.push(hLine(\"โ\", \"โผ\", \"โค\"));\n } else {\n lines.push(pc.dim(\"โ\") + widths.map((w) => pc.dim(\"โ\").repeat(w)).join(pc.dim(\"โผ\")) + pc.dim(\"โค\"));\n }\n\n // Data rows\n for (const row of rows) {\n const rendered = renderRow(row);\n // Highlight if row has an error\n if (row._error) {\n lines.push(colors.error(rendered));\n } else {\n lines.push(rendered);\n }\n }\n\n if (!isCompact) {\n lines.push(hLine(\"โ\", \"โด\", \"โ\"));\n }\n\n return lines.join(\"\\n\");\n}\n\n// โโโ Key-Value Display โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function renderKV(key: string, value: string, options?: { indent?: number; separator?: string }): string {\n const indent = options?.indent ?? 2;\n const sep = options?.separator ?? \":\";\n const pad = \" \".repeat(indent);\n return `${pad}${pc.dim(key + sep)} ${value}`;\n}\n\nexport function renderKVColor(key: string, value: string, color: (s: string) => string = pc.bold): string {\n return ` ${pc.dim(key + \":\")} ${color(value)}`;\n}\n\nexport function renderSection(title: string, content: string): string {\n const line = pc.dim(\"โ\".repeat(40));\n return `\\n ${pc.bold(title)}\\n ${line}\\n${content}\\n`;\n}\n\n// โโโ Box Renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function renderBox(title: string, content: string, options?: {\n width?: number;\n borderColor?: (s: string) => string;\n titleColor?: (s: string) => string;\n}): string {\n const width = options?.width ?? 50;\n const border = options?.borderColor ?? pc.dim;\n const titleColor = options?.titleColor ?? pc.bold;\n\n const lines = content.split(\"\\n\");\n const wrapped = lines.map((l) => {\n const clean = l.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n return l + \" \".repeat(Math.max(0, width - clean.length - 2));\n });\n\n const top = border(`โโ ${titleColor(title)} ${\"โ\".repeat(Math.max(0, width - title.length - 4))}โ`);\n const mid = wrapped.map((l) => border(\"โ \") + l + border(\" โ\")).join(\"\\n\");\n const bot = border(`โ${\"โ\".repeat(width)}โ`);\n\n return `\\n${top}\\n${mid}\\n${bot}\\n`;\n}\n\n// โโโ Spinner (minimal, no dependencies) โโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport interface Spinner {\n update(text: string): void;\n succeed(text: string): void;\n fail(text: string): void;\n warn(text: string): void;\n stop(): void;\n}\n\nexport function createSpinner(text: string): Spinner {\n const frames = [\"โ \", \"โ \", \"โ น\", \"โ ธ\", \"โ ผ\", \"โ ด\", \"โ ฆ\", \"โ ง\", \"โ \", \"โ \"];\n let i = 0;\n let running = true;\n\n const interval = setInterval(() => {\n if (!running) return;\n process.stdout.write(`\\r${pc.cyan(frames[i])} ${text}`);\n i = (i + 1) % frames.length;\n }, 80);\n\n return {\n update(newText: string) {\n if (!running) return;\n process.stdout.write(`\\r${pc.cyan(frames[i])} ${newText}`);\n },\n succeed(msg: string) {\n running = false;\n clearInterval(interval);\n process.stdout.write(`\\r${pc.green(\"โ\")} ${msg}\\n`);\n },\n fail(msg: string) {\n running = false;\n clearInterval(interval);\n process.stdout.write(`\\r${pc.red(\"โ\")} ${msg}\\n`);\n },\n warn(msg: string) {\n running = false;\n clearInterval(interval);\n process.stdout.write(`\\r${pc.yellow(\"โ \")} ${msg}\\n`);\n },\n stop() {\n running = false;\n clearInterval(interval);\n },\n };\n}\n\n// โโโ Interactive Select (enhanced) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport async function selectPrompt<T extends string>(\n message: string,\n options: { value: T; label: string; description?: string }[],\n): Promise<T | null> {\n console.log(`\\n ${pc.bold(message)}\\n`);\n options.forEach((opt, i) => {\n const num = pc.cyan(` ${i + 1}`);\n console.log(`${num} ${opt.label}`);\n if (opt.description) {\n console.log(` ${pc.dim(opt.description)}`);\n }\n });\n console.log(` ${pc.dim(\"0) Cancel\")}\\n`);\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = await new Promise<string>((resolve) => {\n rl.question(` ${pc.bold(\"Enter number\")} ${pc.dim(\"(0-\" + options.length + \")\")}: `, (ans) => {\n rl.close();\n resolve(ans.trim());\n });\n });\n\n const num = parseInt(answer, 10);\n if (isNaN(num) || num === 0) return null;\n const selected = options[num - 1];\n if (!selected) {\n console.log(` ${pc.red(\"Invalid selection.\")}`);\n return null;\n }\n return selected.value;\n}\n\nexport async function confirmPrompt(message: string, defaultValue = false): Promise<boolean> {\n const hint = defaultValue ? pc.bold(\"Y\") + pc.dim(\"/n\") : pc.dim(\"y/\") + pc.bold(\"N\");\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = await new Promise<string>((resolve) => {\n rl.question(`\\n ${message} ${pc.dim(\"(\" + hint + \")\")}: `, (ans) => {\n rl.close();\n resolve(ans.trim().toLowerCase());\n });\n });\n\n if (!answer) return defaultValue;\n return answer === \"y\" || answer === \"yes\";\n}\n\n// โโโ Divider โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function divider(text?: string): string {\n if (text) {\n const len = 50 - text.length - 2;\n const left = Math.floor(len / 2);\n const right = len - left;\n return pc.dim(\"โ\".repeat(left) + ` ${text} ` + \"โ\".repeat(right));\n }\n return pc.dim(\"โ\".repeat(50));\n}\n\n// โโโ Timestamp โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function timestamp(date?: Date): string {\n const d = date ?? new Date();\n const time = d.toLocaleTimeString(\"en-US\", { hour12: false });\n return pc.dim(`[${time}]`);\n}\n\n// โโโ Command Line โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function renderCommand(cmd: string): string {\n return fennecOrange(`$ ${cmd}`);\n}\n\nexport function renderAppName(name: string): string {\n return fennecOrange(pc.bold(name));\n}\n\n// โโโ Error Display โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function renderError(title: string, details?: string, suggestions?: string[]): string {\n const lines: string[] = [];\n lines.push(`\\n ${pc.bgRed(pc.white(\" ERROR \"))} ${pc.bold(title)}`);\n if (details) {\n lines.push(` ${pc.dim(details)}`);\n }\n if (suggestions && suggestions.length > 0) {\n lines.push(`\\n ${pc.bold(\"Suggestions:\")}`);\n for (const s of suggestions) {\n lines.push(` ${pc.cyan(\"โ\")} ${s}`);\n }\n }\n return lines.join(\"\\n\") + \"\\n\";\n}\n\nexport function renderSuccess(message: string): string {\n return ` ${pc.green(\"โ\")} ${message}`;\n}\n\nexport function renderWarning(message: string): string {\n return ` ${pc.yellow(\"โ \")} ${message}`;\n}\n"],"mappings":"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","names":["existsSync","resolve","pc","pc","fennecOrange","pc","args","args","args","args","pc","fennecOrange","pc","num","resolve","fennecOrange","pc","args","resolve","existsSync"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumpslabs/fennec-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Fennec CLI โ pipe, attach, watch, and start the Fennec MCP server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"LICENSE"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"
|
|
41
|
+
"picocolors": "^1.1.1",
|
|
42
|
+
"@plumpslabs/fennec-core": "1.11.0"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"playwright": "^1.42.0"
|