@chriscode/devmux 1.0.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-32R7KDZB.js +319 -0
- package/dist/chunk-66UOCF5R.js +36 -0
- package/dist/chunk-6EU6ODXX.js +372 -0
- package/dist/chunk-ALENFKSX.js +631 -0
- package/dist/chunk-T6I3CPOV.js +437 -0
- package/dist/cli.js +484 -9
- package/dist/dashboard-3GHLOSV3.js +8 -0
- package/dist/index.d.ts +215 -10
- package/dist/index.js +24 -5
- package/dist/server-manager-6EZWZK56.js +106 -0
- package/dist/skill/SKILL.md +118 -0
- package/dist/skill/references/SETUP.md +109 -0
- package/dist/watch/watcher-cli.d.ts +1 -0
- package/dist/watch/watcher-cli.js +32 -0
- package/package.json +7 -3
- package/dist/chunk-7JJYYMUP.js +0 -542
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,246 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
attachService,
|
|
4
3
|
discoverFromTurbo,
|
|
5
|
-
ensureService,
|
|
6
4
|
formatDiscoveredConfig,
|
|
5
|
+
getAllWatcherStatuses,
|
|
6
|
+
runWithServices,
|
|
7
|
+
startAllWatchers,
|
|
8
|
+
startWatcher,
|
|
9
|
+
stopAllWatchers,
|
|
10
|
+
stopWatcher
|
|
11
|
+
} from "./chunk-6EU6ODXX.js";
|
|
12
|
+
import {
|
|
13
|
+
clearQueue,
|
|
14
|
+
getPendingEvents
|
|
15
|
+
} from "./chunk-32R7KDZB.js";
|
|
16
|
+
import {
|
|
17
|
+
attachService,
|
|
18
|
+
ensureService,
|
|
7
19
|
getAllStatus,
|
|
20
|
+
getProcessOnPort,
|
|
21
|
+
init_loader,
|
|
8
22
|
loadConfig,
|
|
9
|
-
|
|
23
|
+
loader_exports,
|
|
24
|
+
restartService,
|
|
10
25
|
stopAllServices,
|
|
11
26
|
stopService
|
|
12
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-ALENFKSX.js";
|
|
28
|
+
import {
|
|
29
|
+
__toCommonJS,
|
|
30
|
+
init_esm_shims
|
|
31
|
+
} from "./chunk-66UOCF5R.js";
|
|
13
32
|
|
|
14
33
|
// src/cli.ts
|
|
34
|
+
init_esm_shims();
|
|
35
|
+
init_loader();
|
|
15
36
|
import { defineCommand, runMain } from "citty";
|
|
37
|
+
import { mkdirSync, existsSync, cpSync } from "fs";
|
|
38
|
+
import { dirname, join } from "path";
|
|
39
|
+
import { fileURLToPath } from "url";
|
|
40
|
+
|
|
41
|
+
// src/utils/diagnose.ts
|
|
42
|
+
init_esm_shims();
|
|
43
|
+
import { networkInterfaces } from "os";
|
|
44
|
+
function getAddressesToCheck() {
|
|
45
|
+
const addresses = ["127.0.0.1", "0.0.0.0", "::1"];
|
|
46
|
+
const interfaces = networkInterfaces();
|
|
47
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
48
|
+
if (!addrs) continue;
|
|
49
|
+
for (const addr of addrs) {
|
|
50
|
+
if (addr.internal) continue;
|
|
51
|
+
if (addr.address.startsWith("fe80")) continue;
|
|
52
|
+
if (addr.address.startsWith("169.254")) continue;
|
|
53
|
+
if (!addresses.includes(addr.address)) {
|
|
54
|
+
addresses.push(addr.address);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return addresses;
|
|
59
|
+
}
|
|
60
|
+
async function checkPortStatus(port, address) {
|
|
61
|
+
const proc = await getProcessOnPort(port);
|
|
62
|
+
if (proc) {
|
|
63
|
+
return {
|
|
64
|
+
address,
|
|
65
|
+
status: "occupied",
|
|
66
|
+
process: { name: proc.name, pid: proc.pid, cmd: proc.cmd }
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { address, status: "free" };
|
|
70
|
+
}
|
|
71
|
+
function detectBlockerType(processName, cmd) {
|
|
72
|
+
const lowerName = processName.toLowerCase();
|
|
73
|
+
const lowerCmd = cmd?.toLowerCase() || "";
|
|
74
|
+
const vpnPatterns = [
|
|
75
|
+
{ pattern: /tailscale/, name: "Tailscale" },
|
|
76
|
+
{ pattern: /wireguard/, name: "WireGuard" },
|
|
77
|
+
{ pattern: /openvpn/, name: "OpenVPN" },
|
|
78
|
+
{ pattern: /anyconnect/, name: "Cisco AnyConnect" },
|
|
79
|
+
{ pattern: /forticlient/, name: "FortiClient" },
|
|
80
|
+
{ pattern: /globalprotect/, name: "GlobalProtect" },
|
|
81
|
+
{ pattern: /netextender/, name: "NetExtender" },
|
|
82
|
+
{ pattern: /warp/, name: "Cloudflare WARP" },
|
|
83
|
+
{ pattern: /zerotier/, name: "ZeroTier" },
|
|
84
|
+
{ pattern: /hamachi/, name: "LogMeIn Hamachi" },
|
|
85
|
+
{ pattern: /nordvpn/, name: "NordVPN" },
|
|
86
|
+
{ pattern: /expressvpn/, name: "ExpressVPN" },
|
|
87
|
+
{ pattern: /protonvpn/, name: "ProtonVPN" },
|
|
88
|
+
{ pattern: /surfshark/, name: "Surfshark" },
|
|
89
|
+
{ pattern: /tunnelbear/, name: "TunnelBear" },
|
|
90
|
+
{ pattern: /ipvanish/, name: "IPVanish" }
|
|
91
|
+
];
|
|
92
|
+
for (const { pattern, name } of vpnPatterns) {
|
|
93
|
+
if (pattern.test(lowerName) || pattern.test(lowerCmd)) {
|
|
94
|
+
return { type: "vpn", name };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const dockerPatterns = [
|
|
98
|
+
{ pattern: /docker/, name: "Docker" },
|
|
99
|
+
{ pattern: /containerd/, name: "containerd" },
|
|
100
|
+
{ pattern: /podman/, name: "Podman" },
|
|
101
|
+
{ pattern: /nerdctl/, name: "nerdctl" }
|
|
102
|
+
];
|
|
103
|
+
for (const { pattern, name } of dockerPatterns) {
|
|
104
|
+
if (pattern.test(lowerName) || pattern.test(lowerCmd)) {
|
|
105
|
+
return { type: "docker", name };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const systemPatterns = [
|
|
109
|
+
{ pattern: /systemd/, name: "systemd" },
|
|
110
|
+
{ pattern: /launchd/, name: "launchd" },
|
|
111
|
+
{ pattern: /inetd/, name: "inetd" },
|
|
112
|
+
{ pattern: /xinetd/, name: "xinetd" }
|
|
113
|
+
];
|
|
114
|
+
for (const { pattern, name } of systemPatterns) {
|
|
115
|
+
if (pattern.test(lowerName) || pattern.test(lowerCmd)) {
|
|
116
|
+
return { type: "system", name };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { type: "other", name: processName };
|
|
120
|
+
}
|
|
121
|
+
function generateSuggestion(blockerType, blockerName, port) {
|
|
122
|
+
switch (blockerType) {
|
|
123
|
+
case "vpn":
|
|
124
|
+
if (blockerName === "Tailscale") {
|
|
125
|
+
return `Tailscale may be proxying this port. Run:
|
|
126
|
+
tailscale serve status
|
|
127
|
+
tailscale serve reset # to clear all
|
|
128
|
+
|
|
129
|
+
Or check for Funnel:
|
|
130
|
+
tailscale funnel status`;
|
|
131
|
+
}
|
|
132
|
+
return `${blockerName} may be intercepting traffic on this port. Check ${blockerName} settings or temporarily disconnect to test.`;
|
|
133
|
+
case "docker":
|
|
134
|
+
return `A Docker container is using this port. Check running containers:
|
|
135
|
+
docker ps --format "table {{.Names}}\\t{{.Ports}}"
|
|
136
|
+
|
|
137
|
+
Stop the container or map it to a different port.`;
|
|
138
|
+
case "system":
|
|
139
|
+
return `A system service (${blockerName}) is listening on this port. You may need to:
|
|
140
|
+
sudo lsof -i :${port}
|
|
141
|
+
sudo systemctl stop <service> # if using systemd`;
|
|
142
|
+
default:
|
|
143
|
+
return `Process is occupying the port. To free it:
|
|
144
|
+
lsof -ti :${port} | xargs kill -9`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function diagnosePort(port, serviceName) {
|
|
148
|
+
const addresses = getAddressesToCheck();
|
|
149
|
+
const bindings = [];
|
|
150
|
+
for (const address of addresses) {
|
|
151
|
+
const binding = await checkPortStatus(port, address);
|
|
152
|
+
if (binding) {
|
|
153
|
+
bindings.push(binding);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const occupiedBindings = bindings.filter((b) => b.status === "occupied");
|
|
157
|
+
const isBlocked = occupiedBindings.length > 0;
|
|
158
|
+
let blockerType;
|
|
159
|
+
let blockerName;
|
|
160
|
+
let suggestion;
|
|
161
|
+
if (isBlocked && occupiedBindings.length > 0) {
|
|
162
|
+
for (const binding of occupiedBindings) {
|
|
163
|
+
if (binding.process) {
|
|
164
|
+
const detected = detectBlockerType(binding.process.name, binding.process.cmd);
|
|
165
|
+
if (detected) {
|
|
166
|
+
if (detected.type === "vpn") {
|
|
167
|
+
blockerType = detected.type;
|
|
168
|
+
blockerName = detected.name;
|
|
169
|
+
break;
|
|
170
|
+
} else if (!blockerType) {
|
|
171
|
+
blockerType = detected.type;
|
|
172
|
+
blockerName = detected.name;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!blockerName && occupiedBindings[0].process) {
|
|
178
|
+
blockerName = occupiedBindings[0].process.name;
|
|
179
|
+
blockerType = "other";
|
|
180
|
+
}
|
|
181
|
+
if (blockerType && blockerName) {
|
|
182
|
+
suggestion = generateSuggestion(blockerType, blockerName, port);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
port,
|
|
187
|
+
serviceName,
|
|
188
|
+
bindings,
|
|
189
|
+
summary: {
|
|
190
|
+
isBlocked,
|
|
191
|
+
blockerType,
|
|
192
|
+
blockerName,
|
|
193
|
+
suggestion
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function formatDiagnosis(result) {
|
|
198
|
+
const lines = [];
|
|
199
|
+
lines.push(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
200
|
+
lines.push(` Port Diagnosis: ${result.serviceName}`);
|
|
201
|
+
lines.push(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
202
|
+
lines.push("");
|
|
203
|
+
lines.push(`Port ${result.port} status:`);
|
|
204
|
+
lines.push("");
|
|
205
|
+
for (const binding of result.bindings) {
|
|
206
|
+
const icon = binding.status === "free" ? "\u2705" : "\u274C";
|
|
207
|
+
const addressDisplay = binding.address.padEnd(20);
|
|
208
|
+
lines.push(` ${icon} ${addressDisplay} ${binding.status.toUpperCase()}`);
|
|
209
|
+
if (binding.process) {
|
|
210
|
+
lines.push(` \u2514\u2500 ${binding.process.name} (PID ${binding.process.pid})`);
|
|
211
|
+
if (binding.process.cmd) {
|
|
212
|
+
const shortCmd = binding.process.cmd.length > 60 ? binding.process.cmd.slice(0, 57) + "..." : binding.process.cmd;
|
|
213
|
+
lines.push(` ${shortCmd}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
lines.push("");
|
|
218
|
+
if (result.summary.isBlocked) {
|
|
219
|
+
lines.push("\u26A0\uFE0F PORT IS BLOCKED");
|
|
220
|
+
if (result.summary.blockerName) {
|
|
221
|
+
lines.push(` Blocker: ${result.summary.blockerName}`);
|
|
222
|
+
}
|
|
223
|
+
lines.push("");
|
|
224
|
+
if (result.summary.suggestion) {
|
|
225
|
+
lines.push("Suggestion:");
|
|
226
|
+
for (const line of result.summary.suggestion.split("\n")) {
|
|
227
|
+
lines.push(` ${line}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
lines.push("\u2705 Port is free on all interfaces");
|
|
232
|
+
}
|
|
233
|
+
lines.push("");
|
|
234
|
+
return lines.join("\n");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/cli.ts
|
|
238
|
+
if (process.platform === "win32" && !process.env.WSL_DISTRO_NAME) {
|
|
239
|
+
console.error("\u274C DevMux requires Windows Subsystem for Linux (WSL) on Windows");
|
|
240
|
+
console.error(" Install WSL: https://docs.microsoft.com/en-us/windows/wsl/install");
|
|
241
|
+
console.error(" Then run DevMux from within your WSL environment");
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
16
244
|
var ensure = defineCommand({
|
|
17
245
|
meta: { name: "ensure", description: "Ensure a service is running (idempotent)" },
|
|
18
246
|
args: {
|
|
@@ -35,16 +263,20 @@ var status = defineCommand({
|
|
|
35
263
|
const config = loadConfig();
|
|
36
264
|
const statuses = await getAllStatus(config);
|
|
37
265
|
if (args.json) {
|
|
38
|
-
console.log(JSON.stringify(statuses, null, 2));
|
|
266
|
+
console.log(JSON.stringify({ instanceId: config.instanceId || null, services: statuses }, null, 2));
|
|
39
267
|
return;
|
|
40
268
|
}
|
|
41
269
|
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
42
270
|
console.log(" Service Status");
|
|
271
|
+
if (config.instanceId) {
|
|
272
|
+
console.log(` Instance: ${config.instanceId}`);
|
|
273
|
+
}
|
|
43
274
|
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
44
275
|
console.log("");
|
|
45
276
|
for (const s of statuses) {
|
|
46
277
|
const icon = s.healthy ? "\u2705" : "\u274C";
|
|
47
|
-
const
|
|
278
|
+
const portDisplay = s.resolvedPort ?? s.port;
|
|
279
|
+
const portInfo = portDisplay ? ` (port ${portDisplay})` : "";
|
|
48
280
|
console.log(`${icon} ${s.name}${portInfo}: ${s.healthy ? "Running" : "Not running"}`);
|
|
49
281
|
if (s.tmuxSession) {
|
|
50
282
|
console.log(` \u2514\u2500 tmux: ${s.tmuxSession}`);
|
|
@@ -71,6 +303,34 @@ var stop = defineCommand({
|
|
|
71
303
|
}
|
|
72
304
|
}
|
|
73
305
|
});
|
|
306
|
+
var restart = defineCommand({
|
|
307
|
+
meta: { name: "restart", description: "Restart a service (stop + start)" },
|
|
308
|
+
args: {
|
|
309
|
+
service: { type: "positional", description: "Service name", required: true },
|
|
310
|
+
timeout: { type: "string", description: "Startup timeout in seconds" },
|
|
311
|
+
force: { type: "boolean", description: "Also kill processes on ports before restarting" }
|
|
312
|
+
},
|
|
313
|
+
async run({ args }) {
|
|
314
|
+
const config = loadConfig();
|
|
315
|
+
await restartService(config, args.service, {
|
|
316
|
+
timeout: args.timeout ? parseInt(args.timeout) : void 0,
|
|
317
|
+
killPorts: args.force
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
var start = defineCommand({
|
|
322
|
+
meta: { name: "start", description: "Start a service (alias for ensure)" },
|
|
323
|
+
args: {
|
|
324
|
+
service: { type: "positional", description: "Service name", required: true },
|
|
325
|
+
timeout: { type: "string", description: "Startup timeout in seconds" }
|
|
326
|
+
},
|
|
327
|
+
async run({ args }) {
|
|
328
|
+
const config = loadConfig();
|
|
329
|
+
await ensureService(config, args.service, {
|
|
330
|
+
timeout: args.timeout ? parseInt(args.timeout) : void 0
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
74
334
|
var attach = defineCommand({
|
|
75
335
|
meta: { name: "attach", description: "Attach to a service's tmux session" },
|
|
76
336
|
args: {
|
|
@@ -86,7 +346,7 @@ var run = defineCommand({
|
|
|
86
346
|
args: {
|
|
87
347
|
with: { type: "string", description: "Comma-separated services to ensure", required: true },
|
|
88
348
|
"no-stop": { type: "boolean", description: "Don't stop services on exit" },
|
|
89
|
-
|
|
349
|
+
"no-dashboard": { type: "boolean", description: "Skip auto-launching the dashboard" }
|
|
90
350
|
},
|
|
91
351
|
async run({ args }) {
|
|
92
352
|
const config = loadConfig();
|
|
@@ -98,7 +358,8 @@ var run = defineCommand({
|
|
|
98
358
|
}
|
|
99
359
|
const exitCode = await runWithServices(config, command, {
|
|
100
360
|
services,
|
|
101
|
-
stopOnExit: !args["no-stop"]
|
|
361
|
+
stopOnExit: !args["no-stop"],
|
|
362
|
+
dashboard: args["no-dashboard"] ? false : void 0
|
|
102
363
|
});
|
|
103
364
|
process.exit(exitCode);
|
|
104
365
|
}
|
|
@@ -143,6 +404,213 @@ var init = defineCommand({
|
|
|
143
404
|
console.log("Save this as devmux.config.json in your project root.");
|
|
144
405
|
}
|
|
145
406
|
});
|
|
407
|
+
var diagnose = defineCommand({
|
|
408
|
+
meta: { name: "diagnose", description: "Diagnose port issues for a service" },
|
|
409
|
+
args: {
|
|
410
|
+
service: { type: "positional", description: "Service name", required: true },
|
|
411
|
+
json: { type: "boolean", description: "Output as JSON" }
|
|
412
|
+
},
|
|
413
|
+
async run({ args }) {
|
|
414
|
+
const config = loadConfig();
|
|
415
|
+
const { getResolvedPort } = (init_loader(), __toCommonJS(loader_exports));
|
|
416
|
+
const port = getResolvedPort(config, args.service);
|
|
417
|
+
if (port === void 0) {
|
|
418
|
+
console.error(`\u274C No port configured for service: ${args.service}`);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
const result = await diagnosePort(port, args.service);
|
|
422
|
+
if (args.json) {
|
|
423
|
+
console.log(JSON.stringify(result, null, 2));
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
console.log(formatDiagnosis(result));
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
var installSkill = defineCommand({
|
|
430
|
+
meta: { name: "install-skill", description: "Install DevMux skills to .claude/skills" },
|
|
431
|
+
run() {
|
|
432
|
+
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
433
|
+
const skillDir = join(__dirname2, "skill");
|
|
434
|
+
const targetDir = join(process.cwd(), ".claude", "skills", "devmux");
|
|
435
|
+
try {
|
|
436
|
+
if (!existsSync(skillDir)) {
|
|
437
|
+
throw new Error(`Skill directory not found at ${skillDir}`);
|
|
438
|
+
}
|
|
439
|
+
mkdirSync(targetDir, { recursive: true });
|
|
440
|
+
cpSync(skillDir, targetDir, { recursive: true });
|
|
441
|
+
console.log(`\u2705 Installed DevMux skill to .claude/skills/devmux/`);
|
|
442
|
+
} catch (e) {
|
|
443
|
+
console.error("\u274C Failed to install skills:");
|
|
444
|
+
console.error(e);
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
var watchStart = defineCommand({
|
|
450
|
+
meta: { name: "start", description: "Start watching a service for errors" },
|
|
451
|
+
args: {
|
|
452
|
+
service: { type: "positional", description: "Service name (or 'all')" }
|
|
453
|
+
},
|
|
454
|
+
run({ args }) {
|
|
455
|
+
const config = loadConfig();
|
|
456
|
+
const serviceName = args.service;
|
|
457
|
+
if (!serviceName || serviceName === "all") {
|
|
458
|
+
startAllWatchers(config);
|
|
459
|
+
} else {
|
|
460
|
+
startWatcher(config, serviceName);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
var watchStop = defineCommand({
|
|
465
|
+
meta: { name: "stop", description: "Stop watching a service" },
|
|
466
|
+
args: {
|
|
467
|
+
service: { type: "positional", description: "Service name (or 'all')" }
|
|
468
|
+
},
|
|
469
|
+
run({ args }) {
|
|
470
|
+
const config = loadConfig();
|
|
471
|
+
const serviceName = args.service;
|
|
472
|
+
if (!serviceName || serviceName === "all") {
|
|
473
|
+
stopAllWatchers(config);
|
|
474
|
+
} else {
|
|
475
|
+
stopWatcher(config, serviceName);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
var watchStatus = defineCommand({
|
|
480
|
+
meta: { name: "status", description: "Show watcher status" },
|
|
481
|
+
args: {
|
|
482
|
+
json: { type: "boolean", description: "Output as JSON" }
|
|
483
|
+
},
|
|
484
|
+
run({ args }) {
|
|
485
|
+
const config = loadConfig();
|
|
486
|
+
const statuses = getAllWatcherStatuses(config);
|
|
487
|
+
if (args.json) {
|
|
488
|
+
console.log(JSON.stringify(statuses, null, 2));
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
console.log("");
|
|
492
|
+
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
493
|
+
console.log(" Watcher Status");
|
|
494
|
+
console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
495
|
+
console.log("");
|
|
496
|
+
for (const s of statuses) {
|
|
497
|
+
const icon = s.pipeActive ? "\u{1F441}\uFE0F" : "\u26AB";
|
|
498
|
+
console.log(`${icon} ${s.service}: ${s.pipeActive ? "Watching" : "Not watching"}`);
|
|
499
|
+
if (s.pipeActive) {
|
|
500
|
+
console.log(` \u2514\u2500 session: ${s.sessionName}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
console.log("");
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
var watchQueue = defineCommand({
|
|
507
|
+
meta: { name: "queue", description: "Show pending errors in queue" },
|
|
508
|
+
args: {
|
|
509
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
510
|
+
clear: { type: "boolean", description: "Clear the queue" }
|
|
511
|
+
},
|
|
512
|
+
run({ args }) {
|
|
513
|
+
if (args.clear) {
|
|
514
|
+
clearQueue();
|
|
515
|
+
console.log("\u2705 Queue cleared");
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const events = getPendingEvents();
|
|
519
|
+
if (args.json) {
|
|
520
|
+
console.log(JSON.stringify(events, null, 2));
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (events.length === 0) {
|
|
524
|
+
console.log("No pending errors in queue.");
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
console.log("");
|
|
528
|
+
console.log(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
529
|
+
console.log(` Pending Errors (${events.length})`);
|
|
530
|
+
console.log(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
|
|
531
|
+
console.log("");
|
|
532
|
+
for (const e of events) {
|
|
533
|
+
const severityIcon = e.severity === "critical" ? "\u{1F534}" : e.severity === "error" ? "\u{1F7E0}" : e.severity === "warning" ? "\u{1F7E1}" : "\u{1F535}";
|
|
534
|
+
console.log(`${severityIcon} [${e.service}] ${e.pattern}`);
|
|
535
|
+
console.log(` ${e.rawContent.slice(0, 80)}${e.rawContent.length > 80 ? "..." : ""}`);
|
|
536
|
+
console.log(` \u2514\u2500 ${e.firstSeen}`);
|
|
537
|
+
console.log("");
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
var watch = defineCommand({
|
|
542
|
+
meta: { name: "watch", description: "Manage error watchers for services" },
|
|
543
|
+
subCommands: {
|
|
544
|
+
start: watchStart,
|
|
545
|
+
stop: watchStop,
|
|
546
|
+
status: watchStatus,
|
|
547
|
+
queue: watchQueue
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
var telemetryStart = defineCommand({
|
|
551
|
+
meta: { name: "start", description: "Start the telemetry server" },
|
|
552
|
+
args: {
|
|
553
|
+
port: { type: "string", description: "Port to listen on (default: 9876)" },
|
|
554
|
+
host: { type: "string", description: "Host to bind to (default: 0.0.0.0)" }
|
|
555
|
+
},
|
|
556
|
+
async run({ args }) {
|
|
557
|
+
const { startServer } = await import("./server-manager-6EZWZK56.js");
|
|
558
|
+
startServer({
|
|
559
|
+
port: args.port ? parseInt(args.port) : void 0,
|
|
560
|
+
host: args.host
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
var telemetryStop = defineCommand({
|
|
565
|
+
meta: { name: "stop", description: "Stop the telemetry server" },
|
|
566
|
+
async run() {
|
|
567
|
+
const { stopServer } = await import("./server-manager-6EZWZK56.js");
|
|
568
|
+
stopServer();
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
var telemetryStatus = defineCommand({
|
|
572
|
+
meta: { name: "status", description: "Show telemetry server status" },
|
|
573
|
+
args: {
|
|
574
|
+
json: { type: "boolean", description: "Output as JSON" }
|
|
575
|
+
},
|
|
576
|
+
async run({ args }) {
|
|
577
|
+
const { getServerStatus } = await import("./server-manager-6EZWZK56.js");
|
|
578
|
+
const status2 = getServerStatus();
|
|
579
|
+
if (args.json) {
|
|
580
|
+
console.log(JSON.stringify(status2, null, 2));
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (status2.running) {
|
|
584
|
+
console.log(`Telemetry Server: Running (PID: ${status2.pid})`);
|
|
585
|
+
console.log(` Listening on: ws://${status2.host}:${status2.port}`);
|
|
586
|
+
} else {
|
|
587
|
+
console.log("Telemetry Server: Not running");
|
|
588
|
+
console.log(" Start with: devmux telemetry start");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
var telemetry = defineCommand({
|
|
593
|
+
meta: { name: "telemetry", description: "Manage telemetry server for browser/app logs" },
|
|
594
|
+
subCommands: {
|
|
595
|
+
start: telemetryStart,
|
|
596
|
+
stop: telemetryStop,
|
|
597
|
+
status: telemetryStatus
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
var dashboard = defineCommand({
|
|
601
|
+
meta: { name: "dashboard", description: "Launch web dashboard for service monitoring (experimental)" },
|
|
602
|
+
args: {
|
|
603
|
+
port: { type: "string", description: "Port to listen on (default: 9000)" },
|
|
604
|
+
"no-open": { type: "boolean", description: "Don't open browser automatically" }
|
|
605
|
+
},
|
|
606
|
+
async run({ args }) {
|
|
607
|
+
const { startDashboard } = await import("./dashboard-3GHLOSV3.js");
|
|
608
|
+
startDashboard({
|
|
609
|
+
port: args.port ? parseInt(args.port) : void 0,
|
|
610
|
+
open: !args["no-open"]
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
});
|
|
146
614
|
var main = defineCommand({
|
|
147
615
|
meta: {
|
|
148
616
|
name: "devmux",
|
|
@@ -151,12 +619,19 @@ var main = defineCommand({
|
|
|
151
619
|
},
|
|
152
620
|
subCommands: {
|
|
153
621
|
ensure,
|
|
622
|
+
start,
|
|
623
|
+
restart,
|
|
154
624
|
status,
|
|
155
625
|
stop,
|
|
156
626
|
attach,
|
|
157
627
|
run,
|
|
158
628
|
discover,
|
|
159
|
-
init
|
|
629
|
+
init,
|
|
630
|
+
diagnose,
|
|
631
|
+
watch,
|
|
632
|
+
telemetry,
|
|
633
|
+
dashboard,
|
|
634
|
+
"install-skill": installSkill
|
|
160
635
|
}
|
|
161
636
|
});
|
|
162
637
|
runMain(main);
|