switchman-dev 0.1.8 → 0.1.9
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/README.md +118 -303
- package/package.json +1 -1
- package/src/cli/commands/audit.js +77 -0
- package/src/cli/commands/claude.js +37 -0
- package/src/cli/commands/gate.js +278 -0
- package/src/cli/commands/lease.js +256 -0
- package/src/cli/commands/mcp.js +45 -0
- package/src/cli/commands/monitor.js +191 -0
- package/src/cli/commands/queue.js +549 -0
- package/src/cli/commands/task.js +248 -0
- package/src/cli/commands/telemetry.js +108 -0
- package/src/cli/commands/worktree.js +85 -0
- package/src/cli/index.js +780 -1697
- package/src.zip +0 -0
- package/tests.zip +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export function registerMonitorCommands(program, {
|
|
2
|
+
chalk,
|
|
3
|
+
clearMonitorState,
|
|
4
|
+
getDb,
|
|
5
|
+
getRepo,
|
|
6
|
+
isProcessRunning,
|
|
7
|
+
monitorWorktreesOnce,
|
|
8
|
+
processExecPath,
|
|
9
|
+
readMonitorState,
|
|
10
|
+
renderMonitorEvent,
|
|
11
|
+
resolveMonitoredWorktrees,
|
|
12
|
+
spawn,
|
|
13
|
+
startBackgroundMonitor,
|
|
14
|
+
}) {
|
|
15
|
+
const monitorCmd = program.command('monitor').description('Observe workspaces for runtime file changes');
|
|
16
|
+
monitorCmd._switchmanAdvanced = true;
|
|
17
|
+
|
|
18
|
+
monitorCmd
|
|
19
|
+
.command('once')
|
|
20
|
+
.description('Capture one monitoring pass and log observed file changes')
|
|
21
|
+
.option('--json', 'Output raw JSON')
|
|
22
|
+
.option('--quarantine', 'Move or restore denied runtime changes immediately after detection')
|
|
23
|
+
.action((opts) => {
|
|
24
|
+
const repoRoot = getRepo();
|
|
25
|
+
const db = getDb(repoRoot);
|
|
26
|
+
const worktrees = resolveMonitoredWorktrees(db, repoRoot);
|
|
27
|
+
const result = monitorWorktreesOnce(db, repoRoot, worktrees, { quarantine: opts.quarantine });
|
|
28
|
+
db.close();
|
|
29
|
+
|
|
30
|
+
if (opts.json) {
|
|
31
|
+
console.log(JSON.stringify(result, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (result.events.length === 0) {
|
|
36
|
+
console.log(chalk.dim('No file changes observed since the last monitor snapshot.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(`${chalk.green('✓')} Observed ${result.summary.total} file change(s)`);
|
|
41
|
+
for (const event of result.events) {
|
|
42
|
+
renderMonitorEvent(event);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
monitorCmd
|
|
47
|
+
.command('watch')
|
|
48
|
+
.description('Poll workspaces continuously and log observed file changes')
|
|
49
|
+
.option('--interval-ms <ms>', 'Polling interval in milliseconds', '2000')
|
|
50
|
+
.option('--quarantine', 'Move or restore denied runtime changes immediately after detection')
|
|
51
|
+
.option('--daemonized', 'Internal flag used by monitor start', false)
|
|
52
|
+
.action(async (opts) => {
|
|
53
|
+
const repoRoot = getRepo();
|
|
54
|
+
const intervalMs = Number.parseInt(opts.intervalMs, 10);
|
|
55
|
+
|
|
56
|
+
if (!Number.isFinite(intervalMs) || intervalMs < 100) {
|
|
57
|
+
console.error(chalk.red('--interval-ms must be at least 100'));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(chalk.cyan(`Watching workspaces every ${intervalMs}ms. Press Ctrl+C to stop.`));
|
|
62
|
+
|
|
63
|
+
let stopped = false;
|
|
64
|
+
const stop = () => {
|
|
65
|
+
stopped = true;
|
|
66
|
+
process.stdout.write('\n');
|
|
67
|
+
if (opts.daemonized) {
|
|
68
|
+
clearMonitorState(repoRoot);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
process.on('SIGINT', stop);
|
|
72
|
+
process.on('SIGTERM', stop);
|
|
73
|
+
|
|
74
|
+
while (!stopped) {
|
|
75
|
+
const db = getDb(repoRoot);
|
|
76
|
+
const worktrees = resolveMonitoredWorktrees(db, repoRoot);
|
|
77
|
+
const result = monitorWorktreesOnce(db, repoRoot, worktrees, { quarantine: opts.quarantine });
|
|
78
|
+
db.close();
|
|
79
|
+
|
|
80
|
+
for (const event of result.events) {
|
|
81
|
+
renderMonitorEvent(event);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (stopped) break;
|
|
85
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, intervalMs));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(chalk.dim('Stopped worktree monitor.'));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
monitorCmd
|
|
92
|
+
.command('start')
|
|
93
|
+
.description('Start the worktree monitor as a background process')
|
|
94
|
+
.option('--interval-ms <ms>', 'Polling interval in milliseconds', '2000')
|
|
95
|
+
.option('--quarantine', 'Move or restore denied runtime changes immediately after detection')
|
|
96
|
+
.action((opts) => {
|
|
97
|
+
const repoRoot = getRepo();
|
|
98
|
+
const intervalMs = Number.parseInt(opts.intervalMs, 10);
|
|
99
|
+
const state = startBackgroundMonitor(repoRoot, {
|
|
100
|
+
intervalMs,
|
|
101
|
+
quarantine: Boolean(opts.quarantine),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (state.already_running) {
|
|
105
|
+
console.log(chalk.yellow(`Monitor already running with pid ${state.state.pid}`));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(`${chalk.green('✓')} Started monitor pid ${chalk.cyan(String(state.state.pid))}`);
|
|
110
|
+
console.log(`${chalk.dim('State:')} ${state.state_path}`);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
monitorCmd
|
|
114
|
+
.command('stop')
|
|
115
|
+
.description('Stop the background worktree monitor')
|
|
116
|
+
.action(() => {
|
|
117
|
+
const repoRoot = getRepo();
|
|
118
|
+
const state = readMonitorState(repoRoot);
|
|
119
|
+
|
|
120
|
+
if (!state) {
|
|
121
|
+
console.log(chalk.dim('Monitor is not running.'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!isProcessRunning(state.pid)) {
|
|
126
|
+
clearMonitorState(repoRoot);
|
|
127
|
+
console.log(chalk.dim('Monitor state was stale and has been cleared.'));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
process.kill(state.pid, 'SIGTERM');
|
|
132
|
+
clearMonitorState(repoRoot);
|
|
133
|
+
console.log(`${chalk.green('✓')} Stopped monitor pid ${chalk.cyan(String(state.pid))}`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
monitorCmd
|
|
137
|
+
.command('status')
|
|
138
|
+
.description('Show background monitor process status')
|
|
139
|
+
.action(() => {
|
|
140
|
+
const repoRoot = getRepo();
|
|
141
|
+
const state = readMonitorState(repoRoot);
|
|
142
|
+
|
|
143
|
+
if (!state) {
|
|
144
|
+
console.log(chalk.dim('Monitor is not running.'));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const running = isProcessRunning(state.pid);
|
|
149
|
+
if (!running) {
|
|
150
|
+
clearMonitorState(repoRoot);
|
|
151
|
+
console.log(chalk.yellow('Monitor state existed but the process is no longer running.'));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`${chalk.green('✓')} Monitor running`);
|
|
156
|
+
console.log(` ${chalk.dim('pid')} ${state.pid}`);
|
|
157
|
+
console.log(` ${chalk.dim('interval_ms')} ${state.interval_ms}`);
|
|
158
|
+
console.log(` ${chalk.dim('quarantine')} ${state.quarantine ? 'true' : 'false'}`);
|
|
159
|
+
console.log(` ${chalk.dim('started_at')} ${state.started_at}`);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
program
|
|
163
|
+
.command('watch')
|
|
164
|
+
.description('Watch worktrees for direct writes and rogue edits in real time')
|
|
165
|
+
.option('--interval-ms <ms>', 'Polling interval in milliseconds', '2000')
|
|
166
|
+
.option('--quarantine', 'Move or restore denied runtime changes immediately after detection')
|
|
167
|
+
.action(async (opts) => {
|
|
168
|
+
const repoRoot = getRepo();
|
|
169
|
+
const child = spawn(processExecPath, [
|
|
170
|
+
process.argv[1],
|
|
171
|
+
'monitor',
|
|
172
|
+
'watch',
|
|
173
|
+
'--interval-ms',
|
|
174
|
+
String(opts.intervalMs || '2000'),
|
|
175
|
+
...(opts.quarantine ? ['--quarantine'] : []),
|
|
176
|
+
], {
|
|
177
|
+
cwd: repoRoot,
|
|
178
|
+
stdio: 'inherit',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await new Promise((resolve, reject) => {
|
|
182
|
+
child.on('exit', (code) => {
|
|
183
|
+
process.exitCode = code ?? 0;
|
|
184
|
+
resolve();
|
|
185
|
+
});
|
|
186
|
+
child.on('error', reject);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return monitorCmd;
|
|
191
|
+
}
|