@jackwener/opencli 1.0.1 → 1.0.3
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-extension.yml +62 -0
- package/.github/workflows/ci.yml +6 -6
- package/.github/workflows/e2e-headed.yml +2 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release.yml +2 -5
- package/.github/workflows/security.yml +2 -2
- package/CDP.md +1 -1
- package/CDP.zh-CN.md +1 -1
- package/README.md +15 -7
- package/README.zh-CN.md +15 -7
- package/SKILL.md +3 -5
- package/dist/browser/cdp.d.ts +27 -0
- package/dist/browser/cdp.js +295 -0
- package/dist/browser/index.d.ts +3 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/page.js +2 -23
- package/dist/browser/utils.d.ts +10 -0
- package/dist/browser/utils.js +27 -0
- package/dist/browser.test.js +42 -1
- package/dist/chaoxing.d.ts +58 -0
- package/dist/chaoxing.js +225 -0
- package/dist/chaoxing.test.d.ts +1 -0
- package/dist/chaoxing.test.js +38 -0
- package/dist/cli-manifest.json +203 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +197 -0
- package/dist/clis/boss/chatlist.d.ts +1 -0
- package/dist/clis/boss/chatlist.js +50 -0
- package/dist/clis/boss/chatmsg.d.ts +1 -0
- package/dist/clis/boss/chatmsg.js +73 -0
- package/dist/clis/boss/send.d.ts +1 -0
- package/dist/clis/boss/send.js +176 -0
- package/dist/clis/chaoxing/assignments.d.ts +1 -0
- package/dist/clis/chaoxing/assignments.js +74 -0
- package/dist/clis/chaoxing/exams.d.ts +1 -0
- package/dist/clis/chaoxing/exams.js +74 -0
- package/dist/clis/chatgpt/ask.js +15 -14
- package/dist/clis/chatgpt/ax.d.ts +1 -0
- package/dist/clis/chatgpt/ax.js +78 -0
- package/dist/clis/chatgpt/read.js +5 -6
- package/dist/clis/twitter/post.js +9 -2
- package/dist/clis/twitter/search.js +14 -33
- package/dist/clis/xiaohongshu/download.d.ts +1 -1
- package/dist/clis/xiaohongshu/download.js +1 -1
- package/dist/engine.js +24 -13
- package/dist/explore.js +46 -101
- package/dist/main.js +4 -193
- package/dist/output.d.ts +1 -1
- package/dist/registry.d.ts +3 -3
- package/dist/scripts/framework.d.ts +4 -0
- package/dist/scripts/framework.js +21 -0
- package/dist/scripts/interact.d.ts +4 -0
- package/dist/scripts/interact.js +20 -0
- package/dist/scripts/store.d.ts +9 -0
- package/dist/scripts/store.js +44 -0
- package/dist/synthesize.js +1 -1
- package/extension/dist/background.js +338 -430
- package/extension/manifest.json +2 -2
- package/extension/src/background.ts +2 -2
- package/package.json +1 -1
- package/src/browser/cdp.ts +295 -0
- package/src/browser/index.ts +4 -0
- package/src/browser/page.ts +2 -24
- package/src/browser/utils.ts +27 -0
- package/src/browser.test.ts +46 -0
- package/src/chaoxing.test.ts +45 -0
- package/src/chaoxing.ts +268 -0
- package/src/cli.ts +185 -0
- package/src/clis/antigravity/SKILL.md +5 -0
- package/src/clis/boss/chatlist.ts +50 -0
- package/src/clis/boss/chatmsg.ts +70 -0
- package/src/clis/boss/send.ts +193 -0
- package/src/clis/chaoxing/README.md +36 -0
- package/src/clis/chaoxing/README.zh-CN.md +35 -0
- package/src/clis/chaoxing/assignments.ts +88 -0
- package/src/clis/chaoxing/exams.ts +88 -0
- package/src/clis/chatgpt/ask.ts +14 -15
- package/src/clis/chatgpt/ax.ts +81 -0
- package/src/clis/chatgpt/read.ts +5 -7
- package/src/clis/twitter/post.ts +9 -2
- package/src/clis/twitter/search.ts +15 -33
- package/src/clis/xiaohongshu/download.ts +1 -1
- package/src/engine.ts +20 -13
- package/src/explore.ts +51 -100
- package/src/main.ts +4 -180
- package/src/output.ts +12 -12
- package/src/registry.ts +3 -3
- package/src/scripts/framework.ts +20 -0
- package/src/scripts/interact.ts +22 -0
- package/src/scripts/store.ts +40 -0
- package/src/synthesize.ts +1 -1
|
@@ -1,484 +1,392 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var WS_RECONNECT_BASE_DELAY = 2e3;
|
|
9
|
-
/** Max reconnect delay (ms) */
|
|
10
|
-
var WS_RECONNECT_MAX_DELAY = 6e4;
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region src/cdp.ts
|
|
13
|
-
/**
|
|
14
|
-
* CDP execution via chrome.debugger API.
|
|
15
|
-
*
|
|
16
|
-
* chrome.debugger only needs the "debugger" permission — no host_permissions.
|
|
17
|
-
* It can attach to any http/https tab. Avoid chrome:// and chrome-extension://
|
|
18
|
-
* tabs (resolveTabId in background.ts filters them).
|
|
19
|
-
*/
|
|
20
|
-
var attached = /* @__PURE__ */ new Set();
|
|
1
|
+
const DAEMON_PORT = 19825;
|
|
2
|
+
const DAEMON_HOST = "localhost";
|
|
3
|
+
const DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
|
|
4
|
+
const WS_RECONNECT_BASE_DELAY = 2e3;
|
|
5
|
+
const WS_RECONNECT_MAX_DELAY = 6e4;
|
|
6
|
+
|
|
7
|
+
const attached = /* @__PURE__ */ new Set();
|
|
21
8
|
async function ensureAttached(tabId) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
9
|
+
if (attached.has(tabId)) return;
|
|
10
|
+
try {
|
|
11
|
+
await chrome.debugger.attach({ tabId }, "1.3");
|
|
12
|
+
} catch (e) {
|
|
13
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
14
|
+
if (msg.includes("Another debugger is already attached")) {
|
|
15
|
+
try {
|
|
16
|
+
await chrome.debugger.detach({ tabId });
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
await chrome.debugger.attach({ tabId }, "1.3");
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error(`attach failed: ${msg}`);
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error(`attach failed: ${msg}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
attached.add(tabId);
|
|
29
|
+
try {
|
|
30
|
+
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
42
33
|
}
|
|
43
34
|
async function evaluate(tabId, expression) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
35
|
+
await ensureAttached(tabId);
|
|
36
|
+
const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
|
|
37
|
+
expression,
|
|
38
|
+
returnByValue: true,
|
|
39
|
+
awaitPromise: true
|
|
40
|
+
});
|
|
41
|
+
if (result.exceptionDetails) {
|
|
42
|
+
const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
|
|
43
|
+
throw new Error(errMsg);
|
|
44
|
+
}
|
|
45
|
+
return result.result?.value;
|
|
55
46
|
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Capture a screenshot via CDP Page.captureScreenshot.
|
|
59
|
-
* Returns base64-encoded image data.
|
|
60
|
-
*/
|
|
47
|
+
const evaluateAsync = evaluate;
|
|
61
48
|
async function screenshot(tabId, options = {}) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
49
|
+
await ensureAttached(tabId);
|
|
50
|
+
const format = options.format ?? "png";
|
|
51
|
+
if (options.fullPage) {
|
|
52
|
+
const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics");
|
|
53
|
+
const size = metrics.cssContentSize || metrics.contentSize;
|
|
54
|
+
if (size) {
|
|
55
|
+
await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
|
|
56
|
+
mobile: false,
|
|
57
|
+
width: Math.ceil(size.width),
|
|
58
|
+
height: Math.ceil(size.height),
|
|
59
|
+
deviceScaleFactor: 1
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const params = { format };
|
|
65
|
+
if (format === "jpeg" && options.quality !== void 0) {
|
|
66
|
+
params.quality = Math.max(0, Math.min(100, options.quality));
|
|
67
|
+
}
|
|
68
|
+
const result = await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params);
|
|
69
|
+
return result.data;
|
|
70
|
+
} finally {
|
|
71
|
+
if (options.fullPage) {
|
|
72
|
+
await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
81
76
|
}
|
|
82
77
|
function detach(tabId) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
if (!attached.has(tabId)) return;
|
|
79
|
+
attached.delete(tabId);
|
|
80
|
+
try {
|
|
81
|
+
chrome.debugger.detach({ tabId });
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
88
84
|
}
|
|
89
85
|
function registerListeners() {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
86
|
+
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
87
|
+
attached.delete(tabId);
|
|
88
|
+
});
|
|
89
|
+
chrome.debugger.onDetach.addListener((source) => {
|
|
90
|
+
if (source.tabId) attached.delete(source.tabId);
|
|
91
|
+
});
|
|
96
92
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
var _origError = console.error.bind(console);
|
|
93
|
+
|
|
94
|
+
let ws = null;
|
|
95
|
+
let reconnectTimer = null;
|
|
96
|
+
let reconnectAttempts = 0;
|
|
97
|
+
const _origLog = console.log.bind(console);
|
|
98
|
+
const _origWarn = console.warn.bind(console);
|
|
99
|
+
const _origError = console.error.bind(console);
|
|
105
100
|
function forwardLog(level, args) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
msg,
|
|
113
|
-
ts: Date.now()
|
|
114
|
-
}));
|
|
115
|
-
} catch {}
|
|
101
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
102
|
+
try {
|
|
103
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
104
|
+
ws.send(JSON.stringify({ type: "log", level, msg, ts: Date.now() }));
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
116
107
|
}
|
|
117
108
|
console.log = (...args) => {
|
|
118
|
-
|
|
119
|
-
|
|
109
|
+
_origLog(...args);
|
|
110
|
+
forwardLog("info", args);
|
|
120
111
|
};
|
|
121
112
|
console.warn = (...args) => {
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
_origWarn(...args);
|
|
114
|
+
forwardLog("warn", args);
|
|
124
115
|
};
|
|
125
116
|
console.error = (...args) => {
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
_origError(...args);
|
|
118
|
+
forwardLog("error", args);
|
|
128
119
|
};
|
|
129
120
|
function connect() {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
121
|
+
if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
|
|
122
|
+
try {
|
|
123
|
+
ws = new WebSocket(DAEMON_WS_URL);
|
|
124
|
+
} catch {
|
|
125
|
+
scheduleReconnect();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
ws.onopen = () => {
|
|
129
|
+
console.log("[opencli] Connected to daemon");
|
|
130
|
+
reconnectAttempts = 0;
|
|
131
|
+
if (reconnectTimer) {
|
|
132
|
+
clearTimeout(reconnectTimer);
|
|
133
|
+
reconnectTimer = null;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
ws.onmessage = async (event) => {
|
|
137
|
+
try {
|
|
138
|
+
const command = JSON.parse(event.data);
|
|
139
|
+
const result = await handleCommand(command);
|
|
140
|
+
ws?.send(JSON.stringify(result));
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error("[opencli] Message handling error:", err);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
ws.onclose = () => {
|
|
146
|
+
console.log("[opencli] Disconnected from daemon");
|
|
147
|
+
ws = null;
|
|
148
|
+
scheduleReconnect();
|
|
149
|
+
};
|
|
150
|
+
ws.onerror = () => {
|
|
151
|
+
ws?.close();
|
|
152
|
+
};
|
|
161
153
|
}
|
|
162
154
|
function scheduleReconnect() {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
if (reconnectTimer) return;
|
|
156
|
+
reconnectAttempts++;
|
|
157
|
+
const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
|
|
158
|
+
reconnectTimer = setTimeout(() => {
|
|
159
|
+
reconnectTimer = null;
|
|
160
|
+
connect();
|
|
161
|
+
}, delay);
|
|
170
162
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
163
|
+
let automationWindowId = null;
|
|
164
|
+
let windowIdleTimer = null;
|
|
165
|
+
const WINDOW_IDLE_TIMEOUT = 3e4;
|
|
174
166
|
function resetWindowIdleTimer() {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
167
|
+
if (windowIdleTimer) clearTimeout(windowIdleTimer);
|
|
168
|
+
windowIdleTimer = setTimeout(async () => {
|
|
169
|
+
if (automationWindowId !== null) {
|
|
170
|
+
try {
|
|
171
|
+
await chrome.windows.remove(automationWindowId);
|
|
172
|
+
console.log(`[opencli] Automation window ${automationWindowId} closed (idle timeout)`);
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
automationWindowId = null;
|
|
176
|
+
}
|
|
177
|
+
windowIdleTimer = null;
|
|
178
|
+
}, WINDOW_IDLE_TIMEOUT);
|
|
186
179
|
}
|
|
187
|
-
/** Get or create the dedicated automation window. */
|
|
188
180
|
async function getAutomationWindow() {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
181
|
+
if (automationWindowId !== null) {
|
|
182
|
+
try {
|
|
183
|
+
await chrome.windows.get(automationWindowId);
|
|
184
|
+
return automationWindowId;
|
|
185
|
+
} catch {
|
|
186
|
+
automationWindowId = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const win = await chrome.windows.create({
|
|
190
|
+
url: "about:blank",
|
|
191
|
+
focused: false,
|
|
192
|
+
width: 1280,
|
|
193
|
+
height: 900,
|
|
194
|
+
type: "normal"
|
|
195
|
+
});
|
|
196
|
+
automationWindowId = win.id;
|
|
197
|
+
console.log(`[opencli] Created automation window ${automationWindowId}`);
|
|
198
|
+
return automationWindowId;
|
|
204
199
|
}
|
|
205
200
|
chrome.windows.onRemoved.addListener((windowId) => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
201
|
+
if (windowId === automationWindowId) {
|
|
202
|
+
console.log("[opencli] Automation window closed");
|
|
203
|
+
automationWindowId = null;
|
|
204
|
+
if (windowIdleTimer) {
|
|
205
|
+
clearTimeout(windowIdleTimer);
|
|
206
|
+
windowIdleTimer = null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
214
209
|
});
|
|
215
|
-
|
|
210
|
+
let initialized = false;
|
|
216
211
|
function initialize() {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
212
|
+
if (initialized) return;
|
|
213
|
+
initialized = true;
|
|
214
|
+
chrome.alarms.create("keepalive", { periodInMinutes: 0.4 });
|
|
215
|
+
registerListeners();
|
|
216
|
+
connect();
|
|
217
|
+
console.log("[opencli] OpenCLI extension initialized");
|
|
223
218
|
}
|
|
224
219
|
chrome.runtime.onInstalled.addListener(() => {
|
|
225
|
-
|
|
220
|
+
initialize();
|
|
226
221
|
});
|
|
227
222
|
chrome.runtime.onStartup.addListener(() => {
|
|
228
|
-
|
|
223
|
+
initialize();
|
|
229
224
|
});
|
|
230
225
|
chrome.alarms.onAlarm.addListener((alarm) => {
|
|
231
|
-
|
|
226
|
+
if (alarm.name === "keepalive") connect();
|
|
232
227
|
});
|
|
233
228
|
async function handleCommand(cmd) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
229
|
+
resetWindowIdleTimer();
|
|
230
|
+
try {
|
|
231
|
+
switch (cmd.action) {
|
|
232
|
+
case "exec":
|
|
233
|
+
return await handleExec(cmd);
|
|
234
|
+
case "navigate":
|
|
235
|
+
return await handleNavigate(cmd);
|
|
236
|
+
case "tabs":
|
|
237
|
+
return await handleTabs(cmd);
|
|
238
|
+
case "cookies":
|
|
239
|
+
return await handleCookies(cmd);
|
|
240
|
+
case "screenshot":
|
|
241
|
+
return await handleScreenshot(cmd);
|
|
242
|
+
case "close-window":
|
|
243
|
+
return await handleCloseWindow(cmd);
|
|
244
|
+
default:
|
|
245
|
+
return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
|
|
246
|
+
}
|
|
247
|
+
} catch (err) {
|
|
248
|
+
return {
|
|
249
|
+
id: cmd.id,
|
|
250
|
+
ok: false,
|
|
251
|
+
error: err instanceof Error ? err.message : String(err)
|
|
252
|
+
};
|
|
253
|
+
}
|
|
256
254
|
}
|
|
257
|
-
/** Check if a URL is a debuggable web page (not chrome:// or extension page) */
|
|
258
255
|
function isWebUrl(url) {
|
|
259
|
-
|
|
260
|
-
|
|
256
|
+
if (!url) return false;
|
|
257
|
+
return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
|
|
261
258
|
}
|
|
262
|
-
/**
|
|
263
|
-
* Resolve target tab in the automation window.
|
|
264
|
-
* If explicit tabId is given, use that directly.
|
|
265
|
-
* Otherwise, find or create a tab in the dedicated automation window.
|
|
266
|
-
*/
|
|
267
259
|
async function resolveTabId(tabId) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
active: true
|
|
278
|
-
});
|
|
279
|
-
if (!newTab.id) throw new Error("Failed to create tab in automation window");
|
|
280
|
-
return newTab.id;
|
|
260
|
+
if (tabId !== void 0) return tabId;
|
|
261
|
+
const windowId = await getAutomationWindow();
|
|
262
|
+
const tabs = await chrome.tabs.query({ windowId });
|
|
263
|
+
const webTab = tabs.find((t) => t.id && isWebUrl(t.url));
|
|
264
|
+
if (webTab?.id) return webTab.id;
|
|
265
|
+
if (tabs.length > 0 && tabs[0]?.id) return tabs[0].id;
|
|
266
|
+
const newTab = await chrome.tabs.create({ windowId, url: "about:blank", active: true });
|
|
267
|
+
if (!newTab.id) throw new Error("Failed to create tab in automation window");
|
|
268
|
+
return newTab.id;
|
|
281
269
|
}
|
|
282
270
|
async function handleExec(cmd) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return {
|
|
292
|
-
id: cmd.id,
|
|
293
|
-
ok: true,
|
|
294
|
-
data
|
|
295
|
-
};
|
|
296
|
-
} catch (err) {
|
|
297
|
-
return {
|
|
298
|
-
id: cmd.id,
|
|
299
|
-
ok: false,
|
|
300
|
-
error: err instanceof Error ? err.message : String(err)
|
|
301
|
-
};
|
|
302
|
-
}
|
|
271
|
+
if (!cmd.code) return { id: cmd.id, ok: false, error: "Missing code" };
|
|
272
|
+
const tabId = await resolveTabId(cmd.tabId);
|
|
273
|
+
try {
|
|
274
|
+
const data = await evaluateAsync(tabId, cmd.code);
|
|
275
|
+
return { id: cmd.id, ok: true, data };
|
|
276
|
+
} catch (err) {
|
|
277
|
+
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
278
|
+
}
|
|
303
279
|
}
|
|
304
280
|
async function handleNavigate(cmd) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
const tab = await chrome.tabs.get(tabId);
|
|
332
|
-
return {
|
|
333
|
-
id: cmd.id,
|
|
334
|
-
ok: true,
|
|
335
|
-
data: {
|
|
336
|
-
title: tab.title,
|
|
337
|
-
url: tab.url,
|
|
338
|
-
tabId
|
|
339
|
-
}
|
|
340
|
-
};
|
|
281
|
+
if (!cmd.url) return { id: cmd.id, ok: false, error: "Missing url" };
|
|
282
|
+
const tabId = await resolveTabId(cmd.tabId);
|
|
283
|
+
await chrome.tabs.update(tabId, { url: cmd.url });
|
|
284
|
+
await new Promise((resolve) => {
|
|
285
|
+
chrome.tabs.get(tabId).then((tab2) => {
|
|
286
|
+
if (tab2.status === "complete") {
|
|
287
|
+
resolve();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const listener = (id, info) => {
|
|
291
|
+
if (id === tabId && info.status === "complete") {
|
|
292
|
+
chrome.tabs.onUpdated.removeListener(listener);
|
|
293
|
+
resolve();
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
chrome.tabs.onUpdated.addListener(listener);
|
|
297
|
+
setTimeout(() => {
|
|
298
|
+
chrome.tabs.onUpdated.removeListener(listener);
|
|
299
|
+
resolve();
|
|
300
|
+
}, 15e3);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
const tab = await chrome.tabs.get(tabId);
|
|
304
|
+
return { id: cmd.id, ok: true, data: { title: tab.title, url: tab.url, tabId } };
|
|
341
305
|
}
|
|
342
306
|
async function handleTabs(cmd) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
await chrome.tabs.remove(tabId);
|
|
390
|
-
detach(tabId);
|
|
391
|
-
return {
|
|
392
|
-
id: cmd.id,
|
|
393
|
-
ok: true,
|
|
394
|
-
data: { closed: tabId }
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
case "select": {
|
|
398
|
-
if (cmd.index === void 0 && cmd.tabId === void 0) return {
|
|
399
|
-
id: cmd.id,
|
|
400
|
-
ok: false,
|
|
401
|
-
error: "Missing index or tabId"
|
|
402
|
-
};
|
|
403
|
-
if (cmd.tabId !== void 0) {
|
|
404
|
-
await chrome.tabs.update(cmd.tabId, { active: true });
|
|
405
|
-
return {
|
|
406
|
-
id: cmd.id,
|
|
407
|
-
ok: true,
|
|
408
|
-
data: { selected: cmd.tabId }
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
const target = (await chrome.tabs.query({}))[cmd.index];
|
|
412
|
-
if (!target?.id) return {
|
|
413
|
-
id: cmd.id,
|
|
414
|
-
ok: false,
|
|
415
|
-
error: `Tab index ${cmd.index} not found`
|
|
416
|
-
};
|
|
417
|
-
await chrome.tabs.update(target.id, { active: true });
|
|
418
|
-
return {
|
|
419
|
-
id: cmd.id,
|
|
420
|
-
ok: true,
|
|
421
|
-
data: { selected: target.id }
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
default: return {
|
|
425
|
-
id: cmd.id,
|
|
426
|
-
ok: false,
|
|
427
|
-
error: `Unknown tabs op: ${cmd.op}`
|
|
428
|
-
};
|
|
429
|
-
}
|
|
307
|
+
switch (cmd.op) {
|
|
308
|
+
case "list": {
|
|
309
|
+
const tabs = await chrome.tabs.query({});
|
|
310
|
+
const data = tabs.filter((t) => isWebUrl(t.url)).map((t, i) => ({
|
|
311
|
+
index: i,
|
|
312
|
+
tabId: t.id,
|
|
313
|
+
url: t.url,
|
|
314
|
+
title: t.title,
|
|
315
|
+
active: t.active
|
|
316
|
+
}));
|
|
317
|
+
return { id: cmd.id, ok: true, data };
|
|
318
|
+
}
|
|
319
|
+
case "new": {
|
|
320
|
+
const tab = await chrome.tabs.create({ url: cmd.url, active: true });
|
|
321
|
+
return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
|
|
322
|
+
}
|
|
323
|
+
case "close": {
|
|
324
|
+
if (cmd.index !== void 0) {
|
|
325
|
+
const tabs = await chrome.tabs.query({});
|
|
326
|
+
const target = tabs[cmd.index];
|
|
327
|
+
if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
|
|
328
|
+
await chrome.tabs.remove(target.id);
|
|
329
|
+
detach(target.id);
|
|
330
|
+
return { id: cmd.id, ok: true, data: { closed: target.id } };
|
|
331
|
+
}
|
|
332
|
+
const tabId = await resolveTabId(cmd.tabId);
|
|
333
|
+
await chrome.tabs.remove(tabId);
|
|
334
|
+
detach(tabId);
|
|
335
|
+
return { id: cmd.id, ok: true, data: { closed: tabId } };
|
|
336
|
+
}
|
|
337
|
+
case "select": {
|
|
338
|
+
if (cmd.index === void 0 && cmd.tabId === void 0)
|
|
339
|
+
return { id: cmd.id, ok: false, error: "Missing index or tabId" };
|
|
340
|
+
if (cmd.tabId !== void 0) {
|
|
341
|
+
await chrome.tabs.update(cmd.tabId, { active: true });
|
|
342
|
+
return { id: cmd.id, ok: true, data: { selected: cmd.tabId } };
|
|
343
|
+
}
|
|
344
|
+
const tabs = await chrome.tabs.query({});
|
|
345
|
+
const target = tabs[cmd.index];
|
|
346
|
+
if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
|
|
347
|
+
await chrome.tabs.update(target.id, { active: true });
|
|
348
|
+
return { id: cmd.id, ok: true, data: { selected: target.id } };
|
|
349
|
+
}
|
|
350
|
+
default:
|
|
351
|
+
return { id: cmd.id, ok: false, error: `Unknown tabs op: ${cmd.op}` };
|
|
352
|
+
}
|
|
430
353
|
}
|
|
431
354
|
async function handleCookies(cmd) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
ok: true,
|
|
447
|
-
data
|
|
448
|
-
};
|
|
355
|
+
const details = {};
|
|
356
|
+
if (cmd.domain) details.domain = cmd.domain;
|
|
357
|
+
if (cmd.url) details.url = cmd.url;
|
|
358
|
+
const cookies = await chrome.cookies.getAll(details);
|
|
359
|
+
const data = cookies.map((c) => ({
|
|
360
|
+
name: c.name,
|
|
361
|
+
value: c.value,
|
|
362
|
+
domain: c.domain,
|
|
363
|
+
path: c.path,
|
|
364
|
+
secure: c.secure,
|
|
365
|
+
httpOnly: c.httpOnly,
|
|
366
|
+
expirationDate: c.expirationDate
|
|
367
|
+
}));
|
|
368
|
+
return { id: cmd.id, ok: true, data };
|
|
449
369
|
}
|
|
450
370
|
async function handleScreenshot(cmd) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
};
|
|
463
|
-
} catch (err) {
|
|
464
|
-
return {
|
|
465
|
-
id: cmd.id,
|
|
466
|
-
ok: false,
|
|
467
|
-
error: err instanceof Error ? err.message : String(err)
|
|
468
|
-
};
|
|
469
|
-
}
|
|
371
|
+
const tabId = await resolveTabId(cmd.tabId);
|
|
372
|
+
try {
|
|
373
|
+
const data = await screenshot(tabId, {
|
|
374
|
+
format: cmd.format,
|
|
375
|
+
quality: cmd.quality,
|
|
376
|
+
fullPage: cmd.fullPage
|
|
377
|
+
});
|
|
378
|
+
return { id: cmd.id, ok: true, data };
|
|
379
|
+
} catch (err) {
|
|
380
|
+
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
381
|
+
}
|
|
470
382
|
}
|
|
471
383
|
async function handleCloseWindow(cmd) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
ok: true,
|
|
481
|
-
data: { closed: true }
|
|
482
|
-
};
|
|
384
|
+
if (automationWindowId !== null) {
|
|
385
|
+
try {
|
|
386
|
+
await chrome.windows.remove(automationWindowId);
|
|
387
|
+
} catch {
|
|
388
|
+
}
|
|
389
|
+
automationWindowId = null;
|
|
390
|
+
}
|
|
391
|
+
return { id: cmd.id, ok: true, data: { closed: true } };
|
|
483
392
|
}
|
|
484
|
-
//#endregion
|