@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.
Files changed (91) hide show
  1. package/.github/workflows/build-extension.yml +62 -0
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/e2e-headed.yml +2 -2
  4. package/.github/workflows/pkg-pr-new.yml +2 -2
  5. package/.github/workflows/release.yml +2 -5
  6. package/.github/workflows/security.yml +2 -2
  7. package/CDP.md +1 -1
  8. package/CDP.zh-CN.md +1 -1
  9. package/README.md +15 -7
  10. package/README.zh-CN.md +15 -7
  11. package/SKILL.md +3 -5
  12. package/dist/browser/cdp.d.ts +27 -0
  13. package/dist/browser/cdp.js +295 -0
  14. package/dist/browser/index.d.ts +3 -0
  15. package/dist/browser/index.js +4 -0
  16. package/dist/browser/page.js +2 -23
  17. package/dist/browser/utils.d.ts +10 -0
  18. package/dist/browser/utils.js +27 -0
  19. package/dist/browser.test.js +42 -1
  20. package/dist/chaoxing.d.ts +58 -0
  21. package/dist/chaoxing.js +225 -0
  22. package/dist/chaoxing.test.d.ts +1 -0
  23. package/dist/chaoxing.test.js +38 -0
  24. package/dist/cli-manifest.json +203 -0
  25. package/dist/cli.d.ts +1 -0
  26. package/dist/cli.js +197 -0
  27. package/dist/clis/boss/chatlist.d.ts +1 -0
  28. package/dist/clis/boss/chatlist.js +50 -0
  29. package/dist/clis/boss/chatmsg.d.ts +1 -0
  30. package/dist/clis/boss/chatmsg.js +73 -0
  31. package/dist/clis/boss/send.d.ts +1 -0
  32. package/dist/clis/boss/send.js +176 -0
  33. package/dist/clis/chaoxing/assignments.d.ts +1 -0
  34. package/dist/clis/chaoxing/assignments.js +74 -0
  35. package/dist/clis/chaoxing/exams.d.ts +1 -0
  36. package/dist/clis/chaoxing/exams.js +74 -0
  37. package/dist/clis/chatgpt/ask.js +15 -14
  38. package/dist/clis/chatgpt/ax.d.ts +1 -0
  39. package/dist/clis/chatgpt/ax.js +78 -0
  40. package/dist/clis/chatgpt/read.js +5 -6
  41. package/dist/clis/twitter/post.js +9 -2
  42. package/dist/clis/twitter/search.js +14 -33
  43. package/dist/clis/xiaohongshu/download.d.ts +1 -1
  44. package/dist/clis/xiaohongshu/download.js +1 -1
  45. package/dist/engine.js +24 -13
  46. package/dist/explore.js +46 -101
  47. package/dist/main.js +4 -193
  48. package/dist/output.d.ts +1 -1
  49. package/dist/registry.d.ts +3 -3
  50. package/dist/scripts/framework.d.ts +4 -0
  51. package/dist/scripts/framework.js +21 -0
  52. package/dist/scripts/interact.d.ts +4 -0
  53. package/dist/scripts/interact.js +20 -0
  54. package/dist/scripts/store.d.ts +9 -0
  55. package/dist/scripts/store.js +44 -0
  56. package/dist/synthesize.js +1 -1
  57. package/extension/dist/background.js +338 -430
  58. package/extension/manifest.json +2 -2
  59. package/extension/src/background.ts +2 -2
  60. package/package.json +1 -1
  61. package/src/browser/cdp.ts +295 -0
  62. package/src/browser/index.ts +4 -0
  63. package/src/browser/page.ts +2 -24
  64. package/src/browser/utils.ts +27 -0
  65. package/src/browser.test.ts +46 -0
  66. package/src/chaoxing.test.ts +45 -0
  67. package/src/chaoxing.ts +268 -0
  68. package/src/cli.ts +185 -0
  69. package/src/clis/antigravity/SKILL.md +5 -0
  70. package/src/clis/boss/chatlist.ts +50 -0
  71. package/src/clis/boss/chatmsg.ts +70 -0
  72. package/src/clis/boss/send.ts +193 -0
  73. package/src/clis/chaoxing/README.md +36 -0
  74. package/src/clis/chaoxing/README.zh-CN.md +35 -0
  75. package/src/clis/chaoxing/assignments.ts +88 -0
  76. package/src/clis/chaoxing/exams.ts +88 -0
  77. package/src/clis/chatgpt/ask.ts +14 -15
  78. package/src/clis/chatgpt/ax.ts +81 -0
  79. package/src/clis/chatgpt/read.ts +5 -7
  80. package/src/clis/twitter/post.ts +9 -2
  81. package/src/clis/twitter/search.ts +15 -33
  82. package/src/clis/xiaohongshu/download.ts +1 -1
  83. package/src/engine.ts +20 -13
  84. package/src/explore.ts +51 -100
  85. package/src/main.ts +4 -180
  86. package/src/output.ts +12 -12
  87. package/src/registry.ts +3 -3
  88. package/src/scripts/framework.ts +20 -0
  89. package/src/scripts/interact.ts +22 -0
  90. package/src/scripts/store.ts +40 -0
  91. package/src/synthesize.ts +1 -1
@@ -1,484 +1,392 @@
1
- //#region src/protocol.ts
2
- /** Default daemon port */
3
- var DAEMON_PORT = 19825;
4
- var DAEMON_HOST = "localhost";
5
- var DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
6
- `${DAEMON_HOST}${DAEMON_PORT}`;
7
- /** Base reconnect delay for extension WebSocket (ms) */
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
- if (attached.has(tabId)) return;
23
- try {
24
- await chrome.debugger.attach({ tabId }, "1.3");
25
- } catch (e) {
26
- const msg = e instanceof Error ? e.message : String(e);
27
- if (msg.includes("Another debugger is already attached")) {
28
- try {
29
- await chrome.debugger.detach({ tabId });
30
- } catch {}
31
- try {
32
- await chrome.debugger.attach({ tabId }, "1.3");
33
- } catch {
34
- throw new Error(`attach failed: ${msg}`);
35
- }
36
- } else throw new Error(`attach failed: ${msg}`);
37
- }
38
- attached.add(tabId);
39
- try {
40
- await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
41
- } catch {}
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
- await ensureAttached(tabId);
45
- const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
46
- expression,
47
- returnByValue: true,
48
- awaitPromise: true
49
- });
50
- if (result.exceptionDetails) {
51
- const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
52
- throw new Error(errMsg);
53
- }
54
- return result.result?.value;
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
- var evaluateAsync = evaluate;
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
- await ensureAttached(tabId);
63
- const format = options.format ?? "png";
64
- if (options.fullPage) {
65
- const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics");
66
- const size = metrics.cssContentSize || metrics.contentSize;
67
- if (size) await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
68
- mobile: false,
69
- width: Math.ceil(size.width),
70
- height: Math.ceil(size.height),
71
- deviceScaleFactor: 1
72
- });
73
- }
74
- try {
75
- const params = { format };
76
- if (format === "jpeg" && options.quality !== void 0) params.quality = Math.max(0, Math.min(100, options.quality));
77
- return (await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params)).data;
78
- } finally {
79
- if (options.fullPage) await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {});
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
- if (!attached.has(tabId)) return;
84
- attached.delete(tabId);
85
- try {
86
- chrome.debugger.detach({ tabId });
87
- } catch {}
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
- chrome.tabs.onRemoved.addListener((tabId) => {
91
- attached.delete(tabId);
92
- });
93
- chrome.debugger.onDetach.addListener((source) => {
94
- if (source.tabId) attached.delete(source.tabId);
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
- //#endregion
98
- //#region src/background.ts
99
- var ws = null;
100
- var reconnectTimer = null;
101
- var reconnectAttempts = 0;
102
- var _origLog = console.log.bind(console);
103
- var _origWarn = console.warn.bind(console);
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
- if (!ws || ws.readyState !== WebSocket.OPEN) return;
107
- try {
108
- const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
109
- ws.send(JSON.stringify({
110
- type: "log",
111
- level,
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
- _origLog(...args);
119
- forwardLog("info", args);
109
+ _origLog(...args);
110
+ forwardLog("info", args);
120
111
  };
121
112
  console.warn = (...args) => {
122
- _origWarn(...args);
123
- forwardLog("warn", args);
113
+ _origWarn(...args);
114
+ forwardLog("warn", args);
124
115
  };
125
116
  console.error = (...args) => {
126
- _origError(...args);
127
- forwardLog("error", args);
117
+ _origError(...args);
118
+ forwardLog("error", args);
128
119
  };
129
120
  function connect() {
130
- if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
131
- try {
132
- ws = new WebSocket(DAEMON_WS_URL);
133
- } catch {
134
- scheduleReconnect();
135
- return;
136
- }
137
- ws.onopen = () => {
138
- console.log("[opencli] Connected to daemon");
139
- reconnectAttempts = 0;
140
- if (reconnectTimer) {
141
- clearTimeout(reconnectTimer);
142
- reconnectTimer = null;
143
- }
144
- };
145
- ws.onmessage = async (event) => {
146
- try {
147
- const result = await handleCommand(JSON.parse(event.data));
148
- ws?.send(JSON.stringify(result));
149
- } catch (err) {
150
- console.error("[opencli] Message handling error:", err);
151
- }
152
- };
153
- ws.onclose = () => {
154
- console.log("[opencli] Disconnected from daemon");
155
- ws = null;
156
- scheduleReconnect();
157
- };
158
- ws.onerror = () => {
159
- ws?.close();
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
- if (reconnectTimer) return;
164
- reconnectAttempts++;
165
- const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
166
- reconnectTimer = setTimeout(() => {
167
- reconnectTimer = null;
168
- connect();
169
- }, delay);
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
- var automationWindowId = null;
172
- var windowIdleTimer = null;
173
- var WINDOW_IDLE_TIMEOUT = 3e4;
163
+ let automationWindowId = null;
164
+ let windowIdleTimer = null;
165
+ const WINDOW_IDLE_TIMEOUT = 3e4;
174
166
  function resetWindowIdleTimer() {
175
- if (windowIdleTimer) clearTimeout(windowIdleTimer);
176
- windowIdleTimer = setTimeout(async () => {
177
- if (automationWindowId !== null) {
178
- try {
179
- await chrome.windows.remove(automationWindowId);
180
- console.log(`[opencli] Automation window ${automationWindowId} closed (idle timeout)`);
181
- } catch {}
182
- automationWindowId = null;
183
- }
184
- windowIdleTimer = null;
185
- }, WINDOW_IDLE_TIMEOUT);
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
- if (automationWindowId !== null) try {
190
- await chrome.windows.get(automationWindowId);
191
- return automationWindowId;
192
- } catch {
193
- automationWindowId = null;
194
- }
195
- automationWindowId = (await chrome.windows.create({
196
- url: "about:blank",
197
- focused: false,
198
- width: 1280,
199
- height: 900,
200
- type: "normal"
201
- })).id;
202
- console.log(`[opencli] Created automation window ${automationWindowId}`);
203
- return automationWindowId;
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
- if (windowId === automationWindowId) {
207
- console.log("[opencli] Automation window closed");
208
- automationWindowId = null;
209
- if (windowIdleTimer) {
210
- clearTimeout(windowIdleTimer);
211
- windowIdleTimer = null;
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
- var initialized = false;
210
+ let initialized = false;
216
211
  function initialize() {
217
- if (initialized) return;
218
- initialized = true;
219
- chrome.alarms.create("keepalive", { periodInMinutes: .4 });
220
- registerListeners();
221
- connect();
222
- console.log("[opencli] Browser Bridge extension initialized");
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
- initialize();
220
+ initialize();
226
221
  });
227
222
  chrome.runtime.onStartup.addListener(() => {
228
- initialize();
223
+ initialize();
229
224
  });
230
225
  chrome.alarms.onAlarm.addListener((alarm) => {
231
- if (alarm.name === "keepalive") connect();
226
+ if (alarm.name === "keepalive") connect();
232
227
  });
233
228
  async function handleCommand(cmd) {
234
- resetWindowIdleTimer();
235
- try {
236
- switch (cmd.action) {
237
- case "exec": return await handleExec(cmd);
238
- case "navigate": return await handleNavigate(cmd);
239
- case "tabs": return await handleTabs(cmd);
240
- case "cookies": return await handleCookies(cmd);
241
- case "screenshot": return await handleScreenshot(cmd);
242
- case "close-window": return await handleCloseWindow(cmd);
243
- default: return {
244
- id: cmd.id,
245
- ok: false,
246
- error: `Unknown action: ${cmd.action}`
247
- };
248
- }
249
- } catch (err) {
250
- return {
251
- id: cmd.id,
252
- ok: false,
253
- error: err instanceof Error ? err.message : String(err)
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
- if (!url) return false;
260
- return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
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
- if (tabId !== void 0) return tabId;
269
- const windowId = await getAutomationWindow();
270
- const tabs = await chrome.tabs.query({ windowId });
271
- const webTab = tabs.find((t) => t.id && isWebUrl(t.url));
272
- if (webTab?.id) return webTab.id;
273
- if (tabs.length > 0 && tabs[0]?.id) return tabs[0].id;
274
- const newTab = await chrome.tabs.create({
275
- windowId,
276
- url: "about:blank",
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
- if (!cmd.code) return {
284
- id: cmd.id,
285
- ok: false,
286
- error: "Missing code"
287
- };
288
- const tabId = await resolveTabId(cmd.tabId);
289
- try {
290
- const data = await evaluateAsync(tabId, cmd.code);
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
- if (!cmd.url) return {
306
- id: cmd.id,
307
- ok: false,
308
- error: "Missing url"
309
- };
310
- const tabId = await resolveTabId(cmd.tabId);
311
- await chrome.tabs.update(tabId, { url: cmd.url });
312
- await new Promise((resolve) => {
313
- chrome.tabs.get(tabId).then((tab) => {
314
- if (tab.status === "complete") {
315
- resolve();
316
- return;
317
- }
318
- const listener = (id, info) => {
319
- if (id === tabId && info.status === "complete") {
320
- chrome.tabs.onUpdated.removeListener(listener);
321
- resolve();
322
- }
323
- };
324
- chrome.tabs.onUpdated.addListener(listener);
325
- setTimeout(() => {
326
- chrome.tabs.onUpdated.removeListener(listener);
327
- resolve();
328
- }, 15e3);
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
- switch (cmd.op) {
344
- case "list": {
345
- const data = (await chrome.tabs.query({})).filter((t) => isWebUrl(t.url)).map((t, i) => ({
346
- index: i,
347
- tabId: t.id,
348
- url: t.url,
349
- title: t.title,
350
- active: t.active
351
- }));
352
- return {
353
- id: cmd.id,
354
- ok: true,
355
- data
356
- };
357
- }
358
- case "new": {
359
- const tab = await chrome.tabs.create({
360
- url: cmd.url,
361
- active: true
362
- });
363
- return {
364
- id: cmd.id,
365
- ok: true,
366
- data: {
367
- tabId: tab.id,
368
- url: tab.url
369
- }
370
- };
371
- }
372
- case "close": {
373
- if (cmd.index !== void 0) {
374
- const target = (await chrome.tabs.query({}))[cmd.index];
375
- if (!target?.id) return {
376
- id: cmd.id,
377
- ok: false,
378
- error: `Tab index ${cmd.index} not found`
379
- };
380
- await chrome.tabs.remove(target.id);
381
- detach(target.id);
382
- return {
383
- id: cmd.id,
384
- ok: true,
385
- data: { closed: target.id }
386
- };
387
- }
388
- const tabId = await resolveTabId(cmd.tabId);
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
- const details = {};
433
- if (cmd.domain) details.domain = cmd.domain;
434
- if (cmd.url) details.url = cmd.url;
435
- const data = (await chrome.cookies.getAll(details)).map((c) => ({
436
- name: c.name,
437
- value: c.value,
438
- domain: c.domain,
439
- path: c.path,
440
- secure: c.secure,
441
- httpOnly: c.httpOnly,
442
- expirationDate: c.expirationDate
443
- }));
444
- return {
445
- id: cmd.id,
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
- const tabId = await resolveTabId(cmd.tabId);
452
- try {
453
- const data = await screenshot(tabId, {
454
- format: cmd.format,
455
- quality: cmd.quality,
456
- fullPage: cmd.fullPage
457
- });
458
- return {
459
- id: cmd.id,
460
- ok: true,
461
- data
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
- if (automationWindowId !== null) {
473
- try {
474
- await chrome.windows.remove(automationWindowId);
475
- } catch {}
476
- automationWindowId = null;
477
- }
478
- return {
479
- id: cmd.id,
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