@rigstate/cli 0.7.23 → 0.7.25
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 +875 -787
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +875 -787
- 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 +30 -0
- package/src/utils/service-manager.ts +109 -0
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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import ora from 'ora';
|
|
12
12
|
import * as fs from 'fs/promises';
|
|
13
|
+
import path from 'path';
|
|
13
14
|
import { EventEmitter } from 'events';
|
|
14
15
|
import { createFileWatcher, type FileWatcherEvents } from './file-watcher.js';
|
|
15
16
|
import { createHeuristicEngine } from './heuristic-engine.js';
|
|
@@ -85,6 +86,7 @@ export class GuardianDaemon extends EventEmitter {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
this.printActive();
|
|
89
|
+
await this.updateViolationReport([]); // Initialize empty report
|
|
88
90
|
this.emit('started', this.state);
|
|
89
91
|
}
|
|
90
92
|
|
|
@@ -167,13 +169,41 @@ export class GuardianDaemon extends EventEmitter {
|
|
|
167
169
|
|
|
168
170
|
if (result.violations.length > 0) {
|
|
169
171
|
this.handleViolations(filePath, result.violations);
|
|
172
|
+
} else {
|
|
173
|
+
// Success - might need to clear previous violations for this file
|
|
174
|
+
await this.updateViolationReport([]);
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
|
|
178
|
+
private async updateViolationReport(violations: any[]) {
|
|
179
|
+
const reportPath = path.join(process.cwd(), '.rigstate', 'ACTIVE_VIOLATIONS.md');
|
|
180
|
+
|
|
181
|
+
// This is a simplified version. In a real build, we'd aggregate across all files.
|
|
182
|
+
// For now, let's show the most recent or active ones.
|
|
183
|
+
let content = `# 🛡️ Guardian Status: ${violations.length > 0 ? '⚠️ ATTENTION' : '✅ PASS'}\n\n`;
|
|
184
|
+
content += `*Last check: ${new Date().toLocaleString()}*\n\n`;
|
|
185
|
+
|
|
186
|
+
if (violations.length === 0) {
|
|
187
|
+
content += "All systems within architectural limits. Frank is satisfied. 🤫\n";
|
|
188
|
+
} else {
|
|
189
|
+
content += "### 🚨 Active Violations\n\n";
|
|
190
|
+
for (const v of violations) {
|
|
191
|
+
content += `- **[${v.severity.toUpperCase()}]**: ${v.message}\n`;
|
|
192
|
+
}
|
|
193
|
+
content += "\n---\n*Rigstate Daemon is watching. Fix violations to clear this report.*";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
await fs.writeFile(reportPath, content, 'utf-8');
|
|
198
|
+
} catch (e) { /* ignore */ }
|
|
199
|
+
}
|
|
200
|
+
|
|
173
201
|
private handleViolations(filePath: string, violations: any[]) {
|
|
174
202
|
this.state.violationsFound += violations.length;
|
|
175
203
|
this.emit('violation', { file: filePath, violations });
|
|
176
204
|
|
|
205
|
+
this.updateViolationReport(violations); // Push to IDE dashboard
|
|
206
|
+
|
|
177
207
|
for (const v of violations) {
|
|
178
208
|
const level = v.severity === 'critical' ? 'error' : v.severity === 'warning' ? 'warn' : 'info';
|
|
179
209
|
Logger[level as 'info'](`[${v.severity.toUpperCase()}] ${filePath}: ${v.message}`);
|
|
@@ -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
|
+
}
|