@jackwener/opencli 1.0.0 → 1.0.1
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/README.md +20 -1
- package/README.zh-CN.md +20 -1
- package/dist/browser/daemon-client.d.ts +1 -1
- package/dist/browser/index.d.ts +1 -2
- package/dist/browser/index.js +1 -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 +23 -17
- package/dist/browser.test.js +6 -6
- package/dist/cli-manifest.json +394 -14
- 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/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/reply-dm.d.ts +1 -0
- package/dist/clis/twitter/reply-dm.js +181 -0
- package/dist/clis/twitter/search.js +50 -12
- 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/daemon.js +2 -2
- package/dist/doctor.d.ts +0 -21
- package/dist/doctor.js +2 -24
- package/dist/main.js +6 -16
- package/dist/runtime.d.ts +1 -4
- package/dist/runtime.js +1 -4
- package/dist/setup.js +2 -2
- package/extension/dist/background.js +484 -0
- package/extension/manifest.json +1 -1
- package/extension/package.json +1 -1
- package/extension/src/background.ts +99 -22
- package/extension/src/protocol.ts +1 -1
- package/package.json +1 -1
- package/src/browser/daemon-client.ts +1 -1
- package/src/browser/index.ts +1 -6
- package/src/browser/mcp.ts +14 -15
- package/src/browser/page.ts +23 -17
- package/src/browser.test.ts +6 -6
- 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/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/reply-dm.ts +193 -0
- package/src/clis/twitter/search.ts +53 -13
- 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/daemon.ts +2 -2
- package/src/doctor.ts +2 -19
- package/src/main.ts +5 -11
- package/src/runtime.ts +2 -6
- package/src/setup.ts +2 -2
- 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/doctor.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { checkDaemonStatus } from './browser/discover.js';
|
|
10
|
-
import {
|
|
10
|
+
import { BrowserBridge } from './browser/index.js';
|
|
11
11
|
import { browserSession } from './runtime.js';
|
|
12
12
|
|
|
13
13
|
export type DoctorOptions = {
|
|
@@ -37,7 +37,7 @@ export type DoctorReport = {
|
|
|
37
37
|
export async function checkConnectivity(opts?: { timeout?: number }): Promise<ConnectivityResult> {
|
|
38
38
|
const start = Date.now();
|
|
39
39
|
try {
|
|
40
|
-
const mcp = new
|
|
40
|
+
const mcp = new BrowserBridge();
|
|
41
41
|
const page = await mcp.connect({ timeout: opts?.timeout ?? 8 });
|
|
42
42
|
// Try a simple eval to verify end-to-end connectivity
|
|
43
43
|
await page.evaluate('1 + 1');
|
|
@@ -116,20 +116,3 @@ export function renderBrowserDoctorReport(report: DoctorReport): string {
|
|
|
116
116
|
return lines.join('\n');
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
// Backward compatibility exports (no-ops for things that no longer exist)
|
|
120
|
-
export const PLAYWRIGHT_TOKEN_ENV = 'PLAYWRIGHT_MCP_EXTENSION_TOKEN';
|
|
121
|
-
export function discoverExtensionToken(): string | null { return null; }
|
|
122
|
-
export function checkExtensionInstalled(): { installed: boolean; browsers: string[] } { return { installed: false, browsers: [] }; }
|
|
123
|
-
export function applyBrowserDoctorFix(): Promise<string[]> { return Promise.resolve([]); }
|
|
124
|
-
export function getDefaultShellRcPath(): string { return ''; }
|
|
125
|
-
export function getDefaultMcpConfigPaths(): string[] { return []; }
|
|
126
|
-
export function readTokenFromShellContent(_content: string): string | null { return null; }
|
|
127
|
-
export function upsertShellToken(content: string): string { return content; }
|
|
128
|
-
export function upsertJsonConfigToken(content: string): string { return content; }
|
|
129
|
-
export function readTomlConfigToken(_content: string): string | null { return null; }
|
|
130
|
-
export function upsertTomlConfigToken(content: string): string { return content; }
|
|
131
|
-
export function shortenPath(p: string): string { return p; }
|
|
132
|
-
export function toolName(_p: string): string { return ''; }
|
|
133
|
-
export function fileExists(filePath: string): boolean { try { return require('node:fs').existsSync(filePath); } catch { return false; } }
|
|
134
|
-
export function writeFileWithMkdir(_p: string, _c: string): void {}
|
|
135
|
-
export async function checkTokenConnectivity(opts?: { timeout?: number }): Promise<ConnectivityResult> { return checkConnectivity(opts); }
|
package/src/main.ts
CHANGED
|
@@ -11,7 +11,7 @@ import chalk from 'chalk';
|
|
|
11
11
|
import { discoverClis, executeCommand } from './engine.js';
|
|
12
12
|
import { Strategy, type CliCommand, fullName, getRegistry, strategyLabel } from './registry.js';
|
|
13
13
|
import { render as renderOutput } from './output.js';
|
|
14
|
-
import {
|
|
14
|
+
import { BrowserBridge } from './browser/index.js';
|
|
15
15
|
import { browserSession, DEFAULT_BROWSER_COMMAND_TIMEOUT, runWithTimeout } from './runtime.js';
|
|
16
16
|
import { PKG_VERSION } from './version.js';
|
|
17
17
|
import { getCompletions, printCompletionScript } from './completion.js';
|
|
@@ -99,18 +99,18 @@ program.command('verify').description('Validate + smoke test').argument('[target
|
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
program.command('explore').alias('probe').description('Explore a website: discover APIs, stores, and recommend strategies').argument('<url>').option('--site <name>').option('--goal <text>').option('--wait <s>', '', '3').option('--auto', 'Enable interactive fuzzing (simulate clicks to trigger lazy APIs)').option('--click <labels>', 'Comma-separated labels to click before fuzzing (e.g. "字幕,CC,评论")')
|
|
102
|
-
.action(async (url, opts) => { const { exploreUrl, renderExploreSummary } = await import('./explore.js'); const clickLabels = opts.click ? opts.click.split(',').map((s: string) => s.trim()) : undefined; console.log(renderExploreSummary(await exploreUrl(url, { BrowserFactory:
|
|
102
|
+
.action(async (url, opts) => { const { exploreUrl, renderExploreSummary } = await import('./explore.js'); const clickLabels = opts.click ? opts.click.split(',').map((s: string) => s.trim()) : undefined; console.log(renderExploreSummary(await exploreUrl(url, { BrowserFactory: BrowserBridge, site: opts.site, goal: opts.goal, waitSeconds: parseFloat(opts.wait), auto: opts.auto, clickLabels }))); });
|
|
103
103
|
|
|
104
104
|
program.command('synthesize').description('Synthesize CLIs from explore').argument('<target>').option('--top <n>', '', '3')
|
|
105
105
|
.action(async (target, opts) => { const { synthesizeFromExplore, renderSynthesizeSummary } = await import('./synthesize.js'); console.log(renderSynthesizeSummary(synthesizeFromExplore(target, { top: parseInt(opts.top) }))); });
|
|
106
106
|
|
|
107
107
|
program.command('generate').description('One-shot: explore → synthesize → register').argument('<url>').option('--goal <text>').option('--site <name>')
|
|
108
|
-
.action(async (url, opts) => { const { generateCliFromUrl, renderGenerateSummary } = await import('./generate.js'); const r = await generateCliFromUrl({ url, BrowserFactory:
|
|
108
|
+
.action(async (url, opts) => { const { generateCliFromUrl, renderGenerateSummary } = await import('./generate.js'); const r = await generateCliFromUrl({ url, BrowserFactory: BrowserBridge, builtinClis: BUILTIN_CLIS, userClis: USER_CLIS, goal: opts.goal, site: opts.site }); console.log(renderGenerateSummary(r)); process.exitCode = r.ok ? 0 : 1; });
|
|
109
109
|
|
|
110
110
|
program.command('cascade').description('Strategy cascade: find simplest working strategy').argument('<url>').option('--site <name>')
|
|
111
111
|
.action(async (url, opts) => {
|
|
112
112
|
const { cascadeProbe, renderCascadeResult } = await import('./cascade.js');
|
|
113
|
-
const result = await browserSession(
|
|
113
|
+
const result = await browserSession(BrowserBridge, async (page) => {
|
|
114
114
|
// Navigate to the site first for cookie context
|
|
115
115
|
try { const siteUrl = new URL(url); await page.goto(`${siteUrl.protocol}//${siteUrl.host}`); await page.wait(2); } catch {}
|
|
116
116
|
return cascadeProbe(page, url);
|
|
@@ -192,13 +192,7 @@ for (const [, cmd] of registry) {
|
|
|
192
192
|
if (actionOpts.verbose) process.env.OPENCLI_VERBOSE = '1';
|
|
193
193
|
let result: any;
|
|
194
194
|
if (cmd.browser) {
|
|
195
|
-
result = await browserSession(
|
|
196
|
-
// Cookie/header strategies require same-origin context for credentialed fetch.
|
|
197
|
-
// In CDP mode the active tab may be on an unrelated domain, causing CORS failures.
|
|
198
|
-
// Navigate to the command's domain first (mirrors cascade command behavior).
|
|
199
|
-
if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
|
|
200
|
-
try { await page.goto(`https://${cmd.domain}`); await page.wait(2); } catch {}
|
|
201
|
-
}
|
|
195
|
+
result = await browserSession(BrowserBridge, async (page) => {
|
|
202
196
|
return runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) });
|
|
203
197
|
});
|
|
204
198
|
} else { result = await executeCommand(cmd, null, kwargs, actionOpts.verbose); }
|
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>;
|
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();
|
|
@@ -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
|
-
});
|