@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
package/dist/browser/page.js
CHANGED
|
@@ -11,19 +11,29 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { formatSnapshot } from '../snapshotFormatter.js';
|
|
13
13
|
import { sendCommand } from './daemon-client.js';
|
|
14
|
+
import { wrapForEval } from './utils.js';
|
|
15
|
+
import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, scrollJs, autoScrollJs, networkRequestsJs, } from './dom-helpers.js';
|
|
14
16
|
/**
|
|
15
17
|
* Page — implements IPage by talking to the daemon via HTTP.
|
|
16
18
|
*/
|
|
17
19
|
export class Page {
|
|
20
|
+
workspace;
|
|
21
|
+
constructor(workspace = 'default') {
|
|
22
|
+
this.workspace = workspace;
|
|
23
|
+
}
|
|
18
24
|
/** Active tab ID, set after navigate and used in all subsequent commands */
|
|
19
25
|
_tabId;
|
|
20
26
|
/** Helper: spread tabId into command params if we have one */
|
|
21
27
|
_tabOpt() {
|
|
22
28
|
return this._tabId !== undefined ? { tabId: this._tabId } : {};
|
|
23
29
|
}
|
|
30
|
+
_workspaceOpt() {
|
|
31
|
+
return { workspace: this.workspace };
|
|
32
|
+
}
|
|
24
33
|
async goto(url) {
|
|
25
34
|
const result = await sendCommand('navigate', {
|
|
26
35
|
url,
|
|
36
|
+
...this._workspaceOpt(),
|
|
27
37
|
...this._tabOpt(),
|
|
28
38
|
});
|
|
29
39
|
// Remember the tabId for subsequent exec calls
|
|
@@ -34,7 +44,7 @@ export class Page {
|
|
|
34
44
|
/** Close the automation window in the extension */
|
|
35
45
|
async closeWindow() {
|
|
36
46
|
try {
|
|
37
|
-
await sendCommand('close-window', {});
|
|
47
|
+
await sendCommand('close-window', { ...this._workspaceOpt() });
|
|
38
48
|
}
|
|
39
49
|
catch {
|
|
40
50
|
// Window may already be closed or daemon may be down
|
|
@@ -42,7 +52,11 @@ export class Page {
|
|
|
42
52
|
}
|
|
43
53
|
async evaluate(js) {
|
|
44
54
|
const code = wrapForEval(js);
|
|
45
|
-
return sendCommand('exec', { code, ...this._tabOpt() });
|
|
55
|
+
return sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
|
|
56
|
+
}
|
|
57
|
+
async getCookies(opts = {}) {
|
|
58
|
+
const result = await sendCommand('cookies', { ...this._workspaceOpt(), ...opts });
|
|
59
|
+
return Array.isArray(result) ? result : [];
|
|
46
60
|
}
|
|
47
61
|
async snapshot(opts = {}) {
|
|
48
62
|
const maxDepth = Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200));
|
|
@@ -73,7 +87,7 @@ export class Page {
|
|
|
73
87
|
return buildTree(document.body, 0);
|
|
74
88
|
})()
|
|
75
89
|
`;
|
|
76
|
-
const raw = await sendCommand('exec', { code, ...this._tabOpt() });
|
|
90
|
+
const raw = await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
|
|
77
91
|
if (opts.raw)
|
|
78
92
|
return raw;
|
|
79
93
|
if (typeof raw === 'string')
|
|
@@ -81,48 +95,16 @@ export class Page {
|
|
|
81
95
|
return raw;
|
|
82
96
|
}
|
|
83
97
|
async click(ref) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
(() => {
|
|
87
|
-
const ref = ${safeRef};
|
|
88
|
-
const el = document.querySelector('[data-ref="' + ref + '"]')
|
|
89
|
-
|| document.querySelectorAll('a, button, input, [role="button"], [tabindex]')[parseInt(ref, 10) || 0];
|
|
90
|
-
if (!el) throw new Error('Element not found: ' + ref);
|
|
91
|
-
el.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
92
|
-
el.click();
|
|
93
|
-
return 'clicked';
|
|
94
|
-
})()
|
|
95
|
-
`;
|
|
96
|
-
await sendCommand('exec', { code, ...this._tabOpt() });
|
|
98
|
+
const code = clickJs(ref);
|
|
99
|
+
await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
|
|
97
100
|
}
|
|
98
101
|
async typeText(ref, text) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
const code = `
|
|
102
|
-
(() => {
|
|
103
|
-
const ref = ${safeRef};
|
|
104
|
-
const el = document.querySelector('[data-ref="' + ref + '"]')
|
|
105
|
-
|| document.querySelectorAll('input, textarea, [contenteditable]')[parseInt(ref, 10) || 0];
|
|
106
|
-
if (!el) throw new Error('Element not found: ' + ref);
|
|
107
|
-
el.focus();
|
|
108
|
-
el.value = ${safeText};
|
|
109
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
110
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
111
|
-
return 'typed';
|
|
112
|
-
})()
|
|
113
|
-
`;
|
|
114
|
-
await sendCommand('exec', { code, ...this._tabOpt() });
|
|
102
|
+
const code = typeTextJs(ref, text);
|
|
103
|
+
await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
|
|
115
104
|
}
|
|
116
105
|
async pressKey(key) {
|
|
117
|
-
const code =
|
|
118
|
-
|
|
119
|
-
const el = document.activeElement || document.body;
|
|
120
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: ${JSON.stringify(key)}, bubbles: true }));
|
|
121
|
-
el.dispatchEvent(new KeyboardEvent('keyup', { key: ${JSON.stringify(key)}, bubbles: true }));
|
|
122
|
-
return 'pressed';
|
|
123
|
-
})()
|
|
124
|
-
`;
|
|
125
|
-
await sendCommand('exec', { code, ...this._tabOpt() });
|
|
106
|
+
const code = pressKeyJs(key);
|
|
107
|
+
await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
|
|
126
108
|
}
|
|
127
109
|
async wait(options) {
|
|
128
110
|
if (typeof options === 'number') {
|
|
@@ -135,47 +117,25 @@ export class Page {
|
|
|
135
117
|
}
|
|
136
118
|
if (options.text) {
|
|
137
119
|
const timeout = (options.timeout ?? 30) * 1000;
|
|
138
|
-
const code =
|
|
139
|
-
|
|
140
|
-
const deadline = Date.now() + ${timeout};
|
|
141
|
-
const check = () => {
|
|
142
|
-
if (document.body.innerText.includes(${JSON.stringify(options.text)})) return resolve('found');
|
|
143
|
-
if (Date.now() > deadline) return reject(new Error('Text not found: ' + ${JSON.stringify(options.text)}));
|
|
144
|
-
setTimeout(check, 200);
|
|
145
|
-
};
|
|
146
|
-
check();
|
|
147
|
-
})
|
|
148
|
-
`;
|
|
149
|
-
await sendCommand('exec', { code, ...this._tabOpt() });
|
|
120
|
+
const code = waitForTextJs(options.text, timeout);
|
|
121
|
+
await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
|
|
150
122
|
}
|
|
151
123
|
}
|
|
152
124
|
async tabs() {
|
|
153
|
-
return sendCommand('tabs', { op: 'list' });
|
|
125
|
+
return sendCommand('tabs', { op: 'list', ...this._workspaceOpt() });
|
|
154
126
|
}
|
|
155
127
|
async closeTab(index) {
|
|
156
|
-
await sendCommand('tabs', { op: 'close', ...(index !== undefined ? { index } : {}) });
|
|
128
|
+
await sendCommand('tabs', { op: 'close', ...this._workspaceOpt(), ...(index !== undefined ? { index } : {}) });
|
|
157
129
|
}
|
|
158
130
|
async newTab() {
|
|
159
|
-
await sendCommand('tabs', { op: 'new' });
|
|
131
|
+
await sendCommand('tabs', { op: 'new', ...this._workspaceOpt() });
|
|
160
132
|
}
|
|
161
133
|
async selectTab(index) {
|
|
162
|
-
await sendCommand('tabs', { op: 'select', index });
|
|
134
|
+
await sendCommand('tabs', { op: 'select', index, ...this._workspaceOpt() });
|
|
163
135
|
}
|
|
164
136
|
async networkRequests(includeStatic = false) {
|
|
165
|
-
const code =
|
|
166
|
-
|
|
167
|
-
const entries = performance.getEntriesByType('resource');
|
|
168
|
-
return entries
|
|
169
|
-
${includeStatic ? '' : '.filter(e => !["img", "font", "css", "script"].some(t => e.initiatorType === t))'}
|
|
170
|
-
.map(e => ({
|
|
171
|
-
url: e.name,
|
|
172
|
-
type: e.initiatorType,
|
|
173
|
-
duration: Math.round(e.duration),
|
|
174
|
-
size: e.transferSize || 0,
|
|
175
|
-
}));
|
|
176
|
-
})()
|
|
177
|
-
`;
|
|
178
|
-
return sendCommand('exec', { code, ...this._tabOpt() });
|
|
137
|
+
const code = networkRequestsJs(includeStatic);
|
|
138
|
+
return sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
|
|
179
139
|
}
|
|
180
140
|
/**
|
|
181
141
|
* Console messages are not available in lightweight daemon mode.
|
|
@@ -194,6 +154,7 @@ export class Page {
|
|
|
194
154
|
*/
|
|
195
155
|
async screenshot(options = {}) {
|
|
196
156
|
const base64 = await sendCommand('screenshot', {
|
|
157
|
+
...this._workspaceOpt(),
|
|
197
158
|
format: options.format,
|
|
198
159
|
quality: options.quality,
|
|
199
160
|
fullPage: options.fullPage,
|
|
@@ -203,43 +164,20 @@ export class Page {
|
|
|
203
164
|
const fs = await import('node:fs');
|
|
204
165
|
const path = await import('node:path');
|
|
205
166
|
const dir = path.dirname(options.path);
|
|
206
|
-
fs.
|
|
207
|
-
fs.
|
|
167
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
168
|
+
await fs.promises.writeFile(options.path, Buffer.from(base64, 'base64'));
|
|
208
169
|
}
|
|
209
170
|
return base64;
|
|
210
171
|
}
|
|
211
172
|
async scroll(direction = 'down', amount = 500) {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
await sendCommand('exec', {
|
|
215
|
-
code: `window.scrollBy(${dx}, ${dy})`,
|
|
216
|
-
...this._tabOpt(),
|
|
217
|
-
});
|
|
173
|
+
const code = scrollJs(direction, amount);
|
|
174
|
+
await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
|
|
218
175
|
}
|
|
219
176
|
async autoScroll(options = {}) {
|
|
220
177
|
const times = options.times ?? 3;
|
|
221
178
|
const delayMs = options.delayMs ?? 2000;
|
|
222
|
-
const code =
|
|
223
|
-
|
|
224
|
-
for (let i = 0; i < ${times}; i++) {
|
|
225
|
-
const lastHeight = document.body.scrollHeight;
|
|
226
|
-
window.scrollTo(0, lastHeight);
|
|
227
|
-
await new Promise(resolve => {
|
|
228
|
-
let timeoutId;
|
|
229
|
-
const observer = new MutationObserver(() => {
|
|
230
|
-
if (document.body.scrollHeight > lastHeight) {
|
|
231
|
-
clearTimeout(timeoutId);
|
|
232
|
-
observer.disconnect();
|
|
233
|
-
setTimeout(resolve, 100);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
237
|
-
timeoutId = setTimeout(() => { observer.disconnect(); resolve(null); }, ${delayMs});
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
})()
|
|
241
|
-
`;
|
|
242
|
-
await sendCommand('exec', { code, ...this._tabOpt() });
|
|
179
|
+
const code = autoScrollJs(times, delayMs);
|
|
180
|
+
await sendCommand('exec', { code, ...this._workspaceOpt(), ...this._tabOpt() });
|
|
243
181
|
}
|
|
244
182
|
async installInterceptor(pattern) {
|
|
245
183
|
const { generateInterceptorJs } = await import('../interceptor.js');
|
|
@@ -257,26 +195,4 @@ export class Page {
|
|
|
257
195
|
return result || [];
|
|
258
196
|
}
|
|
259
197
|
}
|
|
260
|
-
//
|
|
261
|
-
/**
|
|
262
|
-
* Wrap JS code for CDP Runtime.evaluate:
|
|
263
|
-
* - Already an IIFE `(...)()` → send as-is
|
|
264
|
-
* - Arrow/function literal → wrap as IIFE `(code)()`
|
|
265
|
-
* - `new Promise(...)` or raw expression → send as-is (expression)
|
|
266
|
-
*/
|
|
267
|
-
function wrapForEval(js) {
|
|
268
|
-
const code = js.trim();
|
|
269
|
-
if (!code)
|
|
270
|
-
return 'undefined';
|
|
271
|
-
// Already an IIFE: `(async () => { ... })()` or `(function() {...})()`
|
|
272
|
-
if (/^\([\s\S]*\)\s*\(.*\)\s*$/.test(code))
|
|
273
|
-
return code;
|
|
274
|
-
// Arrow function: `() => ...` or `async () => ...`
|
|
275
|
-
if (/^(async\s+)?(\([^)]*\)|[A-Za-z_]\w*)\s*=>/.test(code))
|
|
276
|
-
return `(${code})()`;
|
|
277
|
-
// Function declaration: `function ...` or `async function ...`
|
|
278
|
-
if (/^(async\s+)?function[\s(]/.test(code))
|
|
279
|
-
return `(${code})()`;
|
|
280
|
-
// Everything else: bare expression, `new Promise(...)`, etc. → evaluate directly
|
|
281
|
-
return code;
|
|
282
|
-
}
|
|
198
|
+
// (End of file)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for browser operations
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Wrap JS code for CDP Runtime.evaluate:
|
|
6
|
+
* - Already an IIFE `(...)()` → send as-is
|
|
7
|
+
* - Arrow/function literal → wrap as IIFE `(code)()`
|
|
8
|
+
* - `new Promise(...)` or raw expression → send as-is (expression)
|
|
9
|
+
*/
|
|
10
|
+
export declare function wrapForEval(js: string): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for browser operations
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Wrap JS code for CDP Runtime.evaluate:
|
|
6
|
+
* - Already an IIFE `(...)()` → send as-is
|
|
7
|
+
* - Arrow/function literal → wrap as IIFE `(code)()`
|
|
8
|
+
* - `new Promise(...)` or raw expression → send as-is (expression)
|
|
9
|
+
*/
|
|
10
|
+
export function wrapForEval(js) {
|
|
11
|
+
if (typeof js !== 'string')
|
|
12
|
+
return 'undefined';
|
|
13
|
+
const code = js.trim();
|
|
14
|
+
if (!code)
|
|
15
|
+
return 'undefined';
|
|
16
|
+
// Already an IIFE: `(async () => { ... })()` or `(function() {...})()`
|
|
17
|
+
if (/^\([\s\S]*\)\s*\(.*\)\s*$/.test(code))
|
|
18
|
+
return code;
|
|
19
|
+
// Arrow function: `() => ...` or `async () => ...`
|
|
20
|
+
if (/^(async\s+)?(\([^)]*\)|[A-Za-z_]\w*)\s*=>/.test(code))
|
|
21
|
+
return `(${code})()`;
|
|
22
|
+
// Function declaration: `function ...` or `async function ...`
|
|
23
|
+
if (/^(async\s+)?function[\s(]/.test(code))
|
|
24
|
+
return `(${code})()`;
|
|
25
|
+
// Everything else: bare expression, `new Promise(...)`, etc. → evaluate directly
|
|
26
|
+
return code;
|
|
27
|
+
}
|
package/dist/browser.test.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import { BrowserBridge, __test__ } from './browser/index.js';
|
|
3
|
+
import * as daemonClient from './browser/daemon-client.js';
|
|
3
4
|
describe('browser helpers', () => {
|
|
4
5
|
it('extracts tab entries from string snapshots', () => {
|
|
5
6
|
const entries = __test__.extractTabEntries('Tab 0 https://example.com\nTab 1 Chrome Extension');
|
|
@@ -30,6 +31,47 @@ describe('browser helpers', () => {
|
|
|
30
31
|
it('times out slow promises', async () => {
|
|
31
32
|
await expect(__test__.withTimeoutMs(new Promise(() => { }), 10, 'timeout')).rejects.toThrow('timeout');
|
|
32
33
|
});
|
|
34
|
+
it('prefers the real Electron app target over DevTools and blank pages', () => {
|
|
35
|
+
const target = __test__.selectCDPTarget([
|
|
36
|
+
{
|
|
37
|
+
type: 'page',
|
|
38
|
+
title: 'DevTools - localhost:9224',
|
|
39
|
+
url: 'devtools://devtools/bundled/inspector.html',
|
|
40
|
+
webSocketDebuggerUrl: 'ws://127.0.0.1:9224/devtools',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'page',
|
|
44
|
+
title: '',
|
|
45
|
+
url: 'about:blank',
|
|
46
|
+
webSocketDebuggerUrl: 'ws://127.0.0.1:9224/blank',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
type: 'app',
|
|
50
|
+
title: 'Antigravity',
|
|
51
|
+
url: 'http://localhost:3000/',
|
|
52
|
+
webSocketDebuggerUrl: 'ws://127.0.0.1:9224/app',
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
expect(target?.webSocketDebuggerUrl).toBe('ws://127.0.0.1:9224/app');
|
|
56
|
+
});
|
|
57
|
+
it('honors OPENCLI_CDP_TARGET when multiple inspectable targets exist', () => {
|
|
58
|
+
vi.stubEnv('OPENCLI_CDP_TARGET', 'codex');
|
|
59
|
+
const target = __test__.selectCDPTarget([
|
|
60
|
+
{
|
|
61
|
+
type: 'app',
|
|
62
|
+
title: 'Cursor',
|
|
63
|
+
url: 'http://localhost:3000/cursor',
|
|
64
|
+
webSocketDebuggerUrl: 'ws://127.0.0.1:9226/cursor',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'app',
|
|
68
|
+
title: 'OpenAI Codex',
|
|
69
|
+
url: 'http://localhost:3000/codex',
|
|
70
|
+
webSocketDebuggerUrl: 'ws://127.0.0.1:9226/codex',
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
expect(target?.webSocketDebuggerUrl).toBe('ws://127.0.0.1:9226/codex');
|
|
74
|
+
});
|
|
33
75
|
});
|
|
34
76
|
describe('BrowserBridge state', () => {
|
|
35
77
|
it('transitions to closed after close()', async () => {
|
|
@@ -53,4 +95,10 @@ describe('BrowserBridge state', () => {
|
|
|
53
95
|
mcp._state = 'closing';
|
|
54
96
|
await expect(mcp.connect()).rejects.toThrow('Session is closing');
|
|
55
97
|
});
|
|
98
|
+
it('fails fast when daemon is running but extension is disconnected', async () => {
|
|
99
|
+
vi.spyOn(daemonClient, 'isExtensionConnected').mockResolvedValue(false);
|
|
100
|
+
vi.spyOn(daemonClient, 'isDaemonRunning').mockResolvedValue(true);
|
|
101
|
+
const mcp = new BrowserBridge();
|
|
102
|
+
await expect(mcp.connect()).rejects.toThrow('Browser Extension is not connected');
|
|
103
|
+
});
|
|
56
104
|
});
|
package/dist/build-manifest.js
CHANGED
|
@@ -73,7 +73,7 @@ export function parseTsArgsBlock(argsBlock) {
|
|
|
73
73
|
const args = [];
|
|
74
74
|
let cursor = 0;
|
|
75
75
|
while (cursor < argsBlock.length) {
|
|
76
|
-
const nameMatch = argsBlock.slice(cursor).match(/\{\s*name\s*:\s*['"`](
|
|
76
|
+
const nameMatch = argsBlock.slice(cursor).match(/\{\s*name\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
77
77
|
if (!nameMatch || nameMatch.index === undefined)
|
|
78
78
|
break;
|
|
79
79
|
const objectStart = cursor + nameMatch.index;
|
|
@@ -186,6 +186,8 @@ function scanTs(filePath, site) {
|
|
|
186
186
|
const browserMatch = src.match(/browser\s*:\s*(true|false)/);
|
|
187
187
|
if (browserMatch)
|
|
188
188
|
entry.browser = browserMatch[1] === 'true';
|
|
189
|
+
else
|
|
190
|
+
entry.browser = entry.strategy !== 'public';
|
|
189
191
|
// Extract columns
|
|
190
192
|
const colMatch = src.match(/columns\s*:\s*\[([^\]]*)\]/);
|
|
191
193
|
if (colMatch) {
|
|
@@ -23,4 +23,38 @@ describe('parseTsArgsBlock', () => {
|
|
|
23
23
|
},
|
|
24
24
|
]);
|
|
25
25
|
});
|
|
26
|
+
it('keeps hyphenated arg names from TS adapters', () => {
|
|
27
|
+
const args = parseTsArgsBlock(`
|
|
28
|
+
{
|
|
29
|
+
name: 'tweet-url',
|
|
30
|
+
help: 'Single tweet URL to download',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'download-images',
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
default: false,
|
|
36
|
+
help: 'Download images locally',
|
|
37
|
+
},
|
|
38
|
+
`);
|
|
39
|
+
expect(args).toEqual([
|
|
40
|
+
{
|
|
41
|
+
name: 'tweet-url',
|
|
42
|
+
type: 'str',
|
|
43
|
+
default: undefined,
|
|
44
|
+
required: false,
|
|
45
|
+
positional: undefined,
|
|
46
|
+
help: 'Single tweet URL to download',
|
|
47
|
+
choices: undefined,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'download-images',
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
default: false,
|
|
53
|
+
required: false,
|
|
54
|
+
positional: undefined,
|
|
55
|
+
help: 'Download images locally',
|
|
56
|
+
choices: undefined,
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
26
60
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Strategy } from './registry.js';
|
|
2
|
+
const BROWSER_ONLY_STEPS = new Set([
|
|
3
|
+
'navigate',
|
|
4
|
+
'click',
|
|
5
|
+
'type',
|
|
6
|
+
'wait',
|
|
7
|
+
'press',
|
|
8
|
+
'snapshot',
|
|
9
|
+
'evaluate',
|
|
10
|
+
'intercept',
|
|
11
|
+
'tap',
|
|
12
|
+
]);
|
|
13
|
+
function pipelineNeedsBrowserSession(pipeline) {
|
|
14
|
+
return pipeline.some((step) => {
|
|
15
|
+
if (!step || typeof step !== 'object')
|
|
16
|
+
return false;
|
|
17
|
+
return Object.keys(step).some((op) => BROWSER_ONLY_STEPS.has(op));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function shouldUseBrowserSession(cmd) {
|
|
21
|
+
if (!cmd.browser)
|
|
22
|
+
return false;
|
|
23
|
+
if (cmd.func)
|
|
24
|
+
return true;
|
|
25
|
+
if (!cmd.pipeline || cmd.pipeline.length === 0)
|
|
26
|
+
return true;
|
|
27
|
+
if (cmd.strategy !== Strategy.PUBLIC)
|
|
28
|
+
return true;
|
|
29
|
+
return pipelineNeedsBrowserSession(cmd.pipeline);
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Strategy } from './registry.js';
|
|
3
|
+
import { shouldUseBrowserSession } from './capabilityRouting.js';
|
|
4
|
+
function makeCmd(partial) {
|
|
5
|
+
return {
|
|
6
|
+
site: 'test',
|
|
7
|
+
name: 'command',
|
|
8
|
+
description: '',
|
|
9
|
+
args: [],
|
|
10
|
+
...partial,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
describe('shouldUseBrowserSession', () => {
|
|
14
|
+
it('skips browser session for public fetch-only pipelines', () => {
|
|
15
|
+
expect(shouldUseBrowserSession(makeCmd({
|
|
16
|
+
browser: true,
|
|
17
|
+
strategy: Strategy.PUBLIC,
|
|
18
|
+
pipeline: [{ fetch: 'https://example.com/api' }, { select: 'items' }],
|
|
19
|
+
}))).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it('keeps browser session for public pipelines with browser-only steps', () => {
|
|
22
|
+
expect(shouldUseBrowserSession(makeCmd({
|
|
23
|
+
browser: true,
|
|
24
|
+
strategy: Strategy.PUBLIC,
|
|
25
|
+
pipeline: [{ navigate: 'https://example.com' }, { evaluate: '() => []' }],
|
|
26
|
+
}))).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it('keeps browser session for non-public strategies', () => {
|
|
29
|
+
expect(shouldUseBrowserSession(makeCmd({
|
|
30
|
+
browser: true,
|
|
31
|
+
strategy: Strategy.COOKIE,
|
|
32
|
+
pipeline: [{ fetch: 'https://example.com/api' }],
|
|
33
|
+
}))).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('keeps browser session for function adapters', () => {
|
|
36
|
+
expect(shouldUseBrowserSession(makeCmd({
|
|
37
|
+
browser: true,
|
|
38
|
+
strategy: Strategy.PUBLIC,
|
|
39
|
+
func: async () => [],
|
|
40
|
+
}))).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chaoxing (学习通) shared helpers.
|
|
3
|
+
*
|
|
4
|
+
* Flow: initSession → getCourses → enterCourse → getTabIframeUrl → navigate → parse DOM
|
|
5
|
+
* Chaoxing has no flat "list all assignments" API; data is behind session-gated
|
|
6
|
+
* course pages loaded as iframes.
|
|
7
|
+
*/
|
|
8
|
+
import type { IPage } from './types.js';
|
|
9
|
+
/** Sleep for given milliseconds (anti-scraping delay). */
|
|
10
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
11
|
+
/** Execute a credentialed fetch in the browser context, returning JSON or text. */
|
|
12
|
+
export declare function fetchChaoxing(page: IPage, url: string): Promise<any>;
|
|
13
|
+
/** Format a timestamp (seconds or milliseconds or date string) to YYYY-MM-DD HH:mm. */
|
|
14
|
+
export declare function formatTimestamp(ts: unknown): string;
|
|
15
|
+
/** Map numeric work status to Chinese label. */
|
|
16
|
+
export declare function workStatusLabel(status: unknown): string;
|
|
17
|
+
export interface ChaoxingCourse {
|
|
18
|
+
courseId: string;
|
|
19
|
+
classId: string;
|
|
20
|
+
cpi: string;
|
|
21
|
+
title: string;
|
|
22
|
+
}
|
|
23
|
+
/** Fetch enrolled course list via backclazzdata JSON API. */
|
|
24
|
+
export declare function getCourses(page: IPage): Promise<ChaoxingCourse[]>;
|
|
25
|
+
/** Navigate to the interaction page to establish a Chaoxing session. */
|
|
26
|
+
export declare function initSession(page: IPage): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Enter a course via stucoursemiddle redirect (establishes course session + enc).
|
|
29
|
+
* After this call the browser is on the course page.
|
|
30
|
+
*/
|
|
31
|
+
export declare function enterCourse(page: IPage, course: ChaoxingCourse): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* On the course page, click a tab (作业 / 考试) and return the iframe src
|
|
34
|
+
* that gets loaded. Returns empty string if the tab is not found.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getTabIframeUrl(page: IPage, tabName: string): Promise<string>;
|
|
37
|
+
export interface AssignmentRow {
|
|
38
|
+
course: string;
|
|
39
|
+
title: string;
|
|
40
|
+
deadline: string;
|
|
41
|
+
status: string;
|
|
42
|
+
score: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse assignments from the current page DOM (the 作业列表 page).
|
|
46
|
+
* The page uses `.ulDiv li` items with status/deadline/score info.
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseAssignmentsFromDom(page: IPage, courseName: string): Promise<AssignmentRow[]>;
|
|
49
|
+
export interface ExamRow {
|
|
50
|
+
course: string;
|
|
51
|
+
title: string;
|
|
52
|
+
start: string;
|
|
53
|
+
end: string;
|
|
54
|
+
status: string;
|
|
55
|
+
score: string;
|
|
56
|
+
}
|
|
57
|
+
/** Parse exams from the current page DOM (the 考试列表 page). */
|
|
58
|
+
export declare function parseExamsFromDom(page: IPage, courseName: string): Promise<ExamRow[]>;
|