@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/chunk-32R7KDZB.js +319 -0
- package/dist/chunk-66UOCF5R.js +36 -0
- package/dist/chunk-6EU6ODXX.js +372 -0
- package/dist/chunk-ALENFKSX.js +631 -0
- package/dist/chunk-T6I3CPOV.js +437 -0
- package/dist/cli.js +484 -9
- package/dist/dashboard-3GHLOSV3.js +8 -0
- package/dist/index.d.ts +215 -10
- package/dist/index.js +24 -5
- package/dist/server-manager-6EZWZK56.js +106 -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 +32 -0
- package/package.json +7 -3
- package/dist/chunk-7JJYYMUP.js +0 -542
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
|
|
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
|
|
97
|
-
declare function
|
|
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
|
-
|
|
22
|
+
restartService,
|
|
14
23
|
stopAllServices,
|
|
15
24
|
stopService
|
|
16
|
-
} from "./chunk-
|
|
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
|