@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
package/package.json CHANGED
@@ -1,69 +1,75 @@
1
- {
2
- "name": "@traisetech/autopilot",
3
- "version": "2.3.0",
4
- "publishConfig": {
5
- "access": "public"
6
- },
7
- "files": [
8
- "bin",
9
- "src",
10
- "docs",
11
- "README.md",
12
- "LICENSE",
13
- "CHANGELOG.md"
14
- ],
15
- "description": "An intelligent Git automation CLI that safely commits and pushes your code so you can focus on building.",
16
- "keywords": [
17
- "git",
18
- "automation",
19
- "autocommit",
20
- "autopush",
21
- "developer-tools",
22
- "cli",
23
- "productivity",
24
- "git-workflow"
25
- ],
26
- "author": "Praise Masunga (PraiseTechzw)",
27
- "license": "MIT",
28
- "type": "commonjs",
29
- "bin": {
30
- "autopilot": "bin/autopilot.js"
31
- },
32
- "main": "src/index.js",
33
- "scripts": {
34
- "dev": "node bin/autopilot.js",
35
- "test": "node --test --test-concurrency=1",
36
- "lint": "node -c bin/autopilot.js && node -c src/index.js",
37
- "verify": "node bin/autopilot.js --help && node bin/autopilot.js doctor && npm test",
38
- "prepublishOnly": "npm run verify",
39
- "release:patch": "npm run verify && npm version patch && git push --follow-tags && echo \"\nπŸš€ Release initiated! GitHub Action will publish to NPM.\"",
40
- "release:minor": "npm run verify && npm version minor && git push --follow-tags && echo \"\nπŸš€ Release initiated! GitHub Action will publish to NPM.\"",
41
- "release:major": "npm run verify && npm version major && git push --follow-tags && echo \"\nπŸš€ Release initiated! GitHub Action will publish to NPM.\""
42
- },
43
- "engines": {
44
- "node": ">=18.0.0"
45
- },
46
- "repository": {
47
- "type": "git",
48
- "url": "git+https://github.com/PraiseTechzw/autopilot-cli.git"
49
- },
50
- "bugs": {
51
- "url": "https://github.com/PraiseTechzw/autopilot-cli/issues"
52
- },
53
- "homepage": "https://github.com/PraiseTechzw/autopilot-cli#readme",
54
- "dependencies": {
55
- "@traisetech/autopilot": "^0.1.7",
56
- "chokidar": "^3.6.0",
57
- "commander": "^14.0.3",
58
- "csv-writer": "^1.6.0",
59
- "execa": "^5.1.1",
60
- "fs-extra": "^11.3.3",
61
- "ink": "^6.6.0",
62
- "ink-big-text": "^2.0.0",
63
- "ink-gradient": "^4.0.0",
64
- "ink-spinner": "^5.0.0",
65
- "open": "^11.0.0",
66
- "prop-types": "^15.8.1",
67
- "react": "^19.2.4"
68
- }
69
- }
1
+ {
2
+ "name": "@traisetech/autopilot",
3
+ "version": "2.5.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "files": [
8
+ "bin",
9
+ "src",
10
+ "docs",
11
+ "README.md",
12
+ "LICENSE",
13
+ "CHANGELOG.md"
14
+ ],
15
+ "description": "An intelligent Git automation CLI that safely commits and pushes your code so you can focus on building.",
16
+ "keywords": [
17
+ "git",
18
+ "automation",
19
+ "autocommit",
20
+ "autopush",
21
+ "developer-tools",
22
+ "cli",
23
+ "productivity",
24
+ "git-workflow"
25
+ ],
26
+ "author": "Praise Masunga (PraiseTechzw)",
27
+ "license": "MIT",
28
+ "type": "commonjs",
29
+ "bin": {
30
+ "autopilot": "bin/autopilot.js"
31
+ },
32
+ "main": "src/index.js",
33
+ "scripts": {
34
+ "dev": "node bin/autopilot.js",
35
+ "test": "node --test --test-concurrency=1",
36
+ "lint": "node -c bin/autopilot.js && node -c src/index.js",
37
+ "verify": "node bin/autopilot.js --help && node bin/autopilot.js doctor && npm test",
38
+ "prepublishOnly": "npm run verify",
39
+ "release:patch": "npm run verify && npm version patch && git push --follow-tags && echo \"\nπŸš€ Release initiated! GitHub Action will publish to NPM.\"",
40
+ "release:minor": "npm run verify && npm version minor && git push --follow-tags && echo \"\nπŸš€ Release initiated! GitHub Action will publish to NPM.\"",
41
+ "release:major": "npm run verify && npm version major && git push --follow-tags && echo \"\nπŸš€ Release initiated! GitHub Action will publish to NPM.\""
42
+ },
43
+ "engines": {
44
+ "node": ">=18.0.0"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/PraiseTechzw/autopilot-cli.git"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/PraiseTechzw/autopilot-cli/issues"
52
+ },
53
+ "homepage": "https://github.com/PraiseTechzw/autopilot-cli#readme",
54
+ "dependencies": {
55
+ "@traisetech/autopilot": "^0.1.7",
56
+ "chokidar": "^3.6.0",
57
+ "commander": "^14.0.3",
58
+ "csv-writer": "^1.6.0",
59
+ "execa": "^5.1.1",
60
+ "fs-extra": "^11.3.3",
61
+ "ink": "^6.6.0",
62
+ "ink-big-text": "^2.0.0",
63
+ "ink-gradient": "^4.0.0",
64
+ "ink-spinner": "^5.0.0",
65
+ "node-notifier": "^10.0.1",
66
+ "open": "^11.0.0",
67
+ "prop-types": "^15.8.1",
68
+ "react": "^19.2.4"
69
+ },
70
+ "directories": {
71
+ "doc": "docs",
72
+ "test": "test"
73
+ },
74
+ "devDependencies": {}
75
+ }
@@ -1,110 +1,110 @@
1
- /**
2
- * Config Command
3
- * View and modify Autopilot configuration
4
- */
5
-
6
- const logger = require('../utils/logger');
7
- const { loadConfig, saveConfig, getGlobalConfigPath } = require('../config/loader');
8
- const fs = require('fs-extra');
9
- const { getConfigPath } = require('../utils/paths');
10
-
11
- /**
12
- * Helper to get value by dot notation
13
- */
14
- const getByDot = (obj, path) => {
15
- return path.split('.').reduce((acc, part) => acc && acc[part], obj);
16
- };
17
-
18
- /**
19
- * Helper to set value by dot notation
20
- */
21
- const setByDot = (obj, path, value) => {
22
- const parts = path.split('.');
23
- const last = parts.pop();
24
- const target = parts.reduce((acc, part) => {
25
- if (!acc[part]) acc[part] = {};
26
- return acc[part];
27
- }, obj);
28
- target[last] = value;
29
- };
30
-
31
- /**
32
- * Parse value string to appropriate type
33
- */
34
- const parseValue = (val) => {
35
- if (val === 'true') return true;
36
- if (val === 'false') return false;
37
- if (!isNaN(val) && val.trim() !== '') return Number(val);
38
- return val;
39
- };
40
-
41
- async function config(cmd, key, value, options) {
42
- const repoPath = options?.cwd || process.cwd();
43
- const isGlobal = options?.global || false;
44
-
45
- if (cmd === 'list') {
46
- // If list --global, show only global config?
47
- // Or just show effective config?
48
- // Let's show effective config, maybe annotated if we had time.
49
- // But for now, just loadConfig which merges them.
50
- const currentConfig = await loadConfig(repoPath);
51
- console.log(JSON.stringify(currentConfig, null, 2));
52
- if (isGlobal) {
53
- logger.info('(Note: Showing effective merged config. Use --global to set global values.)');
54
- }
55
- return;
56
- }
57
-
58
- if (cmd === 'get') {
59
- if (!key) {
60
- logger.error('Usage: autopilot config get <key>');
61
- return;
62
- }
63
- // Get always shows effective value
64
- const currentConfig = await loadConfig(repoPath);
65
- const val = getByDot(currentConfig, key);
66
- if (val === undefined) {
67
- logger.warn(`Key '${key}' not set`);
68
- } else {
69
- console.log(typeof val === 'object' ? JSON.stringify(val, null, 2) : val);
70
- }
71
- return;
72
- }
73
-
74
- if (cmd === 'set') {
75
- if (!key || value === undefined) {
76
- logger.error('Usage: autopilot config set <key> <value>');
77
- return;
78
- }
79
-
80
- const typedValue = parseValue(value);
81
-
82
- // We need to read the specific file (local or global) to avoid merging defaults into it
83
- let rawConfig = {};
84
- let configPath;
85
-
86
- if (isGlobal) {
87
- configPath = getGlobalConfigPath();
88
- } else {
89
- configPath = getConfigPath(repoPath);
90
- }
91
-
92
- if (await fs.pathExists(configPath)) {
93
- try {
94
- rawConfig = await fs.readJson(configPath);
95
- } catch (e) {
96
- // Ignore read errors, start fresh
97
- }
98
- }
99
-
100
- setByDot(rawConfig, key, typedValue);
101
-
102
- await saveConfig(repoPath, rawConfig, isGlobal);
103
- logger.success(`Set ${key} = ${typedValue} ${isGlobal ? '(Global)' : '(Local)'}`);
104
- return;
105
- }
106
-
107
- logger.error('Unknown config command. Use list, get, or set.');
108
- }
109
-
110
- module.exports = config;
1
+ /**
2
+ * Config Command
3
+ * View and modify Autopilot configuration
4
+ */
5
+
6
+ const logger = require('../utils/logger');
7
+ const { loadConfig, saveConfig, getGlobalConfigPath } = require('../config/loader');
8
+ const fs = require('fs-extra');
9
+ const { getConfigPath } = require('../utils/paths');
10
+
11
+ /**
12
+ * Helper to get value by dot notation
13
+ */
14
+ const getByDot = (obj, path) => {
15
+ return path.split('.').reduce((acc, part) => acc && acc[part], obj);
16
+ };
17
+
18
+ /**
19
+ * Helper to set value by dot notation
20
+ */
21
+ const setByDot = (obj, path, value) => {
22
+ const parts = path.split('.');
23
+ const last = parts.pop();
24
+ const target = parts.reduce((acc, part) => {
25
+ if (!acc[part]) acc[part] = {};
26
+ return acc[part];
27
+ }, obj);
28
+ target[last] = value;
29
+ };
30
+
31
+ /**
32
+ * Parse value string to appropriate type
33
+ */
34
+ const parseValue = (val) => {
35
+ if (val === 'true') return true;
36
+ if (val === 'false') return false;
37
+ if (!isNaN(val) && val.trim() !== '') return Number(val);
38
+ return val;
39
+ };
40
+
41
+ async function config(cmd, key, value, options) {
42
+ const repoPath = options?.cwd || process.cwd();
43
+ const isGlobal = options?.global || false;
44
+
45
+ if (cmd === 'list') {
46
+ // If list --global, show only global config?
47
+ // Or just show effective config?
48
+ // Let's show effective config, maybe annotated if we had time.
49
+ // But for now, just loadConfig which merges them.
50
+ const currentConfig = await loadConfig(repoPath);
51
+ console.log(JSON.stringify(currentConfig, null, 2));
52
+ if (isGlobal) {
53
+ logger.info('(Note: Showing effective merged config. Use --global to set global values.)');
54
+ }
55
+ return;
56
+ }
57
+
58
+ if (cmd === 'get') {
59
+ if (!key) {
60
+ logger.error('Usage: autopilot config get <key>');
61
+ return;
62
+ }
63
+ // Get always shows effective value
64
+ const currentConfig = await loadConfig(repoPath);
65
+ const val = getByDot(currentConfig, key);
66
+ if (val === undefined) {
67
+ logger.warn(`Key '${key}' not set`);
68
+ } else {
69
+ console.log(typeof val === 'object' ? JSON.stringify(val, null, 2) : val);
70
+ }
71
+ return;
72
+ }
73
+
74
+ if (cmd === 'set') {
75
+ if (!key || value === undefined) {
76
+ logger.error('Usage: autopilot config set <key> <value>');
77
+ return;
78
+ }
79
+
80
+ const typedValue = parseValue(value);
81
+
82
+ // We need to read the specific file (local or global) to avoid merging defaults into it
83
+ let rawConfig = {};
84
+ let configPath;
85
+
86
+ if (isGlobal) {
87
+ configPath = getGlobalConfigPath();
88
+ } else {
89
+ configPath = getConfigPath(repoPath);
90
+ }
91
+
92
+ if (await fs.pathExists(configPath)) {
93
+ try {
94
+ rawConfig = await fs.readJson(configPath);
95
+ } catch (e) {
96
+ // Ignore read errors, start fresh
97
+ }
98
+ }
99
+
100
+ setByDot(rawConfig, key, typedValue);
101
+
102
+ await saveConfig(repoPath, rawConfig, isGlobal);
103
+ logger.success(`Set ${key} = ${typedValue} ${isGlobal ? '(Global)' : '(Local)'}`);
104
+ return;
105
+ }
106
+
107
+ logger.error('Unknown config command. Use list, get, or set.');
108
+ }
109
+
110
+ module.exports = config;
@@ -1,151 +1,151 @@
1
- import React from 'react';
2
- import { render, Box, Text, useInput, useApp } from 'ink';
3
- import Gradient from 'ink-gradient';
4
- import BigText from 'ink-big-text';
5
- import Spinner from 'ink-spinner';
6
- import fs from 'fs-extra';
7
- import path from 'path';
8
- import StateManager from '../core/state.js';
9
- import git from '../core/git.js';
10
- import HistoryManager from '../core/history.js';
11
- import processUtils from '../utils/process.js';
12
-
13
- const { useState, useEffect } = React;
14
- const { getRunningPid } = processUtils;
15
-
16
- const e = React.createElement;
17
-
18
- // Dashboard Component
19
- const Dashboard = () => {
20
- const { exit } = useApp();
21
- const root = process.cwd();
22
-
23
- const [status, setStatus] = useState('loading');
24
- const [pid, setPid] = useState(null);
25
- const [lastCommit, setLastCommit] = useState(null);
26
- const [pendingFiles, setPendingFiles] = useState([]);
27
- const [todayStats, setTodayStats] = useState({ commits: 0 });
28
- const [pausedState, setPausedState] = useState(null);
29
-
30
- // Poll for updates
31
- useEffect(() => {
32
- const fetchData = async () => {
33
- try {
34
- // 1. Check process status
35
- const currentPid = await getRunningPid(root);
36
- setPid(currentPid);
37
-
38
- // 2. Check Paused State
39
- const stateManager = new StateManager(root);
40
- if (stateManager.isPaused()) {
41
- setStatus('paused');
42
- setPausedState(stateManager.getState());
43
- } else if (currentPid) {
44
- setStatus('running');
45
- setPausedState(null);
46
- } else {
47
- setStatus('stopped');
48
- setPausedState(null);
49
- }
50
-
51
- // 3. Last Commit
52
- const historyManager = new HistoryManager(root);
53
- const last = historyManager.getLastCommit();
54
- setLastCommit(last);
55
-
56
- // 4. Pending Files
57
- const statusObj = await git.getPorcelainStatus(root);
58
- if (statusObj.ok) {
59
- setPendingFiles(statusObj.files);
60
- }
61
-
62
- // 5. Today Stats (Simple count from history)
63
- const history = historyManager.getHistory();
64
- const today = new Date().toDateString();
65
- const count = history.filter(c => new Date(c.timestamp).toDateString() === today).length;
66
- setTodayStats({ commits: count });
67
-
68
- } catch (err) {
69
- // ignore errors
70
- }
71
- };
72
-
73
- fetchData();
74
- const timer = setInterval(fetchData, 5000);
75
- return () => clearInterval(timer);
76
- }, []);
77
-
78
- // Keyboard controls
79
- useInput((input, key) => {
80
- if (input === 'q') {
81
- exit();
82
- }
83
- if (input === 'p') {
84
- // Toggle pause
85
- const stateManager = new StateManager(root);
86
- if (stateManager.isPaused()) {
87
- stateManager.resume();
88
- } else {
89
- stateManager.pause('Dashboard toggle');
90
- }
91
- // Immediate refresh
92
- }
93
- });
94
-
95
- return e(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" },
96
- // Header
97
- e(Box, { marginBottom: 1 },
98
- e(Gradient, { name: "morning" },
99
- e(BigText, { text: "Autopilot", font: "simple" })
100
- )
101
- ),
102
-
103
- // Status
104
- e(Box, { marginBottom: 1 },
105
- e(Text, { bold: true }, "Status: "),
106
- status === 'running' && e(Text, { color: "green" }, `🟒 Running (PID: ${pid})`),
107
- status === 'paused' && e(Text, { color: "yellow" }, `⏸️ Paused (${pausedState?.reason})`),
108
- status === 'stopped' && e(Text, { color: "red" }, "πŸ”΄ Stopped"),
109
- status === 'loading' && e(Text, {}, e(Spinner, { type: "dots" }), " Loading...")
110
- ),
111
-
112
- // Activity
113
- e(Box, { flexDirection: "column", marginBottom: 1 },
114
- e(Text, { underline: true }, "Activity"),
115
- e(Box, {},
116
- e(Text, {}, "Last Commit: "),
117
- e(Text, { color: "cyan" }, lastCommit ? `${lastCommit.message} (${new Date(lastCommit.timestamp).toLocaleTimeString()})` : 'None')
118
- ),
119
- e(Box, {},
120
- e(Text, {}, "Today's Commits: "),
121
- e(Text, { color: "green" }, todayStats.commits)
122
- )
123
- ),
124
-
125
- // Pending Changes
126
- e(Box, { flexDirection: "column", marginBottom: 1 },
127
- e(Text, { underline: true }, `Pending Changes (${pendingFiles.length})`),
128
- e(Box, { flexDirection: "column" },
129
- pendingFiles.length === 0 ?
130
- e(Text, { color: "gray" }, "No pending changes") :
131
- pendingFiles.slice(0, 5).map((f, idx) =>
132
- e(Text, { key: `${f.file}-${idx}`, color: "yellow" }, ` ${f.status} ${f.file}`)
133
- )
134
- ),
135
- pendingFiles.length > 5 && e(Text, { color: "gray" }, ` ...and ${pendingFiles.length - 5} more`)
136
- ),
137
-
138
- // Footer
139
- e(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray" },
140
- e(Text, {}, "Press 'p' to toggle pause, 'q' to quit dashboard")
141
- )
142
- );
143
- };
144
-
145
- export default function runDashboard() {
146
- if (!process.stdin.isTTY && !process.env.AUTOPILOT_TEST_MODE) {
147
- console.error('Error: Dashboard requires an interactive terminal (TTY).');
148
- process.exit(1);
149
- }
150
- render(e(Dashboard));
151
- }
1
+ import React from 'react';
2
+ import { render, Box, Text, useInput, useApp } from 'ink';
3
+ import Gradient from 'ink-gradient';
4
+ import BigText from 'ink-big-text';
5
+ import Spinner from 'ink-spinner';
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+ import StateManager from '../core/state.js';
9
+ import git from '../core/git.js';
10
+ import HistoryManager from '../core/history.js';
11
+ import processUtils from '../utils/process.js';
12
+
13
+ const { useState, useEffect } = React;
14
+ const { getRunningPid } = processUtils;
15
+
16
+ const e = React.createElement;
17
+
18
+ // Dashboard Component
19
+ const Dashboard = () => {
20
+ const { exit } = useApp();
21
+ const root = process.cwd();
22
+
23
+ const [status, setStatus] = useState('loading');
24
+ const [pid, setPid] = useState(null);
25
+ const [lastCommit, setLastCommit] = useState(null);
26
+ const [pendingFiles, setPendingFiles] = useState([]);
27
+ const [todayStats, setTodayStats] = useState({ commits: 0 });
28
+ const [pausedState, setPausedState] = useState(null);
29
+
30
+ // Poll for updates
31
+ useEffect(() => {
32
+ const fetchData = async () => {
33
+ try {
34
+ // 1. Check process status
35
+ const currentPid = await getRunningPid(root);
36
+ setPid(currentPid);
37
+
38
+ // 2. Check Paused State
39
+ const stateManager = new StateManager(root);
40
+ if (stateManager.isPaused()) {
41
+ setStatus('paused');
42
+ setPausedState(stateManager.getState());
43
+ } else if (currentPid) {
44
+ setStatus('running');
45
+ setPausedState(null);
46
+ } else {
47
+ setStatus('stopped');
48
+ setPausedState(null);
49
+ }
50
+
51
+ // 3. Last Commit
52
+ const historyManager = new HistoryManager(root);
53
+ const last = historyManager.getLastCommit();
54
+ setLastCommit(last);
55
+
56
+ // 4. Pending Files
57
+ const statusObj = await git.getPorcelainStatus(root);
58
+ if (statusObj.ok) {
59
+ setPendingFiles(statusObj.files);
60
+ }
61
+
62
+ // 5. Today Stats (Simple count from history)
63
+ const history = historyManager.getHistory();
64
+ const today = new Date().toDateString();
65
+ const count = history.filter(c => new Date(c.timestamp).toDateString() === today).length;
66
+ setTodayStats({ commits: count });
67
+
68
+ } catch (err) {
69
+ // ignore errors
70
+ }
71
+ };
72
+
73
+ fetchData();
74
+ const timer = setInterval(fetchData, 5000);
75
+ return () => clearInterval(timer);
76
+ }, []);
77
+
78
+ // Keyboard controls
79
+ useInput((input, key) => {
80
+ if (input === 'q') {
81
+ exit();
82
+ }
83
+ if (input === 'p') {
84
+ // Toggle pause
85
+ const stateManager = new StateManager(root);
86
+ if (stateManager.isPaused()) {
87
+ stateManager.resume();
88
+ } else {
89
+ stateManager.pause('Dashboard toggle');
90
+ }
91
+ // Immediate refresh
92
+ }
93
+ });
94
+
95
+ return e(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan" },
96
+ // Header
97
+ e(Box, { marginBottom: 1 },
98
+ e(Gradient, { name: "morning" },
99
+ e(BigText, { text: "Autopilot", font: "simple" })
100
+ )
101
+ ),
102
+
103
+ // Status
104
+ e(Box, { marginBottom: 1 },
105
+ e(Text, { bold: true }, "Status: "),
106
+ status === 'running' && e(Text, { color: "green" }, `🟒 Running (PID: ${pid})`),
107
+ status === 'paused' && e(Text, { color: "yellow" }, `⏸️ Paused (${pausedState?.reason})`),
108
+ status === 'stopped' && e(Text, { color: "red" }, "πŸ”΄ Stopped"),
109
+ status === 'loading' && e(Text, {}, e(Spinner, { type: "dots" }), " Loading...")
110
+ ),
111
+
112
+ // Activity
113
+ e(Box, { flexDirection: "column", marginBottom: 1 },
114
+ e(Text, { underline: true }, "Activity"),
115
+ e(Box, {},
116
+ e(Text, {}, "Last Commit: "),
117
+ e(Text, { color: "cyan" }, lastCommit ? `${lastCommit.message} (${new Date(lastCommit.timestamp).toLocaleTimeString()})` : 'None')
118
+ ),
119
+ e(Box, {},
120
+ e(Text, {}, "Today's Commits: "),
121
+ e(Text, { color: "green" }, todayStats.commits)
122
+ )
123
+ ),
124
+
125
+ // Pending Changes
126
+ e(Box, { flexDirection: "column", marginBottom: 1 },
127
+ e(Text, { underline: true }, `Pending Changes (${pendingFiles.length})`),
128
+ e(Box, { flexDirection: "column" },
129
+ pendingFiles.length === 0 ?
130
+ e(Text, { color: "gray" }, "No pending changes") :
131
+ pendingFiles.slice(0, 5).map((f, idx) =>
132
+ e(Text, { key: `${f.file}-${idx}`, color: "yellow" }, ` ${f.status} ${f.file}`)
133
+ )
134
+ ),
135
+ pendingFiles.length > 5 && e(Text, { color: "gray" }, ` ...and ${pendingFiles.length - 5} more`)
136
+ ),
137
+
138
+ // Footer
139
+ e(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray" },
140
+ e(Text, {}, "Press 'p' to toggle pause, 'q' to quit dashboard")
141
+ )
142
+ );
143
+ };
144
+
145
+ export default function runDashboard() {
146
+ if (!process.stdin.isTTY && !process.env.AUTOPILOT_TEST_MODE) {
147
+ console.error('Error: Dashboard requires an interactive terminal (TTY).');
148
+ process.exit(1);
149
+ }
150
+ render(e(Dashboard));
151
+ }