@traisetech/autopilot 0.1.8 → 2.0.1
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 +35 -0
- package/README.md +72 -90
- package/bin/autopilot.js +59 -1
- package/docs/CONFIGURATION.md +100 -82
- package/docs/TEAM-MODE.md +51 -0
- package/package.json +14 -4
- package/src/commands/dashboard.mjs +144 -0
- package/src/commands/init.js +57 -26
- package/src/commands/insights.js +231 -90
- package/src/commands/leaderboard.js +70 -0
- package/src/commands/pause.js +18 -0
- package/src/commands/preset.js +121 -0
- package/src/commands/resume.js +17 -0
- package/src/commands/undo.js +84 -0
- package/src/config/defaults.js +15 -3
- package/src/core/commit.js +31 -6
- package/src/core/gemini.js +20 -8
- package/src/core/git.js +80 -1
- package/src/core/history.js +69 -0
- package/src/core/safety.js +204 -38
- package/src/core/state.js +71 -0
- package/src/core/watcher.js +54 -11
- package/src/index.js +34 -0
- package/src/utils/logger.js +9 -0
package/src/core/safety.js
CHANGED
|
@@ -1,38 +1,204 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const execa = require('execa');
|
|
4
|
+
const logger = require('../utils/logger');
|
|
5
|
+
const git = require('./git');
|
|
6
|
+
|
|
7
|
+
// Regex patterns for common secrets
|
|
8
|
+
const SECRET_PATTERNS = [
|
|
9
|
+
{ name: 'AWS Access Key', regex: /(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}/ },
|
|
10
|
+
{ name: 'AWS Secret Key', regex: /(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/ },
|
|
11
|
+
{ name: 'GitHub Token', regex: /(gh[pousr]_[a-zA-Z0-9]{36,255})/ },
|
|
12
|
+
{ name: 'Stripe Secret Key', regex: /(sk_live_[0-9a-zA-Z]{24})/ },
|
|
13
|
+
{ name: 'Google API Key', regex: /AIza[0-9A-Za-z\\-_]{35}/ },
|
|
14
|
+
{ name: 'Bearer Token', regex: /Bearer [a-zA-Z0-9\-\._~\+\/]+=*/ },
|
|
15
|
+
{ name: 'Generic Private Key', regex: /-----BEGIN PRIVATE KEY-----/ }
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const MAX_FILE_SIZE_MB = 50;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validate configuration
|
|
22
|
+
*/
|
|
23
|
+
const validateConfig = (config) => {
|
|
24
|
+
const errors = [];
|
|
25
|
+
|
|
26
|
+
if (!config) {
|
|
27
|
+
errors.push('Configuration is missing');
|
|
28
|
+
return errors;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (config.commitMessage && typeof config.commitMessage !== 'string') {
|
|
32
|
+
errors.push('commitMessage must be a string');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (config.autoPush && typeof config.autoPush !== 'boolean') {
|
|
36
|
+
errors.push('autoPush must be a boolean');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (config.ignore && !Array.isArray(config.ignore)) {
|
|
40
|
+
errors.push('ignore must be an array');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return errors;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run pre-commit checks on staged files
|
|
48
|
+
* @param {string} repoPath
|
|
49
|
+
* @param {object} config
|
|
50
|
+
* @returns {Promise<{ok: boolean, errors: string[]}>}
|
|
51
|
+
*/
|
|
52
|
+
const validateBeforeCommit = async (repoPath, config) => {
|
|
53
|
+
const errors = [];
|
|
54
|
+
|
|
55
|
+
if (!config.preCommitChecks) {
|
|
56
|
+
return { ok: true, errors: [] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Get staged files
|
|
61
|
+
const statusObj = await git.getPorcelainStatus(repoPath);
|
|
62
|
+
// Filter only modified/added files (ignore deleted)
|
|
63
|
+
const stagedFiles = statusObj.files.filter(f => f.status !== 'D' && f.status !== ' D');
|
|
64
|
+
|
|
65
|
+
// 1. File Size Check
|
|
66
|
+
if (config.preCommitChecks.fileSize) {
|
|
67
|
+
for (const file of stagedFiles) {
|
|
68
|
+
try {
|
|
69
|
+
const filePath = path.join(repoPath, file.file);
|
|
70
|
+
if (fs.existsSync(filePath)) {
|
|
71
|
+
const stats = await fs.stat(filePath);
|
|
72
|
+
const sizeMb = stats.size / (1024 * 1024);
|
|
73
|
+
if (sizeMb > MAX_FILE_SIZE_MB) {
|
|
74
|
+
errors.push(`File ${file.file} is too large (${sizeMb.toFixed(2)}MB > ${MAX_FILE_SIZE_MB}MB)`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// Ignore missing files
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 2. Secret Detection
|
|
84
|
+
if (config.preCommitChecks.secrets) {
|
|
85
|
+
for (const file of stagedFiles) {
|
|
86
|
+
try {
|
|
87
|
+
const filePath = path.join(repoPath, file.file);
|
|
88
|
+
if (fs.existsSync(filePath)) {
|
|
89
|
+
// Read first 1MB only for performance
|
|
90
|
+
// Use stream or buffer
|
|
91
|
+
const buffer = Buffer.alloc(1024 * 1024);
|
|
92
|
+
const fd = await fs.open(filePath, 'r');
|
|
93
|
+
const { bytesRead } = await fs.read(fd, buffer, 0, buffer.length, 0);
|
|
94
|
+
await fs.close(fd);
|
|
95
|
+
|
|
96
|
+
const content = buffer.toString('utf8', 0, bytesRead);
|
|
97
|
+
|
|
98
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
99
|
+
if (pattern.regex.test(content)) {
|
|
100
|
+
errors.push(`Possible ${pattern.name} detected in ${file.file}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
logger.debug(`Could not read ${file.file} for secret scan: ${err.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 3. Linting
|
|
111
|
+
if (config.preCommitChecks.lint && config.preCommitChecks.lint !== false) {
|
|
112
|
+
logger.info('Running lint check...');
|
|
113
|
+
try {
|
|
114
|
+
await execa.command('npm run lint', { cwd: repoPath });
|
|
115
|
+
} catch (err) {
|
|
116
|
+
errors.push(`Lint check failed: ${err.shortMessage || err.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 4. Tests
|
|
121
|
+
if (config.preCommitChecks.test) {
|
|
122
|
+
logger.info('Running tests...');
|
|
123
|
+
try {
|
|
124
|
+
await execa.command(config.preCommitChecks.test, { cwd: repoPath });
|
|
125
|
+
} catch (err) {
|
|
126
|
+
errors.push(`Test check failed: ${err.shortMessage || err.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
} catch (error) {
|
|
131
|
+
logger.error(`Validation error: ${error.message}`);
|
|
132
|
+
// If validation crashes, safe to fail open? No, fail closed for safety.
|
|
133
|
+
errors.push(`Validation crashed: ${error.message}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { ok: errors.length === 0, errors };
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Handle Team Mode Checks (Pull-Before-Push)
|
|
141
|
+
* @param {string} repoPath
|
|
142
|
+
* @param {object} config
|
|
143
|
+
* @returns {Promise<{ok: boolean, action: 'continue'|'pull'|'abort'}>}
|
|
144
|
+
*/
|
|
145
|
+
const checkTeamStatus = async (repoPath, config) => {
|
|
146
|
+
if (!config.teamMode) {
|
|
147
|
+
return { ok: true, action: 'continue' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
logger.debug('Running Team Mode checks...');
|
|
151
|
+
|
|
152
|
+
// Check unpushed commits limit
|
|
153
|
+
try {
|
|
154
|
+
const branch = await git.getBranch(repoPath);
|
|
155
|
+
if (!branch) return { ok: false, action: 'abort' }; // No branch?
|
|
156
|
+
|
|
157
|
+
const { stdout: unpushedCount } = await execa('git', ['rev-list', '--count', `origin/${branch}..HEAD`], { cwd: repoPath }).catch(() => ({ stdout: '0' }));
|
|
158
|
+
|
|
159
|
+
if (Number(unpushedCount) > (config.maxUnpushedCommits || 5)) {
|
|
160
|
+
logger.warn(`Too many unpushed commits (${unpushedCount}). Pushing required.`);
|
|
161
|
+
// We might force a push here or just warn
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Fetch to see if we are behind
|
|
165
|
+
await git.fetch(repoPath);
|
|
166
|
+
const remoteStatus = await git.isRemoteAhead(repoPath);
|
|
167
|
+
|
|
168
|
+
if (remoteStatus.behind) {
|
|
169
|
+
if (config.pullBeforePush) {
|
|
170
|
+
logger.info('Remote is ahead. Pulling changes...');
|
|
171
|
+
// Try pull --rebase
|
|
172
|
+
try {
|
|
173
|
+
await execa('git', ['pull', '--rebase'], { cwd: repoPath });
|
|
174
|
+
return { ok: true, action: 'continue' };
|
|
175
|
+
} catch (err) {
|
|
176
|
+
logger.error('Pull failed (conflict detected).');
|
|
177
|
+
return { ok: false, action: 'abort' };
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
return { ok: false, action: 'abort' };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Detect potential conflicts without pulling (git diff --check)
|
|
185
|
+
// Actually git diff --check is for whitespace, not merge conflicts.
|
|
186
|
+
// To check for merge conflicts before pulling is hard without actually merging.
|
|
187
|
+
// But since we did fetch, we can dry-run a merge?
|
|
188
|
+
// For now, relies on pull --rebase failure above.
|
|
189
|
+
|
|
190
|
+
} catch (err) {
|
|
191
|
+
logger.warn(`Team check failed: ${err.message}`);
|
|
192
|
+
// If we can't check, maybe safe to continue locally?
|
|
193
|
+
// But "conflictStrategy": "abort" suggests we should stop.
|
|
194
|
+
if (config.conflictStrategy === 'abort') return { ok: false, action: 'abort' };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { ok: true, action: 'continue' };
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
validateConfig,
|
|
202
|
+
validateBeforeCommit,
|
|
203
|
+
checkTeamStatus
|
|
204
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const logger = require('../utils/logger');
|
|
4
|
+
const { getAutopilotHome } = require('../utils/paths');
|
|
5
|
+
|
|
6
|
+
const STATE_FILE = 'state.json';
|
|
7
|
+
|
|
8
|
+
class StateManager {
|
|
9
|
+
constructor(repoPath) {
|
|
10
|
+
this.repoPath = repoPath;
|
|
11
|
+
this.stateDir = path.join(repoPath, '.autopilot');
|
|
12
|
+
this.stateFile = path.join(this.stateDir, STATE_FILE);
|
|
13
|
+
this.init();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
init() {
|
|
17
|
+
fs.ensureDirSync(this.stateDir);
|
|
18
|
+
if (!fs.existsSync(this.stateFile)) {
|
|
19
|
+
this.reset();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getState() {
|
|
24
|
+
if (!fs.existsSync(this.stateFile)) {
|
|
25
|
+
return { status: 'running' };
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return fs.readJsonSync(this.stateFile);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
logger.error('Failed to read state:', error.message);
|
|
31
|
+
return { status: 'running' }; // Default
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setState(newState) {
|
|
36
|
+
try {
|
|
37
|
+
const currentState = this.getState();
|
|
38
|
+
const updatedState = { ...currentState, ...newState };
|
|
39
|
+
fs.writeJsonSync(this.stateFile, updatedState, { spaces: 2 });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
logger.error('Failed to write state:', error.message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
reset() {
|
|
46
|
+
this.setState({
|
|
47
|
+
status: 'running', // running | paused
|
|
48
|
+
reason: null,
|
|
49
|
+
pausedAt: null
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pause(reason) {
|
|
54
|
+
this.setState({
|
|
55
|
+
status: 'paused',
|
|
56
|
+
reason: reason || 'User paused',
|
|
57
|
+
pausedAt: new Date().toISOString()
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
resume() {
|
|
62
|
+
this.reset();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
isPaused() {
|
|
66
|
+
const state = this.getState();
|
|
67
|
+
return state.status === 'paused';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = StateManager;
|
package/src/core/watcher.js
CHANGED
|
@@ -14,6 +14,9 @@ const { generateCommitMessage } = require('./commit');
|
|
|
14
14
|
const { savePid, removePid, registerProcessHandlers } = require('../utils/process');
|
|
15
15
|
const { loadConfig } = require('../config/loader');
|
|
16
16
|
const { readIgnoreFile, createIgnoredFilter, normalizePath } = require('../config/ignore');
|
|
17
|
+
const HistoryManager = require('./history');
|
|
18
|
+
const StateManager = require('./state');
|
|
19
|
+
const { validateBeforeCommit, checkTeamStatus } = require('./safety');
|
|
17
20
|
|
|
18
21
|
class Watcher {
|
|
19
22
|
constructor(repoPath) {
|
|
@@ -30,6 +33,8 @@ class Watcher {
|
|
|
30
33
|
this.ignorePatterns = [];
|
|
31
34
|
this.ignoredFilter = null;
|
|
32
35
|
this.focusEngine = new FocusEngine(repoPath);
|
|
36
|
+
this.historyManager = new HistoryManager(repoPath);
|
|
37
|
+
this.stateManager = new StateManager(repoPath);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
logVerbose(message) {
|
|
@@ -269,6 +274,13 @@ class Watcher {
|
|
|
269
274
|
try {
|
|
270
275
|
logger.debug('Checking git status...');
|
|
271
276
|
|
|
277
|
+
// 0. Pause Check
|
|
278
|
+
if (this.stateManager.isPaused()) {
|
|
279
|
+
const state = this.stateManager.getState();
|
|
280
|
+
logger.debug(`Skipping processing: Autopilot is paused (${state.reason})`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
272
284
|
// 1. Min interval check
|
|
273
285
|
const now = Date.now();
|
|
274
286
|
const minInterval = (this.config?.minSecondsBetweenCommits || 30) * 1000;
|
|
@@ -293,15 +305,24 @@ class Watcher {
|
|
|
293
305
|
return;
|
|
294
306
|
}
|
|
295
307
|
|
|
296
|
-
// 4. Safety:
|
|
297
|
-
logger.debug('Checking remote status...');
|
|
298
|
-
const
|
|
299
|
-
if (
|
|
300
|
-
logger.warn('Skip commit:
|
|
308
|
+
// 4. Safety: Team Mode & Remote check
|
|
309
|
+
logger.debug('Checking team/remote status...');
|
|
310
|
+
const teamStatus = await checkTeamStatus(this.repoPath, this.config);
|
|
311
|
+
if (!teamStatus.ok) {
|
|
312
|
+
logger.warn('Skip commit: Team check failed (Remote ahead or conflict).');
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 5. Safety: Pre-commit checks (Validation)
|
|
317
|
+
logger.debug('Running pre-commit validation...');
|
|
318
|
+
const validation = await validateBeforeCommit(this.repoPath, this.config);
|
|
319
|
+
if (!validation.ok) {
|
|
320
|
+
logger.warn('Skip commit: Pre-commit validation failed:');
|
|
321
|
+
validation.errors.forEach(e => logger.error(`- ${e}`));
|
|
301
322
|
return;
|
|
302
323
|
}
|
|
303
324
|
|
|
304
|
-
//
|
|
325
|
+
// 6. Safety: Custom checks (Legacy)
|
|
305
326
|
if (this.config?.requireChecks) {
|
|
306
327
|
const checksPassed = await this.runChecks();
|
|
307
328
|
if (!checksPassed) {
|
|
@@ -310,7 +331,7 @@ class Watcher {
|
|
|
310
331
|
}
|
|
311
332
|
}
|
|
312
333
|
|
|
313
|
-
//
|
|
334
|
+
// 7. Commit
|
|
314
335
|
logger.info('Committing changes...');
|
|
315
336
|
|
|
316
337
|
// Add all changes
|
|
@@ -335,10 +356,32 @@ class Watcher {
|
|
|
335
356
|
message = approval.message;
|
|
336
357
|
}
|
|
337
358
|
|
|
338
|
-
await git.commit(this.repoPath, message);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
359
|
+
const commitResult = await git.commit(this.repoPath, message);
|
|
360
|
+
|
|
361
|
+
if (commitResult.ok) {
|
|
362
|
+
this.lastCommitAt = Date.now();
|
|
363
|
+
this.focusEngine.onCommit();
|
|
364
|
+
|
|
365
|
+
// Phase 1: Record History
|
|
366
|
+
try {
|
|
367
|
+
// We need the hash of the commit we just made
|
|
368
|
+
const hash = await git.getLatestCommitHash(this.repoPath);
|
|
369
|
+
if (hash) {
|
|
370
|
+
this.historyManager.addCommit({
|
|
371
|
+
hash,
|
|
372
|
+
message,
|
|
373
|
+
files: changedFiles.map(f => f.file)
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
} catch (err) {
|
|
377
|
+
logger.error(`Failed to record history: ${err.message}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
logger.success('Commit complete');
|
|
381
|
+
} else {
|
|
382
|
+
logger.error(`Commit failed: ${commitResult.stderr}`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
342
385
|
|
|
343
386
|
// 7. Auto-push
|
|
344
387
|
if (this.config?.autoPush) {
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,13 @@ const { initRepo } = require('./commands/init');
|
|
|
3
3
|
const { startWatcher } = require('./commands/start');
|
|
4
4
|
const { stopWatcher } = require('./commands/stop');
|
|
5
5
|
const { statusWatcher } = require('./commands/status');
|
|
6
|
+
const undoCommand = require('./commands/undo');
|
|
6
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 runDashboard = require('./commands/dashboard');
|
|
12
|
+
const { leaderboard } = require('./commands/leaderboard');
|
|
7
13
|
const pkg = require('../package.json');
|
|
8
14
|
|
|
9
15
|
function run() {
|
|
@@ -14,6 +20,12 @@ function run() {
|
|
|
14
20
|
.description('Git automation with safety rails')
|
|
15
21
|
.version(pkg.version, '-v, --version', 'Show version');
|
|
16
22
|
|
|
23
|
+
program
|
|
24
|
+
.command('leaderboard')
|
|
25
|
+
.description('View or sync with the global leaderboard')
|
|
26
|
+
.option('--sync', 'Sync your local stats to the leaderboard')
|
|
27
|
+
.action(leaderboard);
|
|
28
|
+
|
|
17
29
|
program
|
|
18
30
|
.command('init')
|
|
19
31
|
.description('Initialize autopilot configuration in repository')
|
|
@@ -34,6 +46,27 @@ function run() {
|
|
|
34
46
|
.description('Show autopilot watcher status')
|
|
35
47
|
.action(statusWatcher);
|
|
36
48
|
|
|
49
|
+
program
|
|
50
|
+
.command('undo')
|
|
51
|
+
.description('Undo the last Autopilot commit')
|
|
52
|
+
.option('-c, --count <n>', 'Number of commits to undo', '1')
|
|
53
|
+
.action(undoCommand);
|
|
54
|
+
|
|
55
|
+
program
|
|
56
|
+
.command('pause [reason]')
|
|
57
|
+
.description('Pause Autopilot watcher')
|
|
58
|
+
.action(pauseCommand);
|
|
59
|
+
|
|
60
|
+
program
|
|
61
|
+
.command('resume')
|
|
62
|
+
.description('Resume Autopilot watcher')
|
|
63
|
+
.action(resumeCommand);
|
|
64
|
+
|
|
65
|
+
program
|
|
66
|
+
.command('dashboard')
|
|
67
|
+
.description('View real-time Autopilot dashboard')
|
|
68
|
+
.action(runDashboard);
|
|
69
|
+
|
|
37
70
|
program
|
|
38
71
|
.command('doctor')
|
|
39
72
|
.description('Diagnose and validate autopilot setup')
|
|
@@ -43,6 +76,7 @@ function run() {
|
|
|
43
76
|
.command('insights')
|
|
44
77
|
.description('View productivity insights and focus analytics')
|
|
45
78
|
.option('-f, --format <type>', 'Output format (json, text)', 'text')
|
|
79
|
+
.option('-e, --export <type>', 'Export insights (csv)')
|
|
46
80
|
.action(insights);
|
|
47
81
|
|
|
48
82
|
program
|
package/src/utils/logger.js
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const logger = {
|
|
7
|
+
colors: {
|
|
8
|
+
cyan: (text) => `\x1b[36m${text}\x1b[0m`,
|
|
9
|
+
green: (text) => `\x1b[32m${text}\x1b[0m`,
|
|
10
|
+
yellow: (text) => `\x1b[33m${text}\x1b[0m`,
|
|
11
|
+
red: (text) => `\x1b[31m${text}\x1b[0m`,
|
|
12
|
+
blue: (text) => `\x1b[34m${text}\x1b[0m`,
|
|
13
|
+
bold: (text) => `\x1b[1m${text}\x1b[0m`
|
|
14
|
+
},
|
|
15
|
+
|
|
7
16
|
/**
|
|
8
17
|
* Log informational message
|
|
9
18
|
* @param {string} message - Message to log
|