clawmate 1.4.1 → 1.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/main/ai-bridge.js CHANGED
@@ -345,6 +345,18 @@ class AIBridge extends EventEmitter {
345
345
  });
346
346
  }
347
347
 
348
+ /**
349
+ * Report proactive trigger event to AI
350
+ * Sent when ProactiveMonitor detects user activity patterns
351
+ */
352
+ reportProactiveEvent(triggerType, context) {
353
+ this.send('proactive_trigger', {
354
+ trigger: triggerType,
355
+ context,
356
+ timestamp: Date.now(),
357
+ });
358
+ }
359
+
348
360
  // === State Updates ===
349
361
 
350
362
  updatePetState(updates) {
package/main/autostart.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * Windows: Registry Run key
5
5
  * macOS: LaunchAgent plist
6
6
  * Linux: Create .desktop file in ~/.config/autostart/ directory
7
+ * WSL: Create .bat file in Windows Startup folder
7
8
  *
8
9
  * Even if AI starts later, ClawMate is already running for immediate connection.
9
10
  * ClawMate runs in autonomous mode first -> switches to AI mode when AI connects.
@@ -12,11 +13,46 @@ const { app } = require('electron');
12
13
  const fs = require('fs');
13
14
  const path = require('path');
14
15
  const os = require('os');
15
- const { isLinux } = require('./platform');
16
+ const { execSync } = require('child_process');
17
+ const { isLinux, isWSL } = require('./platform');
16
18
 
17
19
  const LINUX_AUTOSTART_DIR = path.join(os.homedir(), '.config', 'autostart');
18
20
  const LINUX_DESKTOP_FILE = path.join(LINUX_AUTOSTART_DIR, 'clawmate.desktop');
19
21
 
22
+ /**
23
+ * WSL: Windows 시작프로그램 폴더 경로 + 배치파일명
24
+ */
25
+ function getWSLStartupBatPath() {
26
+ try {
27
+ const winUserProfile = execSync('cmd.exe /c "echo %USERPROFILE%"', {
28
+ encoding: 'utf-8',
29
+ }).trim().replace(/\r/g, '');
30
+ // Windows 경로를 Linux 경로로 변환하여 fs로 접근
31
+ const linuxStartup = execSync(
32
+ `wslpath "${winUserProfile}\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup"`,
33
+ { encoding: 'utf-8' }
34
+ ).trim();
35
+ return path.join(linuxStartup, 'clawmate.bat');
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * WSL: Windows Startup 폴더에 넣을 배치파일 내용
43
+ */
44
+ function getWSLBatContent() {
45
+ try {
46
+ const appRoot = path.resolve(__dirname, '..');
47
+ const winAppRoot = execSync(`wslpath -w "${appRoot}"`, {
48
+ encoding: 'utf-8',
49
+ }).trim();
50
+ return `@echo off\r\nnpx.cmd electron ${winAppRoot}\r\n`;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
20
56
  function getDesktopFileContent() {
21
57
  return [
22
58
  '[Desktop Entry]',
@@ -31,6 +67,15 @@ function getDesktopFileContent() {
31
67
  }
32
68
 
33
69
  function isAutoStartEnabled() {
70
+ if (isWSL()) {
71
+ const batPath = getWSLStartupBatPath();
72
+ if (!batPath) return false;
73
+ try {
74
+ return fs.existsSync(batPath);
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
34
79
  if (isLinux()) {
35
80
  try {
36
81
  return fs.existsSync(LINUX_DESKTOP_FILE);
@@ -42,6 +87,17 @@ function isAutoStartEnabled() {
42
87
  }
43
88
 
44
89
  function enableAutoStart() {
90
+ if (isWSL()) {
91
+ try {
92
+ const batPath = getWSLStartupBatPath();
93
+ const batContent = getWSLBatContent();
94
+ if (!batPath || !batContent) return false;
95
+ fs.writeFileSync(batPath, batContent, 'utf-8');
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
45
101
  if (isLinux()) {
46
102
  try {
47
103
  fs.mkdirSync(LINUX_AUTOSTART_DIR, { recursive: true });
@@ -53,7 +109,7 @@ function enableAutoStart() {
53
109
  }
54
110
  app.setLoginItemSettings({
55
111
  openAtLogin: true,
56
- openAsHidden: true, // Start in background without window
112
+ openAsHidden: true,
57
113
  path: process.execPath,
58
114
  args: [path.resolve(__dirname, '..')],
59
115
  });
@@ -61,6 +117,17 @@ function enableAutoStart() {
61
117
  }
62
118
 
63
119
  function disableAutoStart() {
120
+ if (isWSL()) {
121
+ try {
122
+ const batPath = getWSLStartupBatPath();
123
+ if (batPath && fs.existsSync(batPath)) {
124
+ fs.unlinkSync(batPath);
125
+ }
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
64
131
  if (isLinux()) {
65
132
  try {
66
133
  if (fs.existsSync(LINUX_DESKTOP_FILE)) {
package/main/index.js CHANGED
@@ -4,11 +4,13 @@ const { setupTray } = require('./tray');
4
4
  const { registerIpcHandlers } = require('./ipc-handlers');
5
5
  const { AIBridge } = require('./ai-bridge');
6
6
  const { TelegramBot } = require('./telegram');
7
+ const { ProactiveMonitor } = require('./proactive-monitor');
7
8
 
8
9
  let mainWindow = null;
9
10
  let launcherWindow = null;
10
11
  let aiBridge = null;
11
12
  let telegramBot = null;
13
+ let proactiveMonitor = null;
12
14
 
13
15
  function createMainWindow() {
14
16
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
@@ -154,14 +156,22 @@ function startAIBridge(win) {
154
156
  }
155
157
 
156
158
  app.whenReady().then(() => {
157
- registerIpcHandlers(() => mainWindow, () => aiBridge);
159
+ registerIpcHandlers(() => mainWindow, () => aiBridge, () => proactiveMonitor);
158
160
  const win = createMainWindow();
159
161
  const bridge = startAIBridge(win);
160
- setupTray(win, bridge);
162
+ setupTray(win, bridge, () => proactiveMonitor);
161
163
 
162
164
  // Initialize Telegram bot (silently ignored if no token)
163
165
  telegramBot = new TelegramBot(bridge);
164
166
 
167
+ // Initialize Proactive Monitor
168
+ const Store = require('./store');
169
+ const configStore = new Store('clawmate-config', { proactiveEnabled: true });
170
+ proactiveMonitor = new ProactiveMonitor();
171
+ if (configStore.get('proactiveEnabled') !== false) {
172
+ proactiveMonitor.start(win, bridge);
173
+ }
174
+
165
175
  // Register auto-start on first install
166
176
  const { enableAutoStart, isAutoStartEnabled } = require('./autostart');
167
177
  if (!isAutoStartEnabled()) {
@@ -178,6 +188,7 @@ app.on('window-all-closed', () => {
178
188
  });
179
189
 
180
190
  app.on('before-quit', () => {
191
+ if (proactiveMonitor) proactiveMonitor.stop();
181
192
  if (telegramBot) telegramBot.stop();
182
193
  if (aiBridge) aiBridge.stop();
183
194
  });
@@ -17,7 +17,7 @@ const memoryStore = new Store('clawmate-memory', {
17
17
  milestones: [],
18
18
  });
19
19
 
20
- function registerIpcHandlers(getMainWindow, getAIBridge) {
20
+ function registerIpcHandlers(getMainWindow, getAIBridge, getProactiveMonitor) {
21
21
  // Click-through control
22
22
  ipcMain.on('set-click-through', (event, ignore) => {
23
23
  const win = getMainWindow();
@@ -239,6 +239,29 @@ function registerIpcHandlers(getMainWindow, getAIBridge) {
239
239
  ipcMain.handle('undo-all-smart-moves', async () => {
240
240
  return undoAllSmartMoves();
241
241
  });
242
+
243
+ // === Proactive Monitor ===
244
+
245
+ ipcMain.handle('get-proactive-config', () => {
246
+ const monitor = getProactiveMonitor ? getProactiveMonitor() : null;
247
+ return {
248
+ enabled: monitor ? monitor.enabled : false,
249
+ };
250
+ });
251
+
252
+ ipcMain.handle('set-proactive-enabled', (_, enabled) => {
253
+ const monitor = getProactiveMonitor ? getProactiveMonitor() : null;
254
+ if (monitor) {
255
+ if (enabled && !monitor.enabled) {
256
+ monitor.start(getMainWindow(), getAIBridge());
257
+ } else if (!enabled && monitor.enabled) {
258
+ monitor.stop();
259
+ }
260
+ monitor.enabled = enabled;
261
+ }
262
+ store.set('proactiveEnabled', enabled);
263
+ return enabled;
264
+ });
242
265
  }
243
266
 
244
267
  module.exports = { registerIpcHandlers };
package/main/platform.js CHANGED
@@ -7,6 +7,22 @@ const execAsync = promisify(exec);
7
7
 
8
8
  const platform = os.platform();
9
9
 
10
+ let _isWSL = null;
11
+ function isWSL() {
12
+ if (_isWSL !== null) return _isWSL;
13
+ if (platform !== 'linux') {
14
+ _isWSL = false;
15
+ return false;
16
+ }
17
+ try {
18
+ const procVersion = require('fs').readFileSync('/proc/version', 'utf-8');
19
+ _isWSL = /microsoft/i.test(procVersion);
20
+ } catch {
21
+ _isWSL = false;
22
+ }
23
+ return _isWSL;
24
+ }
25
+
10
26
  function getDesktopPath() {
11
27
  if (platform === 'win32') {
12
28
  try {
@@ -260,4 +276,4 @@ public class FGWin {
260
276
  }
261
277
  }
262
278
 
263
- module.exports = { getDesktopPath, getTrayIconExt, isWindows, isMac, isLinux, platform, getWindowPositions, getActiveWindowTitle };
279
+ module.exports = { getDesktopPath, getTrayIconExt, isWindows, isMac, isLinux, isWSL, platform, getWindowPositions, getActiveWindowTitle };