@jackwener/opencli 0.9.8 → 1.0.1

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 (165) hide show
  1. package/CDP.md +1 -1
  2. package/CDP.zh-CN.md +1 -1
  3. package/CLI-ELECTRON.md +2 -2
  4. package/CLI-EXPLORER.md +4 -4
  5. package/README.md +35 -58
  6. package/README.zh-CN.md +36 -60
  7. package/SKILL.md +10 -8
  8. package/TESTING.md +7 -7
  9. package/dist/browser/daemon-client.d.ts +37 -0
  10. package/dist/browser/daemon-client.js +82 -0
  11. package/dist/browser/discover.d.ts +11 -34
  12. package/dist/browser/discover.js +15 -205
  13. package/dist/browser/errors.d.ts +6 -20
  14. package/dist/browser/errors.js +24 -63
  15. package/dist/browser/index.d.ts +2 -12
  16. package/dist/browser/index.js +2 -12
  17. package/dist/browser/mcp.d.ts +9 -21
  18. package/dist/browser/mcp.js +70 -285
  19. package/dist/browser/page.d.ts +36 -7
  20. package/dist/browser/page.js +212 -81
  21. package/dist/browser.test.js +10 -231
  22. package/dist/cli-manifest.json +561 -14
  23. package/dist/clis/apple-podcasts/episodes.d.ts +1 -0
  24. package/dist/clis/apple-podcasts/episodes.js +28 -0
  25. package/dist/clis/apple-podcasts/search.d.ts +1 -0
  26. package/dist/clis/apple-podcasts/search.js +29 -0
  27. package/dist/clis/apple-podcasts/top.d.ts +1 -0
  28. package/dist/clis/apple-podcasts/top.js +34 -0
  29. package/dist/clis/apple-podcasts/utils.d.ts +11 -0
  30. package/dist/clis/apple-podcasts/utils.js +30 -0
  31. package/dist/clis/apple-podcasts/utils.test.d.ts +1 -0
  32. package/dist/clis/apple-podcasts/utils.test.js +57 -0
  33. package/dist/clis/chatwise/history.js +18 -1
  34. package/dist/clis/discord-app/channels.js +33 -21
  35. package/dist/clis/neteasemusic/like.d.ts +1 -0
  36. package/dist/clis/neteasemusic/like.js +25 -0
  37. package/dist/clis/neteasemusic/lyrics.d.ts +1 -0
  38. package/dist/clis/neteasemusic/lyrics.js +47 -0
  39. package/dist/clis/neteasemusic/next.d.ts +1 -0
  40. package/dist/clis/neteasemusic/next.js +26 -0
  41. package/dist/clis/neteasemusic/play.d.ts +1 -0
  42. package/dist/clis/neteasemusic/play.js +26 -0
  43. package/dist/clis/neteasemusic/playing.d.ts +1 -0
  44. package/dist/clis/neteasemusic/playing.js +59 -0
  45. package/dist/clis/neteasemusic/playlist.d.ts +1 -0
  46. package/dist/clis/neteasemusic/playlist.js +46 -0
  47. package/dist/clis/neteasemusic/prev.d.ts +1 -0
  48. package/dist/clis/neteasemusic/prev.js +25 -0
  49. package/dist/clis/neteasemusic/search.d.ts +1 -0
  50. package/dist/clis/neteasemusic/search.js +52 -0
  51. package/dist/clis/neteasemusic/status.d.ts +1 -0
  52. package/dist/clis/neteasemusic/status.js +16 -0
  53. package/dist/clis/neteasemusic/volume.d.ts +1 -0
  54. package/dist/clis/neteasemusic/volume.js +54 -0
  55. package/dist/clis/twitter/accept.d.ts +1 -0
  56. package/dist/clis/twitter/accept.js +202 -0
  57. package/dist/clis/twitter/followers.js +30 -22
  58. package/dist/clis/twitter/following.js +19 -14
  59. package/dist/clis/twitter/notifications.js +29 -22
  60. package/dist/clis/twitter/reply-dm.d.ts +1 -0
  61. package/dist/clis/twitter/reply-dm.js +181 -0
  62. package/dist/clis/twitter/search.js +50 -12
  63. package/dist/clis/weread/book.d.ts +1 -0
  64. package/dist/clis/weread/book.js +26 -0
  65. package/dist/clis/weread/highlights.d.ts +1 -0
  66. package/dist/clis/weread/highlights.js +23 -0
  67. package/dist/clis/weread/notebooks.d.ts +1 -0
  68. package/dist/clis/weread/notebooks.js +21 -0
  69. package/dist/clis/weread/notes.d.ts +1 -0
  70. package/dist/clis/weread/notes.js +29 -0
  71. package/dist/clis/weread/ranking.d.ts +1 -0
  72. package/dist/clis/weread/ranking.js +28 -0
  73. package/dist/clis/weread/search.d.ts +1 -0
  74. package/dist/clis/weread/search.js +25 -0
  75. package/dist/clis/weread/shelf.d.ts +1 -0
  76. package/dist/clis/weread/shelf.js +24 -0
  77. package/dist/clis/weread/utils.d.ts +20 -0
  78. package/dist/clis/weread/utils.js +72 -0
  79. package/dist/clis/weread/utils.test.d.ts +1 -0
  80. package/dist/clis/weread/utils.test.js +85 -0
  81. package/dist/daemon.d.ts +13 -0
  82. package/dist/daemon.js +187 -0
  83. package/dist/doctor.d.ts +10 -65
  84. package/dist/doctor.js +49 -602
  85. package/dist/doctor.test.js +30 -170
  86. package/dist/main.js +12 -41
  87. package/dist/pipeline/executor.test.js +1 -0
  88. package/dist/pipeline/steps/browser.js +2 -2
  89. package/dist/pipeline/steps/intercept.js +1 -2
  90. package/dist/runtime.d.ts +1 -4
  91. package/dist/runtime.js +1 -4
  92. package/dist/setup.d.ts +6 -0
  93. package/dist/setup.js +46 -160
  94. package/dist/types.d.ts +6 -0
  95. package/extension/dist/background.js +484 -0
  96. package/extension/icons/icon-128.png +0 -0
  97. package/extension/icons/icon-16.png +0 -0
  98. package/extension/icons/icon-32.png +0 -0
  99. package/extension/icons/icon-48.png +0 -0
  100. package/extension/manifest.json +31 -0
  101. package/extension/package.json +16 -0
  102. package/extension/src/background.ts +370 -0
  103. package/extension/src/cdp.ts +125 -0
  104. package/extension/src/protocol.ts +57 -0
  105. package/extension/store-assets/screenshot-1280x800.png +0 -0
  106. package/extension/tsconfig.json +15 -0
  107. package/extension/vite.config.ts +18 -0
  108. package/package.json +5 -5
  109. package/src/browser/daemon-client.ts +113 -0
  110. package/src/browser/discover.ts +18 -232
  111. package/src/browser/errors.ts +30 -100
  112. package/src/browser/index.ts +2 -13
  113. package/src/browser/mcp.ts +81 -282
  114. package/src/browser/page.ts +223 -83
  115. package/src/browser.test.ts +9 -239
  116. package/src/clis/apple-podcasts/episodes.ts +28 -0
  117. package/src/clis/apple-podcasts/search.ts +29 -0
  118. package/src/clis/apple-podcasts/top.ts +34 -0
  119. package/src/clis/apple-podcasts/utils.test.ts +72 -0
  120. package/src/clis/apple-podcasts/utils.ts +37 -0
  121. package/src/clis/chatgpt/README.md +1 -1
  122. package/src/clis/chatgpt/README.zh-CN.md +1 -1
  123. package/src/clis/chatwise/history.ts +15 -1
  124. package/src/clis/discord-app/channels.ts +33 -21
  125. package/src/clis/neteasemusic/README.md +31 -0
  126. package/src/clis/neteasemusic/README.zh-CN.md +31 -0
  127. package/src/clis/neteasemusic/like.ts +28 -0
  128. package/src/clis/neteasemusic/lyrics.ts +53 -0
  129. package/src/clis/neteasemusic/next.ts +30 -0
  130. package/src/clis/neteasemusic/play.ts +30 -0
  131. package/src/clis/neteasemusic/playing.ts +62 -0
  132. package/src/clis/neteasemusic/playlist.ts +51 -0
  133. package/src/clis/neteasemusic/prev.ts +29 -0
  134. package/src/clis/neteasemusic/search.ts +58 -0
  135. package/src/clis/neteasemusic/status.ts +18 -0
  136. package/src/clis/neteasemusic/volume.ts +61 -0
  137. package/src/clis/twitter/accept.ts +213 -0
  138. package/src/clis/twitter/followers.ts +36 -29
  139. package/src/clis/twitter/following.ts +25 -20
  140. package/src/clis/twitter/notifications.ts +34 -27
  141. package/src/clis/twitter/reply-dm.ts +193 -0
  142. package/src/clis/twitter/search.ts +53 -13
  143. package/src/clis/weread/book.ts +28 -0
  144. package/src/clis/weread/highlights.ts +25 -0
  145. package/src/clis/weread/notebooks.ts +23 -0
  146. package/src/clis/weread/notes.ts +31 -0
  147. package/src/clis/weread/ranking.ts +29 -0
  148. package/src/clis/weread/search.ts +26 -0
  149. package/src/clis/weread/shelf.ts +26 -0
  150. package/src/clis/weread/utils.test.ts +104 -0
  151. package/src/clis/weread/utils.ts +74 -0
  152. package/src/daemon.ts +217 -0
  153. package/src/doctor.test.ts +32 -193
  154. package/src/doctor.ts +58 -669
  155. package/src/main.ts +11 -34
  156. package/src/pipeline/executor.test.ts +1 -0
  157. package/src/pipeline/steps/browser.ts +2 -2
  158. package/src/pipeline/steps/intercept.ts +1 -2
  159. package/src/runtime.ts +2 -6
  160. package/src/setup.ts +47 -183
  161. package/src/types.ts +1 -0
  162. package/tests/e2e/public-commands.test.ts +68 -1
  163. package/dist/clis/grok/debug.d.ts +0 -1
  164. package/dist/clis/grok/debug.js +0 -45
  165. package/src/clis/grok/debug.ts +0 -49
@@ -0,0 +1,484 @@
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();
21
+ 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 {}
42
+ }
43
+ 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;
55
+ }
56
+ var evaluateAsync = evaluate;
57
+ /**
58
+ * Capture a screenshot via CDP Page.captureScreenshot.
59
+ * Returns base64-encoded image data.
60
+ */
61
+ 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
+ }
81
+ }
82
+ function detach(tabId) {
83
+ if (!attached.has(tabId)) return;
84
+ attached.delete(tabId);
85
+ try {
86
+ chrome.debugger.detach({ tabId });
87
+ } catch {}
88
+ }
89
+ 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
+ });
96
+ }
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);
105
+ 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 {}
116
+ }
117
+ console.log = (...args) => {
118
+ _origLog(...args);
119
+ forwardLog("info", args);
120
+ };
121
+ console.warn = (...args) => {
122
+ _origWarn(...args);
123
+ forwardLog("warn", args);
124
+ };
125
+ console.error = (...args) => {
126
+ _origError(...args);
127
+ forwardLog("error", args);
128
+ };
129
+ 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
+ };
161
+ }
162
+ 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);
170
+ }
171
+ var automationWindowId = null;
172
+ var windowIdleTimer = null;
173
+ var WINDOW_IDLE_TIMEOUT = 3e4;
174
+ 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);
186
+ }
187
+ /** Get or create the dedicated automation window. */
188
+ 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;
204
+ }
205
+ 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
+ }
214
+ });
215
+ var initialized = false;
216
+ 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");
223
+ }
224
+ chrome.runtime.onInstalled.addListener(() => {
225
+ initialize();
226
+ });
227
+ chrome.runtime.onStartup.addListener(() => {
228
+ initialize();
229
+ });
230
+ chrome.alarms.onAlarm.addListener((alarm) => {
231
+ if (alarm.name === "keepalive") connect();
232
+ });
233
+ 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
+ }
256
+ }
257
+ /** Check if a URL is a debuggable web page (not chrome:// or extension page) */
258
+ function isWebUrl(url) {
259
+ if (!url) return false;
260
+ return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
261
+ }
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
+ 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;
281
+ }
282
+ 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
+ }
303
+ }
304
+ 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
+ };
341
+ }
342
+ 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
+ }
430
+ }
431
+ 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
+ };
449
+ }
450
+ 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
+ }
470
+ }
471
+ 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
+ };
483
+ }
484
+ //#endregion
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,31 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "opencli Browser Bridge",
4
+ "version": "0.2.0",
5
+ "description": "Bridge between opencli CLI and your browser — execute commands, read cookies, manage tabs.",
6
+ "permissions": [
7
+ "debugger",
8
+ "tabs",
9
+ "cookies",
10
+ "activeTab",
11
+ "alarms"
12
+ ],
13
+ "background": {
14
+ "service_worker": "dist/background.js",
15
+ "type": "module"
16
+ },
17
+ "icons": {
18
+ "16": "icons/icon-16.png",
19
+ "32": "icons/icon-32.png",
20
+ "48": "icons/icon-48.png",
21
+ "128": "icons/icon-128.png"
22
+ },
23
+ "action": {
24
+ "default_title": "opencli Browser Bridge",
25
+ "default_icon": {
26
+ "16": "icons/icon-16.png",
27
+ "32": "icons/icon-32.png"
28
+ }
29
+ },
30
+ "homepage_url": "https://github.com/jackwener/opencli"
31
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "opencli-extension",
3
+ "version": "0.2.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite build --watch",
8
+ "build": "vite build",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "devDependencies": {
12
+ "@types/chrome": "^0.0.287",
13
+ "typescript": "^5.7.0",
14
+ "vite": "^6.0.0"
15
+ }
16
+ }