@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigstate/cli",
3
- "version": "0.7.23",
3
+ "version": "0.7.25",
4
4
  "description": "Rigstate CLI - Code audit, sync and supervision tool",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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
- }
@@ -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('🛡️ Checking immunity system...'));
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
- console.log(chalk.green(' ✔ Git hooks already active'));
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
- console.log(chalk.yellow(' ⚠️ Git hooks missing. Run "rigstate hooks install" to secure repo.'));
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
+ }
@@ -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
+ }