cicy-desktop 1.0.8
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/.github/workflows/build.yml +85 -0
- package/.kiro/steering/dev-workflow.md +166 -0
- package/AGENTS.md +247 -0
- package/CLAUDE.md +162 -0
- package/DOCKER.md +85 -0
- package/Dockerfile +46 -0
- package/README.md +720 -0
- package/TODO-anti-detection.md +326 -0
- package/bin/cicy +176 -0
- package/bin/preinstall.sh +32 -0
- package/copy-to-desktop.sh +26 -0
- package/docs/AUTOMATION-API.md +342 -0
- package/docs/REQUEST_MONITORING.md +435 -0
- package/docs/REST-API-FEATURE.md +155 -0
- package/docs/REST-API.md +319 -0
- package/docs/feature-distributed-multi-agent.md +555 -0
- package/docs/yaml.md +255 -0
- package/electron-mcp-fixed.command +134 -0
- package/electron-mcp-simple.command +135 -0
- package/electron-mcp.command +92 -0
- package/generate-openapi.js +158 -0
- package/jest.config.js +10 -0
- package/jest.setup.global.js +13 -0
- package/jest.teardown.global.js +7 -0
- package/package.json +75 -0
- package/service.sh +164 -0
- package/src/config.js +8 -0
- package/src/extension/inject.js +135 -0
- package/src/main-old.js +837 -0
- package/src/main.js +403 -0
- package/src/preload-rpc.js +4 -0
- package/src/server/args-parser.js +37 -0
- package/src/server/electron-setup.js +33 -0
- package/src/server/express-app.js +166 -0
- package/src/server/logging.js +58 -0
- package/src/server/mcp-server.js +53 -0
- package/src/server/tool-registry.js +77 -0
- package/src/server/ui-routes.js +81 -0
- package/src/swagger-ui.html +41 -0
- package/src/tools/account-tools.js +194 -0
- package/src/tools/automation-tools.js +297 -0
- package/src/tools/cdp-tools.js +444 -0
- package/src/tools/clipboard-tools.js +180 -0
- package/src/tools/download-tools.js +57 -0
- package/src/tools/exec-js.js +297 -0
- package/src/tools/exec-tools.js +139 -0
- package/src/tools/file-tools.js +212 -0
- package/src/tools/hook-chatgpt.js +489 -0
- package/src/tools/hook-gemini.js +454 -0
- package/src/tools/index.js +19 -0
- package/src/tools/ipc-bridge.js +31 -0
- package/src/tools/ping.js +60 -0
- package/src/tools/r-reset.js +28 -0
- package/src/tools/screenshot-tools.js +28 -0
- package/src/tools/system-tools.js +531 -0
- package/src/tools/window-tools.js +882 -0
- package/src/ui.html +914 -0
- package/src/utils/auth.js +81 -0
- package/src/utils/cdp-utils.js +8 -0
- package/src/utils/download-manager.js +41 -0
- package/src/utils/process-utils.js +185 -0
- package/src/utils/snapshot-utils.js +56 -0
- package/src/utils/window-monitor.js +605 -0
- package/src/utils/window-state.js +137 -0
- package/src/utils/window-utils.js +336 -0
- package/update-desktop.sh +33 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const yaml = require('js-yaml');
|
|
6
|
+
|
|
7
|
+
// 从服务器获取工具列表和 schemas
|
|
8
|
+
async function getToolsWithSchemas() {
|
|
9
|
+
const http = require('http');
|
|
10
|
+
const tokenPath = path.join(require('os').homedir(), 'data/electron/token.txt');
|
|
11
|
+
const token = fs.readFileSync(tokenPath, 'utf8').trim();
|
|
12
|
+
|
|
13
|
+
// 获取工具列表
|
|
14
|
+
const tools = await new Promise((resolve, reject) => {
|
|
15
|
+
http.get('http://localhost:8101/rpc/tools', {
|
|
16
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
17
|
+
}, (res) => {
|
|
18
|
+
let data = '';
|
|
19
|
+
res.on('data', chunk => data += chunk);
|
|
20
|
+
res.on('end', () => resolve(JSON.parse(data).tools));
|
|
21
|
+
}).on('error', reject);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// 获取 schemas
|
|
25
|
+
const schemas = await new Promise((resolve, reject) => {
|
|
26
|
+
http.get('http://localhost:8101/rpc/schemas', {
|
|
27
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
28
|
+
}, (res) => {
|
|
29
|
+
let data = '';
|
|
30
|
+
res.on('data', chunk => data += chunk);
|
|
31
|
+
res.on('end', () => resolve(JSON.parse(data).schemas));
|
|
32
|
+
}).on('error', reject);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return tools.map(tool => ({
|
|
36
|
+
...tool,
|
|
37
|
+
schema: schemas[tool.name] || { type: 'object' }
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function generateOpenAPI() {
|
|
42
|
+
const tools = await getToolsWithSchemas();
|
|
43
|
+
|
|
44
|
+
const openapi = {
|
|
45
|
+
openapi: '3.0.0',
|
|
46
|
+
info: {
|
|
47
|
+
title: 'Electron MCP REST API',
|
|
48
|
+
version: '1.0.0',
|
|
49
|
+
description: `REST API for Electron MCP tools - ${tools.length} tools available`,
|
|
50
|
+
},
|
|
51
|
+
servers: [
|
|
52
|
+
{
|
|
53
|
+
url: 'http://localhost:8101',
|
|
54
|
+
description: 'Local server',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
url: 'https://gcp-8101.cicy.de5.net',
|
|
58
|
+
description: 'Remote server',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
components: {
|
|
62
|
+
securitySchemes: {
|
|
63
|
+
bearerAuth: {
|
|
64
|
+
type: 'http',
|
|
65
|
+
scheme: 'bearer',
|
|
66
|
+
bearerFormat: 'token',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
security: [{ bearerAuth: [] }],
|
|
71
|
+
paths: {}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// 添加 /rpc/tools 端点
|
|
75
|
+
openapi.paths['/rpc/tools'] = {
|
|
76
|
+
get: {
|
|
77
|
+
summary: 'List all available tools',
|
|
78
|
+
tags: ['Tools'],
|
|
79
|
+
security: [{ bearerAuth: [] }],
|
|
80
|
+
responses: {
|
|
81
|
+
200: {
|
|
82
|
+
description: 'List of tools',
|
|
83
|
+
content: {
|
|
84
|
+
'application/json': {
|
|
85
|
+
schema: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
tools: {
|
|
89
|
+
type: 'array',
|
|
90
|
+
items: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
name: { type: 'string' },
|
|
94
|
+
description: { type: 'string' }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// 为每个工具生成端点
|
|
108
|
+
for (const tool of tools) {
|
|
109
|
+
openapi.paths[`/rpc/${tool.name}`] = {
|
|
110
|
+
post: {
|
|
111
|
+
description: tool.description,
|
|
112
|
+
tags: ['Tools'],
|
|
113
|
+
security: [{ bearerAuth: [] }],
|
|
114
|
+
requestBody: {
|
|
115
|
+
required: true,
|
|
116
|
+
content: {
|
|
117
|
+
'application/json': {
|
|
118
|
+
schema: tool.schema
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
responses: {
|
|
123
|
+
200: {
|
|
124
|
+
description: 'Tool execution result',
|
|
125
|
+
content: {
|
|
126
|
+
'application/json': {
|
|
127
|
+
schema: {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {
|
|
130
|
+
content: {
|
|
131
|
+
type: 'array',
|
|
132
|
+
items: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
type: { type: 'string' },
|
|
136
|
+
text: { type: 'string' }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
404: { description: 'Tool not found' },
|
|
146
|
+
500: { description: 'Execution error' }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 保存为 YAML
|
|
153
|
+
const yamlContent = yaml.dump(openapi, { lineWidth: -1 });
|
|
154
|
+
fs.writeFileSync('openapi.yml', yamlContent);
|
|
155
|
+
console.log('✅ Generated: openapi.yml');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
generateOpenAPI().catch(console.error);
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
testEnvironment: "node",
|
|
3
|
+
testMatch: ["<rootDir>/tests/**/*.test.js"],
|
|
4
|
+
testTimeout: 30000,
|
|
5
|
+
maxWorkers: 1,
|
|
6
|
+
globalSetup: "<rootDir>/jest.setup.global.js",
|
|
7
|
+
globalTeardown: "<rootDir>/jest.teardown.global.js",
|
|
8
|
+
forceExit: true,
|
|
9
|
+
detectOpenHandles: false,
|
|
10
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { startTestServer } = require('./tests/mcp/setup-test-server');
|
|
2
|
+
|
|
3
|
+
module.exports = async () => {
|
|
4
|
+
console.log('\n🚀 Starting test server...\n');
|
|
5
|
+
try {
|
|
6
|
+
await startTestServer();
|
|
7
|
+
console.log('\n✅ Test server ready\n');
|
|
8
|
+
} catch (error) {
|
|
9
|
+
console.error('\n❌ Failed to start test server:', error.message);
|
|
10
|
+
console.log('\n⚠️ Continuing without SSE connection...\n');
|
|
11
|
+
// 不抛出错误,让测试继续(服务器已启动,只是 SSE 连接失败)
|
|
12
|
+
}
|
|
13
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cicy-desktop",
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "CiCy - AI-powered operating system browser",
|
|
5
|
+
"main": "src/main.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cicy": "./bin/cicy"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "electron .",
|
|
11
|
+
"format": "prettier --write \"src/**/*.js\" \"tests/**/*.js\"",
|
|
12
|
+
"format:check": "prettier --check \"src/**/*.js\" \"tests/**/*.js\"",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"test:ls": "node tests/test-ls.js",
|
|
15
|
+
"build": "electron-builder --publish never",
|
|
16
|
+
"build:win": "electron-builder --win --publish never",
|
|
17
|
+
"build:linux": "electron-builder --linux --publish never"
|
|
18
|
+
},
|
|
19
|
+
"build": {
|
|
20
|
+
"appId": "com.electron.mcp",
|
|
21
|
+
"productName": "Electron MCP",
|
|
22
|
+
"directories": {
|
|
23
|
+
"output": "dist"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"src/**/*",
|
|
27
|
+
"package.json"
|
|
28
|
+
],
|
|
29
|
+
"win": {
|
|
30
|
+
"target": [
|
|
31
|
+
"nsis"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"linux": {
|
|
35
|
+
"target": [
|
|
36
|
+
"deb",
|
|
37
|
+
"AppImage"
|
|
38
|
+
],
|
|
39
|
+
"category": "Development"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"cicy",
|
|
44
|
+
"ai",
|
|
45
|
+
"browser",
|
|
46
|
+
"electron",
|
|
47
|
+
"mcp"
|
|
48
|
+
],
|
|
49
|
+
"author": "cicybot",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
53
|
+
"chrome-remote-interface": "^0.33.0",
|
|
54
|
+
"cors": "^2.8.5",
|
|
55
|
+
"electron": "^41.0.2",
|
|
56
|
+
"electron-context-menu": "^4.0.4",
|
|
57
|
+
"electron-log": "^5.2.4",
|
|
58
|
+
"express": "^4.18.2",
|
|
59
|
+
"js-beautify": "^1.15.4",
|
|
60
|
+
"js-yaml": "^4.1.1",
|
|
61
|
+
"serve-index": "^1.9.2",
|
|
62
|
+
"swagger-jsdoc": "^6.2.8",
|
|
63
|
+
"swagger-ui-express": "^5.0.1",
|
|
64
|
+
"zod": "3.25"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@babel/core": "^7.29.0",
|
|
68
|
+
"@babel/preset-env": "^7.29.0",
|
|
69
|
+
"axios": "^1.13.5",
|
|
70
|
+
"electron-builder": "^26.7.0",
|
|
71
|
+
"jest": "^29.7.0",
|
|
72
|
+
"prettier": "^3.8.1",
|
|
73
|
+
"supertest": "^6.3.3"
|
|
74
|
+
}
|
|
75
|
+
}
|
package/service.sh
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Electron MCP 服务管理脚本
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
PROJECT_DIR="$SCRIPT_DIR"
|
|
7
|
+
PID_FILE="$PROJECT_DIR/.electron-mcp.pid"
|
|
8
|
+
LOG_FILE="$HOME/logs/electron-mcp-service.log"
|
|
9
|
+
|
|
10
|
+
# 创建日志目录
|
|
11
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
12
|
+
|
|
13
|
+
# 显示帮助信息
|
|
14
|
+
show_help() {
|
|
15
|
+
echo "Electron MCP 服务管理"
|
|
16
|
+
echo ""
|
|
17
|
+
echo "用法: $0 {start|stop|restart|status|logs}"
|
|
18
|
+
echo ""
|
|
19
|
+
echo "命令:"
|
|
20
|
+
echo " start - 启动服务"
|
|
21
|
+
echo " stop - 停止服务"
|
|
22
|
+
echo " restart - 重启服务"
|
|
23
|
+
echo " status - 查看状态"
|
|
24
|
+
echo " logs - 查看日志"
|
|
25
|
+
echo ""
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# 启动服务
|
|
29
|
+
start_service() {
|
|
30
|
+
if [ -f "$PID_FILE" ]; then
|
|
31
|
+
local pid=$(cat "$PID_FILE")
|
|
32
|
+
if ps -p "$pid" > /dev/null 2>&1; then
|
|
33
|
+
echo "✅ 服务已在运行 (PID: $pid)"
|
|
34
|
+
return 0
|
|
35
|
+
else
|
|
36
|
+
rm -f "$PID_FILE"
|
|
37
|
+
fi
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "🚀 正在启动 Electron MCP 服务..."
|
|
41
|
+
|
|
42
|
+
cd "$PROJECT_DIR"
|
|
43
|
+
|
|
44
|
+
# 检查依赖
|
|
45
|
+
if [ ! -d "node_modules" ]; then
|
|
46
|
+
echo "📦 正在安装依赖..."
|
|
47
|
+
npm install
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 后台启动服务
|
|
51
|
+
nohup npm start > "$LOG_FILE" 2>&1 &
|
|
52
|
+
local pid=$!
|
|
53
|
+
|
|
54
|
+
# 保存 PID
|
|
55
|
+
echo "$pid" > "$PID_FILE"
|
|
56
|
+
|
|
57
|
+
# 等待服务启动
|
|
58
|
+
sleep 3
|
|
59
|
+
|
|
60
|
+
if ps -p "$pid" > /dev/null 2>&1; then
|
|
61
|
+
echo "✅ 服务启动成功 (PID: $pid)"
|
|
62
|
+
echo "📋 端口: 8101"
|
|
63
|
+
echo "📋 日志: $LOG_FILE"
|
|
64
|
+
echo "📋 MCP 端点: http://localhost:8101/mcp"
|
|
65
|
+
echo "📋 API 文档: http://localhost:8101/docs"
|
|
66
|
+
else
|
|
67
|
+
echo "❌ 服务启动失败"
|
|
68
|
+
rm -f "$PID_FILE"
|
|
69
|
+
return 1
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# 停止服务
|
|
74
|
+
stop_service() {
|
|
75
|
+
if [ ! -f "$PID_FILE" ]; then
|
|
76
|
+
echo "⚠️ 服务未运行"
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
local pid=$(cat "$PID_FILE")
|
|
81
|
+
|
|
82
|
+
if ps -p "$pid" > /dev/null 2>&1; then
|
|
83
|
+
echo "🛑 正在停止服务 (PID: $pid)..."
|
|
84
|
+
kill "$pid"
|
|
85
|
+
|
|
86
|
+
# 等待进程结束
|
|
87
|
+
local count=0
|
|
88
|
+
while ps -p "$pid" > /dev/null 2>&1 && [ $count -lt 10 ]; do
|
|
89
|
+
sleep 1
|
|
90
|
+
count=$((count + 1))
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
if ps -p "$pid" > /dev/null 2>&1; then
|
|
94
|
+
echo "⚠️ 强制停止服务..."
|
|
95
|
+
kill -9 "$pid"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
echo "✅ 服务已停止"
|
|
99
|
+
else
|
|
100
|
+
echo "⚠️ 服务进程不存在"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
rm -f "$PID_FILE"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# 查看状态
|
|
107
|
+
show_status() {
|
|
108
|
+
if [ -f "$PID_FILE" ]; then
|
|
109
|
+
local pid=$(cat "$PID_FILE")
|
|
110
|
+
if ps -p "$pid" > /dev/null 2>&1; then
|
|
111
|
+
echo "✅ 服务运行中 (PID: $pid)"
|
|
112
|
+
echo "📋 端口: 8101"
|
|
113
|
+
echo "📋 日志: $LOG_FILE"
|
|
114
|
+
|
|
115
|
+
# 检查端口是否监听
|
|
116
|
+
if lsof -i :8101 > /dev/null 2>&1; then
|
|
117
|
+
echo "📋 端口 8101 正在监听"
|
|
118
|
+
else
|
|
119
|
+
echo "⚠️ 端口 8101 未监听"
|
|
120
|
+
fi
|
|
121
|
+
else
|
|
122
|
+
echo "❌ 服务进程不存在 (PID 文件存在但进程已死)"
|
|
123
|
+
rm -f "$PID_FILE"
|
|
124
|
+
fi
|
|
125
|
+
else
|
|
126
|
+
echo "⚠️ 服务未运行"
|
|
127
|
+
fi
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# 查看日志
|
|
131
|
+
show_logs() {
|
|
132
|
+
if [ -f "$LOG_FILE" ]; then
|
|
133
|
+
echo "📋 日志文件: $LOG_FILE"
|
|
134
|
+
echo "----------------------------------------"
|
|
135
|
+
tail -f "$LOG_FILE"
|
|
136
|
+
else
|
|
137
|
+
echo "⚠️ 日志文件不存在: $LOG_FILE"
|
|
138
|
+
fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# 主逻辑
|
|
142
|
+
case "$1" in
|
|
143
|
+
start)
|
|
144
|
+
start_service
|
|
145
|
+
;;
|
|
146
|
+
stop)
|
|
147
|
+
stop_service
|
|
148
|
+
;;
|
|
149
|
+
restart)
|
|
150
|
+
stop_service
|
|
151
|
+
sleep 2
|
|
152
|
+
start_service
|
|
153
|
+
;;
|
|
154
|
+
status)
|
|
155
|
+
show_status
|
|
156
|
+
;;
|
|
157
|
+
logs)
|
|
158
|
+
show_logs
|
|
159
|
+
;;
|
|
160
|
+
*)
|
|
161
|
+
show_help
|
|
162
|
+
exit 1
|
|
163
|
+
;;
|
|
164
|
+
esac
|
package/src/config.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
console.log("hi cicy v4");
|
|
2
|
+
|
|
3
|
+
window.__cicyInjected = true;
|
|
4
|
+
window.__cicyTime = new Date().toISOString();
|
|
5
|
+
|
|
6
|
+
// 创建全局工具对象
|
|
7
|
+
window._g = window._g || {};
|
|
8
|
+
|
|
9
|
+
// ========================================
|
|
10
|
+
// Electron RPC Bridge - control everything
|
|
11
|
+
// ========================================
|
|
12
|
+
try {
|
|
13
|
+
const { ipcRenderer } = require('electron');
|
|
14
|
+
window.electronRPC = (tool, args) => ipcRenderer.invoke('rpc', tool, args || {});
|
|
15
|
+
window._g.rpc = window.electronRPC;
|
|
16
|
+
console.log('[RPC] electronRPC ready');
|
|
17
|
+
} catch(e) {
|
|
18
|
+
console.log('[RPC] not in electron:', e.message);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ========================================
|
|
22
|
+
// 剪贴板权限支持
|
|
23
|
+
// ========================================
|
|
24
|
+
|
|
25
|
+
// 确保剪贴板 API 可用
|
|
26
|
+
if (navigator.clipboard) {
|
|
27
|
+
console.log("[Clipboard] Clipboard API is available");
|
|
28
|
+
|
|
29
|
+
// 测试剪贴板权限
|
|
30
|
+
navigator.permissions.query({ name: 'clipboard-read' }).then(result => {
|
|
31
|
+
console.log(`[Clipboard] Read permission: ${result.state}`);
|
|
32
|
+
}).catch(e => {
|
|
33
|
+
console.log("[Clipboard] Read permission query failed:", e.message);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
navigator.permissions.query({ name: 'clipboard-write' }).then(result => {
|
|
37
|
+
console.log(`[Clipboard] Write permission: ${result.state}`);
|
|
38
|
+
}).catch(e => {
|
|
39
|
+
console.log("[Clipboard] Write permission query failed:", e.message);
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
console.log("[Clipboard] Clipboard API is not available");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 添加剪贴板工具函数
|
|
46
|
+
window._g.clipboard = {
|
|
47
|
+
// 读取剪贴板文本
|
|
48
|
+
readText: async () => {
|
|
49
|
+
try {
|
|
50
|
+
return await navigator.clipboard.readText();
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error("[Clipboard] Read text failed:", e);
|
|
53
|
+
throw e;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// 写入剪贴板文本
|
|
58
|
+
writeText: async (text) => {
|
|
59
|
+
try {
|
|
60
|
+
await navigator.clipboard.writeText(text);
|
|
61
|
+
console.log("[Clipboard] Text written successfully");
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error("[Clipboard] Write text failed:", e);
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// 读取剪贴板(包括图片)
|
|
69
|
+
read: async () => {
|
|
70
|
+
try {
|
|
71
|
+
return await navigator.clipboard.read();
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error("[Clipboard] Read failed:", e);
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// 写入剪贴板(包括图片)
|
|
79
|
+
write: async (data) => {
|
|
80
|
+
try {
|
|
81
|
+
await navigator.clipboard.write(data);
|
|
82
|
+
console.log("[Clipboard] Data written successfully");
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error("[Clipboard] Write failed:", e);
|
|
85
|
+
throw e;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ========================================
|
|
91
|
+
// IndexedDB 基础工具
|
|
92
|
+
// ========================================
|
|
93
|
+
|
|
94
|
+
window._g.getIndexedDBRows = async (dbName, storeName, limit = 100) => {
|
|
95
|
+
const db = await new Promise((resolve, reject) => {
|
|
96
|
+
const request = indexedDB.open(dbName);
|
|
97
|
+
request.onsuccess = () => resolve(request.result);
|
|
98
|
+
request.onerror = () => reject(request.error);
|
|
99
|
+
});
|
|
100
|
+
const tx = db.transaction(storeName, "readonly");
|
|
101
|
+
const store = tx.objectStore(storeName);
|
|
102
|
+
const results = [];
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
const request = store.openCursor();
|
|
105
|
+
request.onsuccess = (e) => {
|
|
106
|
+
const cursor = e.target.result;
|
|
107
|
+
if (cursor && results.length < limit) {
|
|
108
|
+
results.push(cursor.value);
|
|
109
|
+
cursor.continue();
|
|
110
|
+
} else {
|
|
111
|
+
resolve(results);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
window._g.listIndexedDB = async () => {
|
|
118
|
+
const dbs = await indexedDB.databases();
|
|
119
|
+
const result = {};
|
|
120
|
+
for (const dbInfo of dbs) {
|
|
121
|
+
try {
|
|
122
|
+
const db = await new Promise((resolve, reject) => {
|
|
123
|
+
const req = indexedDB.open(dbInfo.name);
|
|
124
|
+
req.onsuccess = () => resolve(req.result);
|
|
125
|
+
req.onerror = () => reject(req.error);
|
|
126
|
+
setTimeout(() => reject(new Error("timeout")), 1000);
|
|
127
|
+
});
|
|
128
|
+
result[dbInfo.name] = Array.from(db.objectStoreNames);
|
|
129
|
+
db.close();
|
|
130
|
+
} catch (e) {
|
|
131
|
+
result[dbInfo.name] = "error: " + e.message;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
};
|