rust-rpa 0.1.4-beta.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/README.md +787 -0
- package/bin/rpa.js +251 -0
- package/index.d.ts +669 -0
- package/index.js +202 -0
- package/package.json +44 -0
- package/rust-rpa.darwin-arm64.node +0 -0
- package/rust-rpa.darwin-x64.node +0 -0
- package/rust-rpa.win32-ia32-msvc.node +0 -0
- package/rust-rpa.win32-x64-msvc.node +0 -0
package/bin/rpa.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { Command } = require('commander');
|
|
6
|
+
const { Window, Monitor, Mouse, Keyboard, Clipboard, Permission } = require(path.resolve(__dirname, '..', 'index.js'));
|
|
7
|
+
|
|
8
|
+
const VERSION = require(path.resolve(__dirname, '..', 'package.json')).version;
|
|
9
|
+
|
|
10
|
+
const c = {
|
|
11
|
+
bold: s => `\x1b[1m${s}\x1b[0m`,
|
|
12
|
+
dim: s => `\x1b[2m${s}\x1b[0m`,
|
|
13
|
+
green: s => `\x1b[32m${s}\x1b[0m`,
|
|
14
|
+
cyan: s => `\x1b[36m${s}\x1b[0m`,
|
|
15
|
+
yellow:s => `\x1b[33m${s}\x1b[0m`,
|
|
16
|
+
red: s => `\x1b[31m${s}\x1b[0m`,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function die(msg) { console.error(c.red(`错误: ${msg}`)); process.exit(1); }
|
|
20
|
+
|
|
21
|
+
function printTable(rows, keys) {
|
|
22
|
+
if (!rows.length) { console.log(c.dim('(空)')); return; }
|
|
23
|
+
const widths = keys.map(k => Math.max(k.length, ...rows.map(r => String(r[k] ?? '').length)));
|
|
24
|
+
const header = keys.map((k, i) => k.padEnd(widths[i])).join(' ');
|
|
25
|
+
console.log(c.bold(header));
|
|
26
|
+
console.log(widths.map(w => '─'.repeat(w)).join('──'));
|
|
27
|
+
rows.forEach(r => console.log(keys.map((k, i) => String(r[k] ?? '').padEnd(widths[i])).join(' ')));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function findWindow(target) {
|
|
31
|
+
const win = Window.all().find(w =>
|
|
32
|
+
String(w.id()) === target || w.appName().includes(target) || w.title().includes(target)
|
|
33
|
+
);
|
|
34
|
+
if (!win) die(`未找到匹配 "${target}" 的窗口`);
|
|
35
|
+
return win;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const program = new Command();
|
|
39
|
+
program.name('rpa').description('rust-rpa CLI').version(VERSION, '-v, --version');
|
|
40
|
+
|
|
41
|
+
// ── permission ──────────────────────────────────────────────────────────────
|
|
42
|
+
program.command('permission').description('检查系统权限状态')
|
|
43
|
+
.option('--prompt', '弹出授权对话框')
|
|
44
|
+
.action(({ prompt }) => {
|
|
45
|
+
const a = Permission.checkAccessibility(!!prompt);
|
|
46
|
+
const s = Permission.checkScreenCapture(!!prompt);
|
|
47
|
+
console.log(`辅助功能权限: ${a ? c.green('✅ 已授权') : c.red('❌ 未授权')}`);
|
|
48
|
+
console.log(`屏幕录制权限: ${s ? c.green('✅ 已授权') : c.red('❌ 未授权')}`);
|
|
49
|
+
if (prompt && (!a || !s)) console.log(c.yellow('\n提示: macOS 授权后需要重启应用才能生效'));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// ── window ──────────────────────────────────────────────────────────────────
|
|
53
|
+
const window = program.command('window').description('窗口管理');
|
|
54
|
+
|
|
55
|
+
window.command('list').description('列出所有可见窗口')
|
|
56
|
+
.option('--json', '输出 JSON 格式')
|
|
57
|
+
.action(({ json }) => {
|
|
58
|
+
const windows = Window.all();
|
|
59
|
+
if (json) { console.log(JSON.stringify(windows.map(w => w.toJSON()), null, 2)); return; }
|
|
60
|
+
const rows = windows.map(w => ({
|
|
61
|
+
id: w.id(), pid: w.pid(), app: w.appName(), title: w.title().slice(0, 40),
|
|
62
|
+
pos: `${w.x()},${w.y()}`, size: `${w.width()}x${w.height()}`,
|
|
63
|
+
}));
|
|
64
|
+
printTable(rows, ['id', 'pid', 'app', 'title', 'pos', 'size']);
|
|
65
|
+
console.log(c.dim(`\n共 ${rows.length} 个窗口`));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
window.command('focus').description('将窗口置于最前')
|
|
69
|
+
.argument('<target>', '按 appName/title/id 匹配')
|
|
70
|
+
.action(async (target) => {
|
|
71
|
+
const win = findWindow(target);
|
|
72
|
+
await win.bringToFront();
|
|
73
|
+
console.log(c.green(`已将窗口置于最前: ${win.appName()} - ${win.title()}`));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
window.command('bounds').description('获取或设置窗口边界')
|
|
77
|
+
.argument('<target>', '按 appName/title/id 匹配')
|
|
78
|
+
.argument('[x]', 'X 坐标').argument('[y]', 'Y 坐标')
|
|
79
|
+
.argument('[width]', '宽度').argument('[height]', '高度')
|
|
80
|
+
.action(async (target, x, y, width, height) => {
|
|
81
|
+
const win = findWindow(target);
|
|
82
|
+
if (x !== undefined && y !== undefined && width !== undefined && height !== undefined) {
|
|
83
|
+
const b = { x: +x, y: +y, width: +width, height: +height };
|
|
84
|
+
await win.setBounds(b);
|
|
85
|
+
console.log(c.green(`已设置窗口边界: x=${b.x} y=${b.y} ${b.width}x${b.height}`));
|
|
86
|
+
} else {
|
|
87
|
+
const b = win.getBounds();
|
|
88
|
+
console.log(`窗口: ${win.appName()} - ${win.title()}`);
|
|
89
|
+
console.log(`位置: (${b.x}, ${b.y}) 大小: ${b.width}x${b.height}`);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ── monitor ─────────────────────────────────────────────────────────────────
|
|
94
|
+
const monitor = program.command('monitor').description('显示器管理');
|
|
95
|
+
|
|
96
|
+
monitor.command('list').description('列出所有显示器')
|
|
97
|
+
.option('--json', '输出 JSON 格式')
|
|
98
|
+
.action(({ json }) => {
|
|
99
|
+
const monitors = Monitor.all();
|
|
100
|
+
if (json) { console.log(JSON.stringify(monitors.map(m => m.toJSON()), null, 2)); return; }
|
|
101
|
+
const rows = monitors.map(m => ({
|
|
102
|
+
id: m.id(), name: m.name(),
|
|
103
|
+
resolution: `${m.width()}x${m.height()}`,
|
|
104
|
+
pos: `${m.x()},${m.y()}`,
|
|
105
|
+
scale: `${m.scaleFactor()}x`,
|
|
106
|
+
primary: m.isPrimary() ? '✅' : '',
|
|
107
|
+
}));
|
|
108
|
+
printTable(rows, ['id', 'name', 'resolution', 'pos', 'scale', 'primary']);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// ── screenshot ──────────────────────────────────────────────────────────────
|
|
112
|
+
program.command('screenshot').description('截取屏幕或窗口')
|
|
113
|
+
.option('-o, --output <file>', '输出文件路径', 'screenshot.png')
|
|
114
|
+
.option('--window <target>', '截取指定窗口')
|
|
115
|
+
.option('--monitor <id>', '截取指定显示器')
|
|
116
|
+
.action(async ({ output, window: windowTarget, monitor: monitorId }) => {
|
|
117
|
+
let image;
|
|
118
|
+
if (windowTarget) {
|
|
119
|
+
const win = findWindow(windowTarget);
|
|
120
|
+
console.log(`截取窗口: ${win.appName()} - ${win.title()}`);
|
|
121
|
+
image = await win.captureImage();
|
|
122
|
+
} else {
|
|
123
|
+
const monitors = Monitor.all();
|
|
124
|
+
let mon;
|
|
125
|
+
if (monitorId) {
|
|
126
|
+
mon = monitors.find(m => String(m.id()) === monitorId);
|
|
127
|
+
if (!mon) die(`未找到 ID 为 ${monitorId} 的显示器`);
|
|
128
|
+
} else {
|
|
129
|
+
mon = monitors.find(m => m.isPrimary()) || monitors[0];
|
|
130
|
+
}
|
|
131
|
+
console.log(`截取显示器: ${mon.name()}`);
|
|
132
|
+
image = await mon.captureImage();
|
|
133
|
+
}
|
|
134
|
+
await image.toFile(output);
|
|
135
|
+
console.log(c.green(`已保存: ${output} (${image.width}x${image.height})`));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ── mouse ───────────────────────────────────────────────────────────────────
|
|
139
|
+
const mouse = program.command('mouse').description('鼠标操作');
|
|
140
|
+
|
|
141
|
+
mouse.command('position').description('获取当前鼠标位置')
|
|
142
|
+
.action(async () => {
|
|
143
|
+
const pos = await Mouse.position();
|
|
144
|
+
console.log(`${pos.x},${pos.y}`);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
mouse.command('move').description('移动鼠标到指定坐标')
|
|
148
|
+
.argument('<x>', 'X 坐标').argument('<y>', 'Y 坐标')
|
|
149
|
+
.action(async (x, y) => {
|
|
150
|
+
await Mouse.moveTo(+x, +y);
|
|
151
|
+
console.log(c.green(`鼠标已移动到 (${x}, ${y})`));
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
mouse.command('click').description('点击鼠标')
|
|
155
|
+
.argument('[button]', '按钮 (left/right/middle)', 'left')
|
|
156
|
+
.option('--double', '双击')
|
|
157
|
+
.action(async (button, { double }) => {
|
|
158
|
+
if (double) {
|
|
159
|
+
await Mouse.doubleClick(button);
|
|
160
|
+
console.log(c.green(`双击: ${button}`));
|
|
161
|
+
} else {
|
|
162
|
+
await Mouse.click(button);
|
|
163
|
+
console.log(c.green(`点击: ${button}`));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
mouse.command('scroll').description('滚动鼠标滚轮')
|
|
168
|
+
.argument('<deltaX>', 'X 轴偏移').argument('<deltaY>', 'Y 轴偏移')
|
|
169
|
+
.action(async (dx, dy) => {
|
|
170
|
+
await Mouse.scroll(+dx, +dy);
|
|
171
|
+
console.log(c.green(`已滚动 (${dx}, ${dy})`));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ── keyboard ────────────────────────────────────────────────────────────────
|
|
175
|
+
const keyboard = program.command('keyboard').description('键盘操作');
|
|
176
|
+
|
|
177
|
+
keyboard.command('type').description('输入文本')
|
|
178
|
+
.argument('<text...>', '要输入的文本')
|
|
179
|
+
.action(async (parts) => {
|
|
180
|
+
const text = parts.join(' ');
|
|
181
|
+
await Keyboard.typeText(text);
|
|
182
|
+
console.log(c.green(`已输入: "${text}"`));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
keyboard.command('press').description('按键或组合键')
|
|
186
|
+
.argument('<keys>', '按键(组合键用 + 连接,如 ctrl+c)')
|
|
187
|
+
.action(async (raw) => {
|
|
188
|
+
const keys = raw.split('+').map(k => k.trim().toLowerCase());
|
|
189
|
+
if (keys.length === 1) await Keyboard.click(keys[0]);
|
|
190
|
+
else await Keyboard.sequence(keys);
|
|
191
|
+
console.log(c.green(`已按下: ${raw}`));
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ── clipboard ───────────────────────────────────────────────────────────────
|
|
195
|
+
const clipboard = program.command('clipboard').description('剪贴板操作');
|
|
196
|
+
|
|
197
|
+
clipboard.command('read').description('读取剪贴板文本')
|
|
198
|
+
.action(async () => console.log(await Clipboard.readText()));
|
|
199
|
+
|
|
200
|
+
clipboard.command('write').description('写入文本到剪贴板')
|
|
201
|
+
.argument('<text...>', '要写入的文本')
|
|
202
|
+
.action(async (parts) => {
|
|
203
|
+
const text = parts.join(' ');
|
|
204
|
+
await Clipboard.writeText(text);
|
|
205
|
+
console.log(c.green(`已写入剪贴板: "${text}"`));
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
clipboard.command('write-image').description('写入图片到剪贴板')
|
|
209
|
+
.argument('<source>', '图片文件路径或 base64 字符串')
|
|
210
|
+
.action(async (source) => {
|
|
211
|
+
await Clipboard.writeImage(source);
|
|
212
|
+
console.log(c.green(`已写入图片到剪贴板: ${source.length > 60 ? source.slice(0, 60) + '...' : source}`));
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
clipboard.command('write-file').description('写入文件到剪贴板(粘贴时为文件)')
|
|
216
|
+
.argument('<paths...>', '文件路径(支持多个)')
|
|
217
|
+
.action(async (paths) => {
|
|
218
|
+
await Clipboard.writeFile(paths.length === 1 ? paths[0] : paths);
|
|
219
|
+
console.log(c.green(`已写入文件到剪贴板: ${paths.join(', ')}`));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
clipboard.command('paste').description('执行粘贴 (Cmd/Ctrl+V)')
|
|
223
|
+
.action(async () => {
|
|
224
|
+
await Clipboard.paste();
|
|
225
|
+
console.log(c.green('已执行粘贴'));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
clipboard.command('paste-text').description('粘贴文本(不影响剪贴板原内容)')
|
|
229
|
+
.argument('<text...>', '要粘贴的文本')
|
|
230
|
+
.action(async (parts) => {
|
|
231
|
+
const text = parts.join(' ');
|
|
232
|
+
await Clipboard.pasteText(text);
|
|
233
|
+
console.log(c.green(`已粘贴文本: "${text}"(剪贴板已恢复)`));
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
clipboard.command('paste-image').description('粘贴图片(不影响剪贴板原内容)')
|
|
237
|
+
.argument('<path>', '图片文件路径')
|
|
238
|
+
.action(async (p) => {
|
|
239
|
+
await Clipboard.pasteImage(p);
|
|
240
|
+
console.log(c.green(`已粘贴图片: ${p}(剪贴板已恢复)`));
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
clipboard.command('paste-file').description('粘贴文件(不影响剪贴板原内容)')
|
|
244
|
+
.argument('<paths...>', '文件路径(支持多个)')
|
|
245
|
+
.action(async (paths) => {
|
|
246
|
+
await Clipboard.pasteFile(paths.length === 1 ? paths[0] : paths);
|
|
247
|
+
console.log(c.green(`已粘贴文件: ${paths.join(', ')}(剪贴板已恢复)`));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// ── run ─────────────────────────────────────────────────────────────────────
|
|
251
|
+
program.parseAsync().catch(err => die(err.message));
|