@jackwener/opencli 1.5.8 → 1.6.0
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/CHANGELOG.md +42 -0
- package/README.md +35 -1
- package/README.zh-CN.md +17 -1
- package/SKILL.md +31 -851
- package/autoresearch/baseline-browse.txt +1 -0
- package/autoresearch/baseline-skill.txt +1 -0
- package/autoresearch/browse-tasks.json +688 -0
- package/autoresearch/eval-browse.ts +185 -0
- package/autoresearch/eval-skill.ts +248 -0
- package/autoresearch/run-browse.sh +9 -0
- package/autoresearch/run-skill.sh +9 -0
- package/dist/browser/base-page.d.ts +48 -0
- package/dist/browser/base-page.js +160 -0
- package/dist/browser/cdp.js +4 -106
- package/dist/browser/daemon-client.d.ts +20 -7
- package/dist/browser/daemon-client.js +39 -39
- package/dist/browser/daemon-client.test.js +77 -0
- package/dist/browser/discover.d.ts +1 -4
- package/dist/browser/discover.js +9 -23
- package/dist/browser/errors.d.ts +4 -0
- package/dist/browser/errors.js +20 -0
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/page.d.ts +10 -35
- package/dist/browser/page.js +55 -187
- package/dist/browser/tabs.js +5 -5
- package/dist/browser.test.js +15 -15
- package/dist/cli-manifest.json +294 -22
- package/dist/cli.js +392 -0
- package/dist/clis/amazon/bestsellers.d.ts +21 -0
- package/dist/clis/amazon/bestsellers.js +130 -0
- package/dist/clis/amazon/bestsellers.test.js +20 -0
- package/dist/clis/amazon/discussion.d.ts +20 -0
- package/dist/clis/amazon/discussion.js +91 -0
- package/dist/clis/amazon/discussion.test.d.ts +1 -0
- package/dist/clis/amazon/discussion.test.js +36 -0
- package/dist/clis/amazon/offer.d.ts +23 -0
- package/dist/clis/amazon/offer.js +140 -0
- package/dist/clis/amazon/offer.test.d.ts +1 -0
- package/dist/clis/amazon/offer.test.js +29 -0
- package/dist/clis/amazon/product.d.ts +18 -0
- package/dist/clis/amazon/product.js +92 -0
- package/dist/clis/amazon/product.test.d.ts +1 -0
- package/dist/clis/amazon/product.test.js +24 -0
- package/dist/clis/amazon/search.d.ts +18 -0
- package/dist/clis/amazon/search.js +87 -0
- package/dist/clis/amazon/search.test.d.ts +1 -0
- package/dist/clis/amazon/search.test.js +22 -0
- package/dist/clis/amazon/shared.d.ts +64 -0
- package/dist/clis/amazon/shared.js +255 -0
- package/dist/clis/amazon/shared.test.d.ts +1 -0
- package/dist/clis/amazon/shared.test.js +33 -0
- package/dist/clis/gemini/ask.d.ts +1 -0
- package/dist/clis/gemini/ask.js +40 -0
- package/dist/clis/gemini/image.d.ts +1 -0
- package/dist/clis/gemini/image.js +105 -0
- package/dist/clis/gemini/new.d.ts +1 -0
- package/dist/clis/gemini/new.js +20 -0
- package/dist/clis/gemini/utils.d.ts +34 -0
- package/dist/clis/gemini/utils.js +463 -0
- package/dist/clis/gemini/utils.test.d.ts +1 -0
- package/dist/clis/gemini/utils.test.js +31 -0
- package/dist/clis/notebooklm/compat.test.d.ts +1 -1
- package/dist/clis/notebooklm/compat.test.js +3 -3
- package/dist/clis/notebooklm/current.js +2 -3
- package/dist/clis/notebooklm/get.js +2 -3
- package/dist/clis/notebooklm/history.js +2 -3
- package/dist/clis/notebooklm/note-list.js +2 -3
- package/dist/clis/notebooklm/notes-get.js +2 -3
- package/dist/clis/notebooklm/open.d.ts +1 -0
- package/dist/clis/notebooklm/open.js +41 -0
- package/dist/clis/notebooklm/open.test.d.ts +1 -0
- package/dist/clis/notebooklm/open.test.js +63 -0
- package/dist/clis/notebooklm/source-fulltext.js +2 -3
- package/dist/clis/notebooklm/source-get.js +2 -3
- package/dist/clis/notebooklm/source-guide.js +2 -3
- package/dist/clis/notebooklm/source-list.js +2 -3
- package/dist/clis/notebooklm/status.js +1 -2
- package/dist/clis/notebooklm/summary.js +2 -3
- package/dist/clis/notebooklm/utils.d.ts +2 -1
- package/dist/clis/notebooklm/utils.js +20 -21
- package/dist/clis/twitter/article.js +28 -1
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +11 -11
- package/dist/clis/xiaohongshu/creator-notes-summary.test.js +6 -6
- package/dist/clis/xiaohongshu/creator-notes.test.js +22 -22
- package/dist/clis/xiaohongshu/note.js +11 -0
- package/dist/clis/xiaohongshu/note.test.js +49 -0
- package/dist/commanderAdapter.js +7 -4
- package/dist/commanderAdapter.test.js +76 -0
- package/dist/commands/daemon.js +8 -47
- package/dist/commands/daemon.test.js +45 -70
- package/dist/discovery.js +27 -0
- package/dist/doctor.d.ts +1 -2
- package/dist/doctor.js +7 -8
- package/dist/explore.js +1 -1
- package/dist/output.js +28 -0
- package/dist/output.test.js +15 -0
- package/dist/pipeline/executor.js +2 -7
- package/dist/pipeline/steps/browser.js +1 -1
- package/dist/pipeline/template.js +25 -3
- package/dist/record.d.ts +50 -0
- package/dist/record.js +298 -57
- package/dist/record.test.d.ts +1 -0
- package/dist/record.test.js +293 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +1 -0
- package/dist/registry.test.js +10 -0
- package/dist/runtime.js +3 -3
- package/dist/snapshotFormatter.d.ts +1 -1
- package/dist/snapshotFormatter.js +4 -4
- package/dist/snapshotFormatter.test.d.ts +1 -1
- package/dist/snapshotFormatter.test.js +2 -2
- package/dist/types.d.ts +11 -1
- package/dist/types.js +1 -1
- package/docs/.vitepress/config.mts +2 -0
- package/docs/adapters/browser/amazon.md +53 -0
- package/docs/adapters/browser/gemini.md +72 -0
- package/docs/adapters/browser/notebooklm.md +5 -5
- package/docs/adapters/index.md +3 -1
- package/docs/guide/getting-started.md +21 -0
- package/docs/superpowers/specs/2026-04-02-browse-skill-testing-design.md +144 -0
- package/docs/zh/guide/getting-started.md +21 -0
- package/extension/package-lock.json +2 -2
- package/extension/src/background.test.ts +7 -163
- package/extension/src/background.ts +58 -161
- package/extension/src/cdp.ts +77 -124
- package/extension/src/protocol.ts +5 -5
- package/package.json +1 -1
- package/skills/opencli-explorer/SKILL.md +853 -0
- package/skills/opencli-oneshot/SKILL.md +222 -0
- package/skills/opencli-operate/SKILL.md +213 -0
- package/skills/opencli-usage/SKILL.md +152 -0
- package/skills/opencli-usage/browser.md +429 -0
- package/skills/opencli-usage/desktop.md +118 -0
- package/skills/opencli-usage/plugins.md +82 -0
- package/skills/opencli-usage/public-api.md +149 -0
- package/src/browser/base-page.ts +197 -0
- package/src/browser/cdp.ts +7 -131
- package/src/browser/daemon-client.test.ts +103 -0
- package/src/browser/daemon-client.ts +55 -43
- package/src/browser/discover.ts +9 -21
- package/src/browser/errors.ts +22 -0
- package/src/browser/index.ts +1 -1
- package/src/browser/page.ts +57 -209
- package/src/browser/tabs.ts +5 -5
- package/src/browser.test.ts +15 -15
- package/src/cli.ts +392 -0
- package/src/clis/amazon/bestsellers.test.ts +22 -0
- package/src/clis/amazon/bestsellers.ts +180 -0
- package/src/clis/amazon/discussion.test.ts +38 -0
- package/src/clis/amazon/discussion.ts +131 -0
- package/src/clis/amazon/offer.test.ts +35 -0
- package/src/clis/amazon/offer.ts +185 -0
- package/src/clis/amazon/product.test.ts +26 -0
- package/src/clis/amazon/product.ts +131 -0
- package/src/clis/amazon/search.test.ts +24 -0
- package/src/clis/amazon/search.ts +128 -0
- package/src/clis/amazon/shared.test.ts +37 -0
- package/src/clis/amazon/shared.ts +316 -0
- package/src/clis/gemini/ask.ts +46 -0
- package/src/clis/gemini/image.ts +115 -0
- package/src/clis/gemini/new.ts +22 -0
- package/src/clis/gemini/utils.test.ts +36 -0
- package/src/clis/gemini/utils.ts +523 -0
- package/src/clis/notebooklm/compat.test.ts +3 -3
- package/src/clis/notebooklm/current.ts +2 -3
- package/src/clis/notebooklm/get.ts +1 -3
- package/src/clis/notebooklm/history.ts +1 -3
- package/src/clis/notebooklm/note-list.ts +1 -3
- package/src/clis/notebooklm/notes-get.ts +1 -3
- package/src/clis/notebooklm/open.test.ts +78 -0
- package/src/clis/notebooklm/open.ts +61 -0
- package/src/clis/notebooklm/source-fulltext.ts +1 -3
- package/src/clis/notebooklm/source-get.ts +1 -3
- package/src/clis/notebooklm/source-guide.ts +1 -3
- package/src/clis/notebooklm/source-list.ts +1 -3
- package/src/clis/notebooklm/status.ts +1 -2
- package/src/clis/notebooklm/summary.ts +1 -3
- package/src/clis/notebooklm/utils.ts +29 -20
- package/src/clis/twitter/article.ts +31 -1
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +11 -11
- package/src/clis/xiaohongshu/creator-notes-summary.test.ts +6 -6
- package/src/clis/xiaohongshu/creator-notes.test.ts +22 -22
- package/src/clis/xiaohongshu/note.test.ts +51 -0
- package/src/clis/xiaohongshu/note.ts +18 -0
- package/src/commanderAdapter.test.ts +109 -0
- package/src/commanderAdapter.ts +8 -4
- package/src/commands/daemon.test.ts +50 -84
- package/src/commands/daemon.ts +8 -56
- package/src/discovery.ts +22 -0
- package/src/doctor.ts +8 -9
- package/src/explore.ts +1 -1
- package/src/output.test.ts +17 -0
- package/src/output.ts +27 -0
- package/src/pipeline/executor.ts +2 -7
- package/src/pipeline/steps/browser.ts +1 -1
- package/src/pipeline/template.ts +27 -4
- package/src/record.test.ts +362 -0
- package/src/record.ts +341 -62
- package/src/registry.test.ts +12 -0
- package/src/registry.ts +3 -0
- package/src/runtime.ts +3 -3
- package/src/snapshotFormatter.test.ts +2 -2
- package/src/snapshotFormatter.ts +4 -4
- package/src/types.ts +11 -1
- package/.agents/skills/cross-project-adapter-migration/SKILL.md +0 -249
- package/.agents/workflows/cross-project-adapter-migration.md +0 -54
- package/dist/clis/notebooklm/bind-current.js +0 -29
- package/dist/clis/notebooklm/bind-current.test.d.ts +0 -1
- package/dist/clis/notebooklm/bind-current.test.js +0 -35
- package/dist/clis/notebooklm/binding.test.js +0 -44
- package/extension/dist/background.js +0 -819
- package/src/clis/notebooklm/bind-current.test.ts +0 -43
- package/src/clis/notebooklm/bind-current.ts +0 -36
- package/src/clis/notebooklm/binding.test.ts +0 -53
- /package/dist/browser/{mcp.d.ts → bridge.d.ts} +0 -0
- /package/dist/browser/{mcp.js → bridge.js} +0 -0
- /package/dist/{clis/notebooklm/bind-current.d.ts → browser/daemon-client.test.d.ts} +0 -0
- /package/dist/clis/{notebooklm/binding.test.d.ts → amazon/bestsellers.test.d.ts} +0 -0
- /package/src/browser/{mcp.ts → bridge.ts} +0 -0
package/dist/browser/page.js
CHANGED
|
@@ -9,13 +9,12 @@
|
|
|
9
9
|
* where resolveTabId() in the extension picks a chrome:// or
|
|
10
10
|
* chrome-extension:// tab that can't be debugged.
|
|
11
11
|
*/
|
|
12
|
-
import { formatSnapshot } from '../snapshotFormatter.js';
|
|
13
12
|
import { sendCommand } from './daemon-client.js';
|
|
14
13
|
import { wrapForEval } from './utils.js';
|
|
15
14
|
import { saveBase64ToFile } from '../utils.js';
|
|
16
|
-
import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
|
|
17
15
|
import { generateStealthJs } from './stealth.js';
|
|
18
|
-
import {
|
|
16
|
+
import { waitForDomStableJs } from './dom-helpers.js';
|
|
17
|
+
import { BasePage } from './base-page.js';
|
|
19
18
|
export function isRetryableSettleError(err) {
|
|
20
19
|
const message = err instanceof Error ? err.message : String(err);
|
|
21
20
|
return message.includes('Inspected target navigated or closed')
|
|
@@ -24,15 +23,14 @@ export function isRetryableSettleError(err) {
|
|
|
24
23
|
/**
|
|
25
24
|
* Page — implements IPage by talking to the daemon via HTTP.
|
|
26
25
|
*/
|
|
27
|
-
export class Page {
|
|
26
|
+
export class Page extends BasePage {
|
|
28
27
|
workspace;
|
|
29
28
|
constructor(workspace = 'default') {
|
|
29
|
+
super();
|
|
30
30
|
this.workspace = workspace;
|
|
31
31
|
}
|
|
32
32
|
/** Active tab ID, set after navigate and used in all subsequent commands */
|
|
33
33
|
_tabId;
|
|
34
|
-
/** Last navigated URL, tracked in-memory to avoid extra round-trips */
|
|
35
|
-
_lastUrl = null;
|
|
36
34
|
/** Helper: spread workspace into command params */
|
|
37
35
|
_wsOpt() {
|
|
38
36
|
return { workspace: this.workspace };
|
|
@@ -95,29 +93,8 @@ export class Page {
|
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return this._lastUrl;
|
|
101
|
-
try {
|
|
102
|
-
const current = await this.evaluate('window.location.href');
|
|
103
|
-
if (typeof current === 'string' && current) {
|
|
104
|
-
this._lastUrl = current;
|
|
105
|
-
return current;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// Best-effort: some commands may run before a debuggable tab is ready.
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
/** Close the automation window in the extension */
|
|
114
|
-
async closeWindow() {
|
|
115
|
-
try {
|
|
116
|
-
await sendCommand('close-window', { ...this._wsOpt() });
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
// Window may already be closed or daemon may be down
|
|
120
|
-
}
|
|
96
|
+
getActiveTabId() {
|
|
97
|
+
return this._tabId;
|
|
121
98
|
}
|
|
122
99
|
async evaluate(js) {
|
|
123
100
|
const code = wrapForEval(js);
|
|
@@ -135,120 +112,13 @@ export class Page {
|
|
|
135
112
|
const result = await sendCommand('cookies', { ...this._wsOpt(), ...opts });
|
|
136
113
|
return Array.isArray(result) ? result : [];
|
|
137
114
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const snapshotJs = generateSnapshotJs({
|
|
141
|
-
viewportExpand: opts.viewportExpand ?? 800,
|
|
142
|
-
maxDepth: Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200)),
|
|
143
|
-
interactiveOnly: opts.interactive ?? false,
|
|
144
|
-
maxTextLength: opts.maxTextLength ?? 120,
|
|
145
|
-
includeScrollInfo: true,
|
|
146
|
-
bboxDedup: true,
|
|
147
|
-
});
|
|
115
|
+
/** Close the automation window in the extension */
|
|
116
|
+
async closeWindow() {
|
|
148
117
|
try {
|
|
149
|
-
|
|
150
|
-
// The advanced engine already produces a clean, pruned, LLM-friendly output.
|
|
151
|
-
// Do NOT pass through formatSnapshot — its format is incompatible.
|
|
152
|
-
return result;
|
|
118
|
+
await sendCommand('close-window', { ...this._wsOpt() });
|
|
153
119
|
}
|
|
154
120
|
catch {
|
|
155
|
-
//
|
|
156
|
-
return this._basicSnapshot(opts);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/** Fallback basic snapshot — original buildTree approach */
|
|
160
|
-
async _basicSnapshot(opts = {}) {
|
|
161
|
-
const maxDepth = Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200));
|
|
162
|
-
const code = `
|
|
163
|
-
(async () => {
|
|
164
|
-
function buildTree(node, depth) {
|
|
165
|
-
if (depth > ${maxDepth}) return '';
|
|
166
|
-
const role = node.getAttribute?.('role') || node.tagName?.toLowerCase() || 'generic';
|
|
167
|
-
const name = node.getAttribute?.('aria-label') || node.getAttribute?.('alt') || node.textContent?.trim().slice(0, 80) || '';
|
|
168
|
-
const isInteractive = ['a', 'button', 'input', 'select', 'textarea'].includes(node.tagName?.toLowerCase()) || node.getAttribute?.('tabindex') != null;
|
|
169
|
-
|
|
170
|
-
${opts.interactive ? 'if (!isInteractive && !node.children?.length) return "";' : ''}
|
|
171
|
-
|
|
172
|
-
let indent = ' '.repeat(depth);
|
|
173
|
-
let line = indent + role;
|
|
174
|
-
if (name) line += ' "' + name.replace(/"/g, '\\\\\\"') + '"';
|
|
175
|
-
if (node.tagName?.toLowerCase() === 'a' && node.href) line += ' [' + node.href + ']';
|
|
176
|
-
if (node.tagName?.toLowerCase() === 'input') line += ' [' + (node.type || 'text') + ']';
|
|
177
|
-
|
|
178
|
-
let result = line + '\\n';
|
|
179
|
-
if (node.children) {
|
|
180
|
-
for (const child of node.children) {
|
|
181
|
-
result += buildTree(child, depth + 1);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return result;
|
|
185
|
-
}
|
|
186
|
-
return buildTree(document.body, 0);
|
|
187
|
-
})()
|
|
188
|
-
`;
|
|
189
|
-
const raw = await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
190
|
-
if (opts.raw)
|
|
191
|
-
return raw;
|
|
192
|
-
if (typeof raw === 'string')
|
|
193
|
-
return formatSnapshot(raw, opts);
|
|
194
|
-
return raw;
|
|
195
|
-
}
|
|
196
|
-
async click(ref) {
|
|
197
|
-
const code = clickJs(ref);
|
|
198
|
-
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
199
|
-
}
|
|
200
|
-
async typeText(ref, text) {
|
|
201
|
-
const code = typeTextJs(ref, text);
|
|
202
|
-
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
203
|
-
}
|
|
204
|
-
async pressKey(key) {
|
|
205
|
-
const code = pressKeyJs(key);
|
|
206
|
-
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
207
|
-
}
|
|
208
|
-
async scrollTo(ref) {
|
|
209
|
-
const code = scrollToRefJs(ref);
|
|
210
|
-
return sendCommand('exec', { code, ...this._cmdOpts() });
|
|
211
|
-
}
|
|
212
|
-
async getFormState() {
|
|
213
|
-
const code = getFormStateJs();
|
|
214
|
-
return (await sendCommand('exec', { code, ...this._cmdOpts() }));
|
|
215
|
-
}
|
|
216
|
-
async wait(options) {
|
|
217
|
-
if (typeof options === 'number') {
|
|
218
|
-
if (options >= 1) {
|
|
219
|
-
// For waits >= 1s, use DOM-stable check: return early when the page
|
|
220
|
-
// stops mutating, with the original wait time as the hard cap.
|
|
221
|
-
// This turns e.g. `page.wait(5)` from a fixed 5s sleep into
|
|
222
|
-
// "wait until DOM is stable, max 5s" — often completing in <1s.
|
|
223
|
-
try {
|
|
224
|
-
const maxMs = options * 1000;
|
|
225
|
-
await sendCommand('exec', {
|
|
226
|
-
code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
|
|
227
|
-
...this._cmdOpts(),
|
|
228
|
-
});
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
// Fallback: fixed sleep (e.g. if page has no DOM yet)
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
await new Promise(resolve => setTimeout(resolve, options * 1000));
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (typeof options.time === 'number') {
|
|
239
|
-
await new Promise(resolve => setTimeout(resolve, options.time * 1000));
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
if (options.selector) {
|
|
243
|
-
const timeout = (options.timeout ?? 10) * 1000;
|
|
244
|
-
const code = waitForSelectorJs(options.selector, timeout);
|
|
245
|
-
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
if (options.text) {
|
|
249
|
-
const timeout = (options.timeout ?? 30) * 1000;
|
|
250
|
-
const code = waitForTextJs(options.text, timeout);
|
|
251
|
-
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
121
|
+
// Window may already be closed or daemon may be down
|
|
252
122
|
}
|
|
253
123
|
}
|
|
254
124
|
async tabs() {
|
|
@@ -271,25 +141,8 @@ export class Page {
|
|
|
271
141
|
if (result?.selected)
|
|
272
142
|
this._tabId = result.selected;
|
|
273
143
|
}
|
|
274
|
-
async networkRequests(includeStatic = false) {
|
|
275
|
-
const code = networkRequestsJs(includeStatic);
|
|
276
|
-
const result = await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
277
|
-
return Array.isArray(result) ? result : [];
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Console messages are not available in lightweight daemon mode.
|
|
281
|
-
* Would require CDP Runtime.consoleAPICalled event listener.
|
|
282
|
-
* @returns Always returns empty array.
|
|
283
|
-
*/
|
|
284
|
-
async consoleMessages(_level = 'info') {
|
|
285
|
-
return [];
|
|
286
|
-
}
|
|
287
144
|
/**
|
|
288
145
|
* Capture a screenshot via CDP Page.captureScreenshot.
|
|
289
|
-
* @param options.format - 'png' (default) or 'jpeg'
|
|
290
|
-
* @param options.quality - JPEG quality 0-100
|
|
291
|
-
* @param options.fullPage - capture full scrollable page
|
|
292
|
-
* @param options.path - save to file path (returns base64 if omitted)
|
|
293
146
|
*/
|
|
294
147
|
async screenshot(options = {}) {
|
|
295
148
|
const base64 = await sendCommand('screenshot', {
|
|
@@ -303,31 +156,6 @@ export class Page {
|
|
|
303
156
|
}
|
|
304
157
|
return base64;
|
|
305
158
|
}
|
|
306
|
-
async scroll(direction = 'down', amount = 500) {
|
|
307
|
-
const code = scrollJs(direction, amount);
|
|
308
|
-
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
309
|
-
}
|
|
310
|
-
async autoScroll(options = {}) {
|
|
311
|
-
const times = options.times ?? 3;
|
|
312
|
-
const delayMs = options.delayMs ?? 2000;
|
|
313
|
-
const code = autoScrollJs(times, delayMs);
|
|
314
|
-
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
315
|
-
}
|
|
316
|
-
async installInterceptor(pattern) {
|
|
317
|
-
const { generateInterceptorJs } = await import('../interceptor.js');
|
|
318
|
-
// Must use evaluate() so wrapForEval() converts the arrow function into an IIFE;
|
|
319
|
-
// sendCommand('exec') sends the code as-is, and CDP never executes a bare arrow.
|
|
320
|
-
await this.evaluate(generateInterceptorJs(JSON.stringify(pattern), {
|
|
321
|
-
arrayName: '__opencli_xhr',
|
|
322
|
-
patchGuard: '__opencli_interceptor_patched',
|
|
323
|
-
}));
|
|
324
|
-
}
|
|
325
|
-
async getInterceptedRequests() {
|
|
326
|
-
const { generateReadInterceptedJs } = await import('../interceptor.js');
|
|
327
|
-
// Same as installInterceptor: must go through evaluate() for IIFE wrapping
|
|
328
|
-
const result = await this.evaluate(generateReadInterceptedJs('__opencli_xhr'));
|
|
329
|
-
return Array.isArray(result) ? result : [];
|
|
330
|
-
}
|
|
331
159
|
/**
|
|
332
160
|
* Set local file paths on a file input element via CDP DOM.setFileInputFiles.
|
|
333
161
|
* Chrome reads the files directly from the local filesystem, avoiding the
|
|
@@ -343,12 +171,52 @@ export class Page {
|
|
|
343
171
|
throw new Error('setFileInput returned no count — command may not be supported by the extension');
|
|
344
172
|
}
|
|
345
173
|
}
|
|
346
|
-
async
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
174
|
+
async cdp(method, params = {}) {
|
|
175
|
+
return sendCommand('cdp', {
|
|
176
|
+
cdpMethod: method,
|
|
177
|
+
cdpParams: params,
|
|
350
178
|
...this._cmdOpts(),
|
|
351
179
|
});
|
|
352
180
|
}
|
|
181
|
+
async nativeClick(x, y) {
|
|
182
|
+
await this.cdp('Input.dispatchMouseEvent', {
|
|
183
|
+
type: 'mousePressed',
|
|
184
|
+
x, y,
|
|
185
|
+
button: 'left',
|
|
186
|
+
clickCount: 1,
|
|
187
|
+
});
|
|
188
|
+
await this.cdp('Input.dispatchMouseEvent', {
|
|
189
|
+
type: 'mouseReleased',
|
|
190
|
+
x, y,
|
|
191
|
+
button: 'left',
|
|
192
|
+
clickCount: 1,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async nativeType(text) {
|
|
196
|
+
// Use Input.insertText for reliable Unicode/CJK text insertion
|
|
197
|
+
await this.cdp('Input.insertText', { text });
|
|
198
|
+
}
|
|
199
|
+
async nativeKeyPress(key, modifiers = []) {
|
|
200
|
+
let modifierFlags = 0;
|
|
201
|
+
for (const mod of modifiers) {
|
|
202
|
+
if (mod === 'Alt')
|
|
203
|
+
modifierFlags |= 1;
|
|
204
|
+
if (mod === 'Ctrl')
|
|
205
|
+
modifierFlags |= 2;
|
|
206
|
+
if (mod === 'Meta')
|
|
207
|
+
modifierFlags |= 4;
|
|
208
|
+
if (mod === 'Shift')
|
|
209
|
+
modifierFlags |= 8;
|
|
210
|
+
}
|
|
211
|
+
await this.cdp('Input.dispatchKeyEvent', {
|
|
212
|
+
type: 'keyDown',
|
|
213
|
+
key,
|
|
214
|
+
modifiers: modifierFlags,
|
|
215
|
+
});
|
|
216
|
+
await this.cdp('Input.dispatchKeyEvent', {
|
|
217
|
+
type: 'keyUp',
|
|
218
|
+
key,
|
|
219
|
+
modifiers: modifierFlags,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
353
222
|
}
|
|
354
|
-
// (End of file)
|
package/dist/browser/tabs.js
CHANGED
|
@@ -19,12 +19,12 @@ export function extractTabEntries(raw) {
|
|
|
19
19
|
.map(line => line.trim())
|
|
20
20
|
.filter(Boolean)
|
|
21
21
|
.map(line => {
|
|
22
|
-
// Match
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
22
|
+
// Match tab list format: "- 0: (current) [title](url)" or "- 1: [title](url)"
|
|
23
|
+
const tabMatch = line.match(/^-\s+(\d+):\s*(.*)$/);
|
|
24
|
+
if (tabMatch) {
|
|
25
25
|
return {
|
|
26
|
-
index: parseInt(
|
|
27
|
-
identity:
|
|
26
|
+
index: parseInt(tabMatch[1], 10),
|
|
27
|
+
identity: tabMatch[2].trim() || `tab-${tabMatch[1]}`,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
// Legacy format: "Tab 0 ..."
|
package/dist/browser.test.js
CHANGED
|
@@ -84,31 +84,31 @@ describe('browser helpers', () => {
|
|
|
84
84
|
});
|
|
85
85
|
describe('BrowserBridge state', () => {
|
|
86
86
|
it('transitions to closed after close()', async () => {
|
|
87
|
-
const
|
|
88
|
-
expect(
|
|
89
|
-
await
|
|
90
|
-
expect(
|
|
87
|
+
const bridge = new BrowserBridge();
|
|
88
|
+
expect(bridge.state).toBe('idle');
|
|
89
|
+
await bridge.close();
|
|
90
|
+
expect(bridge.state).toBe('closed');
|
|
91
91
|
});
|
|
92
92
|
it('rejects connect() after the session has been closed', async () => {
|
|
93
|
-
const
|
|
94
|
-
await
|
|
95
|
-
await expect(
|
|
93
|
+
const bridge = new BrowserBridge();
|
|
94
|
+
await bridge.close();
|
|
95
|
+
await expect(bridge.connect()).rejects.toThrow('Session is closed');
|
|
96
96
|
});
|
|
97
97
|
it('rejects connect() while already connecting', async () => {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
await expect(
|
|
98
|
+
const bridge = new BrowserBridge();
|
|
99
|
+
bridge._state = 'connecting';
|
|
100
|
+
await expect(bridge.connect()).rejects.toThrow('Already connecting');
|
|
101
101
|
});
|
|
102
102
|
it('rejects connect() while closing', async () => {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
await expect(
|
|
103
|
+
const bridge = new BrowserBridge();
|
|
104
|
+
bridge._state = 'closing';
|
|
105
|
+
await expect(bridge.connect()).rejects.toThrow('Session is closing');
|
|
106
106
|
});
|
|
107
107
|
it('fails fast when daemon is running but extension is disconnected', async () => {
|
|
108
108
|
vi.spyOn(daemonClient, 'isExtensionConnected').mockResolvedValue(false);
|
|
109
109
|
vi.spyOn(daemonClient, 'isDaemonRunning').mockResolvedValue(true);
|
|
110
|
-
const
|
|
111
|
-
await expect(
|
|
110
|
+
const bridge = new BrowserBridge();
|
|
111
|
+
await expect(bridge.connect({ timeout: 0.1 })).rejects.toThrow('Browser Extension is not connected');
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
114
|
describe('stealth anti-detection', () => {
|