@chriscode/devmux 1.0.0 → 1.2.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.
@@ -0,0 +1,311 @@
1
+ // src/watch/queue.ts
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, statSync } from "fs";
3
+ import { join } from "path";
4
+ import { randomUUID } from "crypto";
5
+
6
+ // src/watch/deduper.ts
7
+ import { createHash } from "crypto";
8
+ function computeContentHash(service, patternName, content) {
9
+ const normalized = content.replace(/\d{4}-\d{2}-\d{2}T[\d:.]+Z?/g, "TIMESTAMP").replace(/\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}/g, "TIMESTAMP").replace(/:\d+:\d+/g, ":LINE:COL").replace(/0x[0-9a-f]+/gi, "0xADDR").replace(/\b\d{5,}\b/g, "NUM");
10
+ return createHash("sha256").update(`${service}:${patternName}:${normalized}`).digest("hex").slice(0, 16);
11
+ }
12
+ function createRingBuffer(capacity) {
13
+ const buffer = [];
14
+ let writeIndex = 0;
15
+ let full = false;
16
+ return {
17
+ push(item) {
18
+ if (buffer.length < capacity) {
19
+ buffer.push(item);
20
+ } else {
21
+ buffer[writeIndex] = item;
22
+ full = true;
23
+ }
24
+ writeIndex = (writeIndex + 1) % capacity;
25
+ },
26
+ getAll() {
27
+ if (!full) return [...buffer];
28
+ const result = [];
29
+ for (let i = 0; i < capacity; i++) {
30
+ result.push(buffer[(writeIndex + i) % capacity]);
31
+ }
32
+ return result;
33
+ },
34
+ clear() {
35
+ buffer.length = 0;
36
+ writeIndex = 0;
37
+ full = false;
38
+ }
39
+ };
40
+ }
41
+ var DedupeCache = class {
42
+ cache = /* @__PURE__ */ new Map();
43
+ windowMs;
44
+ maxSize;
45
+ constructor(windowMs, maxSize = 1e3) {
46
+ this.windowMs = windowMs;
47
+ this.maxSize = maxSize;
48
+ }
49
+ isDuplicate(hash) {
50
+ const now = Date.now();
51
+ const lastSeen = this.cache.get(hash);
52
+ if (lastSeen && now - lastSeen < this.windowMs) {
53
+ this.cache.set(hash, now);
54
+ return true;
55
+ }
56
+ this.cache.set(hash, now);
57
+ this.cleanup(now);
58
+ return false;
59
+ }
60
+ cleanup(now) {
61
+ if (this.cache.size <= this.maxSize) return;
62
+ const cutoff = now - this.windowMs * 2;
63
+ for (const [hash, timestamp] of this.cache) {
64
+ if (timestamp < cutoff) {
65
+ this.cache.delete(hash);
66
+ }
67
+ }
68
+ }
69
+ };
70
+
71
+ // src/watch/queue.ts
72
+ var DEFAULT_OUTPUT_DIR = join(process.env.HOME ?? "~", ".opencode", "triggers");
73
+ var AUTO_PRUNE_THRESHOLD_BYTES = 50 * 1024 * 1024;
74
+ var EVENTS_TO_KEEP_AFTER_PRUNE = 1e4;
75
+ function ensureOutputDir(outputDir = DEFAULT_OUTPUT_DIR) {
76
+ if (!existsSync(outputDir)) {
77
+ mkdirSync(outputDir, { recursive: true });
78
+ }
79
+ return outputDir;
80
+ }
81
+ function getQueuePath(outputDir = DEFAULT_OUTPUT_DIR) {
82
+ return join(outputDir, "queue.jsonl");
83
+ }
84
+ function pruneQueueIfNeeded(outputDir = DEFAULT_OUTPUT_DIR) {
85
+ const queuePath = getQueuePath(outputDir);
86
+ if (!existsSync(queuePath)) {
87
+ return false;
88
+ }
89
+ const stats = statSync(queuePath);
90
+ if (stats.size < AUTO_PRUNE_THRESHOLD_BYTES) {
91
+ return false;
92
+ }
93
+ const content = readFileSync(queuePath, "utf-8");
94
+ const lines = content.trim().split("\n").filter(Boolean);
95
+ const linesToKeep = lines.slice(-EVENTS_TO_KEEP_AFTER_PRUNE);
96
+ writeFileSync(queuePath, linesToKeep.join("\n") + "\n");
97
+ return true;
98
+ }
99
+ function writeEvent(options, pattern, rawContent, context, stackTrace) {
100
+ const outputDir = ensureOutputDir(options.outputDir);
101
+ const queuePath = getQueuePath(outputDir);
102
+ const event = {
103
+ id: randomUUID(),
104
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
105
+ source: `devmux:${options.service}`,
106
+ service: options.service,
107
+ project: options.project,
108
+ severity: pattern.severity,
109
+ pattern: pattern.name,
110
+ rawContent,
111
+ context,
112
+ stackTrace: stackTrace && stackTrace.length > 0 ? stackTrace : void 0,
113
+ status: "pending",
114
+ contentHash: computeContentHash(options.service, pattern.name, rawContent),
115
+ firstSeen: (/* @__PURE__ */ new Date()).toISOString()
116
+ };
117
+ appendFileSync(queuePath, JSON.stringify(event) + "\n");
118
+ pruneQueueIfNeeded(outputDir);
119
+ return event;
120
+ }
121
+ function readQueue(outputDir = DEFAULT_OUTPUT_DIR) {
122
+ const queuePath = getQueuePath(outputDir);
123
+ if (!existsSync(queuePath)) {
124
+ return [];
125
+ }
126
+ const content = readFileSync(queuePath, "utf-8");
127
+ const lines = content.trim().split("\n").filter(Boolean);
128
+ return lines.map((line) => JSON.parse(line));
129
+ }
130
+ function getPendingEvents(outputDir = DEFAULT_OUTPUT_DIR) {
131
+ return readQueue(outputDir).filter((e) => e.status === "pending");
132
+ }
133
+ function clearQueue(outputDir = DEFAULT_OUTPUT_DIR) {
134
+ const queuePath = getQueuePath(outputDir);
135
+ if (existsSync(queuePath)) {
136
+ unlinkSync(queuePath);
137
+ }
138
+ }
139
+ function updateEventStatus(eventId, status, outputDir = DEFAULT_OUTPUT_DIR) {
140
+ const queuePath = getQueuePath(outputDir);
141
+ if (!existsSync(queuePath)) {
142
+ return false;
143
+ }
144
+ const events = readQueue(outputDir);
145
+ let found = false;
146
+ const updated = events.map((e) => {
147
+ if (e.id === eventId) {
148
+ found = true;
149
+ return { ...e, status };
150
+ }
151
+ return e;
152
+ });
153
+ if (!found) return false;
154
+ writeFileSync(queuePath, updated.map((e) => JSON.stringify(e)).join("\n") + "\n");
155
+ return true;
156
+ }
157
+
158
+ // src/watch/watcher.ts
159
+ import { createInterface } from "readline";
160
+
161
+ // src/watch/patterns.ts
162
+ var BUILTIN_PATTERN_SETS = {
163
+ node: [
164
+ { name: "unhandled-rejection", regex: "UnhandledPromiseRejection|unhandledRejection", severity: "critical", extractStackTrace: true },
165
+ { name: "oom", regex: "out of memory|OutOfMemoryError|heap out of memory|ENOMEM|OOM killer", severity: "critical" },
166
+ { name: "type-error", regex: "TypeError:|ReferenceError:|SyntaxError:", severity: "error", extractStackTrace: true },
167
+ { name: "js-error", regex: "^Error:|^\\w*Error:|^\\s+at\\s+", severity: "error", extractStackTrace: true }
168
+ ],
169
+ web: [
170
+ { name: "http-5xx", regex: `HTTP/[\\d.]+["'\\s]+5\\d{2}|"(status|statusCode)":\\s*5\\d{2}`, severity: "error" },
171
+ { name: "http-4xx-important", regex: `HTTP/[\\d.]+["'\\s]+40[134]`, severity: "warning" }
172
+ ],
173
+ react: [
174
+ { name: "react-error", regex: "Uncaught Error:|Error: Minified React|Hydration failed", severity: "error", extractStackTrace: true }
175
+ ],
176
+ nextjs: [
177
+ { name: "webpack-error", regex: "^ERROR in|Module build failed|Failed to compile", severity: "error" },
178
+ { name: "hydration-error", regex: "Hydration failed|Text content does not match|There was an error while hydrating", severity: "error" }
179
+ ],
180
+ database: [
181
+ { name: "db-error", regex: "ECONNREFUSED|connection refused|Connection lost|\\bdeadlock\\b", severity: "error" }
182
+ ],
183
+ fatal: [
184
+ { name: "fatal", regex: "\\bFATAL\\b|\\bPANIC\\b|Segmentation fault|SIGKILL|SIGSEGV", severity: "critical" }
185
+ ],
186
+ python: [
187
+ { name: "exception", regex: "^Exception:|Traceback \\(most recent", severity: "error", extractStackTrace: true }
188
+ ]
189
+ };
190
+ var compiledPatterns = /* @__PURE__ */ new Map();
191
+ function getCompiledRegex(pattern) {
192
+ const cached = compiledPatterns.get(pattern.regex);
193
+ if (cached) return cached;
194
+ const compiled = new RegExp(pattern.regex, "i");
195
+ compiledPatterns.set(pattern.regex, compiled);
196
+ return compiled;
197
+ }
198
+ function matchPatterns(line, patterns) {
199
+ for (const pattern of patterns) {
200
+ const regex = getCompiledRegex(pattern);
201
+ if (regex.test(line)) {
202
+ return { pattern, line };
203
+ }
204
+ }
205
+ return null;
206
+ }
207
+ function resolvePatterns(globalConfig, serviceConfig) {
208
+ const patterns = [];
209
+ const excludes = new Set(serviceConfig?.exclude ?? []);
210
+ const overrides = serviceConfig?.overrides ?? {};
211
+ for (const setName of serviceConfig?.include ?? []) {
212
+ const builtinSet = BUILTIN_PATTERN_SETS[setName];
213
+ const customSet = globalConfig?.patternSets?.[setName];
214
+ const set = builtinSet ?? customSet;
215
+ if (!set) continue;
216
+ for (const p of set) {
217
+ if (excludes.has(p.name)) continue;
218
+ patterns.push({
219
+ ...p,
220
+ severity: overrides[p.name] ?? p.severity
221
+ });
222
+ }
223
+ }
224
+ for (const p of serviceConfig?.patterns ?? []) {
225
+ patterns.push(p);
226
+ }
227
+ return patterns;
228
+ }
229
+ function isStackTraceLine(line) {
230
+ return /^\s+at\s+/.test(line) || /^\s+\.\.\.\s*\d+\s*more/.test(line);
231
+ }
232
+
233
+ // src/watch/watcher.ts
234
+ function startWatcher(options) {
235
+ const contextBuffer = createRingBuffer(options.contextLines);
236
+ const dedupeCache = new DedupeCache(options.dedupeWindowMs);
237
+ let stackAccumulator = null;
238
+ const rl = createInterface({
239
+ input: process.stdin,
240
+ crlfDelay: Infinity
241
+ });
242
+ function flushStackTrace() {
243
+ if (!stackAccumulator) return;
244
+ if (stackAccumulator.timeout) {
245
+ clearTimeout(stackAccumulator.timeout);
246
+ }
247
+ const { match, context, lines } = stackAccumulator;
248
+ const stackTrace = lines.slice(1);
249
+ writeEvent(options, match.pattern, match.line, context, stackTrace);
250
+ console.error(`[devmux-watch] Captured ${match.pattern.severity}: ${match.pattern.name}`);
251
+ stackAccumulator = null;
252
+ }
253
+ rl.on("line", (line) => {
254
+ if (stackAccumulator) {
255
+ if (isStackTraceLine(line)) {
256
+ stackAccumulator.lines.push(line);
257
+ return;
258
+ }
259
+ flushStackTrace();
260
+ }
261
+ contextBuffer.push(line);
262
+ const match = matchPatterns(line, options.patterns);
263
+ if (!match) return;
264
+ const context = contextBuffer.getAll().slice(0, -1);
265
+ const hash = `${options.service}:${match.pattern.name}:${line}`;
266
+ if (dedupeCache.isDuplicate(hash)) {
267
+ return;
268
+ }
269
+ if (match.pattern.extractStackTrace) {
270
+ stackAccumulator = {
271
+ lines: [line],
272
+ match,
273
+ context,
274
+ timeout: setTimeout(flushStackTrace, 500)
275
+ };
276
+ return;
277
+ }
278
+ writeEvent(options, match.pattern, line, context);
279
+ console.error(`[devmux-watch] Captured ${match.pattern.severity}: ${match.pattern.name}`);
280
+ });
281
+ rl.on("close", () => {
282
+ flushStackTrace();
283
+ process.exit(0);
284
+ });
285
+ process.on("SIGTERM", () => {
286
+ flushStackTrace();
287
+ rl.close();
288
+ });
289
+ process.on("SIGINT", () => {
290
+ flushStackTrace();
291
+ rl.close();
292
+ });
293
+ }
294
+
295
+ export {
296
+ BUILTIN_PATTERN_SETS,
297
+ matchPatterns,
298
+ resolvePatterns,
299
+ isStackTraceLine,
300
+ computeContentHash,
301
+ createRingBuffer,
302
+ DedupeCache,
303
+ ensureOutputDir,
304
+ getQueuePath,
305
+ writeEvent,
306
+ readQueue,
307
+ getPendingEvents,
308
+ clearQueue,
309
+ updateEventStatus,
310
+ startWatcher
311
+ };
@@ -0,0 +1,9 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ export {
8
+ __export
9
+ };
package/dist/cli.js CHANGED
@@ -5,14 +5,28 @@ import {
5
5
  ensureService,
6
6
  formatDiscoveredConfig,
7
7
  getAllStatus,
8
+ getAllWatcherStatuses,
8
9
  loadConfig,
10
+ restartService,
9
11
  runWithServices,
12
+ startAllWatchers,
13
+ startWatcher,
10
14
  stopAllServices,
11
- stopService
12
- } from "./chunk-7JJYYMUP.js";
15
+ stopAllWatchers,
16
+ stopService,
17
+ stopWatcher
18
+ } from "./chunk-FVUGZCQ3.js";
19
+ import {
20
+ clearQueue,
21
+ getPendingEvents
22
+ } from "./chunk-JDD6USSA.js";
23
+ import "./chunk-MLKGABMK.js";
13
24
 
14
25
  // src/cli.ts
15
26
  import { defineCommand, runMain } from "citty";
27
+ import { mkdirSync, existsSync, cpSync } from "fs";
28
+ import { dirname, join } from "path";
29
+ import { fileURLToPath } from "url";
16
30
  var ensure = defineCommand({
17
31
  meta: { name: "ensure", description: "Ensure a service is running (idempotent)" },
18
32
  args: {
@@ -35,16 +49,20 @@ var status = defineCommand({
35
49
  const config = loadConfig();
36
50
  const statuses = await getAllStatus(config);
37
51
  if (args.json) {
38
- console.log(JSON.stringify(statuses, null, 2));
52
+ console.log(JSON.stringify({ instanceId: config.instanceId || null, services: statuses }, null, 2));
39
53
  return;
40
54
  }
41
55
  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
56
  console.log(" Service Status");
57
+ if (config.instanceId) {
58
+ console.log(` Instance: ${config.instanceId}`);
59
+ }
43
60
  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
61
  console.log("");
45
62
  for (const s of statuses) {
46
63
  const icon = s.healthy ? "\u2705" : "\u274C";
47
- const portInfo = s.port ? ` (port ${s.port})` : "";
64
+ const portDisplay = s.resolvedPort ?? s.port;
65
+ const portInfo = portDisplay ? ` (port ${portDisplay})` : "";
48
66
  console.log(`${icon} ${s.name}${portInfo}: ${s.healthy ? "Running" : "Not running"}`);
49
67
  if (s.tmuxSession) {
50
68
  console.log(` \u2514\u2500 tmux: ${s.tmuxSession}`);
@@ -71,6 +89,34 @@ var stop = defineCommand({
71
89
  }
72
90
  }
73
91
  });
92
+ var restart = defineCommand({
93
+ meta: { name: "restart", description: "Restart a service (stop + start)" },
94
+ args: {
95
+ service: { type: "positional", description: "Service name", required: true },
96
+ timeout: { type: "string", description: "Startup timeout in seconds" },
97
+ force: { type: "boolean", description: "Also kill processes on ports before restarting" }
98
+ },
99
+ async run({ args }) {
100
+ const config = loadConfig();
101
+ await restartService(config, args.service, {
102
+ timeout: args.timeout ? parseInt(args.timeout) : void 0,
103
+ killPorts: args.force
104
+ });
105
+ }
106
+ });
107
+ var start = defineCommand({
108
+ meta: { name: "start", description: "Start a service (alias for ensure)" },
109
+ args: {
110
+ service: { type: "positional", description: "Service name", required: true },
111
+ timeout: { type: "string", description: "Startup timeout in seconds" }
112
+ },
113
+ async run({ args }) {
114
+ const config = loadConfig();
115
+ await ensureService(config, args.service, {
116
+ timeout: args.timeout ? parseInt(args.timeout) : void 0
117
+ });
118
+ }
119
+ });
74
120
  var attach = defineCommand({
75
121
  meta: { name: "attach", description: "Attach to a service's tmux session" },
76
122
  args: {
@@ -85,8 +131,7 @@ var run = defineCommand({
85
131
  meta: { name: "run", description: "Run a command with services, cleanup on exit" },
86
132
  args: {
87
133
  with: { type: "string", description: "Comma-separated services to ensure", required: true },
88
- "no-stop": { type: "boolean", description: "Don't stop services on exit" },
89
- _: { type: "positional", description: "Command to run" }
134
+ "no-stop": { type: "boolean", description: "Don't stop services on exit" }
90
135
  },
91
136
  async run({ args }) {
92
137
  const config = loadConfig();
@@ -143,6 +188,177 @@ var init = defineCommand({
143
188
  console.log("Save this as devmux.config.json in your project root.");
144
189
  }
145
190
  });
191
+ var installSkill = defineCommand({
192
+ meta: { name: "install-skill", description: "Install DevMux skills to .claude/skills" },
193
+ run() {
194
+ const __dirname2 = dirname(fileURLToPath(import.meta.url));
195
+ const skillDir = join(__dirname2, "skill");
196
+ const targetDir = join(process.cwd(), ".claude", "skills", "devmux");
197
+ try {
198
+ if (!existsSync(skillDir)) {
199
+ throw new Error(`Skill directory not found at ${skillDir}`);
200
+ }
201
+ mkdirSync(targetDir, { recursive: true });
202
+ cpSync(skillDir, targetDir, { recursive: true });
203
+ console.log(`\u2705 Installed DevMux skill to .claude/skills/devmux/`);
204
+ } catch (e) {
205
+ console.error("\u274C Failed to install skills:");
206
+ console.error(e);
207
+ process.exit(1);
208
+ }
209
+ }
210
+ });
211
+ var watchStart = defineCommand({
212
+ meta: { name: "start", description: "Start watching a service for errors" },
213
+ args: {
214
+ service: { type: "positional", description: "Service name (or 'all')" }
215
+ },
216
+ run({ args }) {
217
+ const config = loadConfig();
218
+ const serviceName = args.service;
219
+ if (!serviceName || serviceName === "all") {
220
+ startAllWatchers(config);
221
+ } else {
222
+ startWatcher(config, serviceName);
223
+ }
224
+ }
225
+ });
226
+ var watchStop = defineCommand({
227
+ meta: { name: "stop", description: "Stop watching a service" },
228
+ args: {
229
+ service: { type: "positional", description: "Service name (or 'all')" }
230
+ },
231
+ run({ args }) {
232
+ const config = loadConfig();
233
+ const serviceName = args.service;
234
+ if (!serviceName || serviceName === "all") {
235
+ stopAllWatchers(config);
236
+ } else {
237
+ stopWatcher(config, serviceName);
238
+ }
239
+ }
240
+ });
241
+ var watchStatus = defineCommand({
242
+ meta: { name: "status", description: "Show watcher status" },
243
+ args: {
244
+ json: { type: "boolean", description: "Output as JSON" }
245
+ },
246
+ run({ args }) {
247
+ const config = loadConfig();
248
+ const statuses = getAllWatcherStatuses(config);
249
+ if (args.json) {
250
+ console.log(JSON.stringify(statuses, null, 2));
251
+ return;
252
+ }
253
+ console.log("");
254
+ 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");
255
+ console.log(" Watcher Status");
256
+ 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");
257
+ console.log("");
258
+ for (const s of statuses) {
259
+ const icon = s.pipeActive ? "\u{1F441}\uFE0F" : "\u26AB";
260
+ console.log(`${icon} ${s.service}: ${s.pipeActive ? "Watching" : "Not watching"}`);
261
+ if (s.pipeActive) {
262
+ console.log(` \u2514\u2500 session: ${s.sessionName}`);
263
+ }
264
+ }
265
+ console.log("");
266
+ }
267
+ });
268
+ var watchQueue = defineCommand({
269
+ meta: { name: "queue", description: "Show pending errors in queue" },
270
+ args: {
271
+ json: { type: "boolean", description: "Output as JSON" },
272
+ clear: { type: "boolean", description: "Clear the queue" }
273
+ },
274
+ run({ args }) {
275
+ if (args.clear) {
276
+ clearQueue();
277
+ console.log("\u2705 Queue cleared");
278
+ return;
279
+ }
280
+ const events = getPendingEvents();
281
+ if (args.json) {
282
+ console.log(JSON.stringify(events, null, 2));
283
+ return;
284
+ }
285
+ if (events.length === 0) {
286
+ console.log("No pending errors in queue.");
287
+ return;
288
+ }
289
+ console.log("");
290
+ 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`);
291
+ console.log(` Pending Errors (${events.length})`);
292
+ 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`);
293
+ console.log("");
294
+ for (const e of events) {
295
+ const severityIcon = e.severity === "critical" ? "\u{1F534}" : e.severity === "error" ? "\u{1F7E0}" : e.severity === "warning" ? "\u{1F7E1}" : "\u{1F535}";
296
+ console.log(`${severityIcon} [${e.service}] ${e.pattern}`);
297
+ console.log(` ${e.rawContent.slice(0, 80)}${e.rawContent.length > 80 ? "..." : ""}`);
298
+ console.log(` \u2514\u2500 ${e.firstSeen}`);
299
+ console.log("");
300
+ }
301
+ }
302
+ });
303
+ var watch = defineCommand({
304
+ meta: { name: "watch", description: "Manage error watchers for services" },
305
+ subCommands: {
306
+ start: watchStart,
307
+ stop: watchStop,
308
+ status: watchStatus,
309
+ queue: watchQueue
310
+ }
311
+ });
312
+ var telemetryStart = defineCommand({
313
+ meta: { name: "start", description: "Start the telemetry server" },
314
+ args: {
315
+ port: { type: "string", description: "Port to listen on (default: 9876)" },
316
+ host: { type: "string", description: "Host to bind to (default: 0.0.0.0)" }
317
+ },
318
+ async run({ args }) {
319
+ const { startServer } = await import("./server-manager-DO25DFFW.js");
320
+ startServer({
321
+ port: args.port ? parseInt(args.port) : void 0,
322
+ host: args.host
323
+ });
324
+ }
325
+ });
326
+ var telemetryStop = defineCommand({
327
+ meta: { name: "stop", description: "Stop the telemetry server" },
328
+ async run() {
329
+ const { stopServer } = await import("./server-manager-DO25DFFW.js");
330
+ stopServer();
331
+ }
332
+ });
333
+ var telemetryStatus = defineCommand({
334
+ meta: { name: "status", description: "Show telemetry server status" },
335
+ args: {
336
+ json: { type: "boolean", description: "Output as JSON" }
337
+ },
338
+ async run({ args }) {
339
+ const { getServerStatus } = await import("./server-manager-DO25DFFW.js");
340
+ const status2 = getServerStatus();
341
+ if (args.json) {
342
+ console.log(JSON.stringify(status2, null, 2));
343
+ return;
344
+ }
345
+ if (status2.running) {
346
+ console.log(`Telemetry Server: Running (PID: ${status2.pid})`);
347
+ console.log(` Listening on: ws://${status2.host}:${status2.port}`);
348
+ } else {
349
+ console.log("Telemetry Server: Not running");
350
+ console.log(" Start with: devmux telemetry start");
351
+ }
352
+ }
353
+ });
354
+ var telemetry = defineCommand({
355
+ meta: { name: "telemetry", description: "Manage telemetry server for browser/app logs" },
356
+ subCommands: {
357
+ start: telemetryStart,
358
+ stop: telemetryStop,
359
+ status: telemetryStatus
360
+ }
361
+ });
146
362
  var main = defineCommand({
147
363
  meta: {
148
364
  name: "devmux",
@@ -151,12 +367,17 @@ var main = defineCommand({
151
367
  },
152
368
  subCommands: {
153
369
  ensure,
370
+ start,
371
+ restart,
154
372
  status,
155
373
  stop,
156
374
  attach,
157
375
  run,
158
376
  discover,
159
- init
377
+ init,
378
+ watch,
379
+ telemetry,
380
+ "install-skill": installSkill
160
381
  }
161
382
  });
162
383
  runMain(main);