@jackwener/opencli 1.0.1 → 1.0.4

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 (253) hide show
  1. package/.github/workflows/build-extension.yml +80 -0
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/docs.yml +52 -0
  4. package/.github/workflows/e2e-headed.yml +2 -2
  5. package/.github/workflows/pkg-pr-new.yml +2 -2
  6. package/.github/workflows/release.yml +2 -5
  7. package/.github/workflows/security.yml +2 -2
  8. package/CDP.md +1 -1
  9. package/CDP.zh-CN.md +1 -1
  10. package/README.md +42 -34
  11. package/README.zh-CN.md +42 -34
  12. package/SKILL.md +3 -5
  13. package/dist/browser/cdp.d.ts +42 -0
  14. package/dist/browser/cdp.js +339 -0
  15. package/dist/browser/daemon-client.d.ts +3 -1
  16. package/dist/browser/daemon-client.js +4 -0
  17. package/dist/browser/dom-helpers.d.ts +20 -0
  18. package/dist/browser/dom-helpers.js +109 -0
  19. package/dist/browser/index.d.ts +3 -0
  20. package/dist/browser/index.js +4 -0
  21. package/dist/browser/mcp.d.ts +1 -0
  22. package/dist/browser/mcp.js +10 -5
  23. package/dist/browser/page.d.ts +7 -0
  24. package/dist/browser/page.js +39 -123
  25. package/dist/browser/utils.d.ts +10 -0
  26. package/dist/browser/utils.js +27 -0
  27. package/dist/browser.test.js +49 -1
  28. package/dist/build-manifest.js +3 -1
  29. package/dist/build-manifest.test.js +34 -0
  30. package/dist/capabilityRouting.d.ts +2 -0
  31. package/dist/capabilityRouting.js +30 -0
  32. package/dist/capabilityRouting.test.d.ts +1 -0
  33. package/dist/capabilityRouting.test.js +42 -0
  34. package/dist/chaoxing.d.ts +58 -0
  35. package/dist/chaoxing.js +225 -0
  36. package/dist/chaoxing.test.d.ts +1 -0
  37. package/dist/chaoxing.test.js +45 -0
  38. package/dist/cli-manifest.json +885 -48
  39. package/dist/cli.d.ts +1 -0
  40. package/dist/cli.js +234 -0
  41. package/dist/clis/antigravity/serve.d.ts +14 -0
  42. package/dist/clis/antigravity/serve.js +263 -0
  43. package/dist/clis/bilibili/download.js +4 -14
  44. package/dist/clis/boss/chatlist.d.ts +1 -0
  45. package/dist/clis/boss/chatlist.js +50 -0
  46. package/dist/clis/boss/chatmsg.d.ts +1 -0
  47. package/dist/clis/boss/chatmsg.js +73 -0
  48. package/dist/clis/boss/resume.d.ts +1 -0
  49. package/dist/clis/boss/resume.js +249 -0
  50. package/dist/clis/boss/send.d.ts +1 -0
  51. package/dist/clis/boss/send.js +176 -0
  52. package/dist/clis/chaoxing/assignments.d.ts +1 -0
  53. package/dist/clis/chaoxing/assignments.js +74 -0
  54. package/dist/clis/chaoxing/exams.d.ts +1 -0
  55. package/dist/clis/chaoxing/exams.js +74 -0
  56. package/dist/clis/chatgpt/ask.js +15 -14
  57. package/dist/clis/chatgpt/ax.d.ts +1 -0
  58. package/dist/clis/chatgpt/ax.js +78 -0
  59. package/dist/clis/chatgpt/read.js +5 -6
  60. package/dist/clis/hf/top.d.ts +1 -0
  61. package/dist/clis/hf/top.js +119 -0
  62. package/dist/clis/jike/comment.d.ts +1 -0
  63. package/dist/clis/jike/comment.js +107 -0
  64. package/dist/clis/jike/create.d.ts +1 -0
  65. package/dist/clis/jike/create.js +106 -0
  66. package/dist/clis/jike/feed.d.ts +1 -0
  67. package/dist/clis/jike/feed.js +67 -0
  68. package/dist/clis/jike/like.d.ts +1 -0
  69. package/dist/clis/jike/like.js +61 -0
  70. package/dist/clis/jike/notifications.d.ts +1 -0
  71. package/dist/clis/jike/notifications.js +169 -0
  72. package/dist/clis/jike/post.yaml +58 -0
  73. package/dist/clis/jike/repost.d.ts +1 -0
  74. package/dist/clis/jike/repost.js +103 -0
  75. package/dist/clis/jike/search.d.ts +1 -0
  76. package/dist/clis/jike/search.js +67 -0
  77. package/dist/clis/jike/shared.d.ts +19 -0
  78. package/dist/clis/jike/shared.js +25 -0
  79. package/dist/clis/jike/topic.yaml +52 -0
  80. package/dist/clis/jike/user.yaml +51 -0
  81. package/dist/clis/smzdm/search.js +28 -39
  82. package/dist/clis/stackoverflow/bounties.yaml +29 -0
  83. package/dist/clis/stackoverflow/hot.yaml +28 -0
  84. package/dist/clis/stackoverflow/search.yaml +32 -0
  85. package/dist/clis/stackoverflow/unanswered.yaml +28 -0
  86. package/dist/clis/twitter/download.js +6 -16
  87. package/dist/clis/twitter/post.js +9 -2
  88. package/dist/clis/twitter/search.js +14 -33
  89. package/dist/clis/xiaohongshu/download.d.ts +1 -1
  90. package/dist/clis/xiaohongshu/download.js +4 -4
  91. package/dist/clis/zhihu/download.js +3 -3
  92. package/dist/doctor.d.ts +7 -0
  93. package/dist/doctor.js +16 -0
  94. package/dist/download/index.d.ts +12 -8
  95. package/dist/download/index.js +11 -3
  96. package/dist/download/index.test.d.ts +1 -0
  97. package/dist/download/index.test.js +14 -0
  98. package/dist/engine.js +25 -14
  99. package/dist/explore.d.ts +1 -0
  100. package/dist/explore.js +48 -103
  101. package/dist/generate.js +1 -0
  102. package/dist/interceptor.js +3 -2
  103. package/dist/main.js +4 -193
  104. package/dist/output.d.ts +2 -1
  105. package/dist/output.js +3 -1
  106. package/dist/pipeline/executor.test.js +1 -0
  107. package/dist/pipeline/steps/download.js +14 -18
  108. package/dist/registry.d.ts +4 -3
  109. package/dist/registry.js +5 -2
  110. package/dist/runtime.d.ts +4 -1
  111. package/dist/runtime.js +2 -2
  112. package/dist/scripts/framework.d.ts +4 -0
  113. package/dist/scripts/framework.js +21 -0
  114. package/dist/scripts/interact.d.ts +4 -0
  115. package/dist/scripts/interact.js +20 -0
  116. package/dist/scripts/store.d.ts +9 -0
  117. package/dist/scripts/store.js +44 -0
  118. package/dist/synthesize.js +1 -1
  119. package/dist/types.d.ts +12 -0
  120. package/dist/verify.d.ts +6 -1
  121. package/dist/verify.js +54 -2
  122. package/docs/.vitepress/config.mts +193 -0
  123. package/docs/adapters/browser/apple-podcasts.md +28 -0
  124. package/docs/adapters/browser/bbc.md +26 -0
  125. package/docs/adapters/browser/bilibili.md +38 -0
  126. package/docs/adapters/browser/boss.md +28 -0
  127. package/docs/adapters/browser/coupang.md +28 -0
  128. package/docs/adapters/browser/ctrip.md +27 -0
  129. package/docs/adapters/browser/github.md +26 -0
  130. package/docs/adapters/browser/hackernews.md +26 -0
  131. package/docs/adapters/browser/linkedin.md +27 -0
  132. package/docs/adapters/browser/reddit.md +41 -0
  133. package/docs/adapters/browser/reuters.md +27 -0
  134. package/docs/adapters/browser/smzdm.md +27 -0
  135. package/docs/adapters/browser/twitter.md +47 -0
  136. package/docs/adapters/browser/v2ex.md +32 -0
  137. package/docs/adapters/browser/weibo.md +27 -0
  138. package/docs/adapters/browser/xiaohongshu.md +32 -0
  139. package/docs/adapters/browser/xiaoyuzhou.md +28 -0
  140. package/docs/adapters/browser/xueqiu.md +32 -0
  141. package/docs/adapters/browser/yahoo-finance.md +26 -0
  142. package/docs/adapters/browser/youtube.md +29 -0
  143. package/docs/adapters/browser/zhihu.md +30 -0
  144. package/docs/adapters/desktop/antigravity.md +46 -0
  145. package/docs/adapters/desktop/chatgpt.md +43 -0
  146. package/docs/adapters/desktop/chatwise.md +38 -0
  147. package/docs/adapters/desktop/codex.md +32 -0
  148. package/docs/adapters/desktop/cursor.md +33 -0
  149. package/docs/adapters/desktop/discord.md +28 -0
  150. package/docs/adapters/desktop/feishu.md +20 -0
  151. package/docs/adapters/desktop/neteasemusic.md +31 -0
  152. package/docs/adapters/desktop/notion.md +29 -0
  153. package/docs/adapters/desktop/wechat.md +28 -0
  154. package/docs/adapters/index.md +49 -0
  155. package/docs/advanced/cdp.md +103 -0
  156. package/docs/advanced/download.md +63 -0
  157. package/docs/advanced/electron.md +125 -0
  158. package/docs/advanced/remote-chrome.md +72 -0
  159. package/docs/developer/ai-workflow.md +66 -0
  160. package/docs/developer/architecture.md +90 -0
  161. package/docs/developer/contributing.md +136 -0
  162. package/docs/developer/testing.md +237 -0
  163. package/docs/developer/ts-adapter.md +87 -0
  164. package/docs/developer/yaml-adapter.md +108 -0
  165. package/docs/guide/browser-bridge.md +38 -0
  166. package/docs/guide/getting-started.md +56 -0
  167. package/docs/guide/installation.md +37 -0
  168. package/docs/guide/troubleshooting.md +56 -0
  169. package/docs/index.md +35 -0
  170. package/docs/zh/adapters/index.md +5 -0
  171. package/docs/zh/advanced/cdp.md +3 -0
  172. package/docs/zh/developer/contributing.md +24 -0
  173. package/docs/zh/guide/browser-bridge.md +25 -0
  174. package/docs/zh/guide/getting-started.md +40 -0
  175. package/docs/zh/guide/installation.md +37 -0
  176. package/docs/zh/index.md +29 -0
  177. package/extension/dist/background.js +386 -438
  178. package/extension/manifest.json +2 -2
  179. package/extension/package-lock.json +1156 -0
  180. package/extension/src/background.test.ts +151 -0
  181. package/extension/src/background.ts +124 -53
  182. package/extension/src/protocol.ts +3 -1
  183. package/package.json +7 -3
  184. package/src/browser/cdp.ts +367 -0
  185. package/src/browser/daemon-client.ts +7 -1
  186. package/src/browser/dom-helpers.ts +116 -0
  187. package/src/browser/index.ts +4 -0
  188. package/src/browser/mcp.ts +14 -6
  189. package/src/browser/page.ts +47 -124
  190. package/src/browser/utils.ts +27 -0
  191. package/src/browser.test.ts +56 -0
  192. package/src/build-manifest.test.ts +36 -0
  193. package/src/build-manifest.ts +2 -1
  194. package/src/capabilityRouting.test.ts +47 -0
  195. package/src/capabilityRouting.ts +28 -0
  196. package/src/chaoxing.test.ts +53 -0
  197. package/src/chaoxing.ts +268 -0
  198. package/src/cli.ts +205 -0
  199. package/src/clis/antigravity/SKILL.md +5 -0
  200. package/src/clis/antigravity/serve.ts +329 -0
  201. package/src/clis/bilibili/download.ts +4 -15
  202. package/src/clis/boss/chatlist.ts +50 -0
  203. package/src/clis/boss/chatmsg.ts +70 -0
  204. package/src/clis/boss/resume.ts +262 -0
  205. package/src/clis/boss/send.ts +193 -0
  206. package/src/clis/chaoxing/README.md +36 -0
  207. package/src/clis/chaoxing/README.zh-CN.md +35 -0
  208. package/src/clis/chaoxing/assignments.ts +88 -0
  209. package/src/clis/chaoxing/exams.ts +88 -0
  210. package/src/clis/chatgpt/ask.ts +14 -15
  211. package/src/clis/chatgpt/ax.ts +81 -0
  212. package/src/clis/chatgpt/read.ts +5 -7
  213. package/src/clis/hf/top.ts +141 -0
  214. package/src/clis/jike/comment.ts +113 -0
  215. package/src/clis/jike/create.ts +113 -0
  216. package/src/clis/jike/feed.ts +74 -0
  217. package/src/clis/jike/like.ts +65 -0
  218. package/src/clis/jike/notifications.ts +185 -0
  219. package/src/clis/jike/post.yaml +58 -0
  220. package/src/clis/jike/repost.ts +114 -0
  221. package/src/clis/jike/search.ts +74 -0
  222. package/src/clis/jike/shared.ts +36 -0
  223. package/src/clis/jike/topic.yaml +52 -0
  224. package/src/clis/jike/user.yaml +51 -0
  225. package/src/clis/smzdm/search.ts +30 -39
  226. package/src/clis/stackoverflow/bounties.yaml +29 -0
  227. package/src/clis/stackoverflow/hot.yaml +28 -0
  228. package/src/clis/stackoverflow/search.yaml +32 -0
  229. package/src/clis/stackoverflow/unanswered.yaml +28 -0
  230. package/src/clis/twitter/download.ts +6 -17
  231. package/src/clis/twitter/post.ts +9 -2
  232. package/src/clis/twitter/search.ts +15 -33
  233. package/src/clis/xiaohongshu/download.ts +4 -4
  234. package/src/clis/zhihu/download.ts +3 -3
  235. package/src/doctor.ts +18 -2
  236. package/src/download/index.test.ts +16 -0
  237. package/src/download/index.ts +22 -4
  238. package/src/engine.ts +20 -13
  239. package/src/explore.ts +54 -103
  240. package/src/generate.ts +1 -0
  241. package/src/interceptor.ts +3 -2
  242. package/src/main.ts +4 -180
  243. package/src/output.ts +15 -13
  244. package/src/pipeline/executor.test.ts +1 -0
  245. package/src/pipeline/steps/download.ts +14 -17
  246. package/src/registry.ts +9 -5
  247. package/src/runtime.ts +3 -2
  248. package/src/scripts/framework.ts +20 -0
  249. package/src/scripts/interact.ts +22 -0
  250. package/src/scripts/store.ts +40 -0
  251. package/src/synthesize.ts +1 -1
  252. package/src/types.ts +9 -0
  253. package/src/verify.ts +64 -3
@@ -1,484 +1,432 @@
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;
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);
163
+ const automationSessions = /* @__PURE__ */ new Map();
164
+ const WINDOW_IDLE_TIMEOUT = 3e4;
165
+ function getWorkspaceKey(workspace) {
166
+ return workspace?.trim() || "default";
186
167
  }
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;
168
+ function resetWindowIdleTimer(workspace) {
169
+ const session = automationSessions.get(workspace);
170
+ if (!session) return;
171
+ if (session.idleTimer) clearTimeout(session.idleTimer);
172
+ session.idleDeadlineAt = Date.now() + WINDOW_IDLE_TIMEOUT;
173
+ session.idleTimer = setTimeout(async () => {
174
+ const current = automationSessions.get(workspace);
175
+ if (!current) return;
176
+ try {
177
+ await chrome.windows.remove(current.windowId);
178
+ console.log(`[opencli] Automation window ${current.windowId} (${workspace}) closed (idle timeout)`);
179
+ } catch {
180
+ }
181
+ automationSessions.delete(workspace);
182
+ }, WINDOW_IDLE_TIMEOUT);
183
+ }
184
+ async function getAutomationWindow(workspace) {
185
+ const existing = automationSessions.get(workspace);
186
+ if (existing) {
187
+ try {
188
+ await chrome.windows.get(existing.windowId);
189
+ return existing.windowId;
190
+ } catch {
191
+ automationSessions.delete(workspace);
192
+ }
193
+ }
194
+ const win = await chrome.windows.create({
195
+ url: "about:blank",
196
+ focused: false,
197
+ width: 1280,
198
+ height: 900,
199
+ type: "normal"
200
+ });
201
+ const session = {
202
+ windowId: win.id,
203
+ idleTimer: null,
204
+ idleDeadlineAt: Date.now() + WINDOW_IDLE_TIMEOUT
205
+ };
206
+ automationSessions.set(workspace, session);
207
+ console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
208
+ resetWindowIdleTimer(workspace);
209
+ return session.windowId;
204
210
  }
205
211
  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
- }
212
+ for (const [workspace, session] of automationSessions.entries()) {
213
+ if (session.windowId === windowId) {
214
+ console.log(`[opencli] Automation window closed (${workspace})`);
215
+ if (session.idleTimer) clearTimeout(session.idleTimer);
216
+ automationSessions.delete(workspace);
217
+ }
218
+ }
214
219
  });
215
- var initialized = false;
220
+ let initialized = false;
216
221
  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");
222
+ if (initialized) return;
223
+ initialized = true;
224
+ chrome.alarms.create("keepalive", { periodInMinutes: 0.4 });
225
+ registerListeners();
226
+ connect();
227
+ console.log("[opencli] OpenCLI extension initialized");
223
228
  }
224
229
  chrome.runtime.onInstalled.addListener(() => {
225
- initialize();
230
+ initialize();
226
231
  });
227
232
  chrome.runtime.onStartup.addListener(() => {
228
- initialize();
233
+ initialize();
229
234
  });
230
235
  chrome.alarms.onAlarm.addListener((alarm) => {
231
- if (alarm.name === "keepalive") connect();
236
+ if (alarm.name === "keepalive") connect();
232
237
  });
233
238
  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
- }
239
+ const workspace = getWorkspaceKey(cmd.workspace);
240
+ resetWindowIdleTimer(workspace);
241
+ try {
242
+ switch (cmd.action) {
243
+ case "exec":
244
+ return await handleExec(cmd, workspace);
245
+ case "navigate":
246
+ return await handleNavigate(cmd, workspace);
247
+ case "tabs":
248
+ return await handleTabs(cmd, workspace);
249
+ case "cookies":
250
+ return await handleCookies(cmd);
251
+ case "screenshot":
252
+ return await handleScreenshot(cmd, workspace);
253
+ case "close-window":
254
+ return await handleCloseWindow(cmd, workspace);
255
+ case "sessions":
256
+ return await handleSessions(cmd);
257
+ default:
258
+ return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
259
+ }
260
+ } catch (err) {
261
+ return {
262
+ id: cmd.id,
263
+ ok: false,
264
+ error: err instanceof Error ? err.message : String(err)
265
+ };
266
+ }
256
267
  }
257
- /** Check if a URL is a debuggable web page (not chrome:// or extension page) */
258
268
  function isWebUrl(url) {
259
- if (!url) return false;
260
- return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
269
+ if (!url) return false;
270
+ return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
271
+ }
272
+ async function resolveTabId(tabId, workspace) {
273
+ if (tabId !== void 0) return tabId;
274
+ const windowId = await getAutomationWindow(workspace);
275
+ const tabs = await chrome.tabs.query({ windowId });
276
+ const webTab = tabs.find((t) => t.id && isWebUrl(t.url));
277
+ if (webTab?.id) return webTab.id;
278
+ if (tabs.length > 0 && tabs[0]?.id) return tabs[0].id;
279
+ const newTab = await chrome.tabs.create({ windowId, url: "about:blank", active: true });
280
+ if (!newTab.id) throw new Error("Failed to create tab in automation window");
281
+ return newTab.id;
261
282
  }
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;
283
+ async function listAutomationTabs(workspace) {
284
+ const session = automationSessions.get(workspace);
285
+ if (!session) return [];
286
+ try {
287
+ return await chrome.tabs.query({ windowId: session.windowId });
288
+ } catch {
289
+ automationSessions.delete(workspace);
290
+ return [];
291
+ }
281
292
  }
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
- }
293
+ async function listAutomationWebTabs(workspace) {
294
+ const tabs = await listAutomationTabs(workspace);
295
+ return tabs.filter((tab) => isWebUrl(tab.url));
303
296
  }
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
- };
297
+ async function handleExec(cmd, workspace) {
298
+ if (!cmd.code) return { id: cmd.id, ok: false, error: "Missing code" };
299
+ const tabId = await resolveTabId(cmd.tabId, workspace);
300
+ try {
301
+ const data = await evaluateAsync(tabId, cmd.code);
302
+ return { id: cmd.id, ok: true, data };
303
+ } catch (err) {
304
+ return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
305
+ }
341
306
  }
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
- }
307
+ async function handleNavigate(cmd, workspace) {
308
+ if (!cmd.url) return { id: cmd.id, ok: false, error: "Missing url" };
309
+ const tabId = await resolveTabId(cmd.tabId, workspace);
310
+ await chrome.tabs.update(tabId, { url: cmd.url });
311
+ await new Promise((resolve) => {
312
+ chrome.tabs.get(tabId).then((tab2) => {
313
+ if (tab2.status === "complete") {
314
+ resolve();
315
+ return;
316
+ }
317
+ const listener = (id, info) => {
318
+ if (id === tabId && info.status === "complete") {
319
+ chrome.tabs.onUpdated.removeListener(listener);
320
+ resolve();
321
+ }
322
+ };
323
+ chrome.tabs.onUpdated.addListener(listener);
324
+ setTimeout(() => {
325
+ chrome.tabs.onUpdated.removeListener(listener);
326
+ resolve();
327
+ }, 15e3);
328
+ });
329
+ });
330
+ const tab = await chrome.tabs.get(tabId);
331
+ return { id: cmd.id, ok: true, data: { title: tab.title, url: tab.url, tabId } };
332
+ }
333
+ async function handleTabs(cmd, workspace) {
334
+ switch (cmd.op) {
335
+ case "list": {
336
+ const tabs = await listAutomationWebTabs(workspace);
337
+ const data = tabs.map((t, i) => ({
338
+ index: i,
339
+ tabId: t.id,
340
+ url: t.url,
341
+ title: t.title,
342
+ active: t.active
343
+ }));
344
+ return { id: cmd.id, ok: true, data };
345
+ }
346
+ case "new": {
347
+ const windowId = await getAutomationWindow(workspace);
348
+ const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? "about:blank", active: true });
349
+ return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
350
+ }
351
+ case "close": {
352
+ if (cmd.index !== void 0) {
353
+ const tabs = await listAutomationWebTabs(workspace);
354
+ const target = tabs[cmd.index];
355
+ if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
356
+ await chrome.tabs.remove(target.id);
357
+ detach(target.id);
358
+ return { id: cmd.id, ok: true, data: { closed: target.id } };
359
+ }
360
+ const tabId = await resolveTabId(cmd.tabId, workspace);
361
+ await chrome.tabs.remove(tabId);
362
+ detach(tabId);
363
+ return { id: cmd.id, ok: true, data: { closed: tabId } };
364
+ }
365
+ case "select": {
366
+ if (cmd.index === void 0 && cmd.tabId === void 0)
367
+ return { id: cmd.id, ok: false, error: "Missing index or tabId" };
368
+ if (cmd.tabId !== void 0) {
369
+ await chrome.tabs.update(cmd.tabId, { active: true });
370
+ return { id: cmd.id, ok: true, data: { selected: cmd.tabId } };
371
+ }
372
+ const tabs = await listAutomationWebTabs(workspace);
373
+ const target = tabs[cmd.index];
374
+ if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
375
+ await chrome.tabs.update(target.id, { active: true });
376
+ return { id: cmd.id, ok: true, data: { selected: target.id } };
377
+ }
378
+ default:
379
+ return { id: cmd.id, ok: false, error: `Unknown tabs op: ${cmd.op}` };
380
+ }
430
381
  }
431
382
  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
- };
383
+ const details = {};
384
+ if (cmd.domain) details.domain = cmd.domain;
385
+ if (cmd.url) details.url = cmd.url;
386
+ const cookies = await chrome.cookies.getAll(details);
387
+ const data = cookies.map((c) => ({
388
+ name: c.name,
389
+ value: c.value,
390
+ domain: c.domain,
391
+ path: c.path,
392
+ secure: c.secure,
393
+ httpOnly: c.httpOnly,
394
+ expirationDate: c.expirationDate
395
+ }));
396
+ return { id: cmd.id, ok: true, data };
397
+ }
398
+ async function handleScreenshot(cmd, workspace) {
399
+ const tabId = await resolveTabId(cmd.tabId, workspace);
400
+ try {
401
+ const data = await screenshot(tabId, {
402
+ format: cmd.format,
403
+ quality: cmd.quality,
404
+ fullPage: cmd.fullPage
405
+ });
406
+ return { id: cmd.id, ok: true, data };
407
+ } catch (err) {
408
+ return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
409
+ }
449
410
  }
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
- }
411
+ async function handleCloseWindow(cmd, workspace) {
412
+ const session = automationSessions.get(workspace);
413
+ if (session) {
414
+ try {
415
+ await chrome.windows.remove(session.windowId);
416
+ } catch {
417
+ }
418
+ if (session.idleTimer) clearTimeout(session.idleTimer);
419
+ automationSessions.delete(workspace);
420
+ }
421
+ return { id: cmd.id, ok: true, data: { closed: true } };
470
422
  }
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
- };
423
+ async function handleSessions(cmd) {
424
+ const now = Date.now();
425
+ const data = await Promise.all([...automationSessions.entries()].map(async ([workspace, session]) => ({
426
+ workspace,
427
+ windowId: session.windowId,
428
+ tabCount: (await chrome.tabs.query({ windowId: session.windowId })).filter((tab) => isWebUrl(tab.url)).length,
429
+ idleMsRemaining: Math.max(0, session.idleDeadlineAt - now)
430
+ })));
431
+ return { id: cmd.id, ok: true, data };
483
432
  }
484
- //#endregion