@jackwener/opencli 1.0.0 → 1.0.3
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 +62 -0
- package/.github/workflows/ci.yml +6 -6
- 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 +35 -8
- package/README.zh-CN.md +35 -8
- package/SKILL.md +3 -5
- package/dist/browser/cdp.d.ts +27 -0
- package/dist/browser/cdp.js +295 -0
- package/dist/browser/daemon-client.d.ts +1 -1
- package/dist/browser/index.d.ts +4 -2
- package/dist/browser/index.js +5 -5
- package/dist/browser/mcp.d.ts +5 -8
- package/dist/browser/mcp.js +9 -10
- package/dist/browser/page.d.ts +8 -1
- package/dist/browser/page.js +25 -40
- package/dist/browser/utils.d.ts +10 -0
- package/dist/browser/utils.js +27 -0
- package/dist/browser.test.js +48 -7
- 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 +38 -0
- package/dist/cli-manifest.json +597 -14
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +197 -0
- package/dist/clis/apple-podcasts/episodes.d.ts +1 -0
- package/dist/clis/apple-podcasts/episodes.js +28 -0
- package/dist/clis/apple-podcasts/search.d.ts +1 -0
- package/dist/clis/apple-podcasts/search.js +29 -0
- package/dist/clis/apple-podcasts/top.d.ts +1 -0
- package/dist/clis/apple-podcasts/top.js +34 -0
- package/dist/clis/apple-podcasts/utils.d.ts +11 -0
- package/dist/clis/apple-podcasts/utils.js +30 -0
- package/dist/clis/apple-podcasts/utils.test.d.ts +1 -0
- package/dist/clis/apple-podcasts/utils.test.js +57 -0
- 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/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/chatwise/history.js +18 -1
- package/dist/clis/discord-app/channels.js +33 -21
- package/dist/clis/twitter/accept.d.ts +1 -0
- package/dist/clis/twitter/accept.js +202 -0
- package/dist/clis/twitter/followers.js +30 -22
- package/dist/clis/twitter/following.js +19 -14
- package/dist/clis/twitter/notifications.js +29 -22
- package/dist/clis/twitter/post.js +9 -2
- package/dist/clis/twitter/reply-dm.d.ts +1 -0
- package/dist/clis/twitter/reply-dm.js +181 -0
- package/dist/clis/twitter/search.js +30 -11
- package/dist/clis/weread/book.d.ts +1 -0
- package/dist/clis/weread/book.js +26 -0
- package/dist/clis/weread/highlights.d.ts +1 -0
- package/dist/clis/weread/highlights.js +23 -0
- package/dist/clis/weread/notebooks.d.ts +1 -0
- package/dist/clis/weread/notebooks.js +21 -0
- package/dist/clis/weread/notes.d.ts +1 -0
- package/dist/clis/weread/notes.js +29 -0
- package/dist/clis/weread/ranking.d.ts +1 -0
- package/dist/clis/weread/ranking.js +28 -0
- package/dist/clis/weread/search.d.ts +1 -0
- package/dist/clis/weread/search.js +25 -0
- package/dist/clis/weread/shelf.d.ts +1 -0
- package/dist/clis/weread/shelf.js +24 -0
- package/dist/clis/weread/utils.d.ts +20 -0
- package/dist/clis/weread/utils.js +72 -0
- package/dist/clis/weread/utils.test.d.ts +1 -0
- package/dist/clis/weread/utils.test.js +85 -0
- package/dist/clis/xiaohongshu/download.d.ts +1 -1
- package/dist/clis/xiaohongshu/download.js +1 -1
- package/dist/daemon.js +2 -2
- package/dist/doctor.d.ts +0 -21
- package/dist/doctor.js +2 -24
- package/dist/engine.js +24 -13
- package/dist/explore.js +46 -101
- package/dist/main.js +4 -203
- package/dist/output.d.ts +1 -1
- package/dist/registry.d.ts +3 -3
- package/dist/runtime.d.ts +1 -4
- package/dist/runtime.js +1 -4
- 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/setup.js +2 -2
- package/dist/synthesize.js +1 -1
- package/extension/dist/background.js +392 -0
- package/extension/manifest.json +3 -3
- package/extension/package.json +1 -1
- package/extension/src/background.ts +101 -24
- package/extension/src/protocol.ts +1 -1
- package/package.json +1 -1
- package/src/browser/cdp.ts +295 -0
- package/src/browser/daemon-client.ts +1 -1
- package/src/browser/index.ts +5 -6
- package/src/browser/mcp.ts +14 -15
- package/src/browser/page.ts +25 -41
- package/src/browser/utils.ts +27 -0
- package/src/browser.test.ts +52 -6
- package/src/chaoxing.test.ts +45 -0
- package/src/chaoxing.ts +268 -0
- package/src/cli.ts +185 -0
- package/src/clis/antigravity/SKILL.md +5 -0
- package/src/clis/apple-podcasts/episodes.ts +28 -0
- package/src/clis/apple-podcasts/search.ts +29 -0
- package/src/clis/apple-podcasts/top.ts +34 -0
- package/src/clis/apple-podcasts/utils.test.ts +72 -0
- package/src/clis/apple-podcasts/utils.ts +37 -0
- package/src/clis/boss/chatlist.ts +50 -0
- package/src/clis/boss/chatmsg.ts +70 -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/chatwise/history.ts +15 -1
- package/src/clis/discord-app/channels.ts +33 -21
- package/src/clis/twitter/accept.ts +213 -0
- package/src/clis/twitter/followers.ts +36 -29
- package/src/clis/twitter/following.ts +25 -20
- package/src/clis/twitter/notifications.ts +34 -27
- package/src/clis/twitter/post.ts +9 -2
- package/src/clis/twitter/reply-dm.ts +193 -0
- package/src/clis/twitter/search.ts +34 -12
- package/src/clis/weread/book.ts +28 -0
- package/src/clis/weread/highlights.ts +25 -0
- package/src/clis/weread/notebooks.ts +23 -0
- package/src/clis/weread/notes.ts +31 -0
- package/src/clis/weread/ranking.ts +29 -0
- package/src/clis/weread/search.ts +26 -0
- package/src/clis/weread/shelf.ts +26 -0
- package/src/clis/weread/utils.test.ts +104 -0
- package/src/clis/weread/utils.ts +74 -0
- package/src/clis/xiaohongshu/download.ts +1 -1
- package/src/daemon.ts +2 -2
- package/src/doctor.ts +2 -19
- package/src/engine.ts +20 -13
- package/src/explore.ts +51 -100
- package/src/main.ts +4 -186
- package/src/output.ts +12 -12
- package/src/registry.ts +3 -3
- package/src/runtime.ts +2 -6
- package/src/scripts/framework.ts +20 -0
- package/src/scripts/interact.ts +22 -0
- package/src/scripts/store.ts +40 -0
- package/src/setup.ts +2 -2
- package/src/synthesize.ts +1 -1
- package/tests/e2e/public-commands.test.ts +68 -1
- package/dist/clis/grok/debug.d.ts +0 -1
- package/dist/clis/grok/debug.js +0 -45
- package/src/clis/grok/debug.ts +0 -49
package/src/runtime.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime utilities: timeouts and browser session management.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import type { IPage } from './types.js';
|
|
6
2
|
|
|
7
3
|
export const DEFAULT_BROWSER_CONNECT_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_CONNECT_TIMEOUT ?? '30', 10);
|
|
8
|
-
export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_COMMAND_TIMEOUT ?? '
|
|
4
|
+
export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_COMMAND_TIMEOUT ?? '60', 10);
|
|
9
5
|
export const DEFAULT_BROWSER_EXPLORE_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_EXPLORE_TIMEOUT ?? '120', 10);
|
|
10
6
|
export const DEFAULT_BROWSER_SMOKE_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_SMOKE_TIMEOUT ?? '60', 10);
|
|
11
7
|
|
|
@@ -32,7 +28,7 @@ export function withTimeoutMs<T>(promise: Promise<T>, timeoutMs: number, message
|
|
|
32
28
|
});
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
/** Interface for browser factory (
|
|
31
|
+
/** Interface for browser factory (BrowserBridge or test mocks) */
|
|
36
32
|
export interface IBrowserFactory {
|
|
37
33
|
connect(opts?: { timeout?: number }): Promise<IPage>;
|
|
38
34
|
close(): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injected script for detecting frontend frameworks (Vue, React, Next, Nuxt, etc.)
|
|
3
|
+
*/
|
|
4
|
+
export function detectFramework() {
|
|
5
|
+
const r: Record<string, boolean> = {};
|
|
6
|
+
try {
|
|
7
|
+
const app = document.querySelector('#app') as any;
|
|
8
|
+
r.vue3 = !!(app && app.__vue_app__);
|
|
9
|
+
r.vue2 = !!(app && app.__vue__);
|
|
10
|
+
r.react = !!(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ || !!document.querySelector('[data-reactroot]');
|
|
11
|
+
r.nextjs = !!(window as any).__NEXT_DATA__;
|
|
12
|
+
r.nuxt = !!(window as any).__NUXT__;
|
|
13
|
+
if (r.vue3 && app.__vue_app__) {
|
|
14
|
+
const gp = app.__vue_app__.config?.globalProperties;
|
|
15
|
+
r.pinia = !!(gp && gp.$pinia);
|
|
16
|
+
r.vuex = !!(gp && gp.$store);
|
|
17
|
+
}
|
|
18
|
+
} catch {}
|
|
19
|
+
return r;
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injected script for interactive fuzzing (clicking elements to trigger lazy loading)
|
|
3
|
+
*/
|
|
4
|
+
export async function interactFuzz() {
|
|
5
|
+
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
|
|
6
|
+
const clickables = Array.from(document.querySelectorAll(
|
|
7
|
+
'button, [role="button"], [role="tab"], .tab, .btn, a[href="javascript:void(0)"], a[href="#"]'
|
|
8
|
+
)).slice(0, 15); // limit to a small number to avoid endless loops
|
|
9
|
+
|
|
10
|
+
let clicked = 0;
|
|
11
|
+
for (const el of clickables) {
|
|
12
|
+
try {
|
|
13
|
+
const rect = el.getBoundingClientRect();
|
|
14
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
15
|
+
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
|
|
16
|
+
clicked++;
|
|
17
|
+
await sleep(300); // give it time to trigger network
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
21
|
+
return clicked;
|
|
22
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injected script for discovering Pinia or Vuex stores and their actions/state representations
|
|
3
|
+
*/
|
|
4
|
+
export function discoverStores() {
|
|
5
|
+
const stores: Array<{ type: string; id: string; actions: string[]; stateKeys: string[] }> = [];
|
|
6
|
+
try {
|
|
7
|
+
const app = document.querySelector('#app') as any;
|
|
8
|
+
if (!app?.__vue_app__) return stores;
|
|
9
|
+
const gp = app.__vue_app__.config?.globalProperties;
|
|
10
|
+
|
|
11
|
+
// Pinia stores
|
|
12
|
+
const pinia = gp?.$pinia;
|
|
13
|
+
if (pinia?._s) {
|
|
14
|
+
pinia._s.forEach((store: any, id: string) => {
|
|
15
|
+
const actions: string[] = [];
|
|
16
|
+
const stateKeys: string[] = [];
|
|
17
|
+
for (const k in store) {
|
|
18
|
+
try {
|
|
19
|
+
if (k.startsWith('$') || k.startsWith('_')) continue;
|
|
20
|
+
if (typeof store[k] === 'function') actions.push(k);
|
|
21
|
+
else stateKeys.push(k);
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
stores.push({ type: 'pinia', id, actions: actions.slice(0, 20), stateKeys: stateKeys.slice(0, 15) });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Vuex store modules
|
|
29
|
+
const vuex = gp?.$store;
|
|
30
|
+
if (vuex?._modules?.root?._children) {
|
|
31
|
+
const children = vuex._modules.root._children;
|
|
32
|
+
for (const [modName, mod] of Object.entries<any>(children)) {
|
|
33
|
+
const actions = Object.keys(mod._rawModule?.actions ?? {}).slice(0, 20);
|
|
34
|
+
const stateKeys = Object.keys(mod.state ?? {}).slice(0, 15);
|
|
35
|
+
stores.push({ type: 'vuex', id: modName, actions, stateKeys });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch {}
|
|
39
|
+
return stores;
|
|
40
|
+
}
|
package/src/setup.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { checkDaemonStatus } from './browser/discover.js';
|
|
10
10
|
import { checkConnectivity } from './doctor.js';
|
|
11
|
-
import {
|
|
11
|
+
import { BrowserBridge } from './browser/index.js';
|
|
12
12
|
|
|
13
13
|
export async function runSetup(opts: { cliVersion?: string; token?: string } = {}) {
|
|
14
14
|
console.log();
|
|
@@ -27,7 +27,7 @@ export async function runSetup(opts: { cliVersion?: string; token?: string } = {
|
|
|
27
27
|
console.log(chalk.dim(' Starting daemon now...'));
|
|
28
28
|
|
|
29
29
|
// Try to spawn daemon
|
|
30
|
-
const mcp = new
|
|
30
|
+
const mcp = new BrowserBridge();
|
|
31
31
|
try {
|
|
32
32
|
await mcp.connect({ timeout: 5 });
|
|
33
33
|
await mcp.close();
|
package/src/synthesize.ts
CHANGED
|
@@ -116,7 +116,7 @@ function buildEvaluateScript(url: string, itemPath: string, endpoint: any): stri
|
|
|
116
116
|
|
|
117
117
|
return [
|
|
118
118
|
'(async () => {',
|
|
119
|
-
` const res = await fetch(
|
|
119
|
+
` const res = await fetch(${JSON.stringify(url)}, {`,
|
|
120
120
|
` credentials: 'include'`,
|
|
121
121
|
' });',
|
|
122
122
|
' const data = await res.json();',
|
|
@@ -6,12 +6,49 @@
|
|
|
6
6
|
import { describe, it, expect } from 'vitest';
|
|
7
7
|
import { runCli, parseJsonOutput } from './helpers.js';
|
|
8
8
|
|
|
9
|
-
function
|
|
9
|
+
function isExpectedChineseSiteRestriction(code: number, stderr: string): boolean {
|
|
10
10
|
if (code === 0) return false;
|
|
11
11
|
return /Error \[FETCH_ERROR\]: HTTP (403|429|451|503)\b/.test(stderr);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// Keep old name as alias for existing tests
|
|
15
|
+
const isExpectedXiaoyuzhouRestriction = isExpectedChineseSiteRestriction;
|
|
16
|
+
|
|
14
17
|
describe('public commands E2E', () => {
|
|
18
|
+
// ── apple-podcasts ──
|
|
19
|
+
it('apple-podcasts search returns structured podcast results', async () => {
|
|
20
|
+
const { stdout, code } = await runCli(['apple-podcasts', 'search', 'technology', '--limit', '3', '-f', 'json']);
|
|
21
|
+
expect(code).toBe(0);
|
|
22
|
+
const data = parseJsonOutput(stdout);
|
|
23
|
+
expect(Array.isArray(data)).toBe(true);
|
|
24
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
25
|
+
expect(data[0]).toHaveProperty('id');
|
|
26
|
+
expect(data[0]).toHaveProperty('title');
|
|
27
|
+
expect(data[0]).toHaveProperty('author');
|
|
28
|
+
}, 30_000);
|
|
29
|
+
|
|
30
|
+
it('apple-podcasts episodes returns episode list from a known show', async () => {
|
|
31
|
+
const { stdout, code } = await runCli(['apple-podcasts', 'episodes', '275699983', '--limit', '3', '-f', 'json']);
|
|
32
|
+
expect(code).toBe(0);
|
|
33
|
+
const data = parseJsonOutput(stdout);
|
|
34
|
+
expect(Array.isArray(data)).toBe(true);
|
|
35
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
36
|
+
expect(data[0]).toHaveProperty('title');
|
|
37
|
+
expect(data[0]).toHaveProperty('duration');
|
|
38
|
+
expect(data[0]).toHaveProperty('date');
|
|
39
|
+
}, 30_000);
|
|
40
|
+
|
|
41
|
+
it('apple-podcasts top returns ranked podcasts', async () => {
|
|
42
|
+
const { stdout, code } = await runCli(['apple-podcasts', 'top', '--limit', '3', '--country', 'us', '-f', 'json']);
|
|
43
|
+
expect(code).toBe(0);
|
|
44
|
+
const data = parseJsonOutput(stdout);
|
|
45
|
+
expect(Array.isArray(data)).toBe(true);
|
|
46
|
+
expect(data.length).toBe(3);
|
|
47
|
+
expect(data[0]).toHaveProperty('rank');
|
|
48
|
+
expect(data[0]).toHaveProperty('title');
|
|
49
|
+
expect(data[0]).toHaveProperty('id');
|
|
50
|
+
}, 30_000);
|
|
51
|
+
|
|
15
52
|
// ── hackernews ──
|
|
16
53
|
it('hackernews top returns structured data', async () => {
|
|
17
54
|
const { stdout, code } = await runCli(['hackernews', 'top', '--limit', '3', '-f', 'json']);
|
|
@@ -115,4 +152,34 @@ describe('public commands E2E', () => {
|
|
|
115
152
|
expect(code).not.toBe(0);
|
|
116
153
|
expect(stderr).toMatch(/limit must be a positive integer|Argument "limit" must be a valid number/);
|
|
117
154
|
}, 30_000);
|
|
155
|
+
|
|
156
|
+
// ── weread (Chinese site — may return empty on overseas CI runners) ──
|
|
157
|
+
it('weread search returns books', async () => {
|
|
158
|
+
const { stdout, stderr, code } = await runCli(['weread', 'search', 'python', '--limit', '3', '-f', 'json']);
|
|
159
|
+
if (isExpectedChineseSiteRestriction(code, stderr)) {
|
|
160
|
+
console.warn(`weread search skipped: ${stderr.trim()}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
expect(code).toBe(0);
|
|
164
|
+
const data = parseJsonOutput(stdout);
|
|
165
|
+
expect(Array.isArray(data)).toBe(true);
|
|
166
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
167
|
+
expect(data[0]).toHaveProperty('title');
|
|
168
|
+
expect(data[0]).toHaveProperty('bookId');
|
|
169
|
+
}, 30_000);
|
|
170
|
+
|
|
171
|
+
it('weread ranking returns books', async () => {
|
|
172
|
+
const { stdout, stderr, code } = await runCli(['weread', 'ranking', 'all', '--limit', '3', '-f', 'json']);
|
|
173
|
+
if (isExpectedChineseSiteRestriction(code, stderr)) {
|
|
174
|
+
console.warn(`weread ranking skipped: ${stderr.trim()}`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
expect(code).toBe(0);
|
|
178
|
+
const data = parseJsonOutput(stdout);
|
|
179
|
+
expect(Array.isArray(data)).toBe(true);
|
|
180
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
181
|
+
expect(data[0]).toHaveProperty('title');
|
|
182
|
+
expect(data[0]).toHaveProperty('readingCount');
|
|
183
|
+
expect(data[0]).toHaveProperty('bookId');
|
|
184
|
+
}, 30_000);
|
|
118
185
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const debugCommand: import("../../registry.js").CliCommand;
|
package/dist/clis/grok/debug.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { cli, Strategy } from '../../registry.js';
|
|
2
|
-
export const debugCommand = cli({
|
|
3
|
-
site: 'grok',
|
|
4
|
-
name: 'debug',
|
|
5
|
-
description: 'Debug grok page structure',
|
|
6
|
-
domain: 'grok.com',
|
|
7
|
-
strategy: Strategy.COOKIE,
|
|
8
|
-
browser: true,
|
|
9
|
-
columns: ['data'],
|
|
10
|
-
func: async (page, _kwargs) => {
|
|
11
|
-
await page.goto('https://grok.com');
|
|
12
|
-
await page.wait(3);
|
|
13
|
-
// Get all button-like elements near textarea
|
|
14
|
-
const debug = await page.evaluate(`(() => {
|
|
15
|
-
const ta = document.querySelector('textarea');
|
|
16
|
-
if (!ta) return { error: 'no textarea' };
|
|
17
|
-
|
|
18
|
-
// Get parent containers
|
|
19
|
-
let parent = ta.parentElement;
|
|
20
|
-
const parents = [];
|
|
21
|
-
for (let i = 0; i < 5 && parent; i++) {
|
|
22
|
-
parents.push({
|
|
23
|
-
tag: parent.tagName,
|
|
24
|
-
class: parent.className?.substring(0, 80),
|
|
25
|
-
childCount: parent.children.length,
|
|
26
|
-
});
|
|
27
|
-
parent = parent.parentElement;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Find buttons in the form/container near textarea
|
|
31
|
-
const form = ta.closest('form') || ta.closest('[class*="composer"]') || ta.closest('[class*="input"]') || ta.parentElement?.parentElement;
|
|
32
|
-
const buttons = form ? [...form.querySelectorAll('button')].map(b => ({
|
|
33
|
-
testid: b.getAttribute('data-testid'),
|
|
34
|
-
type: b.type,
|
|
35
|
-
disabled: b.disabled,
|
|
36
|
-
text: (b.textContent || '').substring(0, 30),
|
|
37
|
-
html: b.outerHTML.substring(0, 200),
|
|
38
|
-
rect: b.getBoundingClientRect().toJSON(),
|
|
39
|
-
})) : [];
|
|
40
|
-
|
|
41
|
-
return { parents, buttons, formTag: form?.tagName, formClass: form?.className?.substring(0, 80) };
|
|
42
|
-
})()`);
|
|
43
|
-
return [{ data: JSON.stringify(debug, null, 2) }];
|
|
44
|
-
},
|
|
45
|
-
});
|
package/src/clis/grok/debug.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { cli, Strategy } from '../../registry.js';
|
|
2
|
-
import type { IPage } from '../../types.js';
|
|
3
|
-
|
|
4
|
-
export const debugCommand = cli({
|
|
5
|
-
site: 'grok',
|
|
6
|
-
name: 'debug',
|
|
7
|
-
description: 'Debug grok page structure',
|
|
8
|
-
domain: 'grok.com',
|
|
9
|
-
strategy: Strategy.COOKIE,
|
|
10
|
-
browser: true,
|
|
11
|
-
columns: ['data'],
|
|
12
|
-
func: async (page: IPage, _kwargs: Record<string, any>) => {
|
|
13
|
-
await page.goto('https://grok.com');
|
|
14
|
-
await page.wait(3);
|
|
15
|
-
|
|
16
|
-
// Get all button-like elements near textarea
|
|
17
|
-
const debug = await page.evaluate(`(() => {
|
|
18
|
-
const ta = document.querySelector('textarea');
|
|
19
|
-
if (!ta) return { error: 'no textarea' };
|
|
20
|
-
|
|
21
|
-
// Get parent containers
|
|
22
|
-
let parent = ta.parentElement;
|
|
23
|
-
const parents = [];
|
|
24
|
-
for (let i = 0; i < 5 && parent; i++) {
|
|
25
|
-
parents.push({
|
|
26
|
-
tag: parent.tagName,
|
|
27
|
-
class: parent.className?.substring(0, 80),
|
|
28
|
-
childCount: parent.children.length,
|
|
29
|
-
});
|
|
30
|
-
parent = parent.parentElement;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Find buttons in the form/container near textarea
|
|
34
|
-
const form = ta.closest('form') || ta.closest('[class*="composer"]') || ta.closest('[class*="input"]') || ta.parentElement?.parentElement;
|
|
35
|
-
const buttons = form ? [...form.querySelectorAll('button')].map(b => ({
|
|
36
|
-
testid: b.getAttribute('data-testid'),
|
|
37
|
-
type: b.type,
|
|
38
|
-
disabled: b.disabled,
|
|
39
|
-
text: (b.textContent || '').substring(0, 30),
|
|
40
|
-
html: b.outerHTML.substring(0, 200),
|
|
41
|
-
rect: b.getBoundingClientRect().toJSON(),
|
|
42
|
-
})) : [];
|
|
43
|
-
|
|
44
|
-
return { parents, buttons, formTag: form?.tagName, formClass: form?.className?.substring(0, 80) };
|
|
45
|
-
})()`);
|
|
46
|
-
|
|
47
|
-
return [{ data: JSON.stringify(debug, null, 2) }];
|
|
48
|
-
},
|
|
49
|
-
});
|