380 lines
14 KiB
JavaScript
Executable File
380 lines
14 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
|
||
/**
|
||
* 自动化功能诊断工具
|
||
* 详细检测和诊断自动粘贴功能的问题
|
||
*/
|
||
|
||
const { spawn } = require('child_process');
|
||
|
||
class AutomationDiagnostic {
|
||
constructor() {
|
||
this.tests = [];
|
||
}
|
||
|
||
async runDiagnostic() {
|
||
console.log('🔍 自动化功能诊断工具\n');
|
||
|
||
if (process.platform !== 'darwin') {
|
||
console.log('❌ 此诊断工具仅适用于 macOS');
|
||
return;
|
||
}
|
||
|
||
// 运行所有测试
|
||
await this.testBasicAppleScript();
|
||
await this.testSystemEventsAccess();
|
||
await this.testKeystrokePermission();
|
||
await this.testApplicationDetection();
|
||
await this.testClipboardAccess();
|
||
await this.testDetailedPermissions();
|
||
|
||
// 显示总结
|
||
this.showSummary();
|
||
await this.provideSolutions();
|
||
}
|
||
|
||
async testBasicAppleScript() {
|
||
console.log('1. 📋 测试基本 AppleScript 执行...');
|
||
try {
|
||
const result = await this.runAppleScript('return "AppleScript works"');
|
||
if (result === 'AppleScript works') {
|
||
console.log(' ✅ 基本 AppleScript 执行正常');
|
||
this.tests.push({ name: 'AppleScript', status: 'pass' });
|
||
} else {
|
||
console.log(' ❌ AppleScript 返回异常结果');
|
||
this.tests.push({ name: 'AppleScript', status: 'fail', error: 'Unexpected result' });
|
||
}
|
||
} catch (error) {
|
||
console.log(' ❌ AppleScript 执行失败:', error.message);
|
||
this.tests.push({ name: 'AppleScript', status: 'fail', error: error.message });
|
||
}
|
||
console.log('');
|
||
}
|
||
|
||
async testSystemEventsAccess() {
|
||
console.log('2. 🖥️ 测试 System Events 访问...');
|
||
try {
|
||
const script = `
|
||
tell application "System Events"
|
||
return name of first application process whose frontmost is true
|
||
end tell
|
||
`;
|
||
const result = await this.runAppleScript(script);
|
||
if (result && result !== 'permission_denied') {
|
||
console.log(` ✅ 可以访问 System Events,当前前台应用: ${result}`);
|
||
this.tests.push({ name: 'System Events', status: 'pass', data: result });
|
||
} else {
|
||
console.log(' ❌ System Events 访问被拒绝');
|
||
this.tests.push({ name: 'System Events', status: 'fail', error: 'Access denied' });
|
||
}
|
||
} catch (error) {
|
||
console.log(' ❌ System Events 访问失败:', error.message);
|
||
this.tests.push({ name: 'System Events', status: 'fail', error: error.message });
|
||
}
|
||
console.log('');
|
||
}
|
||
|
||
async testKeystrokePermission() {
|
||
console.log('3. ⌨️ 测试按键发送权限...');
|
||
try {
|
||
const script = `
|
||
tell application "System Events"
|
||
try
|
||
keystroke "test" without key down
|
||
return "keystroke_success"
|
||
on error errorMessage
|
||
return "keystroke_failed: " & errorMessage
|
||
end try
|
||
end tell
|
||
`;
|
||
const result = await this.runAppleScript(script);
|
||
if (result === 'keystroke_success') {
|
||
console.log(' ✅ 按键发送权限正常');
|
||
this.tests.push({ name: 'Keystroke', status: 'pass' });
|
||
} else {
|
||
console.log(` ❌ 按键发送失败: ${result}`);
|
||
this.tests.push({ name: 'Keystroke', status: 'fail', error: result });
|
||
}
|
||
} catch (error) {
|
||
console.log(' ❌ 按键测试异常:', error.message);
|
||
this.tests.push({ name: 'Keystroke', status: 'fail', error: error.message });
|
||
}
|
||
console.log('');
|
||
}
|
||
|
||
async testApplicationDetection() {
|
||
console.log('4. 🔍 测试应用程序检测...');
|
||
try {
|
||
const script = `
|
||
tell application "System Events"
|
||
set appList to {"Terminal", "iTerm2", "iTerm", "Visual Studio Code", "Code", "Cursor", "Claude Code"}
|
||
set foundApps to {}
|
||
repeat with appName in appList
|
||
try
|
||
if application process appName exists then
|
||
set foundApps to foundApps & {appName}
|
||
end if
|
||
end try
|
||
end repeat
|
||
return foundApps as string
|
||
end tell
|
||
`;
|
||
const result = await this.runAppleScript(script);
|
||
if (result) {
|
||
console.log(` ✅ 检测到以下应用: ${result}`);
|
||
this.tests.push({ name: 'App Detection', status: 'pass', data: result });
|
||
} else {
|
||
console.log(' ⚠️ 未检测到目标应用程序');
|
||
this.tests.push({ name: 'App Detection', status: 'warn', error: 'No target apps found' });
|
||
}
|
||
} catch (error) {
|
||
console.log(' ❌ 应用检测失败:', error.message);
|
||
this.tests.push({ name: 'App Detection', status: 'fail', error: error.message });
|
||
}
|
||
console.log('');
|
||
}
|
||
|
||
async testClipboardAccess() {
|
||
console.log('5. 📋 测试剪贴板访问...');
|
||
try {
|
||
// 测试写入剪贴板
|
||
await this.setClipboard('test_clipboard_content');
|
||
const content = await this.getClipboard();
|
||
|
||
if (content.includes('test_clipboard_content')) {
|
||
console.log(' ✅ 剪贴板读写正常');
|
||
this.tests.push({ name: 'Clipboard', status: 'pass' });
|
||
} else {
|
||
console.log(' ❌ 剪贴板内容不匹配');
|
||
this.tests.push({ name: 'Clipboard', status: 'fail', error: 'Content mismatch' });
|
||
}
|
||
} catch (error) {
|
||
console.log(' ❌ 剪贴板访问失败:', error.message);
|
||
this.tests.push({ name: 'Clipboard', status: 'fail', error: error.message });
|
||
}
|
||
console.log('');
|
||
}
|
||
|
||
async testDetailedPermissions() {
|
||
console.log('6. 🔐 详细权限检查...');
|
||
|
||
try {
|
||
// 检查当前运行的进程
|
||
const whoami = await this.runCommand('whoami');
|
||
console.log(` 👤 当前用户: ${whoami}`);
|
||
|
||
// 检查终端应用
|
||
const terminal = process.env.TERM_PROGRAM || 'Unknown';
|
||
console.log(` 💻 终端程序: ${terminal}`);
|
||
|
||
// 检查是否在 IDE 中运行
|
||
const isInIDE = process.env.VSCODE_PID || process.env.CURSOR_SESSION_ID || process.env.JB_IDE_PID;
|
||
if (isInIDE) {
|
||
console.log(' 🔧 检测到在 IDE 中运行');
|
||
}
|
||
|
||
// 尝试获取更详细的权限信息
|
||
const script = `
|
||
tell application "System Events"
|
||
try
|
||
set frontApp to name of first application process whose frontmost is true
|
||
set allApps to name of every application process
|
||
return "Front: " & frontApp & ", All: " & (count of allApps)
|
||
on error errorMsg
|
||
return "Error: " & errorMsg
|
||
end try
|
||
end tell
|
||
`;
|
||
|
||
const permResult = await this.runAppleScript(script);
|
||
console.log(` 📊 权限测试结果: ${permResult}`);
|
||
|
||
this.tests.push({
|
||
name: 'Detailed Permissions',
|
||
status: 'info',
|
||
data: { user: whoami, terminal, result: permResult }
|
||
});
|
||
|
||
} catch (error) {
|
||
console.log(' ❌ 详细检查失败:', error.message);
|
||
this.tests.push({ name: 'Detailed Permissions', status: 'fail', error: error.message });
|
||
}
|
||
console.log('');
|
||
}
|
||
|
||
showSummary() {
|
||
console.log('📊 诊断结果总结:\n');
|
||
|
||
const passed = this.tests.filter(t => t.status === 'pass').length;
|
||
const failed = this.tests.filter(t => t.status === 'fail').length;
|
||
const warned = this.tests.filter(t => t.status === 'warn').length;
|
||
|
||
this.tests.forEach(test => {
|
||
const icon = test.status === 'pass' ? '✅' :
|
||
test.status === 'fail' ? '❌' :
|
||
test.status === 'warn' ? '⚠️' : 'ℹ️';
|
||
console.log(`${icon} ${test.name}`);
|
||
if (test.error) {
|
||
console.log(` 错误: ${test.error}`);
|
||
}
|
||
if (test.data) {
|
||
console.log(` 数据: ${test.data}`);
|
||
}
|
||
});
|
||
|
||
console.log(`\n📈 总计: ${passed} 通过, ${failed} 失败, ${warned} 警告\n`);
|
||
}
|
||
|
||
async provideSolutions() {
|
||
const keystrokeTest = this.tests.find(t => t.name === 'Keystroke');
|
||
const systemEventsTest = this.tests.find(t => t.name === 'System Events');
|
||
|
||
console.log('💡 解决方案建议:\n');
|
||
|
||
if (keystrokeTest && keystrokeTest.status === 'fail') {
|
||
console.log('🔧 按键发送问题解决方案:');
|
||
console.log(' 1. 打开 系统偏好设置 > 安全性与隐私 > 隐私 > 辅助功能');
|
||
console.log(' 2. 移除并重新添加你的终端应用 (Terminal/iTerm2/VS Code)');
|
||
console.log(' 3. 确保勾选框已被选中');
|
||
console.log(' 4. 重启终端应用');
|
||
console.log('');
|
||
}
|
||
|
||
if (systemEventsTest && systemEventsTest.status === 'fail') {
|
||
console.log('🔧 System Events 访问问题解决方案:');
|
||
console.log(' 1. 检查 系统偏好设置 > 安全性与隐私 > 隐私 > 自动化');
|
||
console.log(' 2. 确保你的终端应用下勾选了 "System Events"');
|
||
console.log(' 3. 如果没有看到你的应用,先运行一次自动化脚本触发权限请求');
|
||
console.log('');
|
||
}
|
||
|
||
console.log('🚀 额外建议:');
|
||
console.log(' • 尝试完全退出并重启终端应用');
|
||
console.log(' • 在 Terminal 中运行而不是在 IDE 集成终端中');
|
||
console.log(' • 检查是否有安全软件阻止自动化');
|
||
console.log(' • 尝试在不同的终端应用中运行测试');
|
||
|
||
const readline = require('readline');
|
||
const rl = readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout
|
||
});
|
||
|
||
const answer = await this.question(rl, '\n是否尝试一个简单的修复测试?(y/n): ');
|
||
|
||
if (answer.toLowerCase() === 'y') {
|
||
await this.runSimpleFixTest();
|
||
}
|
||
|
||
rl.close();
|
||
}
|
||
|
||
async runSimpleFixTest() {
|
||
console.log('\n🔨 运行简单修复测试...');
|
||
|
||
try {
|
||
// 尝试最基本的自动化
|
||
const script = `
|
||
display dialog "TaskPing 自动化测试" with title "权限测试" buttons {"确定"} default button 1 giving up after 3
|
||
`;
|
||
|
||
await this.runAppleScript(script);
|
||
console.log('✅ 基本对话框测试成功');
|
||
|
||
} catch (error) {
|
||
console.log('❌ 基本测试失败:', error.message);
|
||
}
|
||
}
|
||
|
||
async runAppleScript(script) {
|
||
return new Promise((resolve, reject) => {
|
||
const osascript = spawn('osascript', ['-e', script]);
|
||
let output = '';
|
||
let error = '';
|
||
|
||
osascript.stdout.on('data', (data) => {
|
||
output += data.toString();
|
||
});
|
||
|
||
osascript.stderr.on('data', (data) => {
|
||
error += data.toString();
|
||
});
|
||
|
||
osascript.on('close', (code) => {
|
||
if (code === 0) {
|
||
resolve(output.trim());
|
||
} else {
|
||
reject(new Error(error || `Exit code: ${code}`));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
async runCommand(command) {
|
||
return new Promise((resolve, reject) => {
|
||
const proc = spawn('sh', ['-c', command]);
|
||
let output = '';
|
||
|
||
proc.stdout.on('data', (data) => {
|
||
output += data.toString();
|
||
});
|
||
|
||
proc.on('close', (code) => {
|
||
if (code === 0) {
|
||
resolve(output.trim());
|
||
} else {
|
||
reject(new Error(`Command failed with exit code ${code}`));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
async setClipboard(text) {
|
||
return new Promise((resolve, reject) => {
|
||
const pbcopy = spawn('pbcopy');
|
||
pbcopy.stdin.write(text);
|
||
pbcopy.stdin.end();
|
||
|
||
pbcopy.on('close', (code) => {
|
||
if (code === 0) {
|
||
resolve();
|
||
} else {
|
||
reject(new Error('Failed to set clipboard'));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
async getClipboard() {
|
||
return new Promise((resolve, reject) => {
|
||
const pbpaste = spawn('pbpaste');
|
||
let output = '';
|
||
|
||
pbpaste.stdout.on('data', (data) => {
|
||
output += data.toString();
|
||
});
|
||
|
||
pbpaste.on('close', (code) => {
|
||
if (code === 0) {
|
||
resolve(output);
|
||
} else {
|
||
reject(new Error('Failed to get clipboard'));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
question(rl, prompt) {
|
||
return new Promise(resolve => {
|
||
rl.question(prompt, resolve);
|
||
});
|
||
}
|
||
}
|
||
|
||
// 运行诊断
|
||
if (require.main === module) {
|
||
const diagnostic = new AutomationDiagnostic();
|
||
diagnostic.runDiagnostic().catch(console.error);
|
||
}
|
||
|
||
module.exports = AutomationDiagnostic; |