@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/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: HealthCheckType;
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 checkHealth(health: HealthCheckType): Promise<boolean>;
97
- declare function getHealthPort(health: HealthCheckType): number | undefined;
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
- } from "./chunk-7JJYYMUP.js";
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.0.0",
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",