@traisetech/autopilot 2.3.0 → 2.5.0

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/README.md +215 -202
  3. package/bin/autopilot.js +9 -2
  4. package/docs/CONFIGURATION.md +103 -103
  5. package/docs/DESIGN_PRINCIPLES.md +114 -114
  6. package/docs/TEAM-MODE.md +51 -51
  7. package/docs/TROUBLESHOOTING.md +21 -21
  8. package/package.json +75 -69
  9. package/src/commands/config.js +110 -110
  10. package/src/commands/dashboard.mjs +151 -151
  11. package/src/commands/doctor.js +127 -153
  12. package/src/commands/guide.js +63 -0
  13. package/src/commands/init.js +8 -9
  14. package/src/commands/insights.js +237 -237
  15. package/src/commands/leaderboard.js +116 -116
  16. package/src/commands/pause.js +18 -18
  17. package/src/commands/preset.js +121 -121
  18. package/src/commands/resume.js +17 -17
  19. package/src/commands/start.js +41 -41
  20. package/src/commands/status.js +73 -39
  21. package/src/commands/stop.js +58 -50
  22. package/src/commands/undo.js +84 -84
  23. package/src/config/defaults.js +23 -16
  24. package/src/config/ignore.js +14 -31
  25. package/src/config/loader.js +80 -80
  26. package/src/core/commit.js +45 -52
  27. package/src/core/commitMessageGenerator.js +130 -0
  28. package/src/core/configValidator.js +92 -0
  29. package/src/core/events.js +110 -110
  30. package/src/core/focus.js +2 -1
  31. package/src/core/gemini.js +15 -15
  32. package/src/core/git.js +29 -2
  33. package/src/core/history.js +69 -69
  34. package/src/core/notifier.js +61 -0
  35. package/src/core/retryQueue.js +152 -0
  36. package/src/core/safety.js +224 -210
  37. package/src/core/state.js +69 -71
  38. package/src/core/watcher.js +193 -66
  39. package/src/index.js +70 -70
  40. package/src/utils/banner.js +6 -6
  41. package/src/utils/crypto.js +18 -18
  42. package/src/utils/identity.js +41 -41
  43. package/src/utils/logger.js +86 -68
  44. package/src/utils/paths.js +62 -62
  45. package/src/utils/process.js +141 -141
@@ -19,8 +19,11 @@ const { loadConfig } = require('../config/loader');
19
19
  const { readIgnoreFile, createIgnoredFilter, normalizePath } = require('../config/ignore');
20
20
  const HistoryManager = require('./history');
21
21
  const StateManager = require('./state');
22
- const { validateBeforeCommit, checkTeamStatus } = require('./safety');
23
- const { syncLeaderboard } = require('../commands/leaderboard');
22
+ const { validateBeforeCommit, checkTeamStatus, isProtectedBranch } = require('./safety');
23
+ const { syncLeaderboard } = require('../commands/leaderboard');
24
+ const { validateConfig } = require('./configValidator');
25
+ const { notify } = require('./notifier');
26
+ const RetryQueue = require('./retryQueue');
24
27
 
25
28
  class Watcher {
26
29
  constructor(repoPath) {
@@ -33,12 +36,15 @@ class Watcher {
33
36
  this.maxWaitTimer = null;
34
37
  this.firstChangeTime = null;
35
38
  this.lastCommitAt = 0;
36
- this.logFilePath = path.join(repoPath, 'autopilot.log');
39
+ this.logFilePath = path.join(repoPath, '.autopilot.log');
37
40
  this.ignorePatterns = [];
38
41
  this.ignoredFilter = null;
39
42
  this.focusEngine = new FocusEngine(repoPath);
40
43
  this.historyManager = new HistoryManager(repoPath);
41
44
  this.stateManager = new StateManager(repoPath);
45
+ this.retryQueue = new RetryQueue(repoPath, git.push.bind(git));
46
+ this.statePath = path.join(repoPath, '.autopilot-state.json');
47
+ this.startedAt = Date.now();
42
48
  }
43
49
 
44
50
  logVerbose(message) {
@@ -66,12 +72,21 @@ class Watcher {
66
72
 
67
73
  // Initialize environment
68
74
  await fs.ensureFile(this.logFilePath);
75
+ logger.setTargetPath(this.repoPath);
69
76
  await savePid(this.repoPath);
70
77
 
71
78
  logger.info('Starting Autopilot watcher...');
72
79
 
73
80
  // Load configuration
74
81
  await this.reloadConfig();
82
+
83
+ // Validate configuration
84
+ const validation = validateConfig(this.config);
85
+ if (!validation.valid) {
86
+ logger.error(`Config error: ${validation.errors[0]}. Fix your .autopilotrc.json and try again.`);
87
+ process.exit(1);
88
+ }
89
+
75
90
  await this.reloadIgnore();
76
91
 
77
92
  // Initial safety check
@@ -91,7 +106,7 @@ class Watcher {
91
106
  ignoreInitial: true,
92
107
  persistent: true,
93
108
  awaitWriteFinish: {
94
- stabilityThreshold: 1000,
109
+ stabilityThreshold: 200,
95
110
  pollInterval: 100,
96
111
  }
97
112
  });
@@ -111,6 +126,11 @@ class Watcher {
111
126
  logger.success(`Autopilot is watching ${this.repoPath}`);
112
127
  logger.info(`Logs: ${this.logFilePath}`);
113
128
 
129
+ // Heartbeat to update status file (for uptime/queue length)
130
+ this.heartbeatTimer = setInterval(() => {
131
+ this.updateStatusFile();
132
+ }, 5000);
133
+
114
134
  // Test Mode Support
115
135
  if (process.env.AUTOPILOT_TEST_MODE) {
116
136
  logger.warn('TEST MODE: Running in foreground for test duration...');
@@ -144,6 +164,11 @@ class Watcher {
144
164
  clearTimeout(this.maxWaitTimer);
145
165
  this.maxWaitTimer = null;
146
166
  }
167
+
168
+ if (this.heartbeatTimer) {
169
+ clearInterval(this.heartbeatTimer);
170
+ this.heartbeatTimer = null;
171
+ }
147
172
 
148
173
  if (this.watcher) {
149
174
  await this.watcher.close();
@@ -155,6 +180,15 @@ class Watcher {
155
180
  }
156
181
 
157
182
  await removePid(this.repoPath);
183
+
184
+ // Cleanup files
185
+ if (fs.existsSync(this.statePath)) fs.unlinkSync(this.statePath);
186
+ if (fs.existsSync(this.logFilePath)) {
187
+ // Tell logger to stop writing to this file before deleting it
188
+ logger.setTargetPath(null);
189
+ fs.unlinkSync(this.logFilePath);
190
+ }
191
+
158
192
  this.isWatching = false;
159
193
  logger.info('Watcher stopped');
160
194
  } catch (error) {
@@ -176,11 +210,12 @@ class Watcher {
176
210
  // Internal Ignore Check (Redundant but safe)
177
211
  // We use the same filter function passed to Chokidar
178
212
  if (this.ignoredFilter && this.ignoredFilter(absolutePath)) {
213
+ logger.debug(`Ignoring filtered path: ${relativePath}`);
179
214
  return;
180
215
  }
181
216
 
182
217
  // Additional strict check for critical files just in case
183
- if (relativePath.includes('.git/') || relativePath.endsWith('autopilot.log')) {
218
+ if (relativePath.includes('.git/') || relativePath.includes('.autopilot/') || relativePath.includes('autopilot.log') || relativePath.includes('.autopilot-state.json')) {
184
219
  return;
185
220
  }
186
221
 
@@ -196,7 +231,13 @@ class Watcher {
196
231
  * Schedule processing with debounce
197
232
  */
198
233
  scheduleProcess() {
199
- const debounceMs = (this.config?.debounceSeconds || 5) * 1000;
234
+ // Prioritize debounceSeconds if explicitly set, otherwise use debounceMs
235
+ let debounceMs = 20000;
236
+ if (this.config.debounceSeconds !== undefined) {
237
+ debounceMs = this.config.debounceSeconds * 1000;
238
+ } else if (this.config.debounceMs !== undefined) {
239
+ debounceMs = this.config.debounceMs;
240
+ }
200
241
  const maxWaitMs = (this.config?.maxWaitSeconds || 60) * 1000; // Default 60s max wait
201
242
 
202
243
  // Reset debounce timer
@@ -215,9 +256,10 @@ class Watcher {
215
256
  }, maxWaitMs);
216
257
  }
217
258
 
218
- logger.debug('Debounce fired. Waiting...');
259
+ logger.debug(`Debounce scheduled for ${debounceMs}ms. Timer ID exists: ${!!this.debounceTimer}`);
219
260
 
220
261
  this.debounceTimer = setTimeout(() => {
262
+ logger.debug(`Debounce timer reached (${debounceMs}ms). Processing...`);
221
263
  this.processChanges();
222
264
  }, debounceMs);
223
265
  }
@@ -276,7 +318,8 @@ class Watcher {
276
318
  this.firstChangeTime = null;
277
319
 
278
320
  try {
279
- logger.debug('Checking git status...');
321
+ logger.debug('Starting processChanges cycle...');
322
+ logger.debug('Checking git status...');
280
323
 
281
324
  // 0. Pause Check
282
325
  if (this.stateManager.isPaused()) {
@@ -302,6 +345,9 @@ class Watcher {
302
345
  return;
303
346
  }
304
347
 
348
+ // Update state: processing
349
+ await this.updateStatusFile({ status: 'committing' });
350
+
305
351
  // 3. Safety: Branch check
306
352
  const branch = await git.getBranch(this.repoPath);
307
353
  if (this.config?.blockedBranches?.includes(branch)) {
@@ -311,9 +357,14 @@ class Watcher {
311
357
 
312
358
  // 4. Safety: Team Mode & Remote check
313
359
  logger.debug('Checking team/remote status...');
360
+ // Note: checkTeamStatus in safety.js also does pull --rebase if configured.
361
+ // But our updated git.push also handles it. We'll rely on git.push for the main logic
362
+ // but keep checkTeamStatus for initial safety.
314
363
  const teamStatus = await checkTeamStatus(this.repoPath, this.config);
315
364
  if (!teamStatus.ok) {
316
365
  logger.warn('Skip commit: Team check failed (Remote ahead or conflict).');
366
+ await this.updateStatusFile({ conflicts: 'Detected during team check' });
367
+ notify('conflict', { branch }, this.config.notificationsEnabled);
317
368
  return;
318
369
  }
319
370
 
@@ -341,24 +392,12 @@ class Watcher {
341
392
  // Add all changes
342
393
  await git.addAll(this.repoPath);
343
394
 
344
- // Generate message
345
395
  const changedFiles = statusObj.files;
346
- let message = 'chore: auto-commit changes';
347
-
348
- if (this.config?.commitMessageMode !== 'simple') {
349
- const diff = await git.getDiff(this.repoPath, true); // Staged diff
350
- message = await generateCommitMessage(changedFiles, diff, this.config);
351
- }
396
+ let message = 'update: auto-commit changes';
352
397
 
353
- // Interactive Review (Skip in test mode if not mocked)
354
- if (this.config?.ai?.interactive && !process.env.AUTOPILOT_TEST_MODE) {
355
- const approval = await this.askApproval(message);
356
- if (!approval.approved) {
357
- logger.warn('Commit skipped by user.');
358
- return;
359
- }
360
- message = approval.message;
361
- }
398
+ // Always use AI if enabled, otherwise fallback to rule-based (handled in generateCommitMessage)
399
+ const diff = await git.getDiff(this.repoPath, true); // Staged diff
400
+ message = await generateCommitMessage(changedFiles, diff, this.config);
362
401
 
363
402
  // Add Trust/Attribution Trailers
364
403
  message = await addTrailers(message);
@@ -369,10 +408,10 @@ class Watcher {
369
408
  this.lastCommitAt = Date.now();
370
409
  this.focusEngine.onCommit();
371
410
 
372
- // Phase 1: Record History
411
+ const hash = await git.getLatestCommitHash(this.repoPath);
412
+
413
+ // Record history
373
414
  try {
374
- // We need the hash of the commit we just made
375
- const hash = await git.getLatestCommitHash(this.repoPath);
376
415
  if (hash) {
377
416
  this.historyManager.addCommit({
378
417
  hash,
@@ -384,6 +423,12 @@ class Watcher {
384
423
  logger.error(`Failed to record history: ${err.message}`);
385
424
  }
386
425
 
426
+ await this.updateStatusFile({
427
+ lastCommitHash: hash,
428
+ lastCommitMessage: message,
429
+ lastCommitAt: Date.now()
430
+ });
431
+
387
432
  logger.success('Commit complete');
388
433
  } else {
389
434
  logger.error(`Commit failed: ${commitResult.stderr}`);
@@ -393,48 +438,78 @@ class Watcher {
393
438
  // 7. Auto-push
394
439
  if (this.config?.autoPush) {
395
440
  logger.info('Pushing to remote...');
396
- const pushResult = await git.push(this.repoPath);
441
+
442
+ // Protected branch check
443
+ if (isProtectedBranch(branch, this.config) && !this.config.allowPushToProtected) {
444
+ logger.warn(`Autopilot paused — direct push to ${branch} is blocked. Set allowPushToProtected: true in .autopilotrc.json to override.`);
445
+ await this.updateStatusFile({ status: 'watching', branch: `${branch} (PROTECTED)` });
446
+ return;
447
+ }
448
+
449
+ await this.updateStatusFile({ status: 'pushing' });
450
+ const pushResult = await git.push(this.repoPath, branch);
451
+
397
452
  if (!pushResult.ok) {
398
- logger.warn(`Push failed: ${pushResult.stderr}`);
399
-
400
- // Safety: Pause on critical push failures (Auth, Permissions, or Persistent errors)
401
- // This aligns with "Failure Behavior: If a push fails -> pause watcher"
402
- logger.error('Push failed! Pausing Autopilot to prevent issues.');
403
- this.stateManager.pause(`Push failed: ${pushResult.stderr.split('\n')[0]}`);
404
- logger.info('Run "autopilot resume" to restart after fixing the issue.');
453
+ if (pushResult.conflict) {
454
+ logger.error('Rebase conflict detected — manual intervention required');
455
+ this.stateManager.pause(`Conflict in ${branch}`);
456
+ await this.updateStatusFile({ status: 'paused', conflicts: `Conflict in ${branch}` });
457
+ notify('conflict', { branch }, this.config.notificationsEnabled);
458
+ return;
459
+ }
460
+
461
+ logger.warn(`Push failed: ${pushResult.stderr}. Queuing for retry.`);
462
+ const latestHash = await git.getLatestCommitHash(this.repoPath);
463
+ this.retryQueue.add({
464
+ commitHash: latestHash,
465
+ branch: branch,
466
+ maxAttempts: this.config.maxRetryAttempts || 5
467
+ });
468
+
469
+ await this.updateStatusFile({
470
+ status: 'watching',
471
+ lastPushStatus: 'queued',
472
+ queueLength: this.retryQueue.queue.length
473
+ });
474
+ notify('push_failed', {}, this.config.notificationsEnabled);
405
475
  } else {
406
- logger.success('Push complete');
407
-
408
- // Emit Event for Leaderboard/Telemetry
409
- try {
410
- const identity = await getIdentity();
411
- const latestHash = await git.getLatestCommitHash(this.repoPath);
412
-
413
- await eventSystem.emit({
414
- type: 'push_success',
415
- userId: identity.id,
416
- commitHash: latestHash,
417
- timestamp: Date.now(),
418
- version: version
419
- });
420
- } catch (err) {
421
- logger.debug(`Failed to emit push event: ${err.message}`);
422
- }
423
- try {
424
- const apiUrl = process.env.AUTOPILOT_API_URL || 'https://autopilot-cli.vercel.app';
425
- await syncLeaderboard(apiUrl, { cwd: this.repoPath });
426
- } catch (err) {
427
- logger.debug(`Leaderboard sync failed: ${err.message}`);
428
- }
476
+ logger.success('Push complete');
477
+ const latestHash = await git.getLatestCommitHash(this.repoPath);
478
+
479
+ await this.updateStatusFile({
480
+ status: 'watching',
481
+ lastPushHash: latestHash,
482
+ lastPushStatus: 'succeeded',
483
+ lastPushAt: Date.now()
484
+ });
485
+ notify('push_success', { commitMessage: message }, this.config.notificationsEnabled);
486
+
487
+ // Emit Event for Leaderboard/Telemetry
488
+ try {
489
+ const identity = await getIdentity();
490
+ await eventSystem.emit({
491
+ type: 'push_success',
492
+ userId: identity.id,
493
+ commitHash: latestHash,
494
+ timestamp: Date.now(),
495
+ version: version
496
+ });
497
+ } catch (err) {
498
+ logger.debug(`Failed to emit push event: ${err.message}`);
499
+ }
429
500
  }
430
- } else {
431
- try {
432
- const apiUrl = process.env.AUTOPILOT_API_URL || 'https://autopilot-cli.vercel.app';
433
- await syncLeaderboard(apiUrl, { cwd: this.repoPath });
434
- } catch (err) {
435
- logger.debug(`Leaderboard sync failed: ${err.message}`);
436
- }
437
- }
501
+ }
502
+
503
+ // Cleanup
504
+ await this.updateStatusFile({ status: 'watching' });
505
+
506
+ // Sync leaderboard
507
+ try {
508
+ const apiUrl = process.env.AUTOPILOT_API_URL || 'https://autopilot-cli.vercel.app';
509
+ await syncLeaderboard(apiUrl, { cwd: this.repoPath });
510
+ } catch (err) {
511
+ logger.debug(`Leaderboard sync failed: ${err.message}`);
512
+ }
438
513
 
439
514
  } catch (error) {
440
515
  logger.error(`Process error: ${error.message}`);
@@ -442,6 +517,58 @@ class Watcher {
442
517
  this.isProcessing = false;
443
518
  }
444
519
  }
520
+ /**
521
+ * Update the external status file for the status command
522
+ */
523
+ async updateStatusFile(updates = {}) {
524
+ try {
525
+ let state = {};
526
+ if (fs.existsSync(this.statePath)) {
527
+ state = fs.readJsonSync(this.statePath);
528
+ }
529
+
530
+ if (!this.startedAt) this.startedAt = Date.now();
531
+
532
+ const branch = await git.getBranch(this.repoPath);
533
+ const uptime = Math.floor((Date.now() - this.startedAt) / 1000);
534
+
535
+ const newState = {
536
+ pid: process.pid,
537
+ startedAt: new Date(this.startedAt).toISOString(),
538
+ branch: branch,
539
+ isProtected: isProtectedBranch(branch, this.config),
540
+ status: updates.status || state.status || 'watching',
541
+ lastCommit: updates.lastCommit || state.lastCommit || null,
542
+ lastPush: updates.lastPush || state.lastPush || null,
543
+ queueLength: this.retryQueue ? this.retryQueue.queue.length : 0,
544
+ conflicts: !!(updates.conflicts || state.conflicts),
545
+ watchPath: this.repoPath,
546
+ uptime: uptime
547
+ };
548
+
549
+ // Handle special updates like lastCommitHash/Message to the new structure
550
+ if (updates.lastCommitHash) {
551
+ newState.lastCommit = {
552
+ hash: updates.lastCommitHash,
553
+ message: updates.lastCommitMessage,
554
+ timestamp: new Date(updates.lastCommitAt || Date.now()).toISOString()
555
+ };
556
+ }
557
+
558
+ if (updates.lastPushStatus) {
559
+ newState.lastPush = {
560
+ hash: updates.lastPushHash || (newState.lastCommit ? newState.lastCommit.hash : null),
561
+ success: updates.lastPushStatus === 'succeeded',
562
+ timestamp: new Date(updates.lastPushAt || Date.now()).toISOString()
563
+ };
564
+ }
565
+
566
+ fs.writeJsonSync(this.statePath, newState, { spaces: 2 });
567
+ } catch (err) {
568
+ // Don't fail the watcher if status file write fails
569
+ logger.debug(`Failed to update status file: ${err.message}`);
570
+ }
571
+ }
445
572
  }
446
573
 
447
574
  module.exports = Watcher;
package/src/index.js CHANGED
@@ -1,15 +1,15 @@
1
- const { Command } = require('commander');
2
- const initRepo = require('./commands/init');
3
- const startWatcher = require('./commands/start');
4
- const stopWatcher = require('./commands/stop');
5
- const statusWatcher = require('./commands/status');
6
- const undoCommand = require('./commands/undo');
7
- const doctor = require('./commands/doctor');
8
- const { insights } = require('./commands/insights');
9
- const pauseCommand = require('./commands/pause');
10
- const resumeCommand = require('./commands/resume');
11
- const { leaderboard } = require('./commands/leaderboard');
12
- const pkg = require('../package.json');
1
+ const { Command } = require('commander');
2
+ const initRepo = require('./commands/init');
3
+ const startWatcher = require('./commands/start');
4
+ const stopWatcher = require('./commands/stop');
5
+ const statusWatcher = require('./commands/status');
6
+ const undoCommand = require('./commands/undo');
7
+ const doctor = require('./commands/doctor');
8
+ const { insights } = require('./commands/insights');
9
+ const pauseCommand = require('./commands/pause');
10
+ const resumeCommand = require('./commands/resume');
11
+ const { leaderboard } = require('./commands/leaderboard');
12
+ const pkg = require('../package.json');
13
13
 
14
14
  function run() {
15
15
  const program = new Command();
@@ -19,64 +19,64 @@ function run() {
19
19
  .description('Git automation with safety rails')
20
20
  .version(pkg.version, '-v, --version', 'Show version');
21
21
 
22
- program
23
- .command('leaderboard')
24
- .description('View or sync with the global leaderboard')
25
- .option('--sync', 'Sync your local stats to the leaderboard')
26
- .action(leaderboard);
27
-
28
- program
29
- .command('init')
30
- .description('Initialize autopilot configuration in repository')
31
- .action(initRepo);
32
-
33
- program
34
- .command('start')
35
- .description('Start autopilot watcher in foreground')
36
- .action(startWatcher);
37
-
38
- program
39
- .command('stop')
40
- .description('Stop the running autopilot watcher')
41
- .action(stopWatcher);
42
-
43
- program
44
- .command('status')
45
- .description('Show autopilot watcher status')
46
- .action(statusWatcher);
47
-
48
- program
49
- .command('undo')
50
- .description('Undo the last Autopilot commit')
51
- .option('-c, --count <n>', 'Number of commits to undo', '1')
52
- .action(undoCommand);
53
-
54
- program
55
- .command('pause [reason]')
56
- .description('Pause Autopilot watcher')
57
- .action(pauseCommand);
58
-
59
- program
60
- .command('resume')
61
- .description('Resume Autopilot watcher')
62
- .action(resumeCommand);
63
-
64
- program
65
- .command('dashboard')
66
- .description('View real-time Autopilot dashboard')
67
- .action(async () => {
68
- try {
69
- const { default: runDashboard } = await import('./commands/dashboard.mjs');
70
- runDashboard();
71
- } catch (error) {
72
- console.error('Failed to launch dashboard:', error);
73
- }
74
- });
75
-
76
- program
77
- .command('doctor')
78
- .description('Diagnose and validate autopilot setup')
79
- .action(doctor);
22
+ program
23
+ .command('leaderboard')
24
+ .description('View or sync with the global leaderboard')
25
+ .option('--sync', 'Sync your local stats to the leaderboard')
26
+ .action(leaderboard);
27
+
28
+ program
29
+ .command('init')
30
+ .description('Initialize autopilot configuration in repository')
31
+ .action(initRepo);
32
+
33
+ program
34
+ .command('start')
35
+ .description('Start autopilot watcher in foreground')
36
+ .action(startWatcher);
37
+
38
+ program
39
+ .command('stop')
40
+ .description('Stop the running autopilot watcher')
41
+ .action(stopWatcher);
42
+
43
+ program
44
+ .command('status')
45
+ .description('Show autopilot watcher status')
46
+ .action(statusWatcher);
47
+
48
+ program
49
+ .command('undo')
50
+ .description('Undo the last Autopilot commit')
51
+ .option('-c, --count <n>', 'Number of commits to undo', '1')
52
+ .action(undoCommand);
53
+
54
+ program
55
+ .command('pause [reason]')
56
+ .description('Pause Autopilot watcher')
57
+ .action(pauseCommand);
58
+
59
+ program
60
+ .command('resume')
61
+ .description('Resume Autopilot watcher')
62
+ .action(resumeCommand);
63
+
64
+ program
65
+ .command('dashboard')
66
+ .description('View real-time Autopilot dashboard')
67
+ .action(async () => {
68
+ try {
69
+ const { default: runDashboard } = await import('./commands/dashboard.mjs');
70
+ runDashboard();
71
+ } catch (error) {
72
+ console.error('Failed to launch dashboard:', error);
73
+ }
74
+ });
75
+
76
+ program
77
+ .command('doctor')
78
+ .description('Diagnose and validate autopilot setup')
79
+ .action(doctor);
80
80
 
81
81
  program
82
82
  .command('insights')
@@ -1,6 +1,6 @@
1
- const pkg = require('../../package.json');
2
-
3
- module.exports = () => {
4
- console.log(`\nšŸš€ Autopilot CLI v${pkg.version}`);
5
- console.log(` Built by Praise Masunga (PraiseTechzw)\n`);
6
- };
1
+ const pkg = require('../../package.json');
2
+
3
+ module.exports = () => {
4
+ console.log(`\nšŸš€ Autopilot CLI v${pkg.version}`);
5
+ console.log(` Built by Praise Masunga (PraiseTechzw)\n`);
6
+ };
@@ -1,18 +1,18 @@
1
- const crypto = require('crypto');
2
-
3
- /**
4
- * Generate HMAC signature for commit verification
5
- * @param {string} content - Content to sign (message + timestamp + version)
6
- * @param {string} secret - Secret key (using anonymous ID as salt for now)
7
- * @returns {string} HMAC SHA256 signature
8
- */
9
- function generateSignature(content, secret) {
10
- return crypto
11
- .createHmac('sha256', secret)
12
- .update(content)
13
- .digest('hex');
14
- }
15
-
16
- module.exports = {
17
- generateSignature
18
- };
1
+ const crypto = require('crypto');
2
+
3
+ /**
4
+ * Generate HMAC signature for commit verification
5
+ * @param {string} content - Content to sign (message + timestamp + version)
6
+ * @param {string} secret - Secret key (using anonymous ID as salt for now)
7
+ * @returns {string} HMAC SHA256 signature
8
+ */
9
+ function generateSignature(content, secret) {
10
+ return crypto
11
+ .createHmac('sha256', secret)
12
+ .update(content)
13
+ .digest('hex');
14
+ }
15
+
16
+ module.exports = {
17
+ generateSignature
18
+ };