@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/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Server } from 'node:http';
2
+
1
3
  type HealthCheckType = {
2
4
  type: "port";
3
5
  port: number;
@@ -6,16 +8,39 @@ type HealthCheckType = {
6
8
  type: "http";
7
9
  url: string;
8
10
  expectStatus?: number;
9
- } | {
10
- type: "none";
11
11
  };
12
+ interface ErrorPatternConfig {
13
+ name: string;
14
+ regex: string;
15
+ severity: "info" | "warning" | "error" | "critical";
16
+ extractStackTrace?: boolean;
17
+ }
18
+ interface GlobalWatchConfig$1 {
19
+ enabled?: boolean;
20
+ outputDir?: string;
21
+ dedupeWindowMs?: number;
22
+ contextLines?: number;
23
+ patternSets?: Record<string, ErrorPatternConfig[]>;
24
+ }
25
+ interface ServiceWatchConfig$1 {
26
+ enabled?: boolean;
27
+ include?: string[];
28
+ exclude?: string[];
29
+ patterns?: ErrorPatternConfig[];
30
+ overrides?: Record<string, "info" | "warning" | "error" | "critical">;
31
+ }
12
32
  interface ServiceDefinition {
13
33
  cwd: string;
14
34
  command: string;
15
- health: HealthCheckType;
35
+ health?: HealthCheckType;
16
36
  sessionName?: string;
17
37
  env?: Record<string, string>;
18
38
  stopPorts?: number[];
39
+ dependsOn?: string[];
40
+ port?: number;
41
+ watch?: ServiceWatchConfig$1;
42
+ /** Show this service in the dashboard. Defaults to true for services with a port. */
43
+ dashboard?: boolean;
19
44
  }
20
45
  interface DevMuxConfig {
21
46
  version: 1;
@@ -24,19 +49,26 @@ interface DevMuxConfig {
24
49
  defaults?: {
25
50
  startupTimeoutSeconds?: number;
26
51
  remainOnExit?: boolean;
52
+ dashboard?: boolean | {
53
+ port?: number;
54
+ };
27
55
  };
56
+ watch?: GlobalWatchConfig$1;
28
57
  services: Record<string, ServiceDefinition>;
29
58
  }
30
59
  interface ResolvedConfig extends DevMuxConfig {
31
60
  configRoot: string;
32
61
  resolvedSessionPrefix: string;
62
+ instanceId: string;
33
63
  }
34
64
  interface ServiceStatus {
35
65
  name: string;
36
66
  healthy: boolean;
37
67
  tmuxSession: string | null;
38
68
  port?: number;
69
+ resolvedPort?: number;
39
70
  managedByDevmux: boolean;
71
+ instanceId?: string;
40
72
  }
41
73
 
42
74
  declare function loadConfig(startDir?: string): ResolvedConfig;
@@ -51,23 +83,29 @@ interface EnsureResult {
51
83
  declare function ensureService(config: ResolvedConfig, serviceName: string, options?: {
52
84
  timeout?: number;
53
85
  quiet?: boolean;
54
- }): Promise<EnsureResult>;
86
+ }, _dependencyStack?: Set<string>): Promise<EnsureResult>;
55
87
  declare function getStatus(config: ResolvedConfig, serviceName: string): Promise<ServiceStatus>;
56
88
  declare function getAllStatus(config: ResolvedConfig): Promise<ServiceStatus[]>;
57
89
  declare function stopService(config: ResolvedConfig, serviceName: string, options?: {
58
90
  killPorts?: boolean;
59
91
  quiet?: boolean;
60
- }): void;
92
+ }): Promise<void>;
61
93
  declare function stopAllServices(config: ResolvedConfig, options?: {
62
94
  killPorts?: boolean;
63
95
  quiet?: boolean;
64
- }): void;
96
+ }): Promise<void>;
97
+ declare function restartService(config: ResolvedConfig, serviceName: string, options?: {
98
+ timeout?: number;
99
+ killPorts?: boolean;
100
+ quiet?: boolean;
101
+ }): Promise<EnsureResult>;
65
102
  declare function attachService(config: ResolvedConfig, serviceName: string): void;
66
103
 
67
104
  interface RunOptions {
68
105
  services: string[];
69
106
  stopOnExit?: boolean;
70
107
  quiet?: boolean;
108
+ dashboard?: boolean;
71
109
  }
72
110
  declare function runWithServices(config: ResolvedConfig, command: string[], options: RunOptions): Promise<number>;
73
111
 
@@ -93,15 +131,182 @@ declare namespace driver {
93
131
 
94
132
  declare function checkPort(port: number, host?: string): Promise<boolean>;
95
133
  declare function checkHttp(url: string, expectStatus?: number): Promise<boolean>;
96
- declare function checkHealth(health: HealthCheckType): Promise<boolean>;
97
- declare function getHealthPort(health: HealthCheckType): number | undefined;
134
+ declare function checkTmuxPane(sessionName: string): boolean;
135
+ declare function checkHealth(health: HealthCheckType | undefined, sessionName: string): Promise<boolean>;
136
+ declare function getHealthPort(health: HealthCheckType | undefined): number | undefined;
98
137
 
99
138
  declare const checkers_checkHealth: typeof checkHealth;
100
139
  declare const checkers_checkHttp: typeof checkHttp;
101
140
  declare const checkers_checkPort: typeof checkPort;
141
+ declare const checkers_checkTmuxPane: typeof checkTmuxPane;
102
142
  declare const checkers_getHealthPort: typeof getHealthPort;
103
143
  declare namespace checkers {
104
- export { checkers_checkHealth as checkHealth, checkers_checkHttp as checkHttp, checkers_checkPort as checkPort, checkers_getHealthPort as getHealthPort };
144
+ export { checkers_checkHealth as checkHealth, checkers_checkHttp as checkHttp, checkers_checkPort as checkPort, checkers_checkTmuxPane as checkTmuxPane, checkers_getHealthPort as getHealthPort };
145
+ }
146
+
147
+ interface ErrorPattern {
148
+ name: string;
149
+ regex: string;
150
+ severity: "info" | "warning" | "error" | "critical";
151
+ extractStackTrace?: boolean;
152
+ }
153
+ interface PatternSet {
154
+ [name: string]: ErrorPattern[];
155
+ }
156
+ interface GlobalWatchConfig {
157
+ enabled?: boolean;
158
+ outputDir?: string;
159
+ dedupeWindowMs?: number;
160
+ contextLines?: number;
161
+ patternSets?: PatternSet;
162
+ }
163
+ interface ServiceWatchConfig {
164
+ enabled?: boolean;
165
+ include?: string[];
166
+ exclude?: string[];
167
+ patterns?: ErrorPattern[];
168
+ overrides?: Record<string, "info" | "warning" | "error" | "critical">;
169
+ }
170
+ interface TriggerEvent {
171
+ id: string;
172
+ timestamp: string;
173
+ source: string;
174
+ service: string;
175
+ project: string;
176
+ severity: "info" | "warning" | "error" | "critical";
177
+ pattern: string;
178
+ rawContent: string;
179
+ context: string[];
180
+ stackTrace?: string[];
181
+ status: "pending";
182
+ contentHash: string;
183
+ firstSeen: string;
184
+ }
185
+ interface ServiceWatchState {
186
+ service: string;
187
+ sessionName: string;
188
+ pipeActive: boolean;
189
+ startedAt?: string;
190
+ lastError?: string;
191
+ }
192
+ interface WatcherOptions {
193
+ service: string;
194
+ project: string;
195
+ sessionName: string;
196
+ outputDir: string;
197
+ patterns: ErrorPattern[];
198
+ dedupeWindowMs: number;
199
+ contextLines: number;
200
+ }
201
+ interface PatternMatch {
202
+ pattern: ErrorPattern;
203
+ line: string;
204
+ }
205
+
206
+ declare const BUILTIN_PATTERN_SETS: Record<string, ErrorPattern[]>;
207
+ declare function matchPatterns(line: string, patterns: ErrorPattern[]): PatternMatch | null;
208
+ declare function resolvePatterns(globalConfig: GlobalWatchConfig | undefined, serviceConfig: ServiceWatchConfig | undefined): ErrorPattern[];
209
+ declare function isStackTraceLine(line: string): boolean;
210
+
211
+ declare function computeContentHash(service: string, patternName: string, content: string): string;
212
+ interface RingBuffer<T> {
213
+ push(item: T): void;
214
+ getAll(): T[];
215
+ clear(): void;
216
+ }
217
+ declare function createRingBuffer<T>(capacity: number): RingBuffer<T>;
218
+ declare class DedupeCache {
219
+ private cache;
220
+ private windowMs;
221
+ private maxSize;
222
+ constructor(windowMs: number, maxSize?: number);
223
+ isDuplicate(hash: string): boolean;
224
+ private cleanup;
225
+ }
226
+
227
+ declare function ensureOutputDir(outputDir?: string): string;
228
+ declare function getQueuePath(outputDir?: string): string;
229
+ declare function writeEvent(options: WatcherOptions, pattern: ErrorPattern, rawContent: string, context: string[], stackTrace?: string[]): TriggerEvent;
230
+ declare function readQueue(outputDir?: string): TriggerEvent[];
231
+ declare function getPendingEvents(outputDir?: string): TriggerEvent[];
232
+ declare function clearQueue(outputDir?: string): void;
233
+ declare function updateEventStatus(eventId: string, status: "pending" | "processing" | "resolved" | "dismissed", outputDir?: string): boolean;
234
+
235
+ declare function startWatcher$1(options: WatcherOptions): void;
236
+
237
+ declare function getWatcherStatus(config: ResolvedConfig, serviceName: string): ServiceWatchState;
238
+ declare function getAllWatcherStatuses(config: ResolvedConfig): ServiceWatchState[];
239
+ declare function startWatcher(config: ResolvedConfig, serviceName: string, options?: {
240
+ quiet?: boolean;
241
+ }): boolean;
242
+ declare function stopWatcher(config: ResolvedConfig, serviceName: string, options?: {
243
+ quiet?: boolean;
244
+ }): boolean;
245
+ declare function startAllWatchers(config: ResolvedConfig, options?: {
246
+ quiet?: boolean;
247
+ }): void;
248
+ declare function stopAllWatchers(config: ResolvedConfig, options?: {
249
+ quiet?: boolean;
250
+ }): void;
251
+
252
+ declare const index$1_BUILTIN_PATTERN_SETS: typeof BUILTIN_PATTERN_SETS;
253
+ type index$1_DedupeCache = DedupeCache;
254
+ declare const index$1_DedupeCache: typeof DedupeCache;
255
+ type index$1_ErrorPattern = ErrorPattern;
256
+ type index$1_GlobalWatchConfig = GlobalWatchConfig;
257
+ type index$1_PatternMatch = PatternMatch;
258
+ type index$1_PatternSet = PatternSet;
259
+ type index$1_ServiceWatchConfig = ServiceWatchConfig;
260
+ type index$1_ServiceWatchState = ServiceWatchState;
261
+ type index$1_TriggerEvent = TriggerEvent;
262
+ type index$1_WatcherOptions = WatcherOptions;
263
+ declare const index$1_clearQueue: typeof clearQueue;
264
+ declare const index$1_computeContentHash: typeof computeContentHash;
265
+ declare const index$1_createRingBuffer: typeof createRingBuffer;
266
+ declare const index$1_ensureOutputDir: typeof ensureOutputDir;
267
+ declare const index$1_getAllWatcherStatuses: typeof getAllWatcherStatuses;
268
+ declare const index$1_getPendingEvents: typeof getPendingEvents;
269
+ declare const index$1_getQueuePath: typeof getQueuePath;
270
+ declare const index$1_getWatcherStatus: typeof getWatcherStatus;
271
+ declare const index$1_isStackTraceLine: typeof isStackTraceLine;
272
+ declare const index$1_matchPatterns: typeof matchPatterns;
273
+ declare const index$1_readQueue: typeof readQueue;
274
+ declare const index$1_resolvePatterns: typeof resolvePatterns;
275
+ declare const index$1_startAllWatchers: typeof startAllWatchers;
276
+ declare const index$1_stopAllWatchers: typeof stopAllWatchers;
277
+ declare const index$1_updateEventStatus: typeof updateEventStatus;
278
+ declare const index$1_writeEvent: typeof writeEvent;
279
+ declare namespace index$1 {
280
+ export { index$1_BUILTIN_PATTERN_SETS as BUILTIN_PATTERN_SETS, index$1_DedupeCache as DedupeCache, type index$1_ErrorPattern as ErrorPattern, type index$1_GlobalWatchConfig as GlobalWatchConfig, type index$1_PatternMatch as PatternMatch, type index$1_PatternSet as PatternSet, type index$1_ServiceWatchConfig as ServiceWatchConfig, type index$1_ServiceWatchState as ServiceWatchState, type index$1_TriggerEvent as TriggerEvent, type index$1_WatcherOptions as WatcherOptions, index$1_clearQueue as clearQueue, index$1_computeContentHash as computeContentHash, index$1_createRingBuffer as createRingBuffer, index$1_ensureOutputDir as ensureOutputDir, index$1_getAllWatcherStatuses as getAllWatcherStatuses, index$1_getPendingEvents as getPendingEvents, index$1_getQueuePath as getQueuePath, index$1_getWatcherStatus as getWatcherStatus, index$1_isStackTraceLine as isStackTraceLine, index$1_matchPatterns as matchPatterns, index$1_readQueue as readQueue, index$1_resolvePatterns as resolvePatterns, index$1_startAllWatchers as startAllWatchers, startWatcher as startServiceWatcher, startWatcher$1 as startWatcher, index$1_stopAllWatchers as stopAllWatchers, stopWatcher as stopServiceWatcher, index$1_updateEventStatus as updateEventStatus, index$1_writeEvent as writeEvent };
281
+ }
282
+
283
+ interface DashboardOptions {
284
+ port?: number;
285
+ open?: boolean;
286
+ }
287
+ declare function startDashboard(options?: DashboardOptions): Server;
288
+
289
+ interface DashboardService {
290
+ name: string;
291
+ healthy: boolean;
292
+ port?: number;
293
+ resolvedPort?: number;
294
+ hasHealthCheck: boolean;
295
+ }
296
+ interface DashboardData {
297
+ project: string;
298
+ instanceId: string;
299
+ configPath: string;
300
+ dashboardPort: number;
301
+ services: DashboardService[];
302
+ }
303
+
304
+ type index_DashboardData = DashboardData;
305
+ type index_DashboardOptions = DashboardOptions;
306
+ type index_DashboardService = DashboardService;
307
+ declare const index_startDashboard: typeof startDashboard;
308
+ declare namespace index {
309
+ export { type index_DashboardData as DashboardData, type index_DashboardOptions as DashboardOptions, type index_DashboardService as DashboardService, index_startDashboard as startDashboard };
105
310
  }
106
311
 
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 };
312
+ export { type DevMuxConfig, type EnsureResult, type HealthCheckType, type ResolvedConfig, type RunOptions, type ServiceDefinition, type ServiceStatus, attachService, index as dashboard, discoverFromTurbo, ensureService, formatDiscoveredConfig, getAllStatus, getServiceCwd, getSessionName, getStatus, checkers as health, loadConfig, restartService, runWithServices, stopAllServices, stopService, driver as tmux, index$1 as watch };
package/dist/index.js CHANGED
@@ -1,21 +1,38 @@
1
+ import {
2
+ discoverFromTurbo,
3
+ formatDiscoveredConfig,
4
+ runWithServices,
5
+ watch_exports
6
+ } from "./chunk-6EU6ODXX.js";
7
+ import "./chunk-32R7KDZB.js";
8
+ import {
9
+ dashboard_exports
10
+ } from "./chunk-T6I3CPOV.js";
1
11
  import {
2
12
  attachService,
3
13
  checkers_exports,
4
- discoverFromTurbo,
5
14
  driver_exports,
6
15
  ensureService,
7
- formatDiscoveredConfig,
8
16
  getAllStatus,
9
17
  getServiceCwd,
10
18
  getSessionName,
11
19
  getStatus,
20
+ init_loader,
12
21
  loadConfig,
13
- runWithServices,
22
+ restartService,
14
23
  stopAllServices,
15
24
  stopService
16
- } from "./chunk-7JJYYMUP.js";
25
+ } from "./chunk-ALENFKSX.js";
26
+ import {
27
+ init_esm_shims
28
+ } from "./chunk-66UOCF5R.js";
29
+
30
+ // src/index.ts
31
+ init_esm_shims();
32
+ init_loader();
17
33
  export {
18
34
  attachService,
35
+ dashboard_exports as dashboard,
19
36
  discoverFromTurbo,
20
37
  ensureService,
21
38
  formatDiscoveredConfig,
@@ -25,8 +42,10 @@ export {
25
42
  getStatus,
26
43
  checkers_exports as health,
27
44
  loadConfig,
45
+ restartService,
28
46
  runWithServices,
29
47
  stopAllServices,
30
48
  stopService,
31
- driver_exports as tmux
49
+ driver_exports as tmux,
50
+ watch_exports as watch
32
51
  };
@@ -0,0 +1,106 @@
1
+ import {
2
+ init_esm_shims
3
+ } from "./chunk-66UOCF5R.js";
4
+
5
+ // src/telemetry/server-manager.ts
6
+ init_esm_shims();
7
+ import { spawn } from "child_process";
8
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from "fs";
9
+ import { join } from "path";
10
+ var PID_FILE = join(process.env.HOME ?? "~", ".opencode", "telemetry-server.pid");
11
+ var DEFAULT_PORT = 9876;
12
+ var DEFAULT_HOST = "0.0.0.0";
13
+ function getPid() {
14
+ if (!existsSync(PID_FILE)) return null;
15
+ try {
16
+ const pid = parseInt(readFileSync(PID_FILE, "utf-8").trim(), 10);
17
+ if (isNaN(pid)) return null;
18
+ try {
19
+ process.kill(pid, 0);
20
+ return pid;
21
+ } catch {
22
+ unlinkSync(PID_FILE);
23
+ return null;
24
+ }
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ function savePid(pid) {
30
+ const dir = join(process.env.HOME ?? "~", ".opencode");
31
+ if (!existsSync(dir)) {
32
+ mkdirSync(dir, { recursive: true });
33
+ }
34
+ writeFileSync(PID_FILE, String(pid));
35
+ }
36
+ function clearPid() {
37
+ if (existsSync(PID_FILE)) {
38
+ unlinkSync(PID_FILE);
39
+ }
40
+ }
41
+ function getServerStatus() {
42
+ const pid = getPid();
43
+ if (!pid) {
44
+ return { running: false };
45
+ }
46
+ return {
47
+ running: true,
48
+ pid,
49
+ port: DEFAULT_PORT,
50
+ host: DEFAULT_HOST
51
+ };
52
+ }
53
+ function startServer(options = {}) {
54
+ const existingPid = getPid();
55
+ if (existingPid) {
56
+ console.log(`Telemetry server already running (PID: ${existingPid})`);
57
+ return false;
58
+ }
59
+ const port = options.port ?? DEFAULT_PORT;
60
+ const host = options.host ?? DEFAULT_HOST;
61
+ try {
62
+ const child = spawn("devmux-telemetry-server", ["start", "--port", String(port), "--host", host], {
63
+ detached: true,
64
+ stdio: ["ignore", "pipe", "pipe"]
65
+ });
66
+ if (child.pid) {
67
+ savePid(child.pid);
68
+ child.unref();
69
+ console.log(`Telemetry server started (PID: ${child.pid})`);
70
+ console.log(`Listening on ws://${host}:${port}`);
71
+ return true;
72
+ }
73
+ return false;
74
+ } catch (error) {
75
+ if (error.code === "ENOENT") {
76
+ console.error("devmux-telemetry-server not found.");
77
+ console.error("Install it with: pnpm add -g @chriscode/devmux-telemetry-server");
78
+ console.error("Or add it to your project: pnpm add -D @chriscode/devmux-telemetry-server");
79
+ } else {
80
+ console.error("Failed to start server:", error);
81
+ }
82
+ return false;
83
+ }
84
+ }
85
+ function stopServer() {
86
+ const pid = getPid();
87
+ if (!pid) {
88
+ console.log("Telemetry server is not running");
89
+ return false;
90
+ }
91
+ try {
92
+ process.kill(pid, "SIGTERM");
93
+ clearPid();
94
+ console.log(`Telemetry server stopped (PID: ${pid})`);
95
+ return true;
96
+ } catch (error) {
97
+ clearPid();
98
+ console.error("Failed to stop server:", error);
99
+ return false;
100
+ }
101
+ }
102
+ export {
103
+ getServerStatus,
104
+ startServer,
105
+ stopServer
106
+ };
@@ -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