clawmate 1.4.0 → 1.4.2
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/index.js +441 -442
- package/main/ai-bridge.js +59 -59
- package/main/ai-connector.js +60 -60
- package/main/autostart.js +6 -6
- package/main/desktop-path.js +4 -4
- package/main/file-command-parser.js +46 -46
- package/main/file-ops.js +27 -27
- package/main/index.js +17 -17
- package/main/ipc-handlers.js +24 -24
- package/main/manifest.js +2 -2
- package/main/platform.js +16 -16
- package/main/smart-file-ops.js +64 -64
- package/main/store.js +1 -1
- package/main/telegram.js +137 -137
- package/main/tray.js +61 -61
- package/main/updater.js +13 -13
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/preload/preload.js +18 -18
- package/renderer/css/effects.css +6 -6
- package/renderer/css/pet.css +8 -8
- package/renderer/css/speech.css +5 -5
- package/renderer/first-run.html +14 -14
- package/renderer/index.html +4 -4
- package/renderer/js/ai-controller.js +91 -91
- package/renderer/js/app.js +24 -24
- package/renderer/js/browser-watcher.js +32 -32
- package/renderer/js/character.js +33 -33
- package/renderer/js/interactions.js +21 -21
- package/renderer/js/memory.js +60 -60
- package/renderer/js/metrics.js +141 -141
- package/renderer/js/mode-manager.js +13 -13
- package/renderer/js/pet-engine.js +236 -236
- package/renderer/js/speech.js +19 -19
- package/renderer/js/state-machine.js +23 -23
- package/renderer/js/time-aware.js +15 -15
- package/renderer/launcher.html +8 -8
- package/shared/constants.js +11 -11
- package/shared/messages.js +130 -130
- package/shared/personalities.js +44 -44
- package/skills/launch-pet/index.js +57 -47
- package/skills/launch-pet/skill.json +12 -23
package/main/autostart.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Register ClawMate auto-start on system boot
|
|
3
3
|
*
|
|
4
|
-
* Windows:
|
|
4
|
+
* Windows: Registry Run key
|
|
5
5
|
* macOS: LaunchAgent plist
|
|
6
|
-
* Linux: ~/.config/autostart/
|
|
6
|
+
* Linux: Create .desktop file in ~/.config/autostart/ directory
|
|
7
7
|
*
|
|
8
|
-
* AI
|
|
9
|
-
* ClawMate
|
|
8
|
+
* Even if AI starts later, ClawMate is already running for immediate connection.
|
|
9
|
+
* ClawMate runs in autonomous mode first -> switches to AI mode when AI connects.
|
|
10
10
|
*/
|
|
11
11
|
const { app } = require('electron');
|
|
12
12
|
const fs = require('fs');
|
|
@@ -53,7 +53,7 @@ function enableAutoStart() {
|
|
|
53
53
|
}
|
|
54
54
|
app.setLoginItemSettings({
|
|
55
55
|
openAtLogin: true,
|
|
56
|
-
openAsHidden: true, //
|
|
56
|
+
openAsHidden: true, // Start in background without window
|
|
57
57
|
path: process.execPath,
|
|
58
58
|
args: [path.resolve(__dirname, '..')],
|
|
59
59
|
});
|
package/main/desktop-path.js
CHANGED
|
@@ -3,8 +3,8 @@ const path = require('path');
|
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Windows:
|
|
6
|
+
* Detect desktop path per OS
|
|
7
|
+
* Windows: Accurate path detection via PowerShell (handles OneDrive, etc.)
|
|
8
8
|
* macOS: ~/Desktop
|
|
9
9
|
*/
|
|
10
10
|
function getDesktopPath() {
|
|
@@ -18,10 +18,10 @@ function getDesktopPath() {
|
|
|
18
18
|
).trim();
|
|
19
19
|
if (result && result.length > 0) return result;
|
|
20
20
|
} catch {
|
|
21
|
-
//
|
|
21
|
+
// Fallback on PowerShell failure
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// Environment variable based fallback
|
|
25
25
|
const userProfile = process.env.USERPROFILE || os.homedir();
|
|
26
26
|
return path.join(userProfile, 'Desktop');
|
|
27
27
|
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* File operation command parser
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Parses Korean/English natural language to extract file operation intent.
|
|
5
|
+
* Detects file move, organize, etc. commands from Telegram messages.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* - "바탕화면의 .md 파일을 tata 폴더에 넣어줘"
|
|
9
|
-
* - "스크린샷 폴더에 .png 정리해"
|
|
10
|
-
* - "바탕화면 정리해"
|
|
7
|
+
* Supported patterns:
|
|
8
|
+
* - "바탕화면의 .md 파일을 tata 폴더에 넣어줘" (Korean: move .md files from desktop to tata folder)
|
|
9
|
+
* - "스크린샷 폴더에 .png 정리해" (Korean: organize .png into screenshots folder)
|
|
10
|
+
* - "바탕화면 정리해" (Korean: clean up desktop)
|
|
11
11
|
* - "move .txt files to docs folder"
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const os = require('os');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// Known source path aliases (Korean + English)
|
|
18
18
|
const SOURCE_ALIASES = {
|
|
19
19
|
'바탕화면': () => _getDesktopPath(),
|
|
20
20
|
'데스크탑': () => _getDesktopPath(),
|
|
@@ -27,7 +27,7 @@ const SOURCE_ALIASES = {
|
|
|
27
27
|
'documents': () => path.join(os.homedir(), 'Documents'),
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// Action command keywords -> action mapping
|
|
31
31
|
const ACTION_KEYWORDS = {
|
|
32
32
|
'점프': 'jumping',
|
|
33
33
|
'점프해': 'jumping',
|
|
@@ -58,24 +58,24 @@ const ACTION_KEYWORDS = {
|
|
|
58
58
|
'rappel': 'rappelling',
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
//
|
|
61
|
+
// File operation detection patterns
|
|
62
62
|
const FILE_OP_PATTERNS = [
|
|
63
|
-
// "~의 .ext 파일을 ~폴더에 넣어줘/옮겨줘/이동해"
|
|
63
|
+
// Korean: "~의 .ext 파일을 ~폴더에 넣어줘/옮겨줘/이동해" (move .ext files from ~ to ~ folder)
|
|
64
64
|
/(?:(.+?)(?:의|에서|에 있는)\s+)?([.\w*]+)\s*파일(?:을|들을)?\s+(.+?)(?:폴더)?(?:에|으로)\s*(?:넣어|옮겨|이동|정리|보내)/,
|
|
65
|
-
// "~폴더에 .ext 정리해"
|
|
65
|
+
// Korean: "~폴더에 .ext 정리해" (organize .ext into ~ folder)
|
|
66
66
|
/(.+?)(?:폴더)?(?:에|으로)\s+([.\w*]+)\s*(?:파일\s*)?(?:정리|넣어|옮겨|이동)/,
|
|
67
|
-
// "바탕화면 정리해"
|
|
67
|
+
// Korean: "바탕화면 정리해" (clean up desktop)
|
|
68
68
|
/(.+?)\s*(?:정리|청소|깔끔하게)\s*(?:해|해줘|하자|좀)/,
|
|
69
|
-
//
|
|
69
|
+
// English: "move .ext files to folder"
|
|
70
70
|
/move\s+([.\w*]+)\s+files?\s+(?:to|into)\s+(\S+)/i,
|
|
71
|
-
//
|
|
71
|
+
// English: "clean up desktop"
|
|
72
72
|
/clean\s*(?:up)?\s+(\S+)/i,
|
|
73
|
-
//
|
|
73
|
+
// English: "organize desktop"
|
|
74
74
|
/organize\s+(\S+)/i,
|
|
75
75
|
];
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
78
|
+
* Get desktop path (same logic as file-ops)
|
|
79
79
|
*/
|
|
80
80
|
function _getDesktopPath() {
|
|
81
81
|
try {
|
|
@@ -87,9 +87,9 @@ function _getDesktopPath() {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
91
|
-
* @param {string} alias -
|
|
92
|
-
* @returns {string|null}
|
|
90
|
+
* Resolve source alias to actual path
|
|
91
|
+
* @param {string} alias - Source alias (e.g., "desktop", "바탕화면")
|
|
92
|
+
* @returns {string|null} Actual path or null
|
|
93
93
|
*/
|
|
94
94
|
function resolveSource(alias) {
|
|
95
95
|
if (!alias) return null;
|
|
@@ -103,8 +103,8 @@ function resolveSource(alias) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
106
|
+
* Auto-categorize extension -> folder name mapping
|
|
107
|
+
* Used in generic commands like "clean up desktop"
|
|
108
108
|
*/
|
|
109
109
|
const AUTO_CATEGORIES = {
|
|
110
110
|
'.png': '이미지',
|
|
@@ -141,8 +141,8 @@ const AUTO_CATEGORIES = {
|
|
|
141
141
|
};
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
|
-
*
|
|
145
|
-
* @param {string} text -
|
|
144
|
+
* Detect action commands from message
|
|
145
|
+
* @param {string} text - User message
|
|
146
146
|
* @returns {{ type: 'action', action: string }|null}
|
|
147
147
|
*/
|
|
148
148
|
function parseActionCommand(text) {
|
|
@@ -158,15 +158,15 @@ function parseActionCommand(text) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
162
|
-
* @param {string} text -
|
|
161
|
+
* Detect file operation commands from message
|
|
162
|
+
* @param {string} text - User message
|
|
163
163
|
* @returns {{ type: 'smart_file_op', source: string, filter: string, target: string, autoCategory: boolean }|null}
|
|
164
164
|
*/
|
|
165
165
|
function parseFileCommand(text) {
|
|
166
166
|
if (!text) return null;
|
|
167
167
|
const trimmed = text.trim();
|
|
168
168
|
|
|
169
|
-
//
|
|
169
|
+
// Pattern 1: "바탕화면의 .md 파일을 tata 폴더에 넣어줘" (Korean: move files)
|
|
170
170
|
const pattern1 = /(?:(.+?)(?:의|에서|에 있는)\s+)?([.\w*]+)\s*파일(?:을|들을)?\s+(.+?)(?:\s*폴더)?(?:에|으로)\s*(?:넣어|옮겨|이동|정리|보내)/;
|
|
171
171
|
let match = trimmed.match(pattern1);
|
|
172
172
|
if (match) {
|
|
@@ -184,7 +184,7 @@ function parseFileCommand(text) {
|
|
|
184
184
|
};
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
//
|
|
187
|
+
// Pattern 2: "스크린샷 폴더에 .png 정리해" (Korean: organize into folder)
|
|
188
188
|
const pattern2 = /(.+?)(?:\s*폴더)?(?:에|으로)\s+([.\w*]+)\s*(?:파일\s*)?(?:정리|넣어|옮겨|이동)/;
|
|
189
189
|
match = trimmed.match(pattern2);
|
|
190
190
|
if (match) {
|
|
@@ -200,7 +200,7 @@ function parseFileCommand(text) {
|
|
|
200
200
|
};
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
//
|
|
203
|
+
// Pattern 3: "바탕화면 정리해" (Korean: auto-categorize cleanup)
|
|
204
204
|
const pattern3 = /(.+?)\s*(?:정리|청소|깔끔하게)\s*(?:해|해줘|하자|좀)?$/;
|
|
205
205
|
match = trimmed.match(pattern3);
|
|
206
206
|
if (match) {
|
|
@@ -217,7 +217,7 @@ function parseFileCommand(text) {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
//
|
|
220
|
+
// Pattern 4 (English): "move .txt files to docs"
|
|
221
221
|
const pattern4 = /move\s+([.\w*]+)\s+files?\s+(?:to|into)\s+(\S+)/i;
|
|
222
222
|
match = trimmed.match(pattern4);
|
|
223
223
|
if (match) {
|
|
@@ -233,7 +233,7 @@ function parseFileCommand(text) {
|
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
//
|
|
236
|
+
// Pattern 5 (English): "clean up desktop" / "organize desktop"
|
|
237
237
|
const pattern5 = /(?:clean\s*(?:up)?|organize)\s+(\S+)/i;
|
|
238
238
|
match = trimmed.match(pattern5);
|
|
239
239
|
if (match) {
|
|
@@ -254,15 +254,15 @@ function parseFileCommand(text) {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
/**
|
|
257
|
-
*
|
|
258
|
-
* @param {string} text -
|
|
257
|
+
* Detect character change commands
|
|
258
|
+
* @param {string} text - User message
|
|
259
259
|
* @returns {{ type: 'character_change', concept: string }|null}
|
|
260
260
|
*/
|
|
261
261
|
function parseCharacterCommand(text) {
|
|
262
262
|
if (!text) return null;
|
|
263
263
|
const trimmed = text.trim();
|
|
264
264
|
|
|
265
|
-
//
|
|
265
|
+
// Korean character change patterns
|
|
266
266
|
const krPatterns = [
|
|
267
267
|
/(?:캐릭터|펫|모습|외형|외모)(?:를|을)?\s*(.+?)(?:로|으로)\s*(?:바꿔|변경|변신|만들어|바꿀래|바꾸고|바꿔줘|변경해|만들어줘)/,
|
|
268
268
|
/(.+?)(?:로|으로)\s*(?:캐릭터|펫|모습|외형)\s*(?:바꿔|변경|변신|변경해|바꿔줘)/,
|
|
@@ -276,7 +276,7 @@ function parseCharacterCommand(text) {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
//
|
|
279
|
+
// English character change patterns
|
|
280
280
|
const enPatterns = [
|
|
281
281
|
/(?:change|switch|transform)\s+(?:character|pet|look)\s+(?:to|into)\s+(.+)/i,
|
|
282
282
|
/(?:make|create|generate)\s+(?:a\s+)?(.+?)\s+(?:character|pet)/i,
|
|
@@ -293,25 +293,25 @@ function parseCharacterCommand(text) {
|
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
/**
|
|
296
|
-
*
|
|
297
|
-
* @param {string} text -
|
|
296
|
+
* Comprehensive message parsing: character change > file operation > action command > general chat
|
|
297
|
+
* @param {string} text - User message
|
|
298
298
|
* @returns {{ type: string, ... }}
|
|
299
299
|
*/
|
|
300
300
|
/**
|
|
301
|
-
*
|
|
302
|
-
* @param {string} text -
|
|
301
|
+
* Detect mode/setting change commands
|
|
302
|
+
* @param {string} text - User message
|
|
303
303
|
* @returns {{ type: 'mode_change', mode: string }|{ type: 'setting', key, value }|null}
|
|
304
304
|
*/
|
|
305
305
|
function parseSettingCommand(text) {
|
|
306
306
|
if (!text) return null;
|
|
307
307
|
const t = text.trim().toLowerCase();
|
|
308
308
|
|
|
309
|
-
//
|
|
309
|
+
// Mode change
|
|
310
310
|
if (/(?:펫|pet)\s*모드/.test(t)) return { type: 'mode_change', mode: 'pet' };
|
|
311
311
|
if (/(?:인카|인격|incarnation|claw)\s*모드/.test(t)) return { type: 'mode_change', mode: 'incarnation' };
|
|
312
312
|
if (/둘\s*다\s*모드|both\s*mode/i.test(t)) return { type: 'mode_change', mode: 'both' };
|
|
313
313
|
|
|
314
|
-
//
|
|
314
|
+
// Character preset selection (same as tray presets)
|
|
315
315
|
const presetMap = {
|
|
316
316
|
'파란|파랑|blue': 'blue', '초록|green': 'green', '보라|purple': 'purple',
|
|
317
317
|
'골드|금색|gold': 'gold', '핑크|pink': 'pink',
|
|
@@ -329,23 +329,23 @@ function parseSettingCommand(text) {
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
function parseMessage(text) {
|
|
332
|
-
// 0
|
|
332
|
+
// Priority 0: setting/mode change commands
|
|
333
333
|
const settingCmd = parseSettingCommand(text);
|
|
334
334
|
if (settingCmd) return settingCmd;
|
|
335
335
|
|
|
336
|
-
// 1
|
|
336
|
+
// Priority 1: character change commands (AI generation)
|
|
337
337
|
const charCmd = parseCharacterCommand(text);
|
|
338
338
|
if (charCmd) return charCmd;
|
|
339
339
|
|
|
340
|
-
// 2
|
|
340
|
+
// Priority 2: file operation commands
|
|
341
341
|
const fileCmd = parseFileCommand(text);
|
|
342
342
|
if (fileCmd) return fileCmd;
|
|
343
343
|
|
|
344
|
-
// 3
|
|
344
|
+
// Priority 3: action commands
|
|
345
345
|
const actionCmd = parseActionCommand(text);
|
|
346
346
|
if (actionCmd) return actionCmd;
|
|
347
347
|
|
|
348
|
-
// 4
|
|
348
|
+
// Priority 4: general chat (speak)
|
|
349
349
|
return { type: 'speak', text };
|
|
350
350
|
}
|
|
351
351
|
|
package/main/file-ops.js
CHANGED
|
@@ -4,14 +4,14 @@ const { getDesktopPath } = require('./desktop-path');
|
|
|
4
4
|
const manifest = require('./manifest');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Desktop file move system (with safety measures)
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
9
|
+
* Safety rules:
|
|
10
|
+
* - Max 3 files moved per session
|
|
11
|
+
* - Min 5 minute cooldown between moves
|
|
12
|
+
* - Dangerous extensions excluded
|
|
13
|
+
* - Files over 100MB excluded
|
|
14
|
+
* - Position changes only within desktop folder
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
const MAX_FILES_PER_SESSION = 3;
|
|
@@ -26,7 +26,7 @@ let sessionMoveCount = 0;
|
|
|
26
26
|
let lastMoveTime = 0;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Get desktop file list (safe files only)
|
|
30
30
|
*/
|
|
31
31
|
async function getDesktopFiles() {
|
|
32
32
|
const desktop = getDesktopPath();
|
|
@@ -60,49 +60,49 @@ async function getDesktopFiles() {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* newPosition
|
|
63
|
+
* Rename file on desktop (simulates position change)
|
|
64
|
+
* Actually only moves within desktop folder
|
|
65
|
+
* newPosition is coordinates from renderer (for logging)
|
|
66
66
|
*/
|
|
67
67
|
async function moveFile(fileName, newPosition) {
|
|
68
|
-
//
|
|
68
|
+
// Safety check
|
|
69
69
|
if (sessionMoveCount >= MAX_FILES_PER_SESSION) {
|
|
70
|
-
return { success: false, error: '
|
|
70
|
+
return { success: false, error: 'Session move limit (3) exceeded' };
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const now = Date.now();
|
|
74
74
|
if (now - lastMoveTime < COOLDOWN_MS && lastMoveTime > 0) {
|
|
75
75
|
const remaining = Math.ceil((COOLDOWN_MS - (now - lastMoveTime)) / 1000);
|
|
76
|
-
return { success: false, error:
|
|
76
|
+
return { success: false, error: `Cooldown active (${remaining}s remaining)` };
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const desktop = getDesktopPath();
|
|
80
80
|
const filePath = path.join(desktop, fileName);
|
|
81
81
|
|
|
82
|
-
//
|
|
82
|
+
// Check file exists
|
|
83
83
|
try {
|
|
84
84
|
await fs.promises.access(filePath);
|
|
85
85
|
} catch {
|
|
86
|
-
return { success: false, error: '
|
|
86
|
+
return { success: false, error: 'File not found' };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
//
|
|
89
|
+
// Extension check
|
|
90
90
|
const ext = path.extname(fileName).toLowerCase();
|
|
91
91
|
if (EXCLUDED_EXTS.has(ext)) {
|
|
92
|
-
return { success: false, error: '
|
|
92
|
+
return { success: false, error: 'Protected file type' };
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
//
|
|
95
|
+
// Size check
|
|
96
96
|
try {
|
|
97
97
|
const stat = await fs.promises.stat(filePath);
|
|
98
98
|
if (stat.size > MAX_FILE_SIZE) {
|
|
99
|
-
return { success: false, error: '
|
|
99
|
+
return { success: false, error: 'File size exceeded (100MB)' };
|
|
100
100
|
}
|
|
101
101
|
} catch {
|
|
102
|
-
return { success: false, error: '
|
|
102
|
+
return { success: false, error: 'Failed to read file info' };
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
//
|
|
105
|
+
// Record move (filesystem location stays the same since it's within desktop)
|
|
106
106
|
const entry = manifest.addEntry({
|
|
107
107
|
fileName,
|
|
108
108
|
originalPath: filePath,
|
|
@@ -117,19 +117,19 @@ async function moveFile(fileName, newPosition) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
*
|
|
120
|
+
* Undo single file move
|
|
121
121
|
*/
|
|
122
122
|
async function undoFileMove(moveId) {
|
|
123
123
|
const entry = manifest.markRestored(moveId);
|
|
124
124
|
if (!entry) {
|
|
125
|
-
return { success: false, error: '
|
|
125
|
+
return { success: false, error: 'Move record not found' };
|
|
126
126
|
}
|
|
127
|
-
//
|
|
127
|
+
// Only update record since actual file location only changes within desktop
|
|
128
128
|
return { success: true };
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
*
|
|
132
|
+
* Undo all file moves
|
|
133
133
|
*/
|
|
134
134
|
async function undoAllMoves() {
|
|
135
135
|
const count = manifest.markAllRestored();
|
|
@@ -137,7 +137,7 @@ async function undoAllMoves() {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
/**
|
|
140
|
-
*
|
|
140
|
+
* Get file move history
|
|
141
141
|
*/
|
|
142
142
|
async function getFileManifest() {
|
|
143
143
|
return manifest.getAll();
|
package/main/index.js
CHANGED
|
@@ -31,7 +31,7 @@ function createMainWindow() {
|
|
|
31
31
|
},
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// Click-through — renderer controls which pet areas are clickable
|
|
35
35
|
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
|
36
36
|
|
|
37
37
|
mainWindow.loadFile(path.join(__dirname, '..', 'renderer', 'index.html'));
|
|
@@ -69,26 +69,26 @@ function createLauncherWindow() {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* AI Bridge
|
|
72
|
+
* Start AI Bridge -- AI agent connects to control the pet
|
|
73
73
|
*/
|
|
74
74
|
function startAIBridge(win) {
|
|
75
75
|
aiBridge = new AIBridge();
|
|
76
76
|
aiBridge.start();
|
|
77
77
|
|
|
78
|
-
// AI
|
|
78
|
+
// Forward AI -> ClawMate commands to renderer
|
|
79
79
|
const commandTypes = [
|
|
80
80
|
'action', 'move', 'emote', 'speak', 'think',
|
|
81
81
|
'carry_file', 'drop_file', 'set_mode', 'evolve',
|
|
82
82
|
'accessorize', 'ai_decision',
|
|
83
|
-
//
|
|
83
|
+
// Spatial movement commands (pet roams like it's home)
|
|
84
84
|
'jump_to', 'rappel', 'release_thread', 'move_to_center', 'walk_on_window',
|
|
85
|
-
//
|
|
85
|
+
// Custom movement patterns
|
|
86
86
|
'register_movement', 'custom_move', 'stop_custom_move', 'list_movements',
|
|
87
|
-
//
|
|
87
|
+
// Smart file operations (file move animations triggered by Telegram/AI)
|
|
88
88
|
'smart_file_op',
|
|
89
|
-
//
|
|
89
|
+
// Character customization (AI-generated via Telegram)
|
|
90
90
|
'set_character', 'reset_character',
|
|
91
|
-
//
|
|
91
|
+
// Persona switching (Incarnation mode)
|
|
92
92
|
'set_persona',
|
|
93
93
|
];
|
|
94
94
|
|
|
@@ -100,19 +100,19 @@ function startAIBridge(win) {
|
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
// AI
|
|
103
|
+
// Handle AI window position info request
|
|
104
104
|
aiBridge.on('query_windows', async () => {
|
|
105
105
|
try {
|
|
106
106
|
const { getWindowPositions } = require('./platform');
|
|
107
107
|
const windows = await getWindowPositions();
|
|
108
108
|
aiBridge.send('window_positions', { windows });
|
|
109
109
|
} catch (err) {
|
|
110
|
-
console.error('[AI Bridge]
|
|
110
|
+
console.error('[AI Bridge] Window list failed:', err.message);
|
|
111
111
|
aiBridge.send('window_positions', { windows: [] });
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
// AI
|
|
115
|
+
// Handle AI screen capture request (captured directly in main process)
|
|
116
116
|
aiBridge.on('query_screen', async () => {
|
|
117
117
|
try {
|
|
118
118
|
const primaryDisplay = screen.getPrimaryDisplay();
|
|
@@ -133,11 +133,11 @@ function startAIBridge(win) {
|
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
135
|
} catch (err) {
|
|
136
|
-
console.error('[AI Bridge]
|
|
136
|
+
console.error('[AI Bridge] Screen capture failed:', err.message);
|
|
137
137
|
}
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
-
//
|
|
140
|
+
// Connection/disconnection events
|
|
141
141
|
aiBridge.on('connected', () => {
|
|
142
142
|
if (win && !win.isDestroyed()) {
|
|
143
143
|
win.webContents.send('ai-connected');
|
|
@@ -159,22 +159,22 @@ app.whenReady().then(() => {
|
|
|
159
159
|
const bridge = startAIBridge(win);
|
|
160
160
|
setupTray(win, bridge);
|
|
161
161
|
|
|
162
|
-
//
|
|
162
|
+
// Initialize Telegram bot (silently ignored if no token)
|
|
163
163
|
telegramBot = new TelegramBot(bridge);
|
|
164
164
|
|
|
165
|
-
//
|
|
165
|
+
// Register auto-start on first install
|
|
166
166
|
const { enableAutoStart, isAutoStartEnabled } = require('./autostart');
|
|
167
167
|
if (!isAutoStartEnabled()) {
|
|
168
168
|
enableAutoStart();
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
//
|
|
171
|
+
// Auto-update check (only works in packaged app)
|
|
172
172
|
const { checkForUpdates } = require('./updater');
|
|
173
173
|
checkForUpdates();
|
|
174
174
|
});
|
|
175
175
|
|
|
176
176
|
app.on('window-all-closed', () => {
|
|
177
|
-
//
|
|
177
|
+
// Keep running in tray
|
|
178
178
|
});
|
|
179
179
|
|
|
180
180
|
app.on('before-quit', () => {
|