@jackwener/opencli 0.4.6 → 0.5.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 +6 -12
- package/README.zh-CN.md +7 -13
- package/SKILL.md +3 -7
- package/dist/browser.d.ts +3 -16
- package/dist/browser.js +42 -194
- package/dist/browser.test.js +7 -6
- package/dist/clis/boss/search.js +0 -1
- package/dist/clis/v2ex/daily.js +0 -1
- package/dist/clis/v2ex/me.js +0 -1
- package/dist/clis/v2ex/notifications.js +0 -1
- package/dist/doctor.d.ts +0 -5
- package/dist/doctor.js +4 -62
- package/dist/doctor.test.js +1 -13
- package/dist/main.js +1 -1
- package/dist/registry.d.ts +0 -4
- package/dist/registry.js +0 -1
- package/dist/runtime.d.ts +1 -3
- package/dist/runtime.js +2 -2
- package/package.json +4 -1
- package/src/browser.test.ts +11 -8
- package/src/browser.ts +49 -208
- package/src/clis/boss/search.ts +1 -1
- package/src/clis/v2ex/daily.ts +1 -1
- package/src/clis/v2ex/me.ts +1 -1
- package/src/clis/v2ex/notifications.ts +1 -1
- package/src/doctor.test.ts +1 -13
- package/src/doctor.ts +5 -60
- package/src/main.ts +1 -1
- package/src/registry.ts +0 -7
- package/src/runtime.ts +1 -2
package/dist/doctor.js
CHANGED
|
@@ -3,8 +3,7 @@ import * as os from 'node:os';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { createInterface } from 'node:readline/promises';
|
|
5
5
|
import { stdin as input, stdout as output } from 'node:process';
|
|
6
|
-
import {
|
|
7
|
-
import { browserSession } from './runtime.js';
|
|
6
|
+
import { getTokenFingerprint } from './browser.js';
|
|
8
7
|
const PLAYWRIGHT_SERVER_NAME = 'playwright';
|
|
9
8
|
const PLAYWRIGHT_TOKEN_ENV = 'PLAYWRIGHT_MCP_EXTENSION_TOKEN';
|
|
10
9
|
const PLAYWRIGHT_EXTENSION_ID = 'mmlmfjhmonkocbjadbfplnigmagldckm';
|
|
@@ -170,46 +169,8 @@ function readConfigStatus(filePath) {
|
|
|
170
169
|
};
|
|
171
170
|
}
|
|
172
171
|
}
|
|
173
|
-
async function extractTokenViaCdp() {
|
|
174
|
-
if (!(process.env.OPENCLI_USE_CDP === '1' || process.env.OPENCLI_CDP_ENDPOINT))
|
|
175
|
-
return null;
|
|
176
|
-
const candidates = [
|
|
177
|
-
`chrome-extension://${PLAYWRIGHT_EXTENSION_ID}/options.html`,
|
|
178
|
-
`chrome-extension://${PLAYWRIGHT_EXTENSION_ID}/popup.html`,
|
|
179
|
-
`chrome-extension://${PLAYWRIGHT_EXTENSION_ID}/connect.html`,
|
|
180
|
-
`chrome-extension://${PLAYWRIGHT_EXTENSION_ID}/index.html`,
|
|
181
|
-
];
|
|
182
|
-
const result = await browserSession(PlaywrightMCP, async (page) => {
|
|
183
|
-
for (const url of candidates) {
|
|
184
|
-
try {
|
|
185
|
-
await page.goto(url);
|
|
186
|
-
await page.wait(1);
|
|
187
|
-
const token = await page.evaluate(`() => {
|
|
188
|
-
const values = new Set();
|
|
189
|
-
const push = (value) => {
|
|
190
|
-
if (!value || typeof value !== 'string') return;
|
|
191
|
-
for (const match of value.matchAll(/[A-Za-z0-9_-]{24,}/g)) values.add(match[0]);
|
|
192
|
-
};
|
|
193
|
-
document.querySelectorAll('input, textarea, code, pre, span, div').forEach((el) => {
|
|
194
|
-
push(el.value);
|
|
195
|
-
push(el.textContent || '');
|
|
196
|
-
push(el.getAttribute && el.getAttribute('value'));
|
|
197
|
-
});
|
|
198
|
-
return Array.from(values);
|
|
199
|
-
}`);
|
|
200
|
-
const matches = Array.isArray(token) ? token.filter((v) => v.length >= 24) : [];
|
|
201
|
-
if (matches.length > 0)
|
|
202
|
-
return matches.sort((a, b) => b.length - a.length)[0];
|
|
203
|
-
}
|
|
204
|
-
catch { }
|
|
205
|
-
}
|
|
206
|
-
return null;
|
|
207
|
-
});
|
|
208
|
-
return typeof result === 'string' && result ? result : null;
|
|
209
|
-
}
|
|
210
172
|
export async function runBrowserDoctor(opts = {}) {
|
|
211
173
|
const envToken = process.env[PLAYWRIGHT_TOKEN_ENV] ?? null;
|
|
212
|
-
const remoteDebuggingEndpoint = await discoverChromeEndpoint().catch(() => null);
|
|
213
174
|
const shellPath = opts.shellRc ?? getDefaultShellRcPath();
|
|
214
175
|
const shellFiles = [shellPath].map((filePath) => {
|
|
215
176
|
if (!fileExists(filePath))
|
|
@@ -220,27 +181,20 @@ export async function runBrowserDoctor(opts = {}) {
|
|
|
220
181
|
});
|
|
221
182
|
const configPaths = opts.configPaths?.length ? opts.configPaths : getDefaultMcpConfigPaths();
|
|
222
183
|
const configs = configPaths.map(readConfigStatus);
|
|
223
|
-
const cdpToken = !opts.token && !envToken ? await extractTokenViaCdp().catch(() => null) : null;
|
|
224
184
|
const allTokens = [
|
|
225
185
|
opts.token ?? null,
|
|
226
186
|
envToken,
|
|
227
187
|
...shellFiles.map(s => s.token),
|
|
228
188
|
...configs.map(c => c.token),
|
|
229
|
-
cdpToken,
|
|
230
189
|
].filter((v) => !!v);
|
|
231
190
|
const uniqueTokens = [...new Set(allTokens)];
|
|
232
|
-
const recommendedToken = opts.token ?? envToken ?? (uniqueTokens.length === 1 ? uniqueTokens[0] :
|
|
191
|
+
const recommendedToken = opts.token ?? envToken ?? (uniqueTokens.length === 1 ? uniqueTokens[0] : null) ?? null;
|
|
233
192
|
const report = {
|
|
234
193
|
cliVersion: opts.cliVersion,
|
|
235
194
|
envToken,
|
|
236
195
|
envFingerprint: getTokenFingerprint(envToken ?? undefined),
|
|
237
196
|
shellFiles,
|
|
238
197
|
configs,
|
|
239
|
-
remoteDebuggingEnabled: !!remoteDebuggingEndpoint,
|
|
240
|
-
remoteDebuggingEndpoint,
|
|
241
|
-
cdpEnabled: process.env.OPENCLI_USE_CDP === '1' || !!process.env.OPENCLI_CDP_ENDPOINT,
|
|
242
|
-
cdpToken,
|
|
243
|
-
cdpFingerprint: getTokenFingerprint(cdpToken ?? undefined),
|
|
244
198
|
recommendedToken,
|
|
245
199
|
recommendedFingerprint: getTokenFingerprint(recommendedToken ?? undefined),
|
|
246
200
|
warnings: [],
|
|
@@ -254,17 +208,12 @@ export async function runBrowserDoctor(opts = {}) {
|
|
|
254
208
|
report.issues.push('No scanned MCP config currently contains a Playwright extension token.');
|
|
255
209
|
if (uniqueTokens.length > 1)
|
|
256
210
|
report.issues.push('Detected inconsistent Playwright MCP tokens across env/config files.');
|
|
257
|
-
if (!report.remoteDebuggingEnabled)
|
|
258
|
-
report.warnings.push('Chrome remote debugging appears to be disabled or Chrome is not currently exposing a DevTools endpoint.');
|
|
259
211
|
for (const config of configs) {
|
|
260
212
|
if (config.parseError)
|
|
261
213
|
report.warnings.push(`Could not parse ${config.path}: ${config.parseError}`);
|
|
262
214
|
}
|
|
263
215
|
if (!recommendedToken) {
|
|
264
|
-
|
|
265
|
-
report.warnings.push('CDP is enabled, but no token could be extracted automatically from the extension UI.');
|
|
266
|
-
else
|
|
267
|
-
report.warnings.push('No token source found. Enable OPENCLI_USE_CDP=1 to allow a best-effort token read from the extension page.');
|
|
216
|
+
report.warnings.push('No token source found.');
|
|
268
217
|
}
|
|
269
218
|
return report;
|
|
270
219
|
}
|
|
@@ -277,9 +226,6 @@ export function renderBrowserDoctorReport(report) {
|
|
|
277
226
|
const uniqueFingerprints = [...new Set(tokenFingerprints)];
|
|
278
227
|
const hasMismatch = uniqueFingerprints.length > 1;
|
|
279
228
|
const lines = [`opencli v${report.cliVersion ?? 'unknown'} doctor`, ''];
|
|
280
|
-
lines.push(statusLine(report.remoteDebuggingEnabled ? 'OK' : 'WARN', `Chrome remote debugging: ${report.remoteDebuggingEnabled ? 'enabled' : 'disabled'}`));
|
|
281
|
-
if (report.remoteDebuggingEndpoint)
|
|
282
|
-
lines.push(` ${report.remoteDebuggingEndpoint}`);
|
|
283
229
|
const envStatus = !report.envToken ? 'MISSING' : hasMismatch ? 'MISMATCH' : 'OK';
|
|
284
230
|
lines.push(statusLine(envStatus, `Environment token: ${tokenSummary(report.envToken, report.envFingerprint)}`));
|
|
285
231
|
for (const shell of report.shellFiles) {
|
|
@@ -306,10 +252,6 @@ export function renderBrowserDoctorReport(report) {
|
|
|
306
252
|
}
|
|
307
253
|
if (missingConfigCount > 0)
|
|
308
254
|
lines.push(` Other scanned config locations not present: ${missingConfigCount}`);
|
|
309
|
-
if (report.cdpEnabled) {
|
|
310
|
-
const cdpStatus = report.cdpToken ? 'OK' : 'WARN';
|
|
311
|
-
lines.push(statusLine(cdpStatus, `CDP token probe: ${tokenSummary(report.cdpToken, report.cdpFingerprint)}`));
|
|
312
|
-
}
|
|
313
255
|
lines.push('');
|
|
314
256
|
lines.push(statusLine(hasMismatch ? 'MISMATCH' : report.recommendedToken ? 'OK' : 'WARN', `Recommended token fingerprint: ${report.recommendedFingerprint ?? 'unavailable'}`));
|
|
315
257
|
if (report.issues.length) {
|
|
@@ -341,7 +283,7 @@ function writeFileWithMkdir(filePath, content) {
|
|
|
341
283
|
export async function applyBrowserDoctorFix(report, opts = {}) {
|
|
342
284
|
const token = opts.token ?? report.recommendedToken;
|
|
343
285
|
if (!token)
|
|
344
|
-
throw new Error('No Playwright MCP token is available to write. Provide --token
|
|
286
|
+
throw new Error('No Playwright MCP token is available to write. Provide --token first.');
|
|
345
287
|
const plannedWrites = [];
|
|
346
288
|
const shellPath = opts.shellRc ?? report.shellFiles[0]?.path ?? getDefaultShellRcPath();
|
|
347
289
|
plannedWrites.push(shellPath);
|
package/dist/doctor.test.js
CHANGED
|
@@ -76,17 +76,11 @@ describe('doctor report rendering', () => {
|
|
|
76
76
|
envFingerprint: 'fp1',
|
|
77
77
|
shellFiles: [{ path: '/tmp/.zshrc', exists: true, token: 'abc123', fingerprint: 'fp1' }],
|
|
78
78
|
configs: [{ path: '/tmp/mcp.json', exists: true, format: 'json', token: 'abc123', fingerprint: 'fp1', writable: true }],
|
|
79
|
-
remoteDebuggingEnabled: true,
|
|
80
|
-
remoteDebuggingEndpoint: 'ws://127.0.0.1:9222/devtools/browser/test',
|
|
81
|
-
cdpEnabled: false,
|
|
82
|
-
cdpToken: null,
|
|
83
|
-
cdpFingerprint: null,
|
|
84
79
|
recommendedToken: 'abc123',
|
|
85
80
|
recommendedFingerprint: 'fp1',
|
|
86
81
|
warnings: [],
|
|
87
82
|
issues: [],
|
|
88
83
|
});
|
|
89
|
-
expect(text).toContain('[OK] Chrome remote debugging: enabled');
|
|
90
84
|
expect(text).toContain('[OK] Environment token: configured (fp1)');
|
|
91
85
|
expect(text).toContain('[OK] MCP config /tmp/mcp.json: configured (fp1)');
|
|
92
86
|
});
|
|
@@ -96,17 +90,11 @@ describe('doctor report rendering', () => {
|
|
|
96
90
|
envFingerprint: 'fp1',
|
|
97
91
|
shellFiles: [{ path: '/tmp/.zshrc', exists: true, token: 'def456', fingerprint: 'fp2' }],
|
|
98
92
|
configs: [{ path: '/tmp/mcp.json', exists: true, format: 'json', token: 'abc123', fingerprint: 'fp1', writable: true }],
|
|
99
|
-
remoteDebuggingEnabled: false,
|
|
100
|
-
remoteDebuggingEndpoint: null,
|
|
101
|
-
cdpEnabled: false,
|
|
102
|
-
cdpToken: null,
|
|
103
|
-
cdpFingerprint: null,
|
|
104
93
|
recommendedToken: 'abc123',
|
|
105
94
|
recommendedFingerprint: 'fp1',
|
|
106
|
-
warnings: [
|
|
95
|
+
warnings: [],
|
|
107
96
|
issues: ['Detected inconsistent Playwright MCP tokens across env/config files.'],
|
|
108
97
|
});
|
|
109
|
-
expect(text).toContain('[WARN] Chrome remote debugging: disabled');
|
|
110
98
|
expect(text).toContain('[MISMATCH] Environment token: configured (fp1)');
|
|
111
99
|
expect(text).toContain('[MISMATCH] Shell file /tmp/.zshrc: configured (fp2)');
|
|
112
100
|
expect(text).toContain('[MISMATCH] Recommended token fingerprint: fp1');
|
package/dist/main.js
CHANGED
|
@@ -152,7 +152,7 @@ for (const [, cmd] of registry) {
|
|
|
152
152
|
process.env.OPENCLI_VERBOSE = '1';
|
|
153
153
|
let result;
|
|
154
154
|
if (cmd.browser) {
|
|
155
|
-
result = await browserSession(PlaywrightMCP, async (page) => runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) })
|
|
155
|
+
result = await browserSession(PlaywrightMCP, async (page) => runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) }));
|
|
156
156
|
}
|
|
157
157
|
else {
|
|
158
158
|
result = await executeCommand(cmd, null, kwargs, actionOpts.verbose);
|
package/dist/registry.d.ts
CHANGED
|
@@ -33,8 +33,6 @@ export interface CliCommand {
|
|
|
33
33
|
/** Internal: lazy-loaded TS module support */
|
|
34
34
|
_lazy?: boolean;
|
|
35
35
|
_modulePath?: string;
|
|
36
|
-
/** Force extension bridge mode (bypass CDP), for anti-bot sites */
|
|
37
|
-
forceExtension?: boolean;
|
|
38
36
|
}
|
|
39
37
|
export interface CliOptions {
|
|
40
38
|
site: string;
|
|
@@ -48,8 +46,6 @@ export interface CliOptions {
|
|
|
48
46
|
func?: (page: IPage | null, kwargs: Record<string, any>, debug?: boolean) => Promise<any>;
|
|
49
47
|
pipeline?: any[];
|
|
50
48
|
timeoutSeconds?: number;
|
|
51
|
-
/** Force extension bridge mode (bypass CDP), for anti-bot sites */
|
|
52
|
-
forceExtension?: boolean;
|
|
53
49
|
}
|
|
54
50
|
export declare function cli(opts: CliOptions): CliCommand;
|
|
55
51
|
export declare function getRegistry(): Map<string, CliCommand>;
|
package/dist/registry.js
CHANGED
package/dist/runtime.d.ts
CHANGED
|
@@ -10,6 +10,4 @@ export declare function runWithTimeout<T>(promise: Promise<T>, opts: {
|
|
|
10
10
|
timeout: number;
|
|
11
11
|
label?: string;
|
|
12
12
|
}): Promise<T>;
|
|
13
|
-
export declare function browserSession<T>(BrowserFactory: new () => any, fn: (page: IPage) => Promise<T
|
|
14
|
-
forceExtension?: boolean;
|
|
15
|
-
}): Promise<T>;
|
|
13
|
+
export declare function browserSession<T>(BrowserFactory: new () => any, fn: (page: IPage) => Promise<T>): Promise<T>;
|
package/dist/runtime.js
CHANGED
|
@@ -15,10 +15,10 @@ export async function runWithTimeout(promise, opts) {
|
|
|
15
15
|
.catch((err) => { clearTimeout(timer); reject(err); });
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
export async function browserSession(BrowserFactory, fn
|
|
18
|
+
export async function browserSession(BrowserFactory, fn) {
|
|
19
19
|
const mcp = new BrowserFactory();
|
|
20
20
|
try {
|
|
21
|
-
const page = await mcp.connect({ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT
|
|
21
|
+
const page = await mcp.connect({ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT });
|
|
22
22
|
return await fn(page);
|
|
23
23
|
}
|
|
24
24
|
finally {
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackwener/opencli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
7
|
"description": "Make any website your CLI. AI-powered.",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18.0.0"
|
|
10
|
+
},
|
|
8
11
|
"type": "module",
|
|
9
12
|
"main": "dist/main.js",
|
|
10
13
|
"bin": {
|
package/src/browser.test.ts
CHANGED
|
@@ -20,6 +20,17 @@ describe('browser helpers', () => {
|
|
|
20
20
|
]);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
+
it('extracts tab entries from MCP markdown format', () => {
|
|
24
|
+
const entries = __test__.extractTabEntries(
|
|
25
|
+
'- 0: (current) [Playwright MCP extension](chrome-extension://abc/connect.html)\n- 1: [知乎 - 首页](https://www.zhihu.com/)'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(entries).toEqual([
|
|
29
|
+
{ index: 0, identity: '(current) [Playwright MCP extension](chrome-extension://abc/connect.html)' },
|
|
30
|
+
{ index: 1, identity: '[知乎 - 首页](https://www.zhihu.com/)' },
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
23
34
|
it('closes only tabs that were opened during the session', () => {
|
|
24
35
|
const tabsToClose = __test__.diffTabIndexes(
|
|
25
36
|
['https://example.com', 'Chrome Extension'],
|
|
@@ -75,13 +86,5 @@ describe('PlaywrightMCP state', () => {
|
|
|
75
86
|
await expect(mcp.connect()).rejects.toThrow('Playwright MCP is closing');
|
|
76
87
|
});
|
|
77
88
|
|
|
78
|
-
it('tracks backend mode for lifecycle policy', async () => {
|
|
79
|
-
const mcp = new PlaywrightMCP();
|
|
80
|
-
|
|
81
|
-
expect((mcp as any)._mode).toBeNull();
|
|
82
89
|
|
|
83
|
-
await mcp.close();
|
|
84
|
-
|
|
85
|
-
expect((mcp as any)._mode).toBeNull();
|
|
86
|
-
});
|
|
87
90
|
});
|