@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
package/dist/index.d.ts
CHANGED
|
@@ -6,16 +6,37 @@ type HealthCheckType = {
|
|
|
6
6
|
type: "http";
|
|
7
7
|
url: string;
|
|
8
8
|
expectStatus?: number;
|
|
9
|
-
} | {
|
|
10
|
-
type: "none";
|
|
11
9
|
};
|
|
10
|
+
interface ErrorPatternConfig {
|
|
11
|
+
name: string;
|
|
12
|
+
regex: string;
|
|
13
|
+
severity: "info" | "warning" | "error" | "critical";
|
|
14
|
+
extractStackTrace?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface GlobalWatchConfig$1 {
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
outputDir?: string;
|
|
19
|
+
dedupeWindowMs?: number;
|
|
20
|
+
contextLines?: number;
|
|
21
|
+
patternSets?: Record<string, ErrorPatternConfig[]>;
|
|
22
|
+
}
|
|
23
|
+
interface ServiceWatchConfig$1 {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
include?: string[];
|
|
26
|
+
exclude?: string[];
|
|
27
|
+
patterns?: ErrorPatternConfig[];
|
|
28
|
+
overrides?: Record<string, "info" | "warning" | "error" | "critical">;
|
|
29
|
+
}
|
|
12
30
|
interface ServiceDefinition {
|
|
13
31
|
cwd: string;
|
|
14
32
|
command: string;
|
|
15
|
-
health
|
|
33
|
+
health?: HealthCheckType;
|
|
16
34
|
sessionName?: string;
|
|
17
35
|
env?: Record<string, string>;
|
|
18
36
|
stopPorts?: number[];
|
|
37
|
+
dependsOn?: string[];
|
|
38
|
+
port?: number;
|
|
39
|
+
watch?: ServiceWatchConfig$1;
|
|
19
40
|
}
|
|
20
41
|
interface DevMuxConfig {
|
|
21
42
|
version: 1;
|
|
@@ -25,18 +46,22 @@ interface DevMuxConfig {
|
|
|
25
46
|
startupTimeoutSeconds?: number;
|
|
26
47
|
remainOnExit?: boolean;
|
|
27
48
|
};
|
|
49
|
+
watch?: GlobalWatchConfig$1;
|
|
28
50
|
services: Record<string, ServiceDefinition>;
|
|
29
51
|
}
|
|
30
52
|
interface ResolvedConfig extends DevMuxConfig {
|
|
31
53
|
configRoot: string;
|
|
32
54
|
resolvedSessionPrefix: string;
|
|
55
|
+
instanceId: string;
|
|
33
56
|
}
|
|
34
57
|
interface ServiceStatus {
|
|
35
58
|
name: string;
|
|
36
59
|
healthy: boolean;
|
|
37
60
|
tmuxSession: string | null;
|
|
38
61
|
port?: number;
|
|
62
|
+
resolvedPort?: number;
|
|
39
63
|
managedByDevmux: boolean;
|
|
64
|
+
instanceId?: string;
|
|
40
65
|
}
|
|
41
66
|
|
|
42
67
|
declare function loadConfig(startDir?: string): ResolvedConfig;
|
|
@@ -51,7 +76,7 @@ interface EnsureResult {
|
|
|
51
76
|
declare function ensureService(config: ResolvedConfig, serviceName: string, options?: {
|
|
52
77
|
timeout?: number;
|
|
53
78
|
quiet?: boolean;
|
|
54
|
-
}): Promise<EnsureResult>;
|
|
79
|
+
}, _dependencyStack?: Set<string>): Promise<EnsureResult>;
|
|
55
80
|
declare function getStatus(config: ResolvedConfig, serviceName: string): Promise<ServiceStatus>;
|
|
56
81
|
declare function getAllStatus(config: ResolvedConfig): Promise<ServiceStatus[]>;
|
|
57
82
|
declare function stopService(config: ResolvedConfig, serviceName: string, options?: {
|
|
@@ -62,6 +87,11 @@ declare function stopAllServices(config: ResolvedConfig, options?: {
|
|
|
62
87
|
killPorts?: boolean;
|
|
63
88
|
quiet?: boolean;
|
|
64
89
|
}): void;
|
|
90
|
+
declare function restartService(config: ResolvedConfig, serviceName: string, options?: {
|
|
91
|
+
timeout?: number;
|
|
92
|
+
killPorts?: boolean;
|
|
93
|
+
quiet?: boolean;
|
|
94
|
+
}): Promise<EnsureResult>;
|
|
65
95
|
declare function attachService(config: ResolvedConfig, serviceName: string): void;
|
|
66
96
|
|
|
67
97
|
interface RunOptions {
|
|
@@ -93,15 +123,153 @@ declare namespace driver {
|
|
|
93
123
|
|
|
94
124
|
declare function checkPort(port: number, host?: string): Promise<boolean>;
|
|
95
125
|
declare function checkHttp(url: string, expectStatus?: number): Promise<boolean>;
|
|
96
|
-
declare function
|
|
97
|
-
declare function
|
|
126
|
+
declare function checkTmuxPane(sessionName: string): boolean;
|
|
127
|
+
declare function checkHealth(health: HealthCheckType | undefined, sessionName: string): Promise<boolean>;
|
|
128
|
+
declare function getHealthPort(health: HealthCheckType | undefined): number | undefined;
|
|
98
129
|
|
|
99
130
|
declare const checkers_checkHealth: typeof checkHealth;
|
|
100
131
|
declare const checkers_checkHttp: typeof checkHttp;
|
|
101
132
|
declare const checkers_checkPort: typeof checkPort;
|
|
133
|
+
declare const checkers_checkTmuxPane: typeof checkTmuxPane;
|
|
102
134
|
declare const checkers_getHealthPort: typeof getHealthPort;
|
|
103
135
|
declare namespace checkers {
|
|
104
|
-
export { checkers_checkHealth as checkHealth, checkers_checkHttp as checkHttp, checkers_checkPort as checkPort, checkers_getHealthPort as getHealthPort };
|
|
136
|
+
export { checkers_checkHealth as checkHealth, checkers_checkHttp as checkHttp, checkers_checkPort as checkPort, checkers_checkTmuxPane as checkTmuxPane, checkers_getHealthPort as getHealthPort };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface ErrorPattern {
|
|
140
|
+
name: string;
|
|
141
|
+
regex: string;
|
|
142
|
+
severity: "info" | "warning" | "error" | "critical";
|
|
143
|
+
extractStackTrace?: boolean;
|
|
144
|
+
}
|
|
145
|
+
interface PatternSet {
|
|
146
|
+
[name: string]: ErrorPattern[];
|
|
147
|
+
}
|
|
148
|
+
interface GlobalWatchConfig {
|
|
149
|
+
enabled?: boolean;
|
|
150
|
+
outputDir?: string;
|
|
151
|
+
dedupeWindowMs?: number;
|
|
152
|
+
contextLines?: number;
|
|
153
|
+
patternSets?: PatternSet;
|
|
154
|
+
}
|
|
155
|
+
interface ServiceWatchConfig {
|
|
156
|
+
enabled?: boolean;
|
|
157
|
+
include?: string[];
|
|
158
|
+
exclude?: string[];
|
|
159
|
+
patterns?: ErrorPattern[];
|
|
160
|
+
overrides?: Record<string, "info" | "warning" | "error" | "critical">;
|
|
161
|
+
}
|
|
162
|
+
interface TriggerEvent {
|
|
163
|
+
id: string;
|
|
164
|
+
timestamp: string;
|
|
165
|
+
source: string;
|
|
166
|
+
service: string;
|
|
167
|
+
project: string;
|
|
168
|
+
severity: "info" | "warning" | "error" | "critical";
|
|
169
|
+
pattern: string;
|
|
170
|
+
rawContent: string;
|
|
171
|
+
context: string[];
|
|
172
|
+
stackTrace?: string[];
|
|
173
|
+
status: "pending";
|
|
174
|
+
contentHash: string;
|
|
175
|
+
firstSeen: string;
|
|
176
|
+
}
|
|
177
|
+
interface ServiceWatchState {
|
|
178
|
+
service: string;
|
|
179
|
+
sessionName: string;
|
|
180
|
+
pipeActive: boolean;
|
|
181
|
+
startedAt?: string;
|
|
182
|
+
lastError?: string;
|
|
183
|
+
}
|
|
184
|
+
interface WatcherOptions {
|
|
185
|
+
service: string;
|
|
186
|
+
project: string;
|
|
187
|
+
sessionName: string;
|
|
188
|
+
outputDir: string;
|
|
189
|
+
patterns: ErrorPattern[];
|
|
190
|
+
dedupeWindowMs: number;
|
|
191
|
+
contextLines: number;
|
|
192
|
+
}
|
|
193
|
+
interface PatternMatch {
|
|
194
|
+
pattern: ErrorPattern;
|
|
195
|
+
line: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
declare const BUILTIN_PATTERN_SETS: Record<string, ErrorPattern[]>;
|
|
199
|
+
declare function matchPatterns(line: string, patterns: ErrorPattern[]): PatternMatch | null;
|
|
200
|
+
declare function resolvePatterns(globalConfig: GlobalWatchConfig | undefined, serviceConfig: ServiceWatchConfig | undefined): ErrorPattern[];
|
|
201
|
+
declare function isStackTraceLine(line: string): boolean;
|
|
202
|
+
|
|
203
|
+
declare function computeContentHash(service: string, patternName: string, content: string): string;
|
|
204
|
+
interface RingBuffer<T> {
|
|
205
|
+
push(item: T): void;
|
|
206
|
+
getAll(): T[];
|
|
207
|
+
clear(): void;
|
|
208
|
+
}
|
|
209
|
+
declare function createRingBuffer<T>(capacity: number): RingBuffer<T>;
|
|
210
|
+
declare class DedupeCache {
|
|
211
|
+
private cache;
|
|
212
|
+
private windowMs;
|
|
213
|
+
private maxSize;
|
|
214
|
+
constructor(windowMs: number, maxSize?: number);
|
|
215
|
+
isDuplicate(hash: string): boolean;
|
|
216
|
+
private cleanup;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
declare function ensureOutputDir(outputDir?: string): string;
|
|
220
|
+
declare function getQueuePath(outputDir?: string): string;
|
|
221
|
+
declare function writeEvent(options: WatcherOptions, pattern: ErrorPattern, rawContent: string, context: string[], stackTrace?: string[]): TriggerEvent;
|
|
222
|
+
declare function readQueue(outputDir?: string): TriggerEvent[];
|
|
223
|
+
declare function getPendingEvents(outputDir?: string): TriggerEvent[];
|
|
224
|
+
declare function clearQueue(outputDir?: string): void;
|
|
225
|
+
declare function updateEventStatus(eventId: string, status: "pending" | "processing" | "resolved" | "dismissed", outputDir?: string): boolean;
|
|
226
|
+
|
|
227
|
+
declare function startWatcher$1(options: WatcherOptions): void;
|
|
228
|
+
|
|
229
|
+
declare function getWatcherStatus(config: ResolvedConfig, serviceName: string): ServiceWatchState;
|
|
230
|
+
declare function getAllWatcherStatuses(config: ResolvedConfig): ServiceWatchState[];
|
|
231
|
+
declare function startWatcher(config: ResolvedConfig, serviceName: string, options?: {
|
|
232
|
+
quiet?: boolean;
|
|
233
|
+
}): boolean;
|
|
234
|
+
declare function stopWatcher(config: ResolvedConfig, serviceName: string, options?: {
|
|
235
|
+
quiet?: boolean;
|
|
236
|
+
}): boolean;
|
|
237
|
+
declare function startAllWatchers(config: ResolvedConfig, options?: {
|
|
238
|
+
quiet?: boolean;
|
|
239
|
+
}): void;
|
|
240
|
+
declare function stopAllWatchers(config: ResolvedConfig, options?: {
|
|
241
|
+
quiet?: boolean;
|
|
242
|
+
}): void;
|
|
243
|
+
|
|
244
|
+
declare const index_BUILTIN_PATTERN_SETS: typeof BUILTIN_PATTERN_SETS;
|
|
245
|
+
type index_DedupeCache = DedupeCache;
|
|
246
|
+
declare const index_DedupeCache: typeof DedupeCache;
|
|
247
|
+
type index_ErrorPattern = ErrorPattern;
|
|
248
|
+
type index_GlobalWatchConfig = GlobalWatchConfig;
|
|
249
|
+
type index_PatternMatch = PatternMatch;
|
|
250
|
+
type index_PatternSet = PatternSet;
|
|
251
|
+
type index_ServiceWatchConfig = ServiceWatchConfig;
|
|
252
|
+
type index_ServiceWatchState = ServiceWatchState;
|
|
253
|
+
type index_TriggerEvent = TriggerEvent;
|
|
254
|
+
type index_WatcherOptions = WatcherOptions;
|
|
255
|
+
declare const index_clearQueue: typeof clearQueue;
|
|
256
|
+
declare const index_computeContentHash: typeof computeContentHash;
|
|
257
|
+
declare const index_createRingBuffer: typeof createRingBuffer;
|
|
258
|
+
declare const index_ensureOutputDir: typeof ensureOutputDir;
|
|
259
|
+
declare const index_getAllWatcherStatuses: typeof getAllWatcherStatuses;
|
|
260
|
+
declare const index_getPendingEvents: typeof getPendingEvents;
|
|
261
|
+
declare const index_getQueuePath: typeof getQueuePath;
|
|
262
|
+
declare const index_getWatcherStatus: typeof getWatcherStatus;
|
|
263
|
+
declare const index_isStackTraceLine: typeof isStackTraceLine;
|
|
264
|
+
declare const index_matchPatterns: typeof matchPatterns;
|
|
265
|
+
declare const index_readQueue: typeof readQueue;
|
|
266
|
+
declare const index_resolvePatterns: typeof resolvePatterns;
|
|
267
|
+
declare const index_startAllWatchers: typeof startAllWatchers;
|
|
268
|
+
declare const index_stopAllWatchers: typeof stopAllWatchers;
|
|
269
|
+
declare const index_updateEventStatus: typeof updateEventStatus;
|
|
270
|
+
declare const index_writeEvent: typeof writeEvent;
|
|
271
|
+
declare namespace index {
|
|
272
|
+
export { index_BUILTIN_PATTERN_SETS as BUILTIN_PATTERN_SETS, index_DedupeCache as DedupeCache, type index_ErrorPattern as ErrorPattern, type index_GlobalWatchConfig as GlobalWatchConfig, type index_PatternMatch as PatternMatch, type index_PatternSet as PatternSet, type index_ServiceWatchConfig as ServiceWatchConfig, type index_ServiceWatchState as ServiceWatchState, type index_TriggerEvent as TriggerEvent, type index_WatcherOptions as WatcherOptions, index_clearQueue as clearQueue, index_computeContentHash as computeContentHash, index_createRingBuffer as createRingBuffer, index_ensureOutputDir as ensureOutputDir, index_getAllWatcherStatuses as getAllWatcherStatuses, index_getPendingEvents as getPendingEvents, index_getQueuePath as getQueuePath, index_getWatcherStatus as getWatcherStatus, index_isStackTraceLine as isStackTraceLine, index_matchPatterns as matchPatterns, index_readQueue as readQueue, index_resolvePatterns as resolvePatterns, index_startAllWatchers as startAllWatchers, startWatcher as startServiceWatcher, startWatcher$1 as startWatcher, index_stopAllWatchers as stopAllWatchers, stopWatcher as stopServiceWatcher, index_updateEventStatus as updateEventStatus, index_writeEvent as writeEvent };
|
|
105
273
|
}
|
|
106
274
|
|
|
107
|
-
export { type DevMuxConfig, type EnsureResult, type HealthCheckType, type ResolvedConfig, type RunOptions, type ServiceDefinition, type ServiceStatus, attachService, discoverFromTurbo, ensureService, formatDiscoveredConfig, getAllStatus, getServiceCwd, getSessionName, getStatus, checkers as health, loadConfig, runWithServices, stopAllServices, stopService, driver as tmux };
|
|
275
|
+
export { type DevMuxConfig, type EnsureResult, type HealthCheckType, type ResolvedConfig, type RunOptions, type ServiceDefinition, type ServiceStatus, attachService, discoverFromTurbo, ensureService, formatDiscoveredConfig, getAllStatus, getServiceCwd, getSessionName, getStatus, checkers as health, loadConfig, restartService, runWithServices, stopAllServices, stopService, driver as tmux, index as watch };
|
package/dist/index.js
CHANGED
|
@@ -10,10 +10,14 @@ import {
|
|
|
10
10
|
getSessionName,
|
|
11
11
|
getStatus,
|
|
12
12
|
loadConfig,
|
|
13
|
+
restartService,
|
|
13
14
|
runWithServices,
|
|
14
15
|
stopAllServices,
|
|
15
|
-
stopService
|
|
16
|
-
|
|
16
|
+
stopService,
|
|
17
|
+
watch_exports
|
|
18
|
+
} from "./chunk-FVUGZCQ3.js";
|
|
19
|
+
import "./chunk-JDD6USSA.js";
|
|
20
|
+
import "./chunk-MLKGABMK.js";
|
|
17
21
|
export {
|
|
18
22
|
attachService,
|
|
19
23
|
discoverFromTurbo,
|
|
@@ -25,8 +29,10 @@ export {
|
|
|
25
29
|
getStatus,
|
|
26
30
|
checkers_exports as health,
|
|
27
31
|
loadConfig,
|
|
32
|
+
restartService,
|
|
28
33
|
runWithServices,
|
|
29
34
|
stopAllServices,
|
|
30
35
|
stopService,
|
|
31
|
-
driver_exports as tmux
|
|
36
|
+
driver_exports as tmux,
|
|
37
|
+
watch_exports as watch
|
|
32
38
|
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import "./chunk-MLKGABMK.js";
|
|
2
|
+
|
|
3
|
+
// src/telemetry/server-manager.ts
|
|
4
|
+
import { spawn, execSync } from "child_process";
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
var PID_FILE = join(process.env.HOME ?? "~", ".opencode", "telemetry-server.pid");
|
|
8
|
+
var DEFAULT_PORT = 9876;
|
|
9
|
+
var DEFAULT_HOST = "0.0.0.0";
|
|
10
|
+
function getPid() {
|
|
11
|
+
if (!existsSync(PID_FILE)) return null;
|
|
12
|
+
try {
|
|
13
|
+
const pid = parseInt(readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
14
|
+
if (isNaN(pid)) return null;
|
|
15
|
+
try {
|
|
16
|
+
process.kill(pid, 0);
|
|
17
|
+
return pid;
|
|
18
|
+
} catch {
|
|
19
|
+
unlinkSync(PID_FILE);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function savePid(pid) {
|
|
27
|
+
const dir = join(process.env.HOME ?? "~", ".opencode");
|
|
28
|
+
if (!existsSync(dir)) {
|
|
29
|
+
execSync(`mkdir -p "${dir}"`);
|
|
30
|
+
}
|
|
31
|
+
writeFileSync(PID_FILE, String(pid));
|
|
32
|
+
}
|
|
33
|
+
function clearPid() {
|
|
34
|
+
if (existsSync(PID_FILE)) {
|
|
35
|
+
unlinkSync(PID_FILE);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function getServerStatus() {
|
|
39
|
+
const pid = getPid();
|
|
40
|
+
if (!pid) {
|
|
41
|
+
return { running: false };
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
running: true,
|
|
45
|
+
pid,
|
|
46
|
+
port: DEFAULT_PORT,
|
|
47
|
+
host: DEFAULT_HOST
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function startServer(options = {}) {
|
|
51
|
+
const existingPid = getPid();
|
|
52
|
+
if (existingPid) {
|
|
53
|
+
console.log(`Telemetry server already running (PID: ${existingPid})`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const port = options.port ?? DEFAULT_PORT;
|
|
57
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
58
|
+
try {
|
|
59
|
+
const child = spawn("devmux-telemetry-server", ["start", "--port", String(port), "--host", host], {
|
|
60
|
+
detached: true,
|
|
61
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
62
|
+
});
|
|
63
|
+
if (child.pid) {
|
|
64
|
+
savePid(child.pid);
|
|
65
|
+
child.unref();
|
|
66
|
+
console.log(`Telemetry server started (PID: ${child.pid})`);
|
|
67
|
+
console.log(`Listening on ws://${host}:${port}`);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error.code === "ENOENT") {
|
|
73
|
+
console.error("devmux-telemetry-server not found.");
|
|
74
|
+
console.error("Install it with: pnpm add -g @chriscode/devmux-telemetry-server");
|
|
75
|
+
console.error("Or add it to your project: pnpm add -D @chriscode/devmux-telemetry-server");
|
|
76
|
+
} else {
|
|
77
|
+
console.error("Failed to start server:", error);
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function stopServer() {
|
|
83
|
+
const pid = getPid();
|
|
84
|
+
if (!pid) {
|
|
85
|
+
console.log("Telemetry server is not running");
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
process.kill(pid, "SIGTERM");
|
|
90
|
+
clearPid();
|
|
91
|
+
console.log(`Telemetry server stopped (PID: ${pid})`);
|
|
92
|
+
return true;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
clearPid();
|
|
95
|
+
console.error("Failed to stop server:", error);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
getServerStatus,
|
|
101
|
+
startServer,
|
|
102
|
+
stopServer
|
|
103
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# DevMux Skill
|
|
2
|
+
|
|
3
|
+
> **Skill for AI Agents**: Managing persistent development environments via `devmux`.
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
You are working in a `devmux`-enabled repository. This means:
|
|
8
|
+
1. **Shared Awareness**: Human developers and AI agents share the same running servers.
|
|
9
|
+
2. **Persistence**: Servers run in background `tmux` sessions, surviving your session.
|
|
10
|
+
3. **Safety**: You must NOT kill services randomly. You must reuse existing ones.
|
|
11
|
+
4. **Error Watching**: Errors can be automatically captured from service logs for investigation.
|
|
12
|
+
|
|
13
|
+
## Commands (The "DevMux API")
|
|
14
|
+
|
|
15
|
+
### Service Management
|
|
16
|
+
|
|
17
|
+
| Goal | Command | Behavior |
|
|
18
|
+
|------|---------|----------|
|
|
19
|
+
| **Start a service** | `devmux ensure <name>` | **Idempotent**. Starts if stopped. Does nothing if healthy. |
|
|
20
|
+
| **Check status** | `devmux status` | Lists all services, ports, and health status. |
|
|
21
|
+
| **Read logs** | `devmux attach <name>` | Shows live logs (Press `Ctrl+B, D` to detach). |
|
|
22
|
+
| **Stop service** | `devmux stop <name>` | **Only if necessary**. Prefer leaving running. |
|
|
23
|
+
|
|
24
|
+
### Error Watching
|
|
25
|
+
|
|
26
|
+
| Goal | Command | Behavior |
|
|
27
|
+
|------|---------|----------|
|
|
28
|
+
| **Start watching** | `devmux watch start <name>` | Monitors logs for errors. Captures to queue. |
|
|
29
|
+
| **Stop watching** | `devmux watch stop <name>` | Stops monitoring. |
|
|
30
|
+
| **Check watchers** | `devmux watch status` | Shows which services are being watched. |
|
|
31
|
+
| **View errors** | `devmux watch queue` | Shows pending errors waiting for investigation. |
|
|
32
|
+
| **Clear queue** | `devmux watch queue --clear` | Clears all captured errors. |
|
|
33
|
+
|
|
34
|
+
## Rules of Engagement (CRITICAL)
|
|
35
|
+
|
|
36
|
+
### 1. The "Check First" Rule
|
|
37
|
+
**Before** running `pnpm dev` or `npm start`, you MUST check if it's managed by devmux:
|
|
38
|
+
```bash
|
|
39
|
+
devmux status
|
|
40
|
+
```
|
|
41
|
+
* If listed: **USE DEVMUX**. Run `devmux ensure <name>`.
|
|
42
|
+
* If not listed: Run normally.
|
|
43
|
+
|
|
44
|
+
### 2. The "Idempotency" Rule
|
|
45
|
+
**NEVER** check "is it running?" manually. Just run:
|
|
46
|
+
```bash
|
|
47
|
+
devmux ensure api
|
|
48
|
+
```
|
|
49
|
+
* If it's running: It returns "✅ api already running" (Instant).
|
|
50
|
+
* If it's stopped: It starts it and **waits for health checks** (Port open).
|
|
51
|
+
* **Trust the `ensure` command.**
|
|
52
|
+
|
|
53
|
+
### 3. The "Dependency" Rule
|
|
54
|
+
Services have dependencies. `web` might need `api`.
|
|
55
|
+
* **Old Way**: "Start API, wait 10s, Start Web."
|
|
56
|
+
* **DevMux Way**: `devmux ensure web`. (It handles the graph and health checks automatically).
|
|
57
|
+
|
|
58
|
+
### 4. The "Error Queue" Rule
|
|
59
|
+
If errors are detected from services, check the queue:
|
|
60
|
+
```bash
|
|
61
|
+
devmux watch queue
|
|
62
|
+
```
|
|
63
|
+
* Errors are automatically captured with context (surrounding log lines).
|
|
64
|
+
* Stack traces are extracted when possible.
|
|
65
|
+
* Duplicate errors are deduplicated.
|
|
66
|
+
|
|
67
|
+
### 5. The "Logs" Rule
|
|
68
|
+
If a service fails, **DO NOT** try to run it manually to see errors.
|
|
69
|
+
1. `devmux ensure <name>` (It captures startup errors).
|
|
70
|
+
2. `devmux attach <name>` (View full session logs).
|
|
71
|
+
3. `devmux watch queue` (View captured errors if watching is enabled).
|
|
72
|
+
|
|
73
|
+
## Session Naming
|
|
74
|
+
|
|
75
|
+
Sessions follow the pattern: `omo-{project}-{service}`.
|
|
76
|
+
* Example: `omo-waypoint-api`
|
|
77
|
+
* You can use standard `tmux` commands if needed: `tmux capture-pane -t omo-waypoint-api -p`
|
|
78
|
+
|
|
79
|
+
## Error Queue Format
|
|
80
|
+
|
|
81
|
+
Errors in the queue include:
|
|
82
|
+
- **Service**: Which service produced the error
|
|
83
|
+
- **Pattern**: What type of error was detected (js-error, type-error, fatal, etc.)
|
|
84
|
+
- **Severity**: info, warning, error, or critical
|
|
85
|
+
- **Context**: Lines before the error for debugging
|
|
86
|
+
- **Stack trace**: Extracted if available
|
|
87
|
+
|
|
88
|
+
## Built-in Pattern Sets
|
|
89
|
+
|
|
90
|
+
Pattern sets are **opt-in** via `include` in the service watch config:
|
|
91
|
+
|
|
92
|
+
| Set | Use for |
|
|
93
|
+
|-----|---------|
|
|
94
|
+
| `node` | Node.js/JavaScript (errors, types, OOM) |
|
|
95
|
+
| `web` | HTTP APIs (5xx, 4xx errors) |
|
|
96
|
+
| `react` | React apps (component errors) |
|
|
97
|
+
| `nextjs` | Next.js (webpack, hydration) |
|
|
98
|
+
| `database` | DB connections (refused, deadlock) |
|
|
99
|
+
| `fatal` | System crashes (PANIC, SIGSEGV) |
|
|
100
|
+
| `python` | Python (exceptions, tracebacks) |
|
|
101
|
+
|
|
102
|
+
Example config:
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"watch": { "include": ["node", "web"] }
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Verification
|
|
110
|
+
|
|
111
|
+
After starting a service, `devmux` guarantees it is **healthy** (port is listening). You do NOT need to write a loop to check `curl localhost:port`. `devmux ensure` does not return until the service is ready.
|
|
112
|
+
|
|
113
|
+
## Setup & Configuration
|
|
114
|
+
|
|
115
|
+
If the user asks to set up DevMux or configure a new project, read the guide at:
|
|
116
|
+
`references/SETUP.md`
|
|
117
|
+
|
|
118
|
+
It explains how to correctly configure `devmux.config.json` and `package.json`.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# How to Set Up DevMux
|
|
2
|
+
|
|
3
|
+
> **Guide for AI Agents & Humans**: The proper way to configure a repository for DevMux.
|
|
4
|
+
|
|
5
|
+
## 0. Install DevMux
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -D @chriscode/devmux
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Make `devmux` Available on PATH (Recommended)
|
|
12
|
+
|
|
13
|
+
By default, `devmux` requires `npx devmux` or `pnpm exec devmux`. To use `devmux` directly, add `node_modules/.bin` to your PATH using [direnv](https://direnv.net/).
|
|
14
|
+
|
|
15
|
+
**One-time setup** (if you don't have direnv):
|
|
16
|
+
```bash
|
|
17
|
+
brew install direnv
|
|
18
|
+
# Add to ~/.zshrc or ~/.bashrc:
|
|
19
|
+
eval "$(direnv hook zsh)" # or bash
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Per-project setup**:
|
|
23
|
+
```bash
|
|
24
|
+
# In your project root, create .envrc:
|
|
25
|
+
echo 'PATH_add node_modules/.bin' > .envrc
|
|
26
|
+
direnv allow
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Now `devmux` works directly:
|
|
30
|
+
```bash
|
|
31
|
+
devmux status # Instead of: npx devmux status
|
|
32
|
+
devmux ensure api # Instead of: pnpm exec devmux ensure api
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> **Note**: This also makes all other local binaries available (eslint, tsc, etc.)
|
|
36
|
+
|
|
37
|
+
## 1. Goal
|
|
38
|
+
We want **every** persistent task (servers, watchers) to be managed by DevMux.
|
|
39
|
+
* ✅ `pnpm dev` -> `devmux ensure web`
|
|
40
|
+
* ✅ `pnpm ios` -> `devmux ensure ios`
|
|
41
|
+
* ❌ `pnpm dev` -> `next dev` (Foreground process that blocks the agent)
|
|
42
|
+
|
|
43
|
+
## 2. Configuration (`devmux.config.json`)
|
|
44
|
+
|
|
45
|
+
Use the discovery tool to bootstrap:
|
|
46
|
+
```bash
|
|
47
|
+
devmux discover turbo > devmux.config.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then edit it to ensure:
|
|
51
|
+
1. **Health Checks**: Every service needs a port or HTTP check.
|
|
52
|
+
2. **Dependencies**: Use `dependsOn` to enforce startup order.
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"services": {
|
|
58
|
+
"api": {
|
|
59
|
+
"command": "pnpm start:api",
|
|
60
|
+
"health": { "type": "port", "port": 3000 }
|
|
61
|
+
},
|
|
62
|
+
"web": {
|
|
63
|
+
"command": "pnpm start:web",
|
|
64
|
+
"dependsOn": ["api"],
|
|
65
|
+
"health": { "type": "port", "port": 8080 }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 3. Package.json Scripts (The "DevMux Everywhere" Pattern)
|
|
72
|
+
|
|
73
|
+
Update your root `package.json` so that standard commands use DevMux. This ensures both humans and agents use the safe path.
|
|
74
|
+
|
|
75
|
+
**Before:**
|
|
76
|
+
```json
|
|
77
|
+
"scripts": {
|
|
78
|
+
"dev": "turbo dev",
|
|
79
|
+
"api": "cd api && pnpm dev"
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**After (Recommended):**
|
|
84
|
+
```json
|
|
85
|
+
"scripts": {
|
|
86
|
+
"svc:status": "devmux status",
|
|
87
|
+
"svc:stop": "devmux stop all",
|
|
88
|
+
|
|
89
|
+
"dev": "devmux ensure web",
|
|
90
|
+
"dev:api": "devmux ensure api",
|
|
91
|
+
|
|
92
|
+
"// Note": "Use devmux run for one-off commands that need services",
|
|
93
|
+
"test:e2e": "devmux run --with web -- pnpm playwright test"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 4. Verification
|
|
98
|
+
|
|
99
|
+
1. Run `pnpm dev` (or equivalent).
|
|
100
|
+
2. It should start services in tmux.
|
|
101
|
+
3. Run `devmux status` to confirm.
|
|
102
|
+
4. Ctrl+C should stop them (if running in foreground wrapper) or leave them (if using ensure).
|
|
103
|
+
|
|
104
|
+
## 5. Agent Instructions
|
|
105
|
+
|
|
106
|
+
Ensure the project has an `AGENTS.md` (or similar) that tells the agent to use `devmux`. You can generate the latest instructions via:
|
|
107
|
+
```bash
|
|
108
|
+
devmux skill
|
|
109
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
startWatcher
|
|
4
|
+
} from "../chunk-JDD6USSA.js";
|
|
5
|
+
import "../chunk-MLKGABMK.js";
|
|
6
|
+
|
|
7
|
+
// src/watch/watcher-cli.ts
|
|
8
|
+
function parseArgs() {
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const options2 = {};
|
|
11
|
+
for (const arg of args) {
|
|
12
|
+
if (arg.startsWith("--")) {
|
|
13
|
+
const [key, value] = arg.slice(2).split("=");
|
|
14
|
+
options2[key] = value ?? "true";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const patterns = options2.patterns ? JSON.parse(options2.patterns) : [];
|
|
18
|
+
return {
|
|
19
|
+
service: options2.service ?? "unknown",
|
|
20
|
+
project: options2.project ?? "unknown",
|
|
21
|
+
sessionName: options2.session ?? "unknown",
|
|
22
|
+
outputDir: options2.output ?? `${process.env.HOME}/.opencode/triggers`,
|
|
23
|
+
patterns,
|
|
24
|
+
dedupeWindowMs: parseInt(options2.dedupe ?? "5000"),
|
|
25
|
+
contextLines: parseInt(options2.context ?? "20")
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
var options = parseArgs();
|
|
29
|
+
startWatcher(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chriscode/devmux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "tmux-based service management for monorepos with human-agent shared awareness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "tsup",
|
|
16
|
+
"build": "tsup && cp -R ../skill dist/",
|
|
17
17
|
"dev": "tsup --watch",
|
|
18
18
|
"prepublishOnly": "pnpm build",
|
|
19
19
|
"type-check": "tsc --noEmit",
|