@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/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
- runWithServices,
23
+ loader_exports,
24
+ restartService,
10
25
  stopAllServices,
11
26
  stopService
12
- } from "./chunk-7JJYYMUP.js";
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 portInfo = s.port ? ` (port ${s.port})` : "";
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
- _: { type: "positional", description: "Command to run" }
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);
@@ -0,0 +1,8 @@
1
+ import {
2
+ startDashboard
3
+ } from "./chunk-T6I3CPOV.js";
4
+ import "./chunk-ALENFKSX.js";
5
+ import "./chunk-66UOCF5R.js";
6
+ export {
7
+ startDashboard
8
+ };