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,100 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import logger from '@/utils/logger.js';
4
+
5
+ export class SafeConfigGenerator {
6
+ private configPath: string;
7
+
8
+ constructor(configPath: string) {
9
+ this.configPath = configPath;
10
+ }
11
+
12
+ async generate(): Promise<void> {
13
+ const openclawJsonPath = path.join(this.configPath, 'openclaw.json');
14
+ const safeJsonPath = path.join(this.configPath, 'openclaw.safe.json');
15
+
16
+ try {
17
+ // Read original config
18
+ const content = await fs.readFile(openclawJsonPath, 'utf-8');
19
+ const config = JSON.parse(content);
20
+
21
+ // Redact sensitive data
22
+ const safeConfig = this.redactSensitiveData(config);
23
+
24
+ // Write safe config
25
+ await fs.writeFile(safeJsonPath, JSON.stringify(safeConfig, null, 2), 'utf-8');
26
+
27
+ logger.info('Safe configuration generated', { path: safeJsonPath });
28
+ } catch (error: any) {
29
+ logger.error('Failed to generate safe configuration', { error: error.message });
30
+ throw error;
31
+ }
32
+ }
33
+
34
+ async update(): Promise<void> {
35
+ // Same as generate - always regenerate from openclaw.json
36
+ await this.generate();
37
+ }
38
+
39
+ private redactSensitiveData(obj: any): any {
40
+ if (typeof obj !== 'object' || obj === null) {
41
+ return obj;
42
+ }
43
+
44
+ if (Array.isArray(obj)) {
45
+ return obj.map((item) => this.redactSensitiveData(item));
46
+ }
47
+
48
+ const result: any = {};
49
+
50
+ for (const [key, value] of Object.entries(obj)) {
51
+ const lowerKey = key.toLowerCase();
52
+
53
+ // Check if key indicates sensitive data
54
+ if (
55
+ lowerKey.includes('key') ||
56
+ lowerKey.includes('token') ||
57
+ lowerKey.includes('secret') ||
58
+ lowerKey.includes('password') ||
59
+ lowerKey.includes('apikey') ||
60
+ lowerKey.includes('api_key') ||
61
+ lowerKey === 'bearer'
62
+ ) {
63
+ // Replace with placeholder
64
+ result[key] = this.getPlaceholder(lowerKey, value);
65
+ } else if (typeof value === 'object' && value !== null) {
66
+ // Recursively process nested objects
67
+ result[key] = this.redactSensitiveData(value);
68
+ } else {
69
+ // Keep non-sensitive values
70
+ result[key] = value;
71
+ }
72
+ }
73
+
74
+ return result;
75
+ }
76
+
77
+ private getPlaceholder(key: string, value: any): string {
78
+ if (typeof value !== 'string') {
79
+ return '<REDACTED>';
80
+ }
81
+
82
+ if (key.includes('apikey') || key.includes('api_key')) {
83
+ return '<YOUR_API_KEY_HERE>';
84
+ }
85
+ if (key.includes('token')) {
86
+ return '<YOUR_TOKEN_HERE>';
87
+ }
88
+ if (key.includes('secret')) {
89
+ return '<YOUR_SECRET_HERE>';
90
+ }
91
+ if (key.includes('password')) {
92
+ return '<YOUR_PASSWORD_HERE>';
93
+ }
94
+ if (key.includes('key')) {
95
+ return '<YOUR_KEY_HERE>';
96
+ }
97
+
98
+ return '<REDACTED>';
99
+ }
100
+ }
@@ -0,0 +1,67 @@
1
+ export interface HealthCheckResult {
2
+ healthy: boolean;
3
+ timestamp: Date;
4
+ endpoint: string;
5
+ responseTime?: number;
6
+ error?: string;
7
+ statusCode?: number;
8
+ }
9
+
10
+ export interface DiagnosisResult {
11
+ issue: string;
12
+ rootCause: string;
13
+ suggestedFix: string;
14
+ commands: string[];
15
+ configChanges?: ConfigChange[];
16
+ confidence: number;
17
+ }
18
+
19
+ export interface ConfigChange {
20
+ file: string;
21
+ path: string;
22
+ oldValue: unknown;
23
+ newValue: unknown;
24
+ reason: string;
25
+ }
26
+
27
+ export interface RecoveryResult {
28
+ success: boolean;
29
+ actions: string[];
30
+ errors?: string[];
31
+ configChanges?: ConfigChange[];
32
+ restartRequired: boolean;
33
+ }
34
+
35
+ export interface ChangeRecord {
36
+ timestamp: Date;
37
+ trigger: string;
38
+ diagnosis: DiagnosisResult;
39
+ recovery: RecoveryResult;
40
+ gitCommit?: string;
41
+ }
42
+
43
+ export type AIProvider = 'claude' | 'kimi';
44
+
45
+ export interface AIClientConfig {
46
+ provider: AIProvider;
47
+ timeout?: number;
48
+ }
49
+
50
+ export interface MonitorConfig {
51
+ gatewayUrl: string;
52
+ healthEndpoint: string;
53
+ checkInterval: number;
54
+ timeout: number;
55
+ maxRetries: number;
56
+ failureThreshold: number;
57
+ }
58
+
59
+ export interface RecoveryConfig {
60
+ useGitTracking: boolean;
61
+ useGitHubCli: boolean;
62
+ gitCommitPrefix: string;
63
+ backupBeforeChange: boolean;
64
+ openclawConfigPath: string;
65
+ maxRecoveryRetries: number;
66
+ recoveryCooldownMs: number;
67
+ }
@@ -0,0 +1,75 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import logger from './logger.js';
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ export interface CommandResult {
8
+ stdout: string;
9
+ stderr: string;
10
+ exitCode: number;
11
+ success: boolean;
12
+ }
13
+
14
+ export class CommandExecutor {
15
+ private timeout: number;
16
+
17
+ constructor(timeout: number = 30000) {
18
+ this.timeout = timeout;
19
+ }
20
+
21
+ async execute(command: string, cwd?: string): Promise<CommandResult> {
22
+ logger.info('Executing command', { command, cwd });
23
+
24
+ try {
25
+ const { stdout, stderr } = await execAsync(command, {
26
+ timeout: this.timeout,
27
+ cwd,
28
+ shell: '/bin/bash',
29
+ });
30
+
31
+ logger.info('Command executed successfully', { command, stdout, stderr });
32
+
33
+ return {
34
+ stdout: stdout.trim(),
35
+ stderr: stderr.trim(),
36
+ exitCode: 0,
37
+ success: true,
38
+ };
39
+ } catch (error: any) {
40
+ logger.error('Command execution failed', {
41
+ command,
42
+ error: error.message,
43
+ stdout: error.stdout,
44
+ stderr: error.stderr,
45
+ });
46
+
47
+ return {
48
+ stdout: error.stdout?.trim() || '',
49
+ stderr: error.stderr?.trim() || error.message,
50
+ exitCode: error.code || 1,
51
+ success: false,
52
+ };
53
+ }
54
+ }
55
+
56
+ async executeOpenClawCommand(subcommand: string): Promise<CommandResult> {
57
+ const command = `openclaw ${subcommand}`;
58
+ return this.execute(command);
59
+ }
60
+
61
+ async restartGateway(): Promise<CommandResult> {
62
+ logger.info('Attempting to restart OpenClaw gateway');
63
+ return this.executeOpenClawCommand('gateway restart');
64
+ }
65
+
66
+ async getGatewayStatus(): Promise<CommandResult> {
67
+ return this.executeOpenClawCommand('gateway status');
68
+ }
69
+
70
+ async getGatewayLogs(lines: number = 100): Promise<CommandResult> {
71
+ return this.executeOpenClawCommand(`gateway logs --lines ${lines}`);
72
+ }
73
+ }
74
+
75
+ export const executor = new CommandExecutor();
@@ -0,0 +1,121 @@
1
+ import simpleGit, { SimpleGit } from 'simple-git';
2
+ import logger from './logger.js';
3
+ import path from 'path';
4
+ import fs from 'fs/promises';
5
+
6
+ export class GitManager {
7
+ private git: SimpleGit;
8
+ private repoPath: string;
9
+
10
+ constructor(repoPath: string) {
11
+ this.repoPath = repoPath;
12
+ this.git = simpleGit(repoPath);
13
+ }
14
+
15
+ async init(): Promise<void> {
16
+ try {
17
+ const isRepo = await this.git.checkIsRepo();
18
+ if (!isRepo) {
19
+ logger.info('Initializing git repository', { path: this.repoPath });
20
+ await this.git.init();
21
+ await this.createGitignore();
22
+ await this.git.add('.gitignore');
23
+ await this.git.commit('Initial commit: Setup config tracking');
24
+ }
25
+ } catch (error: any) {
26
+ logger.error('Failed to initialize git repository', { error: error.message });
27
+ throw error;
28
+ }
29
+ }
30
+
31
+ private async createGitignore(): Promise<void> {
32
+ const gitignorePath = path.join(this.repoPath, '.gitignore');
33
+ const content = `# Ignore sensitive files
34
+ *.key
35
+ *.pem
36
+ *.secret
37
+ secrets/
38
+ *.env
39
+ `;
40
+ await fs.writeFile(gitignorePath, content, 'utf-8');
41
+ }
42
+
43
+ async commit(message: string, files?: string[]): Promise<string> {
44
+ try {
45
+ if (files && files.length > 0) {
46
+ await this.git.add(files);
47
+ } else {
48
+ await this.git.add('.');
49
+ }
50
+
51
+ const status = await this.git.status();
52
+ if (status.files.length === 0) {
53
+ logger.info('No changes to commit');
54
+ return '';
55
+ }
56
+
57
+ const result = await this.git.commit(message);
58
+ logger.info('Changes committed', { message, hash: result.commit });
59
+ return result.commit;
60
+ } catch (error: any) {
61
+ logger.error('Failed to commit changes', { error: error.message });
62
+ throw error;
63
+ }
64
+ }
65
+
66
+ async createBackup(files: string[]): Promise<void> {
67
+ try {
68
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
69
+ const backupBranch = `backup-${timestamp}`;
70
+
71
+ const currentBranch = await this.git.revparse(['--abbrev-ref', 'HEAD']);
72
+ await this.git.checkoutBranch(backupBranch, currentBranch.trim());
73
+ await this.git.checkout(currentBranch.trim());
74
+
75
+ logger.info('Backup created', { branch: backupBranch, files });
76
+ } catch (error: any) {
77
+ logger.error('Failed to create backup', { error: error.message });
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ async getDiff(file?: string): Promise<string> {
83
+ try {
84
+ const diff = file ? await this.git.diff([file]) : await this.git.diff();
85
+ return diff;
86
+ } catch (error: any) {
87
+ logger.error('Failed to get diff', { error: error.message });
88
+ return '';
89
+ }
90
+ }
91
+
92
+ async hasRemote(): Promise<boolean> {
93
+ try {
94
+ const remotes = await this.git.getRemotes();
95
+ return remotes.some((r) => r.name === 'origin');
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+
101
+ async push(): Promise<boolean> {
102
+ try {
103
+ await this.git.push('origin', 'HEAD');
104
+ logger.info('Pushed to remote');
105
+ return true;
106
+ } catch (error: any) {
107
+ logger.warn('Failed to push to remote', { error: error.message });
108
+ return false;
109
+ }
110
+ }
111
+
112
+ async getLog(maxCount: number = 10): Promise<string> {
113
+ try {
114
+ const log = await this.git.log({ maxCount });
115
+ return log.all.map((commit) => `${commit.hash.substring(0, 7)} - ${commit.message}`).join('\n');
116
+ } catch (error: any) {
117
+ logger.error('Failed to get log', { error: error.message });
118
+ return '';
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,37 @@
1
+ import { CommandExecutor, CommandResult } from './executor.js';
2
+ import logger from './logger.js';
3
+
4
+ export class GitHubCli {
5
+ private executor: CommandExecutor;
6
+
7
+ constructor() {
8
+ this.executor = new CommandExecutor(60000);
9
+ }
10
+
11
+ async isInstalled(): Promise<boolean> {
12
+ const result = await this.executor.execute('gh --version');
13
+ return result.success;
14
+ }
15
+
16
+ async isAuthenticated(): Promise<boolean> {
17
+ const result = await this.executor.execute('gh auth status');
18
+ return result.success;
19
+ }
20
+
21
+ async createRepoAndPush(
22
+ repoName: string,
23
+ localPath: string
24
+ ): Promise<CommandResult> {
25
+ const command = `gh repo create ${repoName} --private --source="${localPath}" --remote=origin --push`;
26
+ const result = await this.executor.execute(command, localPath);
27
+ if (result.success) {
28
+ logger.info('GitHub repository created and pushed', { repoName });
29
+ } else {
30
+ logger.error('Failed to create GitHub repository', {
31
+ repoName,
32
+ stderr: result.stderr,
33
+ });
34
+ }
35
+ return result;
36
+ }
37
+ }
@@ -0,0 +1,39 @@
1
+ import winston from 'winston';
2
+ import { getLogFilePath } from '@/utils/paths.js';
3
+
4
+ const logLevel = process.env.LOG_LEVEL || 'info';
5
+ const logFilePath = process.env.LOG_FILE_PATH || getLogFilePath();
6
+
7
+ const logger = winston.createLogger({
8
+ level: logLevel,
9
+ format: winston.format.combine(
10
+ winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
11
+ winston.format.errors({ stack: true }),
12
+ winston.format.splat(),
13
+ winston.format.json()
14
+ ),
15
+ defaultMeta: { service: 'openclaw-healthcheck' },
16
+ transports: [
17
+ new winston.transports.File({
18
+ filename: logFilePath.replace('.log', '-error.log'),
19
+ level: 'error',
20
+ }),
21
+ new winston.transports.File({ filename: logFilePath }),
22
+ ],
23
+ });
24
+
25
+ if (process.env.NODE_ENV !== 'production') {
26
+ logger.add(
27
+ new winston.transports.Console({
28
+ format: winston.format.combine(
29
+ winston.format.colorize(),
30
+ winston.format.printf(({ level, message, timestamp, ...meta }) => {
31
+ const metaStr = Object.keys(meta).length ? JSON.stringify(meta, null, 2) : '';
32
+ return `${timestamp} [${level}]: ${message} ${metaStr}`;
33
+ })
34
+ ),
35
+ })
36
+ );
37
+ }
38
+
39
+ export default logger;
@@ -0,0 +1,25 @@
1
+ import path from 'path';
2
+ import os from 'os';
3
+
4
+ const WATCHER_HOME =
5
+ process.env.WATCHER_HOME || path.join(os.homedir(), '.openclaw-watcher');
6
+
7
+ export function getWatcherHome(): string {
8
+ return WATCHER_HOME;
9
+ }
10
+
11
+ export function getConfigPath(): string {
12
+ return path.join(WATCHER_HOME, 'config.json');
13
+ }
14
+
15
+ export function getLogsDir(): string {
16
+ return path.join(WATCHER_HOME, 'logs');
17
+ }
18
+
19
+ export function getLogFilePath(): string {
20
+ return path.join(WATCHER_HOME, 'logs', 'healthcheck.log');
21
+ }
22
+
23
+ export function getChangesFilePath(): string {
24
+ return path.join(WATCHER_HOME, 'logs', 'changes.json');
25
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "bundler",
7
+ "resolveJsonModule": true,
8
+ "allowJs": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "declaration": true,
16
+ "declarationMap": true,
17
+ "sourceMap": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noImplicitReturns": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "allowSyntheticDefaultImports": true,
23
+ "paths": {
24
+ "@/*": ["./src/*"]
25
+ }
26
+ },
27
+ "include": ["src/**/*"],
28
+ "exclude": ["node_modules", "dist"]
29
+ }