@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.
- package/.github/workflows/build-extension.yml +80 -0
- package/.github/workflows/ci.yml +6 -6
- package/.github/workflows/docs.yml +52 -0
- package/.github/workflows/e2e-headed.yml +2 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release.yml +2 -5
- package/.github/workflows/security.yml +2 -2
- package/CDP.md +1 -1
- package/CDP.zh-CN.md +1 -1
- package/README.md +42 -34
- package/README.zh-CN.md +42 -34
- package/SKILL.md +3 -5
- package/dist/browser/cdp.d.ts +42 -0
- package/dist/browser/cdp.js +339 -0
- package/dist/browser/daemon-client.d.ts +3 -1
- package/dist/browser/daemon-client.js +4 -0
- package/dist/browser/dom-helpers.d.ts +20 -0
- package/dist/browser/dom-helpers.js +109 -0
- package/dist/browser/index.d.ts +3 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/mcp.d.ts +1 -0
- package/dist/browser/mcp.js +10 -5
- package/dist/browser/page.d.ts +7 -0
- package/dist/browser/page.js +39 -123
- package/dist/browser/utils.d.ts +10 -0
- package/dist/browser/utils.js +27 -0
- package/dist/browser.test.js +49 -1
- package/dist/build-manifest.js +3 -1
- package/dist/build-manifest.test.js +34 -0
- package/dist/capabilityRouting.d.ts +2 -0
- package/dist/capabilityRouting.js +30 -0
- package/dist/capabilityRouting.test.d.ts +1 -0
- package/dist/capabilityRouting.test.js +42 -0
- package/dist/chaoxing.d.ts +58 -0
- package/dist/chaoxing.js +225 -0
- package/dist/chaoxing.test.d.ts +1 -0
- package/dist/chaoxing.test.js +45 -0
- package/dist/cli-manifest.json +885 -48
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +234 -0
- package/dist/clis/antigravity/serve.d.ts +14 -0
- package/dist/clis/antigravity/serve.js +263 -0
- package/dist/clis/bilibili/download.js +4 -14
- package/dist/clis/boss/chatlist.d.ts +1 -0
- package/dist/clis/boss/chatlist.js +50 -0
- package/dist/clis/boss/chatmsg.d.ts +1 -0
- package/dist/clis/boss/chatmsg.js +73 -0
- package/dist/clis/boss/resume.d.ts +1 -0
- package/dist/clis/boss/resume.js +249 -0
- package/dist/clis/boss/send.d.ts +1 -0
- package/dist/clis/boss/send.js +176 -0
- package/dist/clis/chaoxing/assignments.d.ts +1 -0
- package/dist/clis/chaoxing/assignments.js +74 -0
- package/dist/clis/chaoxing/exams.d.ts +1 -0
- package/dist/clis/chaoxing/exams.js +74 -0
- package/dist/clis/chatgpt/ask.js +15 -14
- package/dist/clis/chatgpt/ax.d.ts +1 -0
- package/dist/clis/chatgpt/ax.js +78 -0
- package/dist/clis/chatgpt/read.js +5 -6
- package/dist/clis/hf/top.d.ts +1 -0
- package/dist/clis/hf/top.js +119 -0
- package/dist/clis/jike/comment.d.ts +1 -0
- package/dist/clis/jike/comment.js +107 -0
- package/dist/clis/jike/create.d.ts +1 -0
- package/dist/clis/jike/create.js +106 -0
- package/dist/clis/jike/feed.d.ts +1 -0
- package/dist/clis/jike/feed.js +67 -0
- package/dist/clis/jike/like.d.ts +1 -0
- package/dist/clis/jike/like.js +61 -0
- package/dist/clis/jike/notifications.d.ts +1 -0
- package/dist/clis/jike/notifications.js +169 -0
- package/dist/clis/jike/post.yaml +58 -0
- package/dist/clis/jike/repost.d.ts +1 -0
- package/dist/clis/jike/repost.js +103 -0
- package/dist/clis/jike/search.d.ts +1 -0
- package/dist/clis/jike/search.js +67 -0
- package/dist/clis/jike/shared.d.ts +19 -0
- package/dist/clis/jike/shared.js +25 -0
- package/dist/clis/jike/topic.yaml +52 -0
- package/dist/clis/jike/user.yaml +51 -0
- package/dist/clis/smzdm/search.js +28 -39
- package/dist/clis/stackoverflow/bounties.yaml +29 -0
- package/dist/clis/stackoverflow/hot.yaml +28 -0
- package/dist/clis/stackoverflow/search.yaml +32 -0
- package/dist/clis/stackoverflow/unanswered.yaml +28 -0
- package/dist/clis/twitter/download.js +6 -16
- package/dist/clis/twitter/post.js +9 -2
- package/dist/clis/twitter/search.js +14 -33
- package/dist/clis/xiaohongshu/download.d.ts +1 -1
- package/dist/clis/xiaohongshu/download.js +4 -4
- package/dist/clis/zhihu/download.js +3 -3
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +16 -0
- package/dist/download/index.d.ts +12 -8
- package/dist/download/index.js +11 -3
- package/dist/download/index.test.d.ts +1 -0
- package/dist/download/index.test.js +14 -0
- package/dist/engine.js +25 -14
- package/dist/explore.d.ts +1 -0
- package/dist/explore.js +48 -103
- package/dist/generate.js +1 -0
- package/dist/interceptor.js +3 -2
- package/dist/main.js +4 -193
- package/dist/output.d.ts +2 -1
- package/dist/output.js +3 -1
- package/dist/pipeline/executor.test.js +1 -0
- package/dist/pipeline/steps/download.js +14 -18
- package/dist/registry.d.ts +4 -3
- package/dist/registry.js +5 -2
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.js +2 -2
- package/dist/scripts/framework.d.ts +4 -0
- package/dist/scripts/framework.js +21 -0
- package/dist/scripts/interact.d.ts +4 -0
- package/dist/scripts/interact.js +20 -0
- package/dist/scripts/store.d.ts +9 -0
- package/dist/scripts/store.js +44 -0
- package/dist/synthesize.js +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/verify.d.ts +6 -1
- package/dist/verify.js +54 -2
- package/docs/.vitepress/config.mts +193 -0
- package/docs/adapters/browser/apple-podcasts.md +28 -0
- package/docs/adapters/browser/bbc.md +26 -0
- package/docs/adapters/browser/bilibili.md +38 -0
- package/docs/adapters/browser/boss.md +28 -0
- package/docs/adapters/browser/coupang.md +28 -0
- package/docs/adapters/browser/ctrip.md +27 -0
- package/docs/adapters/browser/github.md +26 -0
- package/docs/adapters/browser/hackernews.md +26 -0
- package/docs/adapters/browser/linkedin.md +27 -0
- package/docs/adapters/browser/reddit.md +41 -0
- package/docs/adapters/browser/reuters.md +27 -0
- package/docs/adapters/browser/smzdm.md +27 -0
- package/docs/adapters/browser/twitter.md +47 -0
- package/docs/adapters/browser/v2ex.md +32 -0
- package/docs/adapters/browser/weibo.md +27 -0
- package/docs/adapters/browser/xiaohongshu.md +32 -0
- package/docs/adapters/browser/xiaoyuzhou.md +28 -0
- package/docs/adapters/browser/xueqiu.md +32 -0
- package/docs/adapters/browser/yahoo-finance.md +26 -0
- package/docs/adapters/browser/youtube.md +29 -0
- package/docs/adapters/browser/zhihu.md +30 -0
- package/docs/adapters/desktop/antigravity.md +46 -0
- package/docs/adapters/desktop/chatgpt.md +43 -0
- package/docs/adapters/desktop/chatwise.md +38 -0
- package/docs/adapters/desktop/codex.md +32 -0
- package/docs/adapters/desktop/cursor.md +33 -0
- package/docs/adapters/desktop/discord.md +28 -0
- package/docs/adapters/desktop/feishu.md +20 -0
- package/docs/adapters/desktop/neteasemusic.md +31 -0
- package/docs/adapters/desktop/notion.md +29 -0
- package/docs/adapters/desktop/wechat.md +28 -0
- package/docs/adapters/index.md +49 -0
- package/docs/advanced/cdp.md +103 -0
- package/docs/advanced/download.md +63 -0
- package/docs/advanced/electron.md +125 -0
- package/docs/advanced/remote-chrome.md +72 -0
- package/docs/developer/ai-workflow.md +66 -0
- package/docs/developer/architecture.md +90 -0
- package/docs/developer/contributing.md +136 -0
- package/docs/developer/testing.md +237 -0
- package/docs/developer/ts-adapter.md +87 -0
- package/docs/developer/yaml-adapter.md +108 -0
- package/docs/guide/browser-bridge.md +38 -0
- package/docs/guide/getting-started.md +56 -0
- package/docs/guide/installation.md +37 -0
- package/docs/guide/troubleshooting.md +56 -0
- package/docs/index.md +35 -0
- package/docs/zh/adapters/index.md +5 -0
- package/docs/zh/advanced/cdp.md +3 -0
- package/docs/zh/developer/contributing.md +24 -0
- package/docs/zh/guide/browser-bridge.md +25 -0
- package/docs/zh/guide/getting-started.md +40 -0
- package/docs/zh/guide/installation.md +37 -0
- package/docs/zh/index.md +29 -0
- package/extension/dist/background.js +386 -438
- package/extension/manifest.json +2 -2
- package/extension/package-lock.json +1156 -0
- package/extension/src/background.test.ts +151 -0
- package/extension/src/background.ts +124 -53
- package/extension/src/protocol.ts +3 -1
- package/package.json +7 -3
- package/src/browser/cdp.ts +367 -0
- package/src/browser/daemon-client.ts +7 -1
- package/src/browser/dom-helpers.ts +116 -0
- package/src/browser/index.ts +4 -0
- package/src/browser/mcp.ts +14 -6
- package/src/browser/page.ts +47 -124
- package/src/browser/utils.ts +27 -0
- package/src/browser.test.ts +56 -0
- package/src/build-manifest.test.ts +36 -0
- package/src/build-manifest.ts +2 -1
- package/src/capabilityRouting.test.ts +47 -0
- package/src/capabilityRouting.ts +28 -0
- package/src/chaoxing.test.ts +53 -0
- package/src/chaoxing.ts +268 -0
- package/src/cli.ts +205 -0
- package/src/clis/antigravity/SKILL.md +5 -0
- package/src/clis/antigravity/serve.ts +329 -0
- package/src/clis/bilibili/download.ts +4 -15
- package/src/clis/boss/chatlist.ts +50 -0
- package/src/clis/boss/chatmsg.ts +70 -0
- package/src/clis/boss/resume.ts +262 -0
- package/src/clis/boss/send.ts +193 -0
- package/src/clis/chaoxing/README.md +36 -0
- package/src/clis/chaoxing/README.zh-CN.md +35 -0
- package/src/clis/chaoxing/assignments.ts +88 -0
- package/src/clis/chaoxing/exams.ts +88 -0
- package/src/clis/chatgpt/ask.ts +14 -15
- package/src/clis/chatgpt/ax.ts +81 -0
- package/src/clis/chatgpt/read.ts +5 -7
- package/src/clis/hf/top.ts +141 -0
- package/src/clis/jike/comment.ts +113 -0
- package/src/clis/jike/create.ts +113 -0
- package/src/clis/jike/feed.ts +74 -0
- package/src/clis/jike/like.ts +65 -0
- package/src/clis/jike/notifications.ts +185 -0
- package/src/clis/jike/post.yaml +58 -0
- package/src/clis/jike/repost.ts +114 -0
- package/src/clis/jike/search.ts +74 -0
- package/src/clis/jike/shared.ts +36 -0
- package/src/clis/jike/topic.yaml +52 -0
- package/src/clis/jike/user.yaml +51 -0
- package/src/clis/smzdm/search.ts +30 -39
- package/src/clis/stackoverflow/bounties.yaml +29 -0
- package/src/clis/stackoverflow/hot.yaml +28 -0
- package/src/clis/stackoverflow/search.yaml +32 -0
- package/src/clis/stackoverflow/unanswered.yaml +28 -0
- package/src/clis/twitter/download.ts +6 -17
- package/src/clis/twitter/post.ts +9 -2
- package/src/clis/twitter/search.ts +15 -33
- package/src/clis/xiaohongshu/download.ts +4 -4
- package/src/clis/zhihu/download.ts +3 -3
- package/src/doctor.ts +18 -2
- package/src/download/index.test.ts +16 -0
- package/src/download/index.ts +22 -4
- package/src/engine.ts +20 -13
- package/src/explore.ts +54 -103
- package/src/generate.ts +1 -0
- package/src/interceptor.ts +3 -2
- package/src/main.ts +4 -180
- package/src/output.ts +15 -13
- package/src/pipeline/executor.test.ts +1 -0
- package/src/pipeline/steps/download.ts +14 -17
- package/src/registry.ts +9 -5
- package/src/runtime.ts +3 -2
- package/src/scripts/framework.ts +20 -0
- package/src/scripts/interact.ts +22 -0
- package/src/scripts/store.ts +40 -0
- package/src/synthesize.ts +1 -1
- package/src/types.ts +9 -0
- package/src/verify.ts +64 -3
|
@@ -1,484 +1,432 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var WS_RECONNECT_BASE_DELAY = 2e3;
|
|
9
|
-
/** Max reconnect delay (ms) */
|
|
10
|
-
var WS_RECONNECT_MAX_DELAY = 6e4;
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region src/cdp.ts
|
|
13
|
-
/**
|
|
14
|
-
* CDP execution via chrome.debugger API.
|
|
15
|
-
*
|
|
16
|
-
* chrome.debugger only needs the "debugger" permission — no host_permissions.
|
|
17
|
-
* It can attach to any http/https tab. Avoid chrome:// and chrome-extension://
|
|
18
|
-
* tabs (resolveTabId in background.ts filters them).
|
|
19
|
-
*/
|
|
20
|
-
var attached = /* @__PURE__ */ new Set();
|
|
1
|
+
const DAEMON_PORT = 19825;
|
|
2
|
+
const DAEMON_HOST = "localhost";
|
|
3
|
+
const DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
|
|
4
|
+
const WS_RECONNECT_BASE_DELAY = 2e3;
|
|
5
|
+
const WS_RECONNECT_MAX_DELAY = 6e4;
|
|
6
|
+
|
|
7
|
+
const attached = /* @__PURE__ */ new Set();
|
|
21
8
|
async function ensureAttached(tabId) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
9
|
+
if (attached.has(tabId)) return;
|
|
10
|
+
try {
|
|
11
|
+
await chrome.debugger.attach({ tabId }, "1.3");
|
|
12
|
+
} catch (e) {
|
|
13
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
14
|
+
if (msg.includes("Another debugger is already attached")) {
|
|
15
|
+
try {
|
|
16
|
+
await chrome.debugger.detach({ tabId });
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
await chrome.debugger.attach({ tabId }, "1.3");
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error(`attach failed: ${msg}`);
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error(`attach failed: ${msg}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
attached.add(tabId);
|
|
29
|
+
try {
|
|
30
|
+
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
42
33
|
}
|
|
43
34
|
async function evaluate(tabId, expression) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
35
|
+
await ensureAttached(tabId);
|
|
36
|
+
const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
|
|
37
|
+
expression,
|
|
38
|
+
returnByValue: true,
|
|
39
|
+
awaitPromise: true
|
|
40
|
+
});
|
|
41
|
+
if (result.exceptionDetails) {
|
|
42
|
+
const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
|
|
43
|
+
throw new Error(errMsg);
|
|
44
|
+
}
|
|
45
|
+
return result.result?.value;
|
|
55
46
|
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Capture a screenshot via CDP Page.captureScreenshot.
|
|
59
|
-
* Returns base64-encoded image data.
|
|
60
|
-
*/
|
|
47
|
+
const evaluateAsync = evaluate;
|
|
61
48
|
async function screenshot(tabId, options = {}) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
49
|
+
await ensureAttached(tabId);
|
|
50
|
+
const format = options.format ?? "png";
|
|
51
|
+
if (options.fullPage) {
|
|
52
|
+
const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics");
|
|
53
|
+
const size = metrics.cssContentSize || metrics.contentSize;
|
|
54
|
+
if (size) {
|
|
55
|
+
await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
|
|
56
|
+
mobile: false,
|
|
57
|
+
width: Math.ceil(size.width),
|
|
58
|
+
height: Math.ceil(size.height),
|
|
59
|
+
deviceScaleFactor: 1
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const params = { format };
|
|
65
|
+
if (format === "jpeg" && options.quality !== void 0) {
|
|
66
|
+
params.quality = Math.max(0, Math.min(100, options.quality));
|
|
67
|
+
}
|
|
68
|
+
const result = await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params);
|
|
69
|
+
return result.data;
|
|
70
|
+
} finally {
|
|
71
|
+
if (options.fullPage) {
|
|
72
|
+
await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
81
76
|
}
|
|
82
77
|
function detach(tabId) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
if (!attached.has(tabId)) return;
|
|
79
|
+
attached.delete(tabId);
|
|
80
|
+
try {
|
|
81
|
+
chrome.debugger.detach({ tabId });
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
88
84
|
}
|
|
89
85
|
function registerListeners() {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
86
|
+
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
87
|
+
attached.delete(tabId);
|
|
88
|
+
});
|
|
89
|
+
chrome.debugger.onDetach.addListener((source) => {
|
|
90
|
+
if (source.tabId) attached.delete(source.tabId);
|
|
91
|
+
});
|
|
96
92
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
var _origError = console.error.bind(console);
|
|
93
|
+
|
|
94
|
+
let ws = null;
|
|
95
|
+
let reconnectTimer = null;
|
|
96
|
+
let reconnectAttempts = 0;
|
|
97
|
+
const _origLog = console.log.bind(console);
|
|
98
|
+
const _origWarn = console.warn.bind(console);
|
|
99
|
+
const _origError = console.error.bind(console);
|
|
105
100
|
function forwardLog(level, args) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
msg,
|
|
113
|
-
ts: Date.now()
|
|
114
|
-
}));
|
|
115
|
-
} catch {}
|
|
101
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
102
|
+
try {
|
|
103
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
104
|
+
ws.send(JSON.stringify({ type: "log", level, msg, ts: Date.now() }));
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
116
107
|
}
|
|
117
108
|
console.log = (...args) => {
|
|
118
|
-
|
|
119
|
-
|
|
109
|
+
_origLog(...args);
|
|
110
|
+
forwardLog("info", args);
|
|
120
111
|
};
|
|
121
112
|
console.warn = (...args) => {
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
_origWarn(...args);
|
|
114
|
+
forwardLog("warn", args);
|
|
124
115
|
};
|
|
125
116
|
console.error = (...args) => {
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
_origError(...args);
|
|
118
|
+
forwardLog("error", args);
|
|
128
119
|
};
|
|
129
120
|
function connect() {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
121
|
+
if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
|
|
122
|
+
try {
|
|
123
|
+
ws = new WebSocket(DAEMON_WS_URL);
|
|
124
|
+
} catch {
|
|
125
|
+
scheduleReconnect();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
ws.onopen = () => {
|
|
129
|
+
console.log("[opencli] Connected to daemon");
|
|
130
|
+
reconnectAttempts = 0;
|
|
131
|
+
if (reconnectTimer) {
|
|
132
|
+
clearTimeout(reconnectTimer);
|
|
133
|
+
reconnectTimer = null;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
ws.onmessage = async (event) => {
|
|
137
|
+
try {
|
|
138
|
+
const command = JSON.parse(event.data);
|
|
139
|
+
const result = await handleCommand(command);
|
|
140
|
+
ws?.send(JSON.stringify(result));
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error("[opencli] Message handling error:", err);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
ws.onclose = () => {
|
|
146
|
+
console.log("[opencli] Disconnected from daemon");
|
|
147
|
+
ws = null;
|
|
148
|
+
scheduleReconnect();
|
|
149
|
+
};
|
|
150
|
+
ws.onerror = () => {
|
|
151
|
+
ws?.close();
|
|
152
|
+
};
|
|
161
153
|
}
|
|
162
154
|
function scheduleReconnect() {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
if (reconnectTimer) return;
|
|
156
|
+
reconnectAttempts++;
|
|
157
|
+
const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
|
|
158
|
+
reconnectTimer = setTimeout(() => {
|
|
159
|
+
reconnectTimer = null;
|
|
160
|
+
connect();
|
|
161
|
+
}, delay);
|
|
170
162
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
220
|
+
let initialized = false;
|
|
216
221
|
function initialize() {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
230
|
+
initialize();
|
|
226
231
|
});
|
|
227
232
|
chrome.runtime.onStartup.addListener(() => {
|
|
228
|
-
|
|
233
|
+
initialize();
|
|
229
234
|
});
|
|
230
235
|
chrome.alarms.onAlarm.addListener((alarm) => {
|
|
231
|
-
|
|
236
|
+
if (alarm.name === "keepalive") connect();
|
|
232
237
|
});
|
|
233
238
|
async function handleCommand(cmd) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
283
|
-
|
|
284
|
-
|
|
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
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|