openclaw-watcher 0.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.
Files changed (130) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.dockerignore +21 -0
  3. package/.env.example +31 -0
  4. package/.eslintrc.json +26 -0
  5. package/.prettierrc.json +9 -0
  6. package/CHANGELOG.md +93 -0
  7. package/Dockerfile +47 -0
  8. package/README.md +408 -0
  9. package/build.sh +33 -0
  10. package/dist/ai/ai-orchestrator.d.ts +11 -0
  11. package/dist/ai/ai-orchestrator.d.ts.map +1 -0
  12. package/dist/ai/ai-orchestrator.js +85 -0
  13. package/dist/ai/ai-orchestrator.js.map +1 -0
  14. package/dist/ai/cli-client.d.ts +17 -0
  15. package/dist/ai/cli-client.d.ts.map +1 -0
  16. package/dist/ai/cli-client.js +239 -0
  17. package/dist/ai/cli-client.js.map +1 -0
  18. package/dist/cli.d.ts +3 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +33 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/commands/config.d.ts +7 -0
  23. package/dist/commands/config.d.ts.map +1 -0
  24. package/dist/commands/config.js +52 -0
  25. package/dist/commands/config.js.map +1 -0
  26. package/dist/commands/init.d.ts +6 -0
  27. package/dist/commands/init.d.ts.map +1 -0
  28. package/dist/commands/init.js +205 -0
  29. package/dist/commands/init.js.map +1 -0
  30. package/dist/commands/start.d.ts +6 -0
  31. package/dist/commands/start.d.ts.map +1 -0
  32. package/dist/commands/start.js +49 -0
  33. package/dist/commands/start.js.map +1 -0
  34. package/dist/commands/status.d.ts +2 -0
  35. package/dist/commands/status.d.ts.map +1 -0
  36. package/dist/commands/status.js +48 -0
  37. package/dist/commands/status.js.map +1 -0
  38. package/dist/config/default.d.ts +5 -0
  39. package/dist/config/default.d.ts.map +1 -0
  40. package/dist/config/default.js +22 -0
  41. package/dist/config/default.js.map +1 -0
  42. package/dist/healthcheck/gateway-monitor.d.ts +19 -0
  43. package/dist/healthcheck/gateway-monitor.d.ts.map +1 -0
  44. package/dist/healthcheck/gateway-monitor.js +116 -0
  45. package/dist/healthcheck/gateway-monitor.js.map +1 -0
  46. package/dist/healthcheck/health-checker.d.ts +11 -0
  47. package/dist/healthcheck/health-checker.d.ts.map +1 -0
  48. package/dist/healthcheck/health-checker.js +60 -0
  49. package/dist/healthcheck/health-checker.js.map +1 -0
  50. package/dist/index.d.ts +2 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +39 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/recovery/auto-fixer.d.ts +16 -0
  55. package/dist/recovery/auto-fixer.d.ts.map +1 -0
  56. package/dist/recovery/auto-fixer.js +162 -0
  57. package/dist/recovery/auto-fixer.js.map +1 -0
  58. package/dist/recovery/change-recorder.d.ts +8 -0
  59. package/dist/recovery/change-recorder.d.ts.map +1 -0
  60. package/dist/recovery/change-recorder.js +41 -0
  61. package/dist/recovery/change-recorder.js.map +1 -0
  62. package/dist/setup/config-initializer.d.ts +13 -0
  63. package/dist/setup/config-initializer.d.ts.map +1 -0
  64. package/dist/setup/config-initializer.js +46 -0
  65. package/dist/setup/config-initializer.js.map +1 -0
  66. package/dist/setup/config-loader.d.ts +9 -0
  67. package/dist/setup/config-loader.d.ts.map +1 -0
  68. package/dist/setup/config-loader.js +17 -0
  69. package/dist/setup/config-loader.js.map +1 -0
  70. package/dist/setup/git-initializer.d.ts +15 -0
  71. package/dist/setup/git-initializer.d.ts.map +1 -0
  72. package/dist/setup/git-initializer.js +189 -0
  73. package/dist/setup/git-initializer.js.map +1 -0
  74. package/dist/setup/safe-config-generator.d.ts +9 -0
  75. package/dist/setup/safe-config-generator.d.ts.map +1 -0
  76. package/dist/setup/safe-config-generator.js +85 -0
  77. package/dist/setup/safe-config-generator.js.map +1 -0
  78. package/dist/types/index.d.ts +60 -0
  79. package/dist/types/index.d.ts.map +1 -0
  80. package/dist/types/index.js +2 -0
  81. package/dist/types/index.js.map +1 -0
  82. package/dist/utils/executor.d.ts +17 -0
  83. package/dist/utils/executor.d.ts.map +1 -0
  84. package/dist/utils/executor.js +57 -0
  85. package/dist/utils/executor.js.map +1 -0
  86. package/dist/utils/git-manager.d.ts +14 -0
  87. package/dist/utils/git-manager.d.ts.map +1 -0
  88. package/dist/utils/git-manager.js +116 -0
  89. package/dist/utils/git-manager.js.map +1 -0
  90. package/dist/utils/github-cli.d.ts +9 -0
  91. package/dist/utils/github-cli.d.ts.map +1 -0
  92. package/dist/utils/github-cli.js +31 -0
  93. package/dist/utils/github-cli.js.map +1 -0
  94. package/dist/utils/logger.d.ts +4 -0
  95. package/dist/utils/logger.d.ts.map +1 -0
  96. package/dist/utils/logger.js +26 -0
  97. package/dist/utils/logger.js.map +1 -0
  98. package/dist/utils/paths.d.ts +6 -0
  99. package/dist/utils/paths.d.ts.map +1 -0
  100. package/dist/utils/paths.js +19 -0
  101. package/dist/utils/paths.js.map +1 -0
  102. package/docker-compose.yml +43 -0
  103. package/nodemon.json +9 -0
  104. package/package.json +59 -0
  105. package/prompts/fix-openclaw.md +202 -0
  106. package/scripts/setup.sh +105 -0
  107. package/src/ai/ai-orchestrator.ts +95 -0
  108. package/src/ai/cli-client.ts +296 -0
  109. package/src/cli.ts +40 -0
  110. package/src/commands/config.ts +57 -0
  111. package/src/commands/init.ts +239 -0
  112. package/src/commands/start.ts +75 -0
  113. package/src/commands/status.ts +79 -0
  114. package/src/config/default.ts +25 -0
  115. package/src/healthcheck/gateway-monitor.ts +137 -0
  116. package/src/healthcheck/health-checker.ts +71 -0
  117. package/src/index.ts +48 -0
  118. package/src/recovery/auto-fixer.ts +184 -0
  119. package/src/recovery/change-recorder.ts +46 -0
  120. package/src/setup/config-initializer.ts +63 -0
  121. package/src/setup/config-loader.ts +25 -0
  122. package/src/setup/git-initializer.ts +203 -0
  123. package/src/setup/safe-config-generator.ts +100 -0
  124. package/src/types/index.ts +67 -0
  125. package/src/utils/executor.ts +75 -0
  126. package/src/utils/git-manager.ts +121 -0
  127. package/src/utils/github-cli.ts +37 -0
  128. package/src/utils/logger.ts +39 -0
  129. package/src/utils/paths.ts +25 -0
  130. package/tsconfig.json +29 -0
@@ -0,0 +1,71 @@
1
+ import axios, { AxiosError } from 'axios';
2
+ import logger from '@/utils/logger.js';
3
+ import { HealthCheckResult, MonitorConfig } from '@/types';
4
+
5
+ export class HealthChecker {
6
+ private config: MonitorConfig;
7
+ private consecutiveFailures: number = 0;
8
+
9
+ constructor(config: MonitorConfig) {
10
+ this.config = config;
11
+ }
12
+
13
+ async check(): Promise<HealthCheckResult> {
14
+ const url = `${this.config.gatewayUrl}${this.config.healthEndpoint}`;
15
+ const startTime = Date.now();
16
+
17
+ try {
18
+ const response = await axios.get(url, {
19
+ timeout: this.config.timeout,
20
+ validateStatus: (status) => status < 500,
21
+ });
22
+
23
+ const responseTime = Date.now() - startTime;
24
+ const healthy = response.status >= 200 && response.status < 300;
25
+
26
+ if (healthy) {
27
+ this.consecutiveFailures = 0;
28
+ } else {
29
+ this.consecutiveFailures++;
30
+ }
31
+
32
+ const result: HealthCheckResult = {
33
+ healthy,
34
+ timestamp: new Date(),
35
+ endpoint: url,
36
+ responseTime,
37
+ statusCode: response.status,
38
+ };
39
+
40
+ logger.info('Health check completed', result);
41
+ return result;
42
+ } catch (error) {
43
+ this.consecutiveFailures++;
44
+ const responseTime = Date.now() - startTime;
45
+
46
+ const result: HealthCheckResult = {
47
+ healthy: false,
48
+ timestamp: new Date(),
49
+ endpoint: url,
50
+ responseTime,
51
+ error: error instanceof AxiosError ? error.message : String(error),
52
+ statusCode: error instanceof AxiosError ? error.response?.status : undefined,
53
+ };
54
+
55
+ logger.error('Health check failed', result);
56
+ return result;
57
+ }
58
+ }
59
+
60
+ shouldTriggerRecovery(): boolean {
61
+ return this.consecutiveFailures >= this.config.failureThreshold;
62
+ }
63
+
64
+ getConsecutiveFailures(): number {
65
+ return this.consecutiveFailures;
66
+ }
67
+
68
+ reset(): void {
69
+ this.consecutiveFailures = 0;
70
+ }
71
+ }
package/src/index.ts ADDED
@@ -0,0 +1,48 @@
1
+ import 'dotenv/config';
2
+ import { GatewayMonitor } from '@/healthcheck/gateway-monitor.js';
3
+ import { defaultMonitorConfig, defaultAIConfig, defaultRecoveryConfig } from '@/config/default.js';
4
+ import logger from '@/utils/logger.js';
5
+
6
+ async function main() {
7
+ logger.info('🚀 Starting OpenClaw HealthCheck System');
8
+
9
+ logger.info('Configuration loaded', {
10
+ gatewayUrl: defaultMonitorConfig.gatewayUrl,
11
+ checkInterval: defaultMonitorConfig.checkInterval,
12
+ aiProvider: defaultAIConfig.provider,
13
+ gitTracking: defaultRecoveryConfig.useGitTracking,
14
+ });
15
+
16
+ const monitor = new GatewayMonitor(
17
+ defaultMonitorConfig,
18
+ defaultAIConfig,
19
+ defaultRecoveryConfig
20
+ );
21
+
22
+ // Handle graceful shutdown
23
+ process.on('SIGINT', () => {
24
+ logger.info('Received SIGINT, shutting down gracefully');
25
+ monitor.stop();
26
+ process.exit(0);
27
+ });
28
+
29
+ process.on('SIGTERM', () => {
30
+ logger.info('Received SIGTERM, shutting down gracefully');
31
+ monitor.stop();
32
+ process.exit(0);
33
+ });
34
+
35
+ // Start monitoring
36
+ try {
37
+ await monitor.start();
38
+ logger.info('✅ OpenClaw HealthCheck System is running');
39
+ } catch (error: any) {
40
+ logger.error('Failed to start monitoring', { error: error.message });
41
+ process.exit(1);
42
+ }
43
+ }
44
+
45
+ main().catch((error) => {
46
+ logger.error('Fatal error', { error: error.message });
47
+ process.exit(1);
48
+ });
@@ -0,0 +1,184 @@
1
+ import { ChangeRecorder } from './change-recorder.js';
2
+ import { GitManager } from '@/utils/git-manager.js';
3
+ import { SafeConfigGenerator } from '@/setup/safe-config-generator.js';
4
+ import { executor } from '@/utils/executor.js';
5
+ import logger from '@/utils/logger.js';
6
+ import { DiagnosisResult, RecoveryResult, RecoveryConfig, ChangeRecord } from '@/types';
7
+
8
+ export class AutoFixer {
9
+ private changeRecorder: ChangeRecorder;
10
+ private gitManager?: GitManager;
11
+ private config: RecoveryConfig;
12
+
13
+ constructor(config: RecoveryConfig) {
14
+ this.config = config;
15
+ this.changeRecorder = new ChangeRecorder();
16
+
17
+ if (config.useGitTracking) {
18
+ this.gitManager = new GitManager(config.openclawConfigPath);
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Record the fix that AI has already applied
24
+ * AI has already modified files and executed commands
25
+ * We just need to commit to git and record the change
26
+ */
27
+ async recordFix(diagnosis: DiagnosisResult): Promise<RecoveryResult> {
28
+ logger.info('Recording AI-applied fix', { diagnosis });
29
+
30
+ const actions: string[] = [];
31
+ const errors: string[] = [];
32
+ let gitCommit: string | undefined;
33
+
34
+ try {
35
+ // Initialize git if needed
36
+ if (this.gitManager) {
37
+ await this.gitManager.init();
38
+ }
39
+
40
+ // AI has already modified files, so we collect what changed
41
+ actions.push('AI completed diagnosis and fix');
42
+
43
+ if (diagnosis.configChanges && diagnosis.configChanges.length > 0) {
44
+ actions.push(`Modified ${diagnosis.configChanges.length} configuration files`);
45
+ }
46
+
47
+ if (diagnosis.commands && diagnosis.commands.length > 0) {
48
+ actions.push(`Executed ${diagnosis.commands.length} commands`);
49
+ }
50
+
51
+ // Commit changes to git
52
+ if (this.gitManager) {
53
+ const commitMessage = this.createCommitMessage(diagnosis);
54
+
55
+ try {
56
+ // Update safe config before committing
57
+ logger.info('Updating safe configuration');
58
+ const safeConfigGen = new SafeConfigGenerator(this.config.openclawConfigPath);
59
+ await safeConfigGen.update();
60
+ actions.push('Updated openclaw.safe.json');
61
+
62
+ // Get diff to see what actually changed
63
+ const diff = await this.gitManager.getDiff();
64
+
65
+ if (diff) {
66
+ // Stage openclaw.safe.json
67
+ gitCommit = await this.gitManager.commit(commitMessage, ['openclaw.safe.json']);
68
+ actions.push(`Committed changes: ${gitCommit}`);
69
+ logger.info('Changes committed to git', { commit: gitCommit });
70
+
71
+ // Auto-push to GitHub if enabled
72
+ if (gitCommit && this.config.useGitHubCli) {
73
+ if (await this.gitManager.hasRemote()) {
74
+ const pushed = await this.gitManager.push();
75
+ actions.push(pushed ? 'Pushed to GitHub' : 'GitHub push failed (best-effort)');
76
+ }
77
+ }
78
+ } else {
79
+ logger.info('No git changes to commit');
80
+ actions.push('No git changes detected');
81
+ }
82
+ } catch (error: any) {
83
+ logger.error('Git commit failed', { error: error.message });
84
+ errors.push(`Git commit failed: ${error.message}`);
85
+ }
86
+ }
87
+
88
+ // Verify the fix by checking gateway status
89
+ const verificationResult = await this.verifyFix();
90
+
91
+ if (!verificationResult.success) {
92
+ errors.push(`Verification failed: ${verificationResult.message}`);
93
+ } else {
94
+ actions.push(`Verification: ${verificationResult.message}`);
95
+ }
96
+
97
+ const success = errors.length === 0 && verificationResult.success;
98
+ const result: RecoveryResult = {
99
+ success,
100
+ actions,
101
+ errors: errors.length > 0 ? errors : undefined,
102
+ configChanges: diagnosis.configChanges,
103
+ restartRequired: false, // AI should have already restarted if needed
104
+ };
105
+
106
+ // Record the change
107
+ const record: ChangeRecord = {
108
+ timestamp: new Date(),
109
+ trigger: 'ai-auto-fix',
110
+ diagnosis,
111
+ recovery: result,
112
+ gitCommit,
113
+ };
114
+ await this.changeRecorder.record(record);
115
+
116
+ logger.info('Fix recording completed', result);
117
+ return result;
118
+ } catch (error: any) {
119
+ logger.error('Fix recording failed', { error: error.message });
120
+
121
+ return {
122
+ success: false,
123
+ actions,
124
+ errors: [...errors, `Fatal error: ${error.message}`],
125
+ restartRequired: false,
126
+ };
127
+ }
128
+ }
129
+
130
+ private createCommitMessage(diagnosis: DiagnosisResult): string {
131
+ const lines: string[] = [];
132
+
133
+ lines.push(`${this.config.gitCommitPrefix} ${diagnosis.issue}`);
134
+ lines.push('');
135
+ lines.push(`Root Cause: ${diagnosis.rootCause}`);
136
+ lines.push(`Fix Applied: ${diagnosis.suggestedFix}`);
137
+ lines.push(`Confidence: ${diagnosis.confidence}`);
138
+
139
+ if (diagnosis.configChanges && diagnosis.configChanges.length > 0) {
140
+ lines.push('');
141
+ lines.push('Files Modified:');
142
+ diagnosis.configChanges.forEach((change) => {
143
+ lines.push(`- ${change.file}: ${change.reason}`);
144
+ });
145
+ }
146
+
147
+ if (diagnosis.commands && diagnosis.commands.length > 0) {
148
+ lines.push('');
149
+ lines.push('Commands Executed:');
150
+ diagnosis.commands.forEach((cmd) => {
151
+ lines.push(`- ${cmd}`);
152
+ });
153
+ }
154
+
155
+ lines.push('');
156
+ lines.push('Applied by: AI Auto-Fix System');
157
+
158
+ return lines.join('\n');
159
+ }
160
+
161
+ private async verifyFix(): Promise<{ success: boolean; message: string }> {
162
+ try {
163
+ // Check gateway status
164
+ const statusResult = await executor.getGatewayStatus();
165
+
166
+ if (statusResult.success) {
167
+ return {
168
+ success: true,
169
+ message: 'Gateway is running normally',
170
+ };
171
+ } else {
172
+ return {
173
+ success: false,
174
+ message: `Gateway status check failed: ${statusResult.stderr}`,
175
+ };
176
+ }
177
+ } catch (error: any) {
178
+ return {
179
+ success: false,
180
+ message: `Verification error: ${error.message}`,
181
+ };
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,46 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import logger from '@/utils/logger.js';
4
+ import { ChangeRecord } from '@/types';
5
+ import { getChangesFilePath } from '@/utils/paths.js';
6
+
7
+ export class ChangeRecorder {
8
+ private logFilePath: string;
9
+
10
+ constructor(logFilePath?: string) {
11
+ this.logFilePath = logFilePath || getChangesFilePath();
12
+ }
13
+
14
+ async record(record: ChangeRecord): Promise<void> {
15
+ logger.info('Recording change', record);
16
+
17
+ try {
18
+ await fs.mkdir(path.dirname(this.logFilePath), { recursive: true });
19
+
20
+ let records: ChangeRecord[] = [];
21
+ try {
22
+ const content = await fs.readFile(this.logFilePath, 'utf-8');
23
+ records = JSON.parse(content);
24
+ } catch {
25
+ // File doesn't exist yet
26
+ }
27
+
28
+ records.push(record);
29
+
30
+ await fs.writeFile(this.logFilePath, JSON.stringify(records, null, 2), 'utf-8');
31
+ logger.info('Change recorded successfully');
32
+ } catch (error: any) {
33
+ logger.error('Failed to record change', { error: error.message });
34
+ }
35
+ }
36
+
37
+ async getHistory(limit: number = 10): Promise<ChangeRecord[]> {
38
+ try {
39
+ const content = await fs.readFile(this.logFilePath, 'utf-8');
40
+ const records: ChangeRecord[] = JSON.parse(content);
41
+ return records.slice(-limit);
42
+ } catch {
43
+ return [];
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,63 @@
1
+ import fs from 'fs/promises';
2
+ import logger from '@/utils/logger.js';
3
+ import {
4
+ getWatcherHome,
5
+ getConfigPath,
6
+ getLogsDir,
7
+ getLogFilePath,
8
+ } from '@/utils/paths.js';
9
+
10
+ export interface WatcherConfig {
11
+ openclawConfigPath: string;
12
+ gatewayUrl: string;
13
+ healthCheckInterval: number;
14
+ failureThreshold: number;
15
+ aiProvider: string;
16
+ gitTracking: boolean;
17
+ useGitHubCli: boolean;
18
+ }
19
+
20
+ export class ConfigInitializer {
21
+ async create(config: WatcherConfig): Promise<void> {
22
+ const configContent = {
23
+ monitor: {
24
+ gatewayUrl: config.gatewayUrl,
25
+ healthEndpoint: '/',
26
+ checkInterval: config.healthCheckInterval,
27
+ timeout: 10000,
28
+ maxRetries: 3,
29
+ failureThreshold: config.failureThreshold,
30
+ },
31
+ ai: {
32
+ provider: config.aiProvider,
33
+ timeout: 300000,
34
+ },
35
+ recovery: {
36
+ useGitTracking: config.gitTracking,
37
+ useGitHubCli: config.useGitHubCli,
38
+ gitCommitPrefix: '[AutoFix]',
39
+ backupBeforeChange: true,
40
+ openclawConfigPath: config.openclawConfigPath,
41
+ },
42
+ logging: {
43
+ level: 'info',
44
+ filePath: getLogFilePath(),
45
+ },
46
+ };
47
+
48
+ // Ensure watcher home and logs directory exist
49
+ const watcherHome = getWatcherHome();
50
+ await fs.mkdir(watcherHome, { recursive: true });
51
+ await fs.mkdir(getLogsDir(), { recursive: true });
52
+
53
+ const configPath = getConfigPath();
54
+
55
+ try {
56
+ await fs.writeFile(configPath, JSON.stringify(configContent, null, 2), 'utf-8');
57
+ logger.info('Configuration file created', { path: configPath });
58
+ } catch (error: any) {
59
+ logger.error('Failed to create configuration file', { error: error.message });
60
+ throw error;
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,25 @@
1
+ import fs from 'fs/promises';
2
+ import { MonitorConfig, AIClientConfig, RecoveryConfig } from '@/types/index.js';
3
+ import { getConfigPath } from '@/utils/paths.js';
4
+
5
+ export interface LoadedConfig {
6
+ monitor: MonitorConfig;
7
+ ai: AIClientConfig;
8
+ recovery: RecoveryConfig;
9
+ }
10
+
11
+ export async function loadConfig(): Promise<LoadedConfig | null> {
12
+ const configPath = getConfigPath();
13
+
14
+ try {
15
+ const content = await fs.readFile(configPath, 'utf-8');
16
+ const config = JSON.parse(content);
17
+ return config as LoadedConfig;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export function getConfigFilePath(): string {
24
+ return getConfigPath();
25
+ }
@@ -0,0 +1,203 @@
1
+ import simpleGit, { SimpleGit } from 'simple-git';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import logger from '@/utils/logger.js';
5
+
6
+ export interface GitInitOptions {
7
+ repoName: string;
8
+ isPrivate: boolean;
9
+ }
10
+
11
+ export class GitInitializer {
12
+ private git: SimpleGit;
13
+ private repoPath: string;
14
+
15
+ constructor(repoPath: string) {
16
+ this.repoPath = repoPath;
17
+ this.git = simpleGit(repoPath);
18
+ }
19
+
20
+ async init(options: GitInitOptions): Promise<void> {
21
+ try {
22
+ // Check if already a git repo
23
+ const isRepo = await this.git.checkIsRepo();
24
+ if (isRepo) {
25
+ logger.info('Git repository already exists', { path: this.repoPath });
26
+ return;
27
+ }
28
+
29
+ // Initialize git
30
+ await this.git.init();
31
+ logger.info('Git repository initialized', { path: this.repoPath });
32
+
33
+ // Configure git
34
+ await this.git.addConfig('user.name', 'OpenClaw Watcher');
35
+ await this.git.addConfig('user.email', 'watcher@openclaw.local');
36
+
37
+ // Create .gitignore
38
+ await this.createGitignore();
39
+
40
+ // Create README
41
+ await this.createReadme(options);
42
+ } catch (error: any) {
43
+ logger.error('Failed to initialize git repository', { error: error.message });
44
+ throw error;
45
+ }
46
+ }
47
+
48
+ private async createGitignore(): Promise<void> {
49
+ const gitignorePath = path.join(this.repoPath, '.gitignore');
50
+ const content = `# OpenClaw Configuration - Sensitive Files
51
+ # IMPORTANT: openclaw.json contains API keys and tokens
52
+ openclaw.json
53
+
54
+ # Agent auth profiles (API keys)
55
+ agents/*/agent/auth-profiles.json
56
+
57
+ # Session history (large + privacy)
58
+ agents/*/sessions/
59
+
60
+ # Vector index / memory DB (large)
61
+ memory/*.sqlite
62
+
63
+ # Cache
64
+ cache/
65
+
66
+ # Logs
67
+ *.log
68
+
69
+ # Backup files
70
+ *.backup
71
+ *.bak
72
+ *.tmp
73
+
74
+ # System files
75
+ .DS_Store
76
+ Thumbs.db
77
+ `;
78
+ await fs.writeFile(gitignorePath, content, 'utf-8');
79
+ logger.info('.gitignore created');
80
+ }
81
+
82
+ private async createReadme(options: GitInitOptions): Promise<void> {
83
+ const readmePath = path.join(this.repoPath, 'README.md');
84
+ const content = `# ${options.repoName}
85
+
86
+ ${options.isPrivate ? '🔒 **Private Repository**' : '📖 Public Repository'}
87
+
88
+ This repository is managed by **OpenClaw Watcher** — it tracks the entire \`~/.openclaw\` directory, including configuration, agent settings, and AI repair history.
89
+
90
+ ## What's Tracked
91
+
92
+ | Path | Description |
93
+ |------|-------------|
94
+ | \`openclaw.safe.json\` | Safe configuration (secrets redacted) |
95
+ | \`agents/*/agent/*.json\` | Agent configuration (except auth) |
96
+ | \`README.md\` | This file |
97
+ | \`.gitignore\` | Git ignore rules |
98
+
99
+ ## What's Excluded (.gitignore)
100
+
101
+ | Pattern | Reason |
102
+ |---------|--------|
103
+ | \`openclaw.json\` | Contains API keys and tokens |
104
+ | \`agents/*/agent/auth-profiles.json\` | Agent API keys |
105
+ | \`agents/*/sessions/\` | Session history (large + privacy) |
106
+ | \`memory/*.sqlite\` | Vector index / memory DB |
107
+ | \`cache/\` | Runtime cache |
108
+ | \`*.log\` | Log files |
109
+
110
+ ## ⚠️ Security
111
+
112
+ - A **pre-commit hook** blocks commits containing sensitive patterns
113
+ - Never commit \`openclaw.json\` or auth profile files manually
114
+ - All automated commits are created by the AI repair system
115
+
116
+ ## 🔧 Managed by
117
+
118
+ [OpenClaw Watcher](https://github.com/your-org/openclaw-watcher) - AI-powered health monitoring and auto-repair
119
+ `;
120
+ await fs.writeFile(readmePath, content, 'utf-8');
121
+ logger.info('README.md created');
122
+ }
123
+
124
+ async setupPreCommitHook(): Promise<void> {
125
+ const hooksDir = path.join(this.repoPath, '.git', 'hooks');
126
+ const preCommitPath = path.join(hooksDir, 'pre-commit');
127
+
128
+ const hookContent = `#!/bin/bash
129
+
130
+ # OpenClaw Watcher Pre-commit Hook
131
+ # Checks for sensitive data in openclaw.safe.json
132
+
133
+ echo "🔍 Checking for sensitive data..."
134
+
135
+ # Check if openclaw.safe.json is staged
136
+ if git diff --cached --name-only | grep -q "openclaw.safe.json"; then
137
+ # Get the diff content, excluding already redacted lines (placeholders)
138
+ # Lines containing <YOUR_..._HERE> or <REDACTED> are considered safe
139
+ DIFF_CONTENT=$(git diff --cached openclaw.safe.json | grep -v "<YOUR_.*_HERE>" | grep -v "<REDACTED>")
140
+
141
+ # Check for common sensitive patterns in non-redacted content
142
+ PATTERNS=(
143
+ "sk-ant-[a-zA-Z0-9]" # Claude API keys
144
+ "Bearer [a-zA-Z0-9_-]{10,}" # Bearer tokens
145
+ "api[_-]?key[^\"]*[\"']\s*:\s*[\"'][^\"']{5,}[\"']" # API keys
146
+ "token[^\"]*[\"']\s*:\s*[\"'][^\"']{5,}[\"']" # Tokens
147
+ "password[^\"]*[\"']\s*:\s*[\"'][^\"']{3,}[\"']" # Passwords
148
+ "secret[^\"]*[\"']\s*:\s*[\"'][^\"']{5,}[\"']" # Secrets
149
+ )
150
+
151
+ FOUND_SENSITIVE=false
152
+
153
+ for pattern in "\${PATTERNS[@]}"; do
154
+ if echo "$DIFF_CONTENT" | grep -iE "$pattern" > /dev/null; then
155
+ echo "❌ ERROR: Possible sensitive data found in openclaw.safe.json"
156
+ echo " Pattern matched: $pattern"
157
+ FOUND_SENSITIVE=true
158
+ fi
159
+ done
160
+
161
+ if [ "$FOUND_SENSITIVE" = true ]; then
162
+ echo ""
163
+ echo "🚨 Commit blocked to prevent sensitive data leak!"
164
+ echo " Please review openclaw.safe.json and ensure all secrets are replaced with placeholders"
165
+ echo " Example: \\"apiKey\\": \\"<YOUR_API_KEY_HERE>\\""
166
+ exit 1
167
+ fi
168
+ fi
169
+
170
+ # Check if openclaw.json is accidentally staged
171
+ if git diff --cached --name-only | grep -q "^openclaw.json$"; then
172
+ echo "❌ ERROR: openclaw.json should NOT be committed!"
173
+ echo " This file contains sensitive information."
174
+ echo " It should be in .gitignore"
175
+ exit 1
176
+ fi
177
+
178
+ echo "✅ Pre-commit checks passed"
179
+ exit 0
180
+ `;
181
+
182
+ try {
183
+ await fs.mkdir(hooksDir, { recursive: true });
184
+ await fs.writeFile(preCommitPath, hookContent, 'utf-8');
185
+ await fs.chmod(preCommitPath, 0o755);
186
+ logger.info('Pre-commit hook installed');
187
+ } catch (error: any) {
188
+ logger.error('Failed to setup pre-commit hook', { error: error.message });
189
+ throw error;
190
+ }
191
+ }
192
+
193
+ async initialCommit(): Promise<void> {
194
+ try {
195
+ await this.git.add('.');
196
+ await this.git.commit('Initial commit: OpenClaw Watcher configuration tracking');
197
+ logger.info('Initial commit created');
198
+ } catch (error: any) {
199
+ logger.error('Failed to create initial commit', { error: error.message });
200
+ throw error;
201
+ }
202
+ }
203
+ }