claude4arc 0.5.0
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/LICENSE +21 -0
- package/README.md +275 -0
- package/bin/claude4arc.js +405 -0
- package/extension/background.js +153 -0
- package/extension/guard.js +66 -0
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-32.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/manifest.json +41 -0
- package/host/host.js +368 -0
- package/lib/blocklist.js +52 -0
- package/lib/browsers.js +56 -0
- package/lib/client.js +89 -0
- package/lib/commands.js +353 -0
- package/lib/config.js +22 -0
- package/lib/dnd.js +60 -0
- package/lib/editors.js +373 -0
- package/lib/frames.js +39 -0
- package/lib/housekeeping.js +46 -0
- package/lib/inpage.js +1381 -0
- package/lib/input.js +242 -0
- package/lib/keys.js +109 -0
- package/lib/page.js +1530 -0
- package/lib/paths.js +10 -0
- package/lib/shim.js +186 -0
- package/lib/task.js +350 -0
- package/lib/util.js +64 -0
- package/package.json +43 -0
- package/skill/SKILL.md +138 -0
package/lib/input.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { describeKey, parseChord, modifierMask, macCommand } from "./keys.js";
|
|
2
|
+
import { ROOT_INPUT } from "./frames.js";
|
|
3
|
+
import { run, sleep, pbcopy } from "./util.js";
|
|
4
|
+
|
|
5
|
+
export class Mouse {
|
|
6
|
+
#page;
|
|
7
|
+
#pressed = new Set();
|
|
8
|
+
#input = ROOT_INPUT;
|
|
9
|
+
|
|
10
|
+
constructor(page) {
|
|
11
|
+
this.#page = page;
|
|
12
|
+
this.x = null;
|
|
13
|
+
this.y = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#buttons() {
|
|
17
|
+
let mask = 0;
|
|
18
|
+
if (this.#pressed.has("left")) mask |= 1;
|
|
19
|
+
if (this.#pressed.has("right")) mask |= 2;
|
|
20
|
+
if (this.#pressed.has("middle")) mask |= 4;
|
|
21
|
+
return mask;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async #ensurePosition() {
|
|
25
|
+
if (this.x !== null) return;
|
|
26
|
+
const size = await this.#page.evaluate(() => ({ width: innerWidth, height: innerHeight }));
|
|
27
|
+
this.x = Math.round(size.width / 2);
|
|
28
|
+
this.y = Math.round(size.height / 2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#dispatch(params) {
|
|
32
|
+
const input = this.#input;
|
|
33
|
+
return this.#page.cdp(
|
|
34
|
+
"Input.dispatchMouseEvent",
|
|
35
|
+
{ ...params, x: params.x - input.x, y: params.y - input.y },
|
|
36
|
+
{ sessionId: input.sessionId },
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async move(x, y, { steps = 1, label, input = ROOT_INPUT } = {}) {
|
|
41
|
+
await this.#ensurePosition();
|
|
42
|
+
this.#input = input;
|
|
43
|
+
const startX = this.x;
|
|
44
|
+
const startY = this.y;
|
|
45
|
+
const buttons = this.#buttons();
|
|
46
|
+
const button = this.#pressed.size ? [...this.#pressed][0] : "none";
|
|
47
|
+
const moves = [];
|
|
48
|
+
for (let step = 1; step <= steps; step++) {
|
|
49
|
+
const nextX = startX + ((x - startX) * step) / steps;
|
|
50
|
+
const nextY = startY + ((y - startY) * step) / steps;
|
|
51
|
+
moves.push({ type: "mouseMoved", x: nextX - input.x, y: nextY - input.y, buttons, button });
|
|
52
|
+
}
|
|
53
|
+
if (moves.length === 1) await this.#dispatch({ type: "mouseMoved", x, y, buttons, button });
|
|
54
|
+
else await this.#page._dispatchBatch("Input.dispatchMouseEvent", moves, { sessionId: input.sessionId });
|
|
55
|
+
this.x = x;
|
|
56
|
+
this.y = y;
|
|
57
|
+
if (label !== false) await this.#page._showCursor(x, y, label);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async down({ button = "left", clickCount = 1, modifiers = [] } = {}) {
|
|
61
|
+
await this.#ensurePosition();
|
|
62
|
+
this.#pressed.add(button);
|
|
63
|
+
await this.#dispatch({
|
|
64
|
+
type: "mousePressed",
|
|
65
|
+
x: this.x,
|
|
66
|
+
y: this.y,
|
|
67
|
+
button,
|
|
68
|
+
buttons: this.#buttons(),
|
|
69
|
+
clickCount,
|
|
70
|
+
modifiers: modifierMask(modifiers),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async up({ button = "left", clickCount = 1, modifiers = [] } = {}) {
|
|
75
|
+
await this.#ensurePosition();
|
|
76
|
+
this.#pressed.delete(button);
|
|
77
|
+
await this.#dispatch({
|
|
78
|
+
type: "mouseReleased",
|
|
79
|
+
x: this.x,
|
|
80
|
+
y: this.y,
|
|
81
|
+
button,
|
|
82
|
+
buttons: this.#buttons(),
|
|
83
|
+
clickCount,
|
|
84
|
+
modifiers: modifierMask(modifiers),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async click(x, y, { button = "left", clickCount = 1, delay = 0, modifiers = [], label, input = ROOT_INPUT } = {}) {
|
|
89
|
+
if (!delay && !this.#pressed.size) {
|
|
90
|
+
await this.#ensurePosition();
|
|
91
|
+
this.#input = input;
|
|
92
|
+
const mask = modifierMask(modifiers);
|
|
93
|
+
const bit = button === "right" ? 2 : button === "middle" ? 4 : 1;
|
|
94
|
+
const local = { x: x - input.x, y: y - input.y };
|
|
95
|
+
const events = [{ type: "mouseMoved", ...local, buttons: 0, button: "none", modifiers: mask }];
|
|
96
|
+
for (let count = 1; count <= clickCount; count++) {
|
|
97
|
+
events.push({ type: "mousePressed", ...local, button, buttons: bit, clickCount: count, modifiers: mask });
|
|
98
|
+
events.push({ type: "mouseReleased", ...local, button, buttons: 0, clickCount: count, modifiers: mask });
|
|
99
|
+
}
|
|
100
|
+
await this.#page._dispatchBatch("Input.dispatchMouseEvent", events, { sessionId: input.sessionId });
|
|
101
|
+
this.x = x;
|
|
102
|
+
this.y = y;
|
|
103
|
+
if (label !== false) this.#page._showCursor(x, y, label);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
await this.move(x, y, { label, input });
|
|
107
|
+
for (let count = 1; count <= clickCount; count++) {
|
|
108
|
+
await this.down({ button, clickCount: count, modifiers });
|
|
109
|
+
if (delay) await sleep(delay);
|
|
110
|
+
await this.up({ button, clickCount: count, modifiers });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async dblclick(x, y, options = {}) {
|
|
115
|
+
await this.click(x, y, { ...options, clickCount: 2 });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async wheel(deltaX, deltaY, { label } = {}) {
|
|
119
|
+
await this.#ensurePosition();
|
|
120
|
+
if (label) await this.#page._showCursor(this.x, this.y, label);
|
|
121
|
+
await this.#dispatch({
|
|
122
|
+
type: "mouseWheel",
|
|
123
|
+
x: this.x,
|
|
124
|
+
y: this.y,
|
|
125
|
+
deltaX,
|
|
126
|
+
deltaY,
|
|
127
|
+
});
|
|
128
|
+
await this.#page._frame();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export class Keyboard {
|
|
133
|
+
#page;
|
|
134
|
+
#modifiers = new Set();
|
|
135
|
+
|
|
136
|
+
constructor(page) {
|
|
137
|
+
this.#page = page;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#mask() {
|
|
141
|
+
return modifierMask([...this.#modifiers]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async down(key, options) {
|
|
145
|
+
await this.#page._focusForKeys();
|
|
146
|
+
await this.#down(key, options);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async #down(key, { commands } = {}) {
|
|
150
|
+
const description = describeKey(key);
|
|
151
|
+
if (["Shift", "Control", "Alt", "Meta"].includes(key)) this.#modifiers.add(key);
|
|
152
|
+
const mask = this.#mask();
|
|
153
|
+
const sendText = description.text && !(mask & (1 | 2 | 4));
|
|
154
|
+
await this.#page.cdp("Input.dispatchKeyEvent", {
|
|
155
|
+
type: sendText ? "keyDown" : "rawKeyDown",
|
|
156
|
+
modifiers: mask,
|
|
157
|
+
key: description.key,
|
|
158
|
+
code: description.code,
|
|
159
|
+
windowsVirtualKeyCode: description.keyCode,
|
|
160
|
+
nativeVirtualKeyCode: description.keyCode,
|
|
161
|
+
text: sendText ? description.text : undefined,
|
|
162
|
+
unmodifiedText: sendText ? description.text : undefined,
|
|
163
|
+
commands,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async up(key) {
|
|
168
|
+
const description = describeKey(key);
|
|
169
|
+
if (["Shift", "Control", "Alt", "Meta"].includes(key)) this.#modifiers.delete(key);
|
|
170
|
+
await this.#page.cdp("Input.dispatchKeyEvent", {
|
|
171
|
+
type: "keyUp",
|
|
172
|
+
modifiers: this.#mask(),
|
|
173
|
+
key: description.key,
|
|
174
|
+
code: description.code,
|
|
175
|
+
windowsVirtualKeyCode: description.keyCode,
|
|
176
|
+
nativeVirtualKeyCode: description.keyCode,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async press(chord, { delay = 0 } = {}) {
|
|
181
|
+
const { modifiers, key } = parseChord(chord);
|
|
182
|
+
const command = macCommand(modifiers, key);
|
|
183
|
+
await this.#page._focusForKeys();
|
|
184
|
+
for (const modifier of modifiers) await this.#down(modifier);
|
|
185
|
+
await this.#down(key, { commands: command ? [command] : undefined });
|
|
186
|
+
if (delay) await sleep(delay);
|
|
187
|
+
await this.up(key);
|
|
188
|
+
for (const modifier of [...modifiers].reverse()) await this.up(modifier);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#keyEvents(char) {
|
|
192
|
+
const named = char === "\n" ? "Enter" : char === "\t" ? "Tab" : null;
|
|
193
|
+
const description = describeKey(named ?? char);
|
|
194
|
+
const text = named === "Enter" ? "\r" : named === "Tab" ? undefined : char;
|
|
195
|
+
const params = {
|
|
196
|
+
key: description.key,
|
|
197
|
+
code: description.code,
|
|
198
|
+
windowsVirtualKeyCode: description.keyCode,
|
|
199
|
+
nativeVirtualKeyCode: description.keyCode,
|
|
200
|
+
modifiers: description.shift ? 8 : 0,
|
|
201
|
+
};
|
|
202
|
+
return [
|
|
203
|
+
{ type: text ? "keyDown" : "rawKeyDown", ...(text ? { text, unmodifiedText: text } : {}), ...params },
|
|
204
|
+
{ type: "keyUp", ...params },
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async type(text, { delay = 0 } = {}) {
|
|
209
|
+
await this.#page._focusForKeys();
|
|
210
|
+
if (!delay) {
|
|
211
|
+
await this.#page._dispatchBatch("Input.dispatchKeyEvent", [...text].flatMap((char) => this.#keyEvents(char)));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
for (const char of text) {
|
|
215
|
+
await this.#page._dispatchBatch("Input.dispatchKeyEvent", this.#keyEvents(char));
|
|
216
|
+
await sleep(delay);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async insertText(text) {
|
|
221
|
+
await this.#page.cdp("Input.insertText", { text });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async paste(content) {
|
|
225
|
+
const text = typeof content === "string" ? content : content.text;
|
|
226
|
+
if (process.platform !== "darwin") {
|
|
227
|
+
await this.insertText(text);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let saved = null;
|
|
231
|
+
try {
|
|
232
|
+
saved = (await run("pbpaste", [], { encoding: "utf8" })).stdout;
|
|
233
|
+
} catch {}
|
|
234
|
+
await pbcopy(text);
|
|
235
|
+
try {
|
|
236
|
+
await this.press("Meta+v");
|
|
237
|
+
await sleep(150);
|
|
238
|
+
} finally {
|
|
239
|
+
if (saved !== null) await pbcopy(saved);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
package/lib/keys.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const MODIFIER_BITS = { Alt: 1, Control: 2, Meta: 4, Shift: 8 };
|
|
2
|
+
|
|
3
|
+
const NAMED_KEYS = {
|
|
4
|
+
Enter: { code: "Enter", keyCode: 13, text: "\r" },
|
|
5
|
+
Tab: { code: "Tab", keyCode: 9 },
|
|
6
|
+
Backspace: { code: "Backspace", keyCode: 8 },
|
|
7
|
+
Delete: { code: "Delete", keyCode: 46 },
|
|
8
|
+
Escape: { code: "Escape", keyCode: 27 },
|
|
9
|
+
Space: { key: " ", code: "Space", keyCode: 32, text: " " },
|
|
10
|
+
ArrowUp: { code: "ArrowUp", keyCode: 38 },
|
|
11
|
+
ArrowDown: { code: "ArrowDown", keyCode: 40 },
|
|
12
|
+
ArrowLeft: { code: "ArrowLeft", keyCode: 37 },
|
|
13
|
+
ArrowRight: { code: "ArrowRight", keyCode: 39 },
|
|
14
|
+
Home: { code: "Home", keyCode: 36 },
|
|
15
|
+
End: { code: "End", keyCode: 35 },
|
|
16
|
+
PageUp: { code: "PageUp", keyCode: 33 },
|
|
17
|
+
PageDown: { code: "PageDown", keyCode: 34 },
|
|
18
|
+
Insert: { code: "Insert", keyCode: 45 },
|
|
19
|
+
Shift: { code: "ShiftLeft", keyCode: 16 },
|
|
20
|
+
Control: { code: "ControlLeft", keyCode: 17 },
|
|
21
|
+
Alt: { code: "AltLeft", keyCode: 18 },
|
|
22
|
+
Meta: { code: "MetaLeft", keyCode: 91 },
|
|
23
|
+
CapsLock: { code: "CapsLock", keyCode: 20 },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
for (let index = 1; index <= 12; index++) {
|
|
27
|
+
NAMED_KEYS[`F${index}`] = { code: `F${index}`, keyCode: 111 + index };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const PUNCTUATION = {
|
|
31
|
+
"-": ["Minus", 189, "_"],
|
|
32
|
+
"=": ["Equal", 187, "+"],
|
|
33
|
+
"[": ["BracketLeft", 219, "{"],
|
|
34
|
+
"]": ["BracketRight", 221, "}"],
|
|
35
|
+
"\\": ["Backslash", 220, "|"],
|
|
36
|
+
";": ["Semicolon", 186, ":"],
|
|
37
|
+
"'": ["Quote", 222, '"'],
|
|
38
|
+
",": ["Comma", 188, "<"],
|
|
39
|
+
".": ["Period", 190, ">"],
|
|
40
|
+
"/": ["Slash", 191, "?"],
|
|
41
|
+
"`": ["Backquote", 192, "~"],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const DIGIT_SHIFTED = ")!@#$%^&*(";
|
|
45
|
+
|
|
46
|
+
const MAC_COMMANDS = {
|
|
47
|
+
"Meta+a": "selectAll",
|
|
48
|
+
"Meta+c": "copy",
|
|
49
|
+
"Meta+x": "cut",
|
|
50
|
+
"Meta+v": "paste",
|
|
51
|
+
"Meta+z": "undo",
|
|
52
|
+
"Meta+Shift+z": "redo",
|
|
53
|
+
"Meta+Backspace": "deleteToBeginningOfLine",
|
|
54
|
+
"Alt+Backspace": "deleteWordBackward",
|
|
55
|
+
"Meta+ArrowLeft": "moveToBeginningOfLine",
|
|
56
|
+
"Meta+ArrowRight": "moveToEndOfLine",
|
|
57
|
+
"Meta+ArrowUp": "moveToBeginningOfDocument",
|
|
58
|
+
"Meta+ArrowDown": "moveToEndOfDocument",
|
|
59
|
+
"Alt+ArrowLeft": "moveWordLeft",
|
|
60
|
+
"Alt+ArrowRight": "moveWordRight",
|
|
61
|
+
"Meta+Shift+ArrowLeft": "moveToBeginningOfLineAndModifySelection",
|
|
62
|
+
"Meta+Shift+ArrowRight": "moveToEndOfLineAndModifySelection",
|
|
63
|
+
"Shift+ArrowLeft": "moveLeftAndModifySelection",
|
|
64
|
+
"Shift+ArrowRight": "moveRightAndModifySelection",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function describeKey(key) {
|
|
68
|
+
if (NAMED_KEYS[key]) {
|
|
69
|
+
const entry = NAMED_KEYS[key];
|
|
70
|
+
return { key: entry.key ?? key, code: entry.code, keyCode: entry.keyCode, text: entry.text };
|
|
71
|
+
}
|
|
72
|
+
if (key.length !== 1) throw new Error(`Unknown key: ${key}`);
|
|
73
|
+
if (/[a-z]/.test(key)) return { key, code: `Key${key.toUpperCase()}`, keyCode: key.toUpperCase().charCodeAt(0), text: key };
|
|
74
|
+
if (/[A-Z]/.test(key)) return { key, code: `Key${key}`, keyCode: key.charCodeAt(0), text: key, shift: true };
|
|
75
|
+
if (/[0-9]/.test(key)) return { key, code: `Digit${key}`, keyCode: key.charCodeAt(0), text: key };
|
|
76
|
+
const shiftedDigit = DIGIT_SHIFTED.indexOf(key);
|
|
77
|
+
if (shiftedDigit >= 0) return { key, code: `Digit${shiftedDigit}`, keyCode: 48 + shiftedDigit, text: key, shift: true };
|
|
78
|
+
if (PUNCTUATION[key]) {
|
|
79
|
+
const [code, keyCode] = PUNCTUATION[key];
|
|
80
|
+
return { key, code, keyCode, text: key };
|
|
81
|
+
}
|
|
82
|
+
for (const [code, keyCode, shifted] of Object.values(PUNCTUATION)) {
|
|
83
|
+
if (shifted === key) return { key, code, keyCode, text: key, shift: true };
|
|
84
|
+
}
|
|
85
|
+
if (key === " ") return { key, code: "Space", keyCode: 32, text: key };
|
|
86
|
+
return { key, code: "", keyCode: 0, text: key };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function parseChord(chord) {
|
|
90
|
+
const parts = chord === "+" ? ["+"] : chord.split(/\+(?!$)/);
|
|
91
|
+
const key = parts.pop();
|
|
92
|
+
const modifiers = parts.map((part) => (part === "ControlOrMeta" ? (process.platform === "darwin" ? "Meta" : "Control") : part));
|
|
93
|
+
for (const modifier of modifiers) {
|
|
94
|
+
if (!(modifier in MODIFIER_BITS)) throw new Error(`Unknown modifier: ${modifier}`);
|
|
95
|
+
}
|
|
96
|
+
return { modifiers, key };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function modifierMask(modifiers) {
|
|
100
|
+
return modifiers.reduce((mask, modifier) => mask | MODIFIER_BITS[modifier], 0);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function macCommand(modifiers, key) {
|
|
104
|
+
if (process.platform !== "darwin") return null;
|
|
105
|
+
const order = ["Meta", "Alt", "Control", "Shift"];
|
|
106
|
+
const sorted = order.filter((modifier) => modifiers.includes(modifier));
|
|
107
|
+
const normalized = [...sorted, key.length === 1 ? key.toLowerCase() : key].join("+");
|
|
108
|
+
return MAC_COMMANDS[normalized] ?? null;
|
|
109
|
+
}
|