@pheem49/mint 1.5.0 → 1.5.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.
- package/README.md +27 -1
- package/main.js +28 -14
- package/mint-cli-logic.js +3 -119
- package/mint-cli.js +497 -23
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json +15 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
- package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +23 -0
- package/package.json +26 -1
- package/src/AI_Brain/Gemini_API.js +147 -46
- package/src/AI_Brain/autonomous_brain.js +2 -1
- package/src/AI_Brain/memory_store.js +299 -3
- package/src/CLI/chat_router.js +18 -6
- package/src/CLI/chat_ui.js +396 -50
- package/src/CLI/code_agent.js +203 -14
- package/src/CLI/image_input.js +90 -0
- package/src/CLI/onboarding.js +72 -15
- package/src/CLI/updater.js +6 -4
- package/src/System/action_executor.js +59 -10
- package/src/System/config_manager.js +31 -1
- package/src/System/granular_automation.js +122 -53
- package/src/System/proactive_loop.js +19 -3
- package/src/System/safety_manager.js +108 -0
- package/src/System/sandbox_runner.js +182 -0
- package/src/System/system_automation.js +127 -81
- package/src/System/system_info.js +70 -0
- package/src/System/tool_registry.js +280 -0
- package/src/System/window_manager.js +4 -2
- package/src/UI/live2d_manager.js +368 -0
- package/src/UI/renderer.js +176 -18
- package/src/UI/styles.css +452 -31
- package/.codex +0 -0
- package/docs/assets/Agent_Mint.png +0 -0
- package/docs/assets/CLI_Screen.png +0 -0
- package/docs/assets/Settings.png +0 -0
- package/docs/assets/icon.png +0 -0
- package/docs/guide.html +0 -632
- package/docs/index.html +0 -133
- package/docs/style.css +0 -579
- package/index.html +0 -16
- package/src/UI/index.html +0 -126
- package/tech_news.txt +0 -3
- package/test_knowledge.txt +0 -3
- package/tests/action_executor_safety.test.js +0 -67
- package/tests/agent_orchestrator.test.js +0 -41
- package/tests/chat_router.test.js +0 -42
- package/tests/code_agent.test.js +0 -69
- package/tests/config_manager.test.js +0 -141
- package/tests/docker.test.js +0 -46
- package/tests/file_operations.test.js +0 -57
- package/tests/gmail.test.js +0 -135
- package/tests/gmail_auth.test.js +0 -129
- package/tests/google_calendar.test.js +0 -113
- package/tests/google_tts_urls.test.js +0 -24
- package/tests/memory_store.test.js +0 -185
- package/tests/notion.test.js +0 -121
- package/tests/provider_routing.test.js +0 -83
- package/tests/safety_manager.test.js +0 -40
- package/tests/spotify.test.js +0 -201
- package/tests/system_monitor.test.js +0 -37
- package/tests/updater.test.js +0 -32
- package/tests/workspace_manager.test.js +0 -56
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
let electronClipboard = null;
|
|
2
|
+
try {
|
|
3
|
+
({ clipboard: electronClipboard } = require('electron'));
|
|
4
|
+
} catch (_) {
|
|
5
|
+
electronClipboard = {
|
|
6
|
+
writeText: () => {}
|
|
7
|
+
};
|
|
8
|
+
}
|
|
2
9
|
const { openApp } = require('../Automation_Layer/open_app');
|
|
3
10
|
const { openWebsite, openSearch } = require('../Automation_Layer/open_website');
|
|
4
11
|
const { performWebAutomation } = require('../Automation_Layer/browser_automation');
|
|
5
12
|
const { createFolder, openFile, deleteFile, findPath } = require('../Automation_Layer/file_operations');
|
|
6
13
|
const { indexFile, indexFolder } = require('../AI_Brain/knowledge_base');
|
|
14
|
+
const { getSystemInfo, getWeather } = require('./system_info');
|
|
7
15
|
const pluginManager = require('../Plugins/plugin_manager');
|
|
8
16
|
const mcpManager = require('../Plugins/mcp_manager');
|
|
9
|
-
const granularAutomation = require('./granular_automation');
|
|
10
17
|
const SystemAutomation = require('./system_automation');
|
|
11
18
|
const safetyManager = require('./safety_manager');
|
|
19
|
+
const toolRegistry = require('./tool_registry');
|
|
20
|
+
const os = require('os');
|
|
21
|
+
const path = require('path');
|
|
12
22
|
|
|
13
23
|
async function executeAction(action, options = {}) {
|
|
14
|
-
|
|
24
|
+
if (process.env.MINT_DEBUG === '1') {
|
|
25
|
+
console.log("Executing action:", action);
|
|
26
|
+
}
|
|
27
|
+
toolRegistry.validateToolInput(action.type, action);
|
|
15
28
|
const clipboard = options.clipboard || electronClipboard;
|
|
16
29
|
const safety = safetyManager.assertActionAllowed(action, {
|
|
30
|
+
allowApproval: options.allowApproval === true,
|
|
17
31
|
allowDangerous: options.allowDangerous === true
|
|
18
32
|
});
|
|
19
33
|
safetyManager.appendActionLog({
|
|
@@ -21,7 +35,7 @@ async function executeAction(action, options = {}) {
|
|
|
21
35
|
action: action.type,
|
|
22
36
|
target: action.target || action.path || '',
|
|
23
37
|
tier: safety.tier,
|
|
24
|
-
approved: options.allowDangerous === true || safety.tier
|
|
38
|
+
approved: options.allowApproval === true || options.allowDangerous === true || safety.tier === safetyManager.TIERS.SAFE
|
|
25
39
|
});
|
|
26
40
|
|
|
27
41
|
switch (action.type) {
|
|
@@ -37,17 +51,23 @@ async function executeAction(action, options = {}) {
|
|
|
37
51
|
case 'web_automation':
|
|
38
52
|
return await performWebAutomation(action.target);
|
|
39
53
|
case 'create_folder':
|
|
54
|
+
safetyManager.assertPathCapability(action.target, 'write', {
|
|
55
|
+
defaultBase: path.join(os.homedir(), 'Desktop')
|
|
56
|
+
});
|
|
40
57
|
createFolder(action.target);
|
|
41
58
|
break;
|
|
42
59
|
case 'open_file': {
|
|
60
|
+
safetyManager.assertPathCapability(action.target, 'read');
|
|
43
61
|
const fileRes = await openFile(action.target);
|
|
44
62
|
return fileRes || `Successfully opened file: ${action.target} ✅`;
|
|
45
63
|
}
|
|
46
64
|
case 'open_folder': {
|
|
65
|
+
safetyManager.assertPathCapability(action.target, 'read');
|
|
47
66
|
const folderRes = await openFile(action.target);
|
|
48
67
|
return folderRes || `Successfully opened folder: ${action.target} ✅`;
|
|
49
68
|
}
|
|
50
69
|
case 'delete_file':
|
|
70
|
+
safetyManager.assertPathCapability(action.target, 'write');
|
|
51
71
|
await deleteFile(action.target);
|
|
52
72
|
break;
|
|
53
73
|
case 'find_path':
|
|
@@ -56,21 +76,33 @@ async function executeAction(action, options = {}) {
|
|
|
56
76
|
clipboard.writeText(action.target);
|
|
57
77
|
break;
|
|
58
78
|
case 'learn_file':
|
|
79
|
+
safetyManager.assertPathCapability(action.target, 'read');
|
|
59
80
|
return await indexFile(action.target);
|
|
60
81
|
case 'learn_folder':
|
|
82
|
+
safetyManager.assertPathCapability(action.target, 'read');
|
|
61
83
|
return await indexFolder(action.target);
|
|
84
|
+
case 'system_info':
|
|
85
|
+
return await handleSystemInfo(action.target);
|
|
62
86
|
case 'mcp_tool': {
|
|
63
87
|
const mcpResult = await mcpManager.callTool(action.server, action.target, action.args);
|
|
64
88
|
return JSON.stringify(mcpResult.content);
|
|
65
89
|
}
|
|
66
|
-
case 'mouse_move':
|
|
90
|
+
case 'mouse_move': {
|
|
91
|
+
const granularAutomation = require('./granular_automation');
|
|
67
92
|
return await granularAutomation.mouseMove(action.x, action.y);
|
|
68
|
-
|
|
93
|
+
}
|
|
94
|
+
case 'mouse_click': {
|
|
95
|
+
const granularAutomation = require('./granular_automation');
|
|
69
96
|
return await granularAutomation.mouseClick(action.x, action.y, action.button || 1);
|
|
70
|
-
|
|
97
|
+
}
|
|
98
|
+
case 'type_text': {
|
|
99
|
+
const granularAutomation = require('./granular_automation');
|
|
71
100
|
return await granularAutomation.typeText(action.target);
|
|
72
|
-
|
|
101
|
+
}
|
|
102
|
+
case 'key_tap': {
|
|
103
|
+
const granularAutomation = require('./granular_automation');
|
|
73
104
|
return await granularAutomation.keyTap(action.target);
|
|
105
|
+
}
|
|
74
106
|
case 'plugin':
|
|
75
107
|
return await pluginManager.executePlugin(action.pluginName, action.target);
|
|
76
108
|
case 'system_automation':
|
|
@@ -80,10 +112,27 @@ async function executeAction(action, options = {}) {
|
|
|
80
112
|
}
|
|
81
113
|
}
|
|
82
114
|
|
|
115
|
+
async function handleSystemInfo(target = '') {
|
|
116
|
+
const query = String(target || '').trim();
|
|
117
|
+
if (query) {
|
|
118
|
+
const weather = await getWeather(query);
|
|
119
|
+
return JSON.stringify({
|
|
120
|
+
type: 'weather',
|
|
121
|
+
target: query,
|
|
122
|
+
...weather
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return JSON.stringify({
|
|
126
|
+
type: 'system_info',
|
|
127
|
+
data: getSystemInfo()
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
83
131
|
async function executeFindPath(action) {
|
|
84
132
|
const result = findPath(action.target, {
|
|
85
133
|
type: action.pathType,
|
|
86
|
-
maxResults: 10
|
|
134
|
+
maxResults: 10,
|
|
135
|
+
roots: safetyManager.getAllowedRoots('read')
|
|
87
136
|
});
|
|
88
137
|
if (!result.success) {
|
|
89
138
|
return result.message;
|
|
@@ -126,4 +175,4 @@ async function handleSystemAutomation(target) {
|
|
|
126
175
|
}
|
|
127
176
|
}
|
|
128
177
|
|
|
129
|
-
module.exports = { executeAction, handleSystemAutomation };
|
|
178
|
+
module.exports = { executeAction, handleSystemAutomation, handleSystemInfo };
|
|
@@ -99,7 +99,37 @@ const DEFAULT_CONFIG = {
|
|
|
99
99
|
enableAgentCollaboration: false,
|
|
100
100
|
enableAutoUpdate: true,
|
|
101
101
|
autoUpdateCheckIntervalHours: 24,
|
|
102
|
-
lastUpdateCheckAt: ''
|
|
102
|
+
lastUpdateCheckAt: '',
|
|
103
|
+
safetyEnabled: true,
|
|
104
|
+
sandboxMode: 'prefer', // off | prefer | enforce
|
|
105
|
+
sandboxCommand: process.platform === 'darwin' ? 'sandbox-exec' : process.platform === 'linux' ? 'bwrap' : '',
|
|
106
|
+
allowedReadPaths: [
|
|
107
|
+
os.homedir(),
|
|
108
|
+
process.cwd(),
|
|
109
|
+
path.join(os.homedir(), 'Desktop'),
|
|
110
|
+
path.join(os.homedir(), 'Documents'),
|
|
111
|
+
path.join(os.homedir(), 'Downloads'),
|
|
112
|
+
path.join(os.homedir(), 'Pictures'),
|
|
113
|
+
path.join(os.homedir(), 'Music'),
|
|
114
|
+
path.join(os.homedir(), 'Videos')
|
|
115
|
+
],
|
|
116
|
+
allowedWritePaths: [
|
|
117
|
+
os.homedir(),
|
|
118
|
+
process.cwd(),
|
|
119
|
+
path.join(os.homedir(), 'Desktop'),
|
|
120
|
+
path.join(os.homedir(), 'Documents'),
|
|
121
|
+
path.join(os.homedir(), 'Downloads'),
|
|
122
|
+
path.join(os.homedir(), 'Pictures'),
|
|
123
|
+
path.join(os.homedir(), 'Music'),
|
|
124
|
+
path.join(os.homedir(), 'Videos')
|
|
125
|
+
],
|
|
126
|
+
blockedPaths: [
|
|
127
|
+
path.join(os.homedir(), '.ssh'),
|
|
128
|
+
path.join(os.homedir(), '.gnupg'),
|
|
129
|
+
path.join(os.homedir(), '.config', 'mint', 'mint-config.json'),
|
|
130
|
+
path.join(os.homedir(), '.mint', 'mint-config.json')
|
|
131
|
+
],
|
|
132
|
+
blockedFileNames: ['.env', 'id_rsa', 'id_ed25519']
|
|
103
133
|
};
|
|
104
134
|
|
|
105
135
|
|
|
@@ -1,88 +1,157 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { execFile, spawnSync } = require('child_process');
|
|
2
2
|
const { screen } = require('electron');
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
function commandExists(command) {
|
|
5
|
+
const lookup = process.platform === 'win32' ? 'where' : 'which';
|
|
6
|
+
const result = spawnSync(lookup, [command], { encoding: 'utf8', shell: false });
|
|
7
|
+
return result.status === 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function run(command, args = []) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
execFile(command, args, (err, stdout, stderr) => {
|
|
13
|
+
if (err) {
|
|
14
|
+
err.stderr = stderr;
|
|
15
|
+
reject(err);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
resolve(stdout);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function unsupported(feature) {
|
|
24
|
+
throw new Error(`${feature} is not supported on ${process.platform} by the current input automation provider.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function escapePowerShellSingleQuoted(value) {
|
|
28
|
+
return String(value || '').replace(/'/g, "''");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function keyToMacKey(key) {
|
|
32
|
+
const value = String(key || '').trim();
|
|
33
|
+
const map = {
|
|
34
|
+
Enter: 'return',
|
|
35
|
+
Return: 'return',
|
|
36
|
+
Escape: 'escape',
|
|
37
|
+
Esc: 'escape',
|
|
38
|
+
Space: 'space',
|
|
39
|
+
Backspace: 'delete',
|
|
40
|
+
Delete: 'forward delete',
|
|
41
|
+
Tab: 'tab'
|
|
42
|
+
};
|
|
43
|
+
return map[value] || value;
|
|
44
|
+
}
|
|
45
|
+
|
|
8
46
|
class GranularAutomation {
|
|
9
47
|
constructor() {
|
|
10
|
-
this.screenWidth = 1920;
|
|
48
|
+
this.screenWidth = 1920;
|
|
11
49
|
this.screenHeight = 1080;
|
|
12
50
|
this.updateScreenSize();
|
|
13
51
|
}
|
|
14
52
|
|
|
15
53
|
updateScreenSize() {
|
|
16
54
|
try {
|
|
17
|
-
// In Electron main process, we can use the screen module
|
|
18
55
|
const primaryDisplay = screen.getPrimaryDisplay();
|
|
19
56
|
if (primaryDisplay && primaryDisplay.size) {
|
|
20
57
|
this.screenWidth = primaryDisplay.size.width;
|
|
21
58
|
this.screenHeight = primaryDisplay.size.height;
|
|
22
|
-
console.log(`[Automation] Screen detected: ${this.screenWidth}x${this.screenHeight}`);
|
|
23
59
|
}
|
|
24
|
-
} catch (
|
|
25
|
-
//
|
|
26
|
-
exec('xdpyinfo | grep dimensions', (err, stdout) => {
|
|
27
|
-
if (!err && stdout) {
|
|
28
|
-
const match = stdout.match(/(\d+)x(\d+) pixels/);
|
|
29
|
-
if (match) {
|
|
30
|
-
this.screenWidth = parseInt(match[1]);
|
|
31
|
-
this.screenHeight = parseInt(match[2]);
|
|
32
|
-
console.log(`[Automation] Screen detected via xdpyinfo: ${this.screenWidth}x${this.screenHeight}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
60
|
+
} catch (_) {
|
|
61
|
+
// Electron screen can be unavailable in CLI-only contexts.
|
|
36
62
|
}
|
|
37
63
|
}
|
|
38
64
|
|
|
39
65
|
scaleX(x) {
|
|
40
|
-
return Math.round((x / 1000) * this.screenWidth);
|
|
66
|
+
return Math.round((Number(x) / 1000) * this.screenWidth);
|
|
41
67
|
}
|
|
42
68
|
|
|
43
69
|
scaleY(y) {
|
|
44
|
-
return Math.round((y / 1000) * this.screenHeight);
|
|
70
|
+
return Math.round((Number(y) / 1000) * this.screenHeight);
|
|
45
71
|
}
|
|
46
72
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
console.error(`[Automation] xdotool error: ${stderr}`);
|
|
52
|
-
reject(err);
|
|
53
|
-
} else {
|
|
54
|
-
resolve(stdout);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
73
|
+
provider() {
|
|
74
|
+
if (process.platform === 'darwin') return macProvider;
|
|
75
|
+
if (process.platform === 'win32') return windowsProvider;
|
|
76
|
+
return linuxProvider;
|
|
58
77
|
}
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const sy = this.scaleY(y);
|
|
63
|
-
console.log(`[Automation] Moving mouse to ${sx}, ${sy}`);
|
|
64
|
-
return this.run(`xdotool mousemove ${sx} ${sy}`);
|
|
79
|
+
mouseMove(x, y) {
|
|
80
|
+
return this.provider().mouseMove(this.scaleX(x), this.scaleY(y));
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const sy = this.scaleY(y);
|
|
70
|
-
console.log(`[Automation] Clicking ${button} at ${sx}, ${sy}`);
|
|
71
|
-
// move first then click to be safe
|
|
72
|
-
return this.run(`xdotool mousemove ${sx} ${sy} click ${button}`);
|
|
83
|
+
mouseClick(x, y, button = 1) {
|
|
84
|
+
return this.provider().mouseClick(this.scaleX(x), this.scaleY(y), button);
|
|
73
85
|
}
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// Escape double quotes for shell
|
|
78
|
-
const escaped = text.replace(/"/g, '\\"');
|
|
79
|
-
return this.run(`xdotool type "${escaped}"`);
|
|
87
|
+
typeText(text) {
|
|
88
|
+
return this.provider().typeText(String(text || ''));
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return this.run(`xdotool key "${key}"`);
|
|
91
|
+
keyTap(key) {
|
|
92
|
+
return this.provider().keyTap(String(key || ''));
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
|
|
96
|
+
const linuxProvider = {
|
|
97
|
+
mouseMove: (x, y) => run('xdotool', ['mousemove', String(x), String(y)]),
|
|
98
|
+
mouseClick: (x, y, button = 1) => run('xdotool', ['mousemove', String(x), String(y), 'click', String(button)]),
|
|
99
|
+
typeText: (text) => run('xdotool', ['type', text]),
|
|
100
|
+
keyTap: (key) => run('xdotool', ['key', key])
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const macProvider = {
|
|
104
|
+
mouseMove(x, y) {
|
|
105
|
+
if (!commandExists('cliclick')) return unsupported('Mouse move');
|
|
106
|
+
return run('cliclick', [`m:${x},${y}`]);
|
|
107
|
+
},
|
|
108
|
+
mouseClick(x, y) {
|
|
109
|
+
if (!commandExists('cliclick')) return unsupported('Mouse click');
|
|
110
|
+
return run('cliclick', [`c:${x},${y}`]);
|
|
111
|
+
},
|
|
112
|
+
typeText(text) {
|
|
113
|
+
if (commandExists('cliclick')) return run('cliclick', [`t:${text}`]);
|
|
114
|
+
return run('osascript', ['-e', `tell application "System Events" to keystroke ${JSON.stringify(text)}`]);
|
|
115
|
+
},
|
|
116
|
+
keyTap(key) {
|
|
117
|
+
const macKey = keyToMacKey(key);
|
|
118
|
+
if (commandExists('cliclick')) return run('cliclick', [`kp:${macKey}`]);
|
|
119
|
+
if (macKey.length === 1) {
|
|
120
|
+
return run('osascript', ['-e', `tell application "System Events" to keystroke ${JSON.stringify(macKey)}`]);
|
|
121
|
+
}
|
|
122
|
+
return unsupported('Special key tap without cliclick');
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const windowsProvider = {
|
|
127
|
+
mouseMove(x, y) {
|
|
128
|
+
const script = `[void][Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${x}, ${y})`;
|
|
129
|
+
return run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
130
|
+
},
|
|
131
|
+
mouseClick(x, y, button = 1) {
|
|
132
|
+
const down = Number(button) === 2 ? '0x0008' : '0x0002';
|
|
133
|
+
const up = Number(button) === 2 ? '0x0010' : '0x0004';
|
|
134
|
+
const script = [
|
|
135
|
+
"Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern bool SetCursorPos(int X,int Y); [DllImport(\"user32.dll\")] public static extern void mouse_event(int dwFlags,int dx,int dy,int dwData,int dwExtraInfo);' -Name NativeMouse -Namespace Mint;",
|
|
136
|
+
`[Mint.NativeMouse]::SetCursorPos(${x}, ${y}) | Out-Null;`,
|
|
137
|
+
`[Mint.NativeMouse]::mouse_event(${down},0,0,0,0);`,
|
|
138
|
+
`[Mint.NativeMouse]::mouse_event(${up},0,0,0,0);`
|
|
139
|
+
].join(' ');
|
|
140
|
+
return run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
141
|
+
},
|
|
142
|
+
typeText(text) {
|
|
143
|
+
const safe = escapePowerShellSingleQuoted(text);
|
|
144
|
+
const script = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${safe}')`;
|
|
145
|
+
return run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
146
|
+
},
|
|
147
|
+
keyTap(key) {
|
|
148
|
+
const safe = escapePowerShellSingleQuoted(key);
|
|
149
|
+
const script = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{${safe}}')`;
|
|
150
|
+
return run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const instance = new GranularAutomation();
|
|
155
|
+
instance._providers = { linuxProvider, macProvider, windowsProvider, commandExists };
|
|
156
|
+
|
|
157
|
+
module.exports = instance;
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
const { BrowserWindow, desktopCapturer, screen, powerMonitor } = require('electron');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { analyzeAndSuggest } = require('../AI_Brain/proactive_engine');
|
|
4
|
-
const { recordBehavior, getBehaviorSummary } = require('../AI_Brain/behavior_memory');
|
|
5
3
|
|
|
6
4
|
const IDLE_THRESHOLD_SEC = 300;
|
|
7
5
|
|
|
6
|
+
let proactiveEngine = null;
|
|
7
|
+
function getProactiveEngine() {
|
|
8
|
+
if (!proactiveEngine) {
|
|
9
|
+
proactiveEngine = require('../AI_Brain/proactive_engine');
|
|
10
|
+
}
|
|
11
|
+
return proactiveEngine;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let behaviorMemory = null;
|
|
15
|
+
function getBehaviorMemory() {
|
|
16
|
+
if (!behaviorMemory) {
|
|
17
|
+
behaviorMemory = require('../AI_Brain/behavior_memory');
|
|
18
|
+
}
|
|
19
|
+
return behaviorMemory;
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
function createProactiveLoop({ app, projectRoot, readConfig, getMainWindow }) {
|
|
9
23
|
let proactiveGlowWindow = null;
|
|
10
24
|
let proactiveIntervalHandle = null;
|
|
@@ -29,6 +43,8 @@ function createProactiveLoop({ app, projectRoot, readConfig, getMainWindow }) {
|
|
|
29
43
|
if (!primarySource || !primarySource.thumbnail) return;
|
|
30
44
|
|
|
31
45
|
const base64Image = primarySource.thumbnail.toJPEG(60).toString('base64');
|
|
46
|
+
const { analyzeAndSuggest } = getProactiveEngine();
|
|
47
|
+
const { recordBehavior, getBehaviorSummary } = getBehaviorMemory();
|
|
32
48
|
const result = await analyzeAndSuggest(base64Image, getBehaviorSummary());
|
|
33
49
|
|
|
34
50
|
if (result && result.message && Array.isArray(result.suggestions)) {
|
|
@@ -130,7 +146,7 @@ function createProactiveLoop({ app, projectRoot, readConfig, getMainWindow }) {
|
|
|
130
146
|
stop,
|
|
131
147
|
startIdleWatcher,
|
|
132
148
|
isRunning: () => Boolean(proactiveIntervalHandle),
|
|
133
|
-
recordBehavior
|
|
149
|
+
recordBehavior: (...args) => getBehaviorMemory().recordBehavior(...args)
|
|
134
150
|
};
|
|
135
151
|
}
|
|
136
152
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const { readConfig } = require('./config_manager');
|
|
4
5
|
|
|
5
6
|
const TIERS = Object.freeze({
|
|
6
7
|
SAFE: 'safe',
|
|
@@ -40,6 +41,7 @@ const SAFE_ACTIONS = new Set([
|
|
|
40
41
|
'clipboard_write',
|
|
41
42
|
'learn_file',
|
|
42
43
|
'learn_folder',
|
|
44
|
+
'system_info',
|
|
43
45
|
'mcp_tool',
|
|
44
46
|
'mouse_move',
|
|
45
47
|
'mouse_click',
|
|
@@ -56,6 +58,103 @@ function normalizeCommand(command) {
|
|
|
56
58
|
return String(command || '').replace(/\s+/g, ' ').trim();
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
function expandHome(targetPath) {
|
|
62
|
+
const value = String(targetPath || '');
|
|
63
|
+
if (value === '~') return os.homedir();
|
|
64
|
+
if (value.startsWith('~/')) return path.join(os.homedir(), value.slice(2));
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeRootList(paths) {
|
|
69
|
+
return (Array.isArray(paths) ? paths : [])
|
|
70
|
+
.filter(Boolean)
|
|
71
|
+
.map((entry) => path.resolve(expandHome(entry)));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getPolicy(config = readConfig()) {
|
|
75
|
+
const enabled = config.safetyEnabled !== false;
|
|
76
|
+
const fallbackRead = [
|
|
77
|
+
os.homedir(),
|
|
78
|
+
process.cwd(),
|
|
79
|
+
path.join(os.homedir(), 'Desktop'),
|
|
80
|
+
path.join(os.homedir(), 'Documents'),
|
|
81
|
+
path.join(os.homedir(), 'Downloads'),
|
|
82
|
+
path.join(os.homedir(), 'Pictures'),
|
|
83
|
+
path.join(os.homedir(), 'Music'),
|
|
84
|
+
path.join(os.homedir(), 'Videos')
|
|
85
|
+
];
|
|
86
|
+
const fallbackWrite = [
|
|
87
|
+
os.homedir(),
|
|
88
|
+
process.cwd(),
|
|
89
|
+
path.join(os.homedir(), 'Desktop'),
|
|
90
|
+
path.join(os.homedir(), 'Documents'),
|
|
91
|
+
path.join(os.homedir(), 'Downloads'),
|
|
92
|
+
path.join(os.homedir(), 'Pictures'),
|
|
93
|
+
path.join(os.homedir(), 'Music'),
|
|
94
|
+
path.join(os.homedir(), 'Videos')
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
enabled,
|
|
99
|
+
sandboxMode: ['off', 'prefer', 'enforce'].includes(config.sandboxMode) ? config.sandboxMode : 'prefer',
|
|
100
|
+
sandboxCommand: config.sandboxCommand || (process.platform === 'darwin' ? 'sandbox-exec' : process.platform === 'linux' ? 'bwrap' : ''),
|
|
101
|
+
allowedReadPaths: normalizeRootList(config.allowedReadPaths && config.allowedReadPaths.length ? config.allowedReadPaths : fallbackRead),
|
|
102
|
+
allowedWritePaths: normalizeRootList(config.allowedWritePaths && config.allowedWritePaths.length ? config.allowedWritePaths : fallbackWrite),
|
|
103
|
+
blockedPaths: normalizeRootList(config.blockedPaths || []),
|
|
104
|
+
blockedFileNames: new Set(Array.isArray(config.blockedFileNames) ? config.blockedFileNames : ['.env', 'id_rsa', 'id_ed25519'])
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isPathWithin(root, targetPath) {
|
|
109
|
+
const relative = path.relative(root, targetPath);
|
|
110
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function resolveCapabilityPath(targetPath, options = {}) {
|
|
114
|
+
if (!targetPath) throw new Error('Target path is required.');
|
|
115
|
+
|
|
116
|
+
const expanded = expandHome(targetPath);
|
|
117
|
+
if (path.isAbsolute(expanded)) return path.resolve(expanded);
|
|
118
|
+
|
|
119
|
+
const firstPart = String(expanded).split(/[/\\]/)[0];
|
|
120
|
+
const commonHomeFolders = new Set(['Desktop', 'Documents', 'Downloads', 'Pictures', 'Music', 'Videos']);
|
|
121
|
+
if (commonHomeFolders.has(firstPart)) {
|
|
122
|
+
return path.resolve(os.homedir(), expanded);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const base = options.defaultBase || process.cwd();
|
|
126
|
+
return path.resolve(base, expanded);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assertPathCapability(targetPath, capability = 'read', options = {}) {
|
|
130
|
+
const policy = getPolicy(options.config);
|
|
131
|
+
const resolved = resolveCapabilityPath(targetPath, options);
|
|
132
|
+
|
|
133
|
+
if (!policy.enabled) return resolved;
|
|
134
|
+
|
|
135
|
+
if (policy.blockedFileNames.has(path.basename(resolved))) {
|
|
136
|
+
throw new Error(`Blocked ${capability} access to sensitive file name: ${path.basename(resolved)}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const blockedRoot = policy.blockedPaths.find((root) => isPathWithin(root, resolved));
|
|
140
|
+
if (blockedRoot) {
|
|
141
|
+
throw new Error(`Blocked ${capability} access to protected path: ${resolved}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const allowedRoots = capability === 'write' ? policy.allowedWritePaths : policy.allowedReadPaths;
|
|
145
|
+
const allowed = allowedRoots.some((root) => isPathWithin(root, resolved));
|
|
146
|
+
if (!allowed) {
|
|
147
|
+
throw new Error(`Path ${capability} denied by capability policy: ${resolved}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return resolved;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getAllowedRoots(capability = 'read', options = {}) {
|
|
154
|
+
const policy = getPolicy(options.config);
|
|
155
|
+
return capability === 'write' ? policy.allowedWritePaths : policy.allowedReadPaths;
|
|
156
|
+
}
|
|
157
|
+
|
|
59
158
|
function classifyShellCommand(command) {
|
|
60
159
|
const normalized = normalizeCommand(command);
|
|
61
160
|
if (!normalized) {
|
|
@@ -105,6 +204,7 @@ function classifyAction(action = {}) {
|
|
|
105
204
|
function assertActionAllowed(action, options = {}) {
|
|
106
205
|
const classification = classifyAction(action);
|
|
107
206
|
const allowDangerous = options.allowDangerous === true;
|
|
207
|
+
const allowApproval = options.allowApproval === true;
|
|
108
208
|
|
|
109
209
|
if (classification.tier === TIERS.BLOCKED) {
|
|
110
210
|
throw new Error(`Blocked action (${classification.reason}): ${action.type}`);
|
|
@@ -114,6 +214,10 @@ function assertActionAllowed(action, options = {}) {
|
|
|
114
214
|
throw new Error(`Dangerous action requires explicit permission (${classification.reason}): ${action.type}`);
|
|
115
215
|
}
|
|
116
216
|
|
|
217
|
+
if (classification.tier === TIERS.APPROVAL && !allowApproval) {
|
|
218
|
+
throw new Error(`Action requires approval (${classification.reason}): ${action.type}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
117
221
|
return classification;
|
|
118
222
|
}
|
|
119
223
|
|
|
@@ -156,10 +260,14 @@ function appendActionLog(entry, options = {}) {
|
|
|
156
260
|
|
|
157
261
|
module.exports = {
|
|
158
262
|
TIERS,
|
|
263
|
+
getPolicy,
|
|
264
|
+
getAllowedRoots,
|
|
159
265
|
classifyShellCommand,
|
|
160
266
|
assertShellCommandAllowed,
|
|
161
267
|
classifyAction,
|
|
162
268
|
assertActionAllowed,
|
|
269
|
+
assertPathCapability,
|
|
270
|
+
resolveCapabilityPath,
|
|
163
271
|
resolveWithinRoot,
|
|
164
272
|
appendActionLog
|
|
165
273
|
};
|