@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.
- package/dist/chunk-FVUGZCQ3.js +840 -0
- package/dist/chunk-JDD6USSA.js +311 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/cli.js +228 -7
- package/dist/index.d.ts +176 -8
- package/dist/index.js +9 -3
- package/dist/server-manager-DO25DFFW.js +103 -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 +29 -0
- package/package.json +2 -2
- package/dist/chunk-7JJYYMUP.js +0 -542
|
@@ -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
|
+
};
|
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
|
-
|
|
12
|
-
|
|
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
|
|
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);
|