@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.
- package/CHANGELOG.md +28 -1
- package/README.md +215 -202
- package/bin/autopilot.js +9 -2
- package/docs/CONFIGURATION.md +103 -103
- package/docs/DESIGN_PRINCIPLES.md +114 -114
- package/docs/TEAM-MODE.md +51 -51
- package/docs/TROUBLESHOOTING.md +21 -21
- package/package.json +75 -69
- package/src/commands/config.js +110 -110
- package/src/commands/dashboard.mjs +151 -151
- package/src/commands/doctor.js +127 -153
- package/src/commands/guide.js +63 -0
- package/src/commands/init.js +8 -9
- package/src/commands/insights.js +237 -237
- package/src/commands/leaderboard.js +116 -116
- package/src/commands/pause.js +18 -18
- package/src/commands/preset.js +121 -121
- package/src/commands/resume.js +17 -17
- package/src/commands/start.js +41 -41
- package/src/commands/status.js +73 -39
- package/src/commands/stop.js +58 -50
- package/src/commands/undo.js +84 -84
- package/src/config/defaults.js +23 -16
- package/src/config/ignore.js +14 -31
- package/src/config/loader.js +80 -80
- package/src/core/commit.js +45 -52
- package/src/core/commitMessageGenerator.js +130 -0
- package/src/core/configValidator.js +92 -0
- package/src/core/events.js +110 -110
- package/src/core/focus.js +2 -1
- package/src/core/gemini.js +15 -15
- package/src/core/git.js +29 -2
- package/src/core/history.js +69 -69
- package/src/core/notifier.js +61 -0
- package/src/core/retryQueue.js +152 -0
- package/src/core/safety.js +224 -210
- package/src/core/state.js +69 -71
- package/src/core/watcher.js +193 -66
- package/src/index.js +70 -70
- package/src/utils/banner.js +6 -6
- package/src/utils/crypto.js +18 -18
- package/src/utils/identity.js +41 -41
- package/src/utils/logger.js +86 -68
- package/src/utils/paths.js +62 -62
- package/src/utils/process.js +141 -141
package/src/core/watcher.js
CHANGED
|
@@ -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:
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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('
|
|
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 = '
|
|
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
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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')
|
package/src/utils/banner.js
CHANGED
|
@@ -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
|
+
};
|
package/src/utils/crypto.js
CHANGED
|
@@ -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
|
+
};
|