@rigstate/cli 0.7.24 → 0.7.26
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.cjs +863 -788
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +863 -788
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/daemon.ts +1 -115
- package/src/commands/link.ts +53 -6
- package/src/daemon/core.ts +28 -11
- package/src/utils/service-manager.ts +109 -0
- package/.rigstate/daemon.state.json +0 -8
package/package.json
CHANGED
package/src/commands/daemon.ts
CHANGED
|
@@ -14,9 +14,8 @@ import chalk from 'chalk';
|
|
|
14
14
|
import ora from 'ora';
|
|
15
15
|
import fs from 'fs/promises';
|
|
16
16
|
import path from 'path';
|
|
17
|
-
import { execSync } from 'child_process';
|
|
18
|
-
import { fileURLToPath } from 'url';
|
|
19
17
|
import { createDaemon } from '../daemon/factory.js';
|
|
18
|
+
import { enableDaemon, disableDaemon } from '../utils/service-manager.js';
|
|
20
19
|
|
|
21
20
|
const PID_FILE = '.rigstate/daemon.pid';
|
|
22
21
|
const STATE_FILE = '.rigstate/daemon.state.json';
|
|
@@ -220,116 +219,3 @@ async function showStatus(): Promise<void> {
|
|
|
220
219
|
console.log('');
|
|
221
220
|
}
|
|
222
221
|
|
|
223
|
-
async function enableDaemon() {
|
|
224
|
-
console.log(chalk.bold('\n⚙️ Enabling Rigstate Background Service (macOS)\n'));
|
|
225
|
-
|
|
226
|
-
if (process.platform !== 'darwin') {
|
|
227
|
-
console.error(chalk.red('❌ Currently only macOS is supported for auto-start.'));
|
|
228
|
-
console.error(chalk.yellow('PRs welcome for Linux/Windows support!'));
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const homeDir = process.env.HOME || '';
|
|
233
|
-
if (!homeDir) {
|
|
234
|
-
console.error(chalk.red('❌ Could not determine HOME directory.'));
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const agentsDir = path.join(homeDir, 'Library/LaunchAgents');
|
|
239
|
-
const logDir = path.join(homeDir, '.rigstate/logs');
|
|
240
|
-
const plistPath = path.join(agentsDir, 'com.rigstate.daemon.plist');
|
|
241
|
-
|
|
242
|
-
// Ensure directories exist
|
|
243
|
-
await fs.mkdir(agentsDir, { recursive: true });
|
|
244
|
-
await fs.mkdir(logDir, { recursive: true });
|
|
245
|
-
|
|
246
|
-
// Find the executable path
|
|
247
|
-
// We assume 'rigstate' is in the PATH, but for launchd we need absolute path
|
|
248
|
-
// Or we use the node executable and the script path
|
|
249
|
-
|
|
250
|
-
// Strategy: Use the currently executing script path
|
|
251
|
-
// This file is likely in dist/commands/daemon.js, we need dist/index.js
|
|
252
|
-
// Strategy: Use the currently executing script path
|
|
253
|
-
// In the bundle, this file is merged into index.js
|
|
254
|
-
const scriptPath = fileURLToPath(import.meta.url);
|
|
255
|
-
const nodePath = process.execPath; // Absolute path to node executable
|
|
256
|
-
|
|
257
|
-
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
258
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
259
|
-
<plist version="1.0">
|
|
260
|
-
<dict>
|
|
261
|
-
<key>Label</key>
|
|
262
|
-
<string>com.rigstate.daemon</string>
|
|
263
|
-
<key>ProgramArguments</key>
|
|
264
|
-
<array>
|
|
265
|
-
<string>${nodePath}</string>
|
|
266
|
-
<string>${scriptPath}</string>
|
|
267
|
-
<string>daemon</string>
|
|
268
|
-
<string>--no-bridge</string>
|
|
269
|
-
</array>
|
|
270
|
-
<key>WorkingDirectory</key>
|
|
271
|
-
<string>${process.cwd()}</string>
|
|
272
|
-
<key>StandardOutPath</key>
|
|
273
|
-
<string>${path.join(logDir, 'daemon.out.log')}</string>
|
|
274
|
-
<key>StandardErrorPath</key>
|
|
275
|
-
<string>${path.join(logDir, 'daemon.err.log')}</string>
|
|
276
|
-
<key>RunAtLoad</key>
|
|
277
|
-
<true/>
|
|
278
|
-
<key>KeepAlive</key>
|
|
279
|
-
<true/>
|
|
280
|
-
<key>EnvironmentVariables</key>
|
|
281
|
-
<dict>
|
|
282
|
-
<key>PATH</key>
|
|
283
|
-
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${process.env.PATH}</string>
|
|
284
|
-
</dict>
|
|
285
|
-
</dict>
|
|
286
|
-
</plist>`;
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
await fs.writeFile(plistPath, plistContent);
|
|
290
|
-
console.log(chalk.dim(`Created plist at: ${plistPath}`));
|
|
291
|
-
|
|
292
|
-
// Load the agent
|
|
293
|
-
try {
|
|
294
|
-
await execShellCommand(`launchctl unload ${plistPath}`); // Unload if exists
|
|
295
|
-
} catch (e) { } // Ignore error if not loaded
|
|
296
|
-
|
|
297
|
-
await execShellCommand(`launchctl load ${plistPath}`);
|
|
298
|
-
|
|
299
|
-
console.log(chalk.green('✅ Successfully enabled background daemon!'));
|
|
300
|
-
console.log(chalk.dim(`Logs: ${logDir}`));
|
|
301
|
-
console.log(chalk.dim('The daemon will now restart automatically if it crashes or on reboot.'));
|
|
302
|
-
|
|
303
|
-
} catch (error: any) {
|
|
304
|
-
console.error(chalk.red('❌ Failed to enable daemon:'), error.message);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async function disableDaemon() {
|
|
309
|
-
console.log(chalk.bold('\n⚙️ Disabling Rigstate Background Service\n'));
|
|
310
|
-
|
|
311
|
-
const homeDir = process.env.HOME || '';
|
|
312
|
-
const plistPath = path.join(homeDir, 'Library/LaunchAgents/com.rigstate.daemon.plist');
|
|
313
|
-
|
|
314
|
-
try {
|
|
315
|
-
await execShellCommand(`launchctl unload ${plistPath}`);
|
|
316
|
-
await fs.unlink(plistPath);
|
|
317
|
-
console.log(chalk.green('✅ Successfully disabled background daemon.'));
|
|
318
|
-
} catch (error: any) {
|
|
319
|
-
// If file doesn't exist, it's already disabled
|
|
320
|
-
if (error.code === 'ENOENT') {
|
|
321
|
-
console.log(chalk.green('✅ Daemon was not enabled.'));
|
|
322
|
-
} else {
|
|
323
|
-
console.error(chalk.red('❌ Failed to disable daemon:'), error.message);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async function execShellCommand(cmd: string) {
|
|
329
|
-
try {
|
|
330
|
-
const output = execSync(cmd, { stdio: 'pipe' }).toString();
|
|
331
|
-
return output;
|
|
332
|
-
} catch (error: any) {
|
|
333
|
-
return error.stderr?.toString() || error.stdout?.toString() || error.message;
|
|
334
|
-
}
|
|
335
|
-
}
|
package/src/commands/link.ts
CHANGED
|
@@ -66,8 +66,14 @@ export function createLinkCommand() {
|
|
|
66
66
|
const { syncProjectRules } = await import('./sync-rules.js');
|
|
67
67
|
await syncProjectRules(projectId, apiKey, apiUrl);
|
|
68
68
|
|
|
69
|
-
// 3. Git Hooks
|
|
70
|
-
console.log(chalk.blue('🛡️
|
|
69
|
+
// 3. Git Hooks (Auto-Vaccine)
|
|
70
|
+
console.log(chalk.blue('🛡️ Injecting Guardian hooks...'));
|
|
71
|
+
const { createHooksCommand } = await import('./hooks.js');
|
|
72
|
+
// We can't easily invoke the command action directly without refactoring,
|
|
73
|
+
// but we can reuse the logic. For now, let's replicate the simple install logic
|
|
74
|
+
// or better yet, make a shared utility.
|
|
75
|
+
// Actually, the simplest way for v0.7.25 is to call the install logic directly if possible.
|
|
76
|
+
// Let's use the helper function at the bottom of this file which I see exists.
|
|
71
77
|
await installHooks(process.cwd());
|
|
72
78
|
|
|
73
79
|
console.log('');
|
|
@@ -110,16 +116,57 @@ async function installHooks(cwd: string) {
|
|
|
110
116
|
// Actually, let's just use the `hooks` command logic if possible, or a lightweight version
|
|
111
117
|
|
|
112
118
|
try {
|
|
113
|
-
// Simpler approach: Check if pre-commit exists
|
|
119
|
+
// Simpler approach: Check if pre-commit exists and install if missing
|
|
114
120
|
const preCommitPath = path.join(cwd, '.git/hooks/pre-commit');
|
|
121
|
+
let shouldInstall = false;
|
|
122
|
+
|
|
115
123
|
try {
|
|
116
124
|
await fs.access(preCommitPath);
|
|
117
|
-
|
|
125
|
+
const content = await fs.readFile(preCommitPath, 'utf-8');
|
|
126
|
+
if (content.includes('rigstate')) {
|
|
127
|
+
console.log(chalk.green(' ✔ Git hooks already active'));
|
|
128
|
+
} else {
|
|
129
|
+
shouldInstall = true;
|
|
130
|
+
}
|
|
118
131
|
} catch {
|
|
119
|
-
|
|
132
|
+
shouldInstall = true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (shouldInstall) {
|
|
136
|
+
const PRE_COMMIT_SCRIPT = `#!/bin/sh
|
|
137
|
+
# Rigstate Guardian Pre-commit Hook
|
|
138
|
+
# Installed by: rigstate link (v0.7.25)
|
|
139
|
+
|
|
140
|
+
# 1. Silent Sentinel Check
|
|
141
|
+
if [ -f .rigstate/guardian.lock ]; then
|
|
142
|
+
echo "🛑 INTERVENTION ACTIVE: Commit blocked by Silent Sentinel."
|
|
143
|
+
exit 1
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
echo "🛡️ Running Guardian checks..."
|
|
147
|
+
rigstate check --staged --strict=critical
|
|
148
|
+
exit $?
|
|
149
|
+
`;
|
|
150
|
+
// Ensure hooks dir exists
|
|
151
|
+
await fs.mkdir(path.dirname(preCommitPath), { recursive: true });
|
|
152
|
+
|
|
153
|
+
// Write hook
|
|
154
|
+
if (await fileExists(preCommitPath)) {
|
|
155
|
+
const existing = await fs.readFile(preCommitPath, 'utf-8');
|
|
156
|
+
await fs.writeFile(preCommitPath, existing + '\n\n' + PRE_COMMIT_SCRIPT.replace('#!/bin/sh\n', ''), { mode: 0o755 });
|
|
157
|
+
} else {
|
|
158
|
+
await fs.writeFile(preCommitPath, PRE_COMMIT_SCRIPT, { mode: 0o755 });
|
|
159
|
+
}
|
|
160
|
+
console.log(chalk.green(' ✔ Applied Guardian protection (git-hooks)'));
|
|
120
161
|
}
|
|
121
162
|
|
|
122
|
-
} catch (e) {
|
|
163
|
+
} catch (e: any) {
|
|
123
164
|
// Ignore hook errors during link
|
|
165
|
+
console.log(chalk.dim(' (Skipped hooks: ' + e.message + ')'));
|
|
124
166
|
}
|
|
125
167
|
}
|
|
168
|
+
|
|
169
|
+
async function fileExists(path: string) {
|
|
170
|
+
const fs = await import('fs/promises');
|
|
171
|
+
try { await fs.access(path); return true; } catch { return false; }
|
|
172
|
+
}
|
package/src/daemon/core.ts
CHANGED
|
@@ -160,6 +160,8 @@ export class GuardianDaemon extends EventEmitter {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
private violationsMap = new Map<string, any[]>();
|
|
164
|
+
|
|
163
165
|
private async runIntegrityCheck(filePath: string) {
|
|
164
166
|
if (!this.guardianMonitor) return;
|
|
165
167
|
|
|
@@ -170,25 +172,37 @@ export class GuardianDaemon extends EventEmitter {
|
|
|
170
172
|
if (result.violations.length > 0) {
|
|
171
173
|
this.handleViolations(filePath, result.violations);
|
|
172
174
|
} else {
|
|
173
|
-
// Success -
|
|
174
|
-
|
|
175
|
+
// Success - clear violations for this file
|
|
176
|
+
if (this.violationsMap.has(filePath)) {
|
|
177
|
+
this.violationsMap.delete(filePath);
|
|
178
|
+
this.updateViolationReport(); // Update the aggregate report
|
|
179
|
+
}
|
|
175
180
|
}
|
|
176
181
|
}
|
|
177
182
|
|
|
178
|
-
private async updateViolationReport(violations
|
|
183
|
+
private async updateViolationReport(violations?: any[]) {
|
|
184
|
+
// If violations arg is passed (from handleViolations), update the map first
|
|
185
|
+
// But usually handleViolations calls this without args to just refresh
|
|
186
|
+
|
|
179
187
|
const reportPath = path.join(process.cwd(), '.rigstate', 'ACTIVE_VIOLATIONS.md');
|
|
188
|
+
const allViolations = Array.from(this.violationsMap.entries());
|
|
189
|
+
const totalCount = allViolations.reduce((acc, [, v]) => acc + v.length, 0);
|
|
180
190
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
content += `*Last check: ${new Date().toLocaleString()}*\n\n`;
|
|
191
|
+
let content = `# 🛡️ Guardian Status: ${totalCount > 0 ? '⚠️ ATTENTION' : '✅ PASS'}\n\n`;
|
|
192
|
+
content += `*Last check: ${new Date().toLocaleString()}*\n`;
|
|
193
|
+
content += `*Files with issues: ${allViolations.length}*\n\n`;
|
|
185
194
|
|
|
186
|
-
if (
|
|
195
|
+
if (totalCount === 0) {
|
|
187
196
|
content += "All systems within architectural limits. Frank is satisfied. 🤫\n";
|
|
188
197
|
} else {
|
|
189
198
|
content += "### 🚨 Active Violations\n\n";
|
|
190
|
-
for (const
|
|
191
|
-
|
|
199
|
+
for (const [file, fileViolations] of allViolations) {
|
|
200
|
+
const relPath = path.relative(process.cwd(), file);
|
|
201
|
+
content += `#### 📄 ${relPath}\n`;
|
|
202
|
+
for (const v of fileViolations) {
|
|
203
|
+
content += `- **[${v.severity.toUpperCase()}]**: ${v.message}\n`;
|
|
204
|
+
}
|
|
205
|
+
content += '\n';
|
|
192
206
|
}
|
|
193
207
|
content += "\n---\n*Rigstate Daemon is watching. Fix violations to clear this report.*";
|
|
194
208
|
}
|
|
@@ -202,7 +216,10 @@ export class GuardianDaemon extends EventEmitter {
|
|
|
202
216
|
this.state.violationsFound += violations.length;
|
|
203
217
|
this.emit('violation', { file: filePath, violations });
|
|
204
218
|
|
|
205
|
-
|
|
219
|
+
// Update state map
|
|
220
|
+
this.violationsMap.set(filePath, violations);
|
|
221
|
+
|
|
222
|
+
this.updateViolationReport(); // Push to IDE dashboard
|
|
206
223
|
|
|
207
224
|
for (const v of violations) {
|
|
208
225
|
const level = v.severity === 'critical' ? 'error' : v.severity === 'warning' ? 'warn' : 'info';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
async function execShellCommand(cmd: string) {
|
|
8
|
+
try {
|
|
9
|
+
const output = execSync(cmd, { stdio: 'pipe' }).toString();
|
|
10
|
+
return output;
|
|
11
|
+
} catch (error: any) {
|
|
12
|
+
return error.stderr?.toString() || error.stdout?.toString() || error.message;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function enableDaemon() {
|
|
17
|
+
console.log(chalk.bold('\n⚙️ Enabling Rigstate Background Service (macOS)\n'));
|
|
18
|
+
|
|
19
|
+
if (process.platform !== 'darwin') {
|
|
20
|
+
console.error(chalk.red('❌ Currently only macOS is supported for auto-start.'));
|
|
21
|
+
console.error(chalk.yellow('PRs welcome for Linux/Windows support!'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const homeDir = process.env.HOME || '';
|
|
26
|
+
if (!homeDir) {
|
|
27
|
+
console.error(chalk.red('❌ Could not determine HOME directory.'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const agentsDir = path.join(homeDir, 'Library/LaunchAgents');
|
|
32
|
+
const logDir = path.join(homeDir, '.rigstate/logs');
|
|
33
|
+
const plistPath = path.join(agentsDir, 'com.rigstate.daemon.plist');
|
|
34
|
+
|
|
35
|
+
// Ensure directories exist
|
|
36
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
37
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
38
|
+
|
|
39
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
40
|
+
const nodePath = process.execPath;
|
|
41
|
+
|
|
42
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
43
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
44
|
+
<plist version="1.0">
|
|
45
|
+
<dict>
|
|
46
|
+
<key>Label</key>
|
|
47
|
+
<string>com.rigstate.daemon</string>
|
|
48
|
+
<key>ProgramArguments</key>
|
|
49
|
+
<array>
|
|
50
|
+
<string>${nodePath}</string>
|
|
51
|
+
<string>${scriptPath}</string>
|
|
52
|
+
<string>daemon</string>
|
|
53
|
+
<string>--no-bridge</string>
|
|
54
|
+
</array>
|
|
55
|
+
<key>WorkingDirectory</key>
|
|
56
|
+
<string>${process.cwd()}</string>
|
|
57
|
+
<key>StandardOutPath</key>
|
|
58
|
+
<string>${path.join(logDir, 'daemon.out.log')}</string>
|
|
59
|
+
<key>StandardErrorPath</key>
|
|
60
|
+
<string>${path.join(logDir, 'daemon.err.log')}</string>
|
|
61
|
+
<key>RunAtLoad</key>
|
|
62
|
+
<true/>
|
|
63
|
+
<key>KeepAlive</key>
|
|
64
|
+
<true/>
|
|
65
|
+
<key>EnvironmentVariables</key>
|
|
66
|
+
<dict>
|
|
67
|
+
<key>PATH</key>
|
|
68
|
+
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${process.env.PATH}</string>
|
|
69
|
+
</dict>
|
|
70
|
+
</dict>
|
|
71
|
+
</plist>`;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
await fs.writeFile(plistPath, plistContent);
|
|
75
|
+
console.log(chalk.dim(`Created plist at: ${plistPath}`));
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await execShellCommand(`launchctl unload ${plistPath}`);
|
|
79
|
+
} catch (e) { }
|
|
80
|
+
|
|
81
|
+
await execShellCommand(`launchctl load ${plistPath}`);
|
|
82
|
+
|
|
83
|
+
console.log(chalk.green('✅ Successfully enabled background daemon!'));
|
|
84
|
+
console.log(chalk.dim(`Logs: ${logDir}`));
|
|
85
|
+
console.log(chalk.dim('The daemon will now restart automatically if it crashes or on reboot.'));
|
|
86
|
+
|
|
87
|
+
} catch (error: any) {
|
|
88
|
+
console.error(chalk.red('❌ Failed to enable daemon:'), error.message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function disableDaemon() {
|
|
93
|
+
console.log(chalk.bold('\n⚙️ Disabling Rigstate Background Service\n'));
|
|
94
|
+
|
|
95
|
+
const homeDir = process.env.HOME || '';
|
|
96
|
+
const plistPath = path.join(homeDir, 'Library/LaunchAgents/com.rigstate.daemon.plist');
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await execShellCommand(`launchctl unload ${plistPath}`);
|
|
100
|
+
await fs.unlink(plistPath);
|
|
101
|
+
console.log(chalk.green('✅ Successfully disabled background daemon.'));
|
|
102
|
+
} catch (error: any) {
|
|
103
|
+
if (error.code === 'ENOENT') {
|
|
104
|
+
console.log(chalk.green('✅ Daemon was not enabled.'));
|
|
105
|
+
} else {
|
|
106
|
+
console.error(chalk.red('❌ Failed to disable daemon:'), error.message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|