@jackwener/opencli 0.9.8 → 1.0.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.
Files changed (97) hide show
  1. package/CDP.md +1 -1
  2. package/CDP.zh-CN.md +1 -1
  3. package/CLI-ELECTRON.md +2 -2
  4. package/CLI-EXPLORER.md +4 -4
  5. package/README.md +15 -57
  6. package/README.zh-CN.md +16 -59
  7. package/SKILL.md +10 -8
  8. package/TESTING.md +7 -7
  9. package/dist/browser/daemon-client.d.ts +37 -0
  10. package/dist/browser/daemon-client.js +82 -0
  11. package/dist/browser/discover.d.ts +11 -34
  12. package/dist/browser/discover.js +15 -205
  13. package/dist/browser/errors.d.ts +6 -20
  14. package/dist/browser/errors.js +24 -63
  15. package/dist/browser/index.d.ts +2 -11
  16. package/dist/browser/index.js +5 -11
  17. package/dist/browser/mcp.d.ts +9 -18
  18. package/dist/browser/mcp.js +70 -284
  19. package/dist/browser/page.d.ts +28 -6
  20. package/dist/browser/page.js +210 -85
  21. package/dist/browser.test.js +4 -225
  22. package/dist/cli-manifest.json +167 -0
  23. package/dist/clis/neteasemusic/like.d.ts +1 -0
  24. package/dist/clis/neteasemusic/like.js +25 -0
  25. package/dist/clis/neteasemusic/lyrics.d.ts +1 -0
  26. package/dist/clis/neteasemusic/lyrics.js +47 -0
  27. package/dist/clis/neteasemusic/next.d.ts +1 -0
  28. package/dist/clis/neteasemusic/next.js +26 -0
  29. package/dist/clis/neteasemusic/play.d.ts +1 -0
  30. package/dist/clis/neteasemusic/play.js +26 -0
  31. package/dist/clis/neteasemusic/playing.d.ts +1 -0
  32. package/dist/clis/neteasemusic/playing.js +59 -0
  33. package/dist/clis/neteasemusic/playlist.d.ts +1 -0
  34. package/dist/clis/neteasemusic/playlist.js +46 -0
  35. package/dist/clis/neteasemusic/prev.d.ts +1 -0
  36. package/dist/clis/neteasemusic/prev.js +25 -0
  37. package/dist/clis/neteasemusic/search.d.ts +1 -0
  38. package/dist/clis/neteasemusic/search.js +52 -0
  39. package/dist/clis/neteasemusic/status.d.ts +1 -0
  40. package/dist/clis/neteasemusic/status.js +16 -0
  41. package/dist/clis/neteasemusic/volume.d.ts +1 -0
  42. package/dist/clis/neteasemusic/volume.js +54 -0
  43. package/dist/daemon.d.ts +13 -0
  44. package/dist/daemon.js +187 -0
  45. package/dist/doctor.d.ts +27 -61
  46. package/dist/doctor.js +70 -601
  47. package/dist/doctor.test.js +30 -170
  48. package/dist/main.js +6 -25
  49. package/dist/pipeline/executor.test.js +1 -0
  50. package/dist/pipeline/steps/browser.js +2 -2
  51. package/dist/pipeline/steps/intercept.js +1 -2
  52. package/dist/setup.d.ts +6 -0
  53. package/dist/setup.js +46 -160
  54. package/dist/types.d.ts +6 -0
  55. package/extension/icons/icon-128.png +0 -0
  56. package/extension/icons/icon-16.png +0 -0
  57. package/extension/icons/icon-32.png +0 -0
  58. package/extension/icons/icon-48.png +0 -0
  59. package/extension/manifest.json +31 -0
  60. package/extension/package.json +16 -0
  61. package/extension/src/background.ts +293 -0
  62. package/extension/src/cdp.ts +125 -0
  63. package/extension/src/protocol.ts +57 -0
  64. package/extension/store-assets/screenshot-1280x800.png +0 -0
  65. package/extension/tsconfig.json +15 -0
  66. package/extension/vite.config.ts +18 -0
  67. package/package.json +5 -5
  68. package/src/browser/daemon-client.ts +113 -0
  69. package/src/browser/discover.ts +18 -232
  70. package/src/browser/errors.ts +30 -100
  71. package/src/browser/index.ts +6 -12
  72. package/src/browser/mcp.ts +78 -278
  73. package/src/browser/page.ts +222 -88
  74. package/src/browser.test.ts +3 -233
  75. package/src/clis/chatgpt/README.md +1 -1
  76. package/src/clis/chatgpt/README.zh-CN.md +1 -1
  77. package/src/clis/neteasemusic/README.md +31 -0
  78. package/src/clis/neteasemusic/README.zh-CN.md +31 -0
  79. package/src/clis/neteasemusic/like.ts +28 -0
  80. package/src/clis/neteasemusic/lyrics.ts +53 -0
  81. package/src/clis/neteasemusic/next.ts +30 -0
  82. package/src/clis/neteasemusic/play.ts +30 -0
  83. package/src/clis/neteasemusic/playing.ts +62 -0
  84. package/src/clis/neteasemusic/playlist.ts +51 -0
  85. package/src/clis/neteasemusic/prev.ts +29 -0
  86. package/src/clis/neteasemusic/search.ts +58 -0
  87. package/src/clis/neteasemusic/status.ts +18 -0
  88. package/src/clis/neteasemusic/volume.ts +61 -0
  89. package/src/daemon.ts +217 -0
  90. package/src/doctor.test.ts +32 -193
  91. package/src/doctor.ts +74 -668
  92. package/src/main.ts +6 -23
  93. package/src/pipeline/executor.test.ts +1 -0
  94. package/src/pipeline/steps/browser.ts +2 -2
  95. package/src/pipeline/steps/intercept.ts +1 -2
  96. package/src/setup.ts +47 -183
  97. package/src/types.ts +1 -0
package/src/main.ts CHANGED
@@ -119,36 +119,19 @@ program.command('cascade').description('Strategy cascade: find simplest working
119
119
  });
120
120
 
121
121
  program.command('doctor')
122
- .description('Diagnose Playwright MCP Bridge, token consistency, and Chrome remote debugging')
123
- .option('--fix', 'Apply suggested fixes to shell rc and detected MCP configs', false)
124
- .option('-y, --yes', 'Skip confirmation prompts when applying fixes', false)
125
- .option('--token <token>', 'Override token to write instead of auto-detecting')
122
+ .description('Diagnose opencli browser bridge connectivity')
126
123
  .option('--live', 'Test browser connectivity (requires Chrome running)', false)
127
- .option('--shell-rc <path>', 'Shell startup file to update')
128
- .option('--mcp-config <paths>', 'Comma-separated MCP config paths to scan/update')
129
124
  .action(async (opts) => {
130
- const { runBrowserDoctor, renderBrowserDoctorReport, applyBrowserDoctorFix } = await import('./doctor.js');
131
- const configPaths = opts.mcpConfig ? String(opts.mcpConfig).split(',').map((s: string) => s.trim()).filter(Boolean) : undefined;
132
- const report = await runBrowserDoctor({ token: opts.token, live: opts.live, shellRc: opts.shellRc, configPaths, cliVersion: PKG_VERSION });
125
+ const { runBrowserDoctor, renderBrowserDoctorReport } = await import('./doctor.js');
126
+ const report = await runBrowserDoctor({ live: opts.live, cliVersion: PKG_VERSION });
133
127
  console.log(renderBrowserDoctorReport(report));
134
- if (opts.fix) {
135
- const written = await applyBrowserDoctorFix(report, { fix: true, yes: opts.yes, token: opts.token, shellRc: opts.shellRc, configPaths });
136
- console.log();
137
- if (written.length > 0) {
138
- console.log(chalk.green('Updated files:'));
139
- for (const filePath of written) console.log(`- ${filePath}`);
140
- } else {
141
- console.log(chalk.yellow('No files were changed.'));
142
- }
143
- }
144
128
  });
145
129
 
146
130
  program.command('setup')
147
- .description('Interactive setup: configure Playwright MCP token across all detected tools')
148
- .option('--token <token>', 'Provide token directly instead of auto-detecting')
149
- .action(async (opts) => {
131
+ .description('Interactive setup: verify browser bridge connectivity')
132
+ .action(async () => {
150
133
  const { runSetup } = await import('./setup.js');
151
- await runSetup({ cliVersion: PKG_VERSION, token: opts.token });
134
+ await runSetup({ cliVersion: PKG_VERSION });
152
135
  });
153
136
 
154
137
  program.command('completion')
@@ -26,6 +26,7 @@ function createMockPage(overrides: Partial<IPage> = {}): IPage {
26
26
  autoScroll: vi.fn(),
27
27
  installInterceptor: vi.fn(),
28
28
  getInterceptedRequests: vi.fn().mockResolvedValue([]),
29
+ screenshot: vi.fn().mockResolvedValue(''),
29
30
  ...overrides,
30
31
  };
31
32
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { IPage } from '../../types.js';
7
- import { render, normalizeEvaluateSource } from '../template.js';
7
+ import { render } from '../template.js';
8
8
 
9
9
  export async function stepNavigate(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any> {
10
10
  const url = render(params, { args, data });
@@ -52,7 +52,7 @@ export async function stepSnapshot(page: IPage | null, params: any, _data: any,
52
52
 
53
53
  export async function stepEvaluate(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any> {
54
54
  const js = String(render(params, { args, data }));
55
- let result = await page!.evaluate(normalizeEvaluateSource(js));
55
+ let result = await page!.evaluate(js);
56
56
  // MCP may return JSON as a string — auto-parse it
57
57
  if (typeof result === 'string') {
58
58
  const trimmed = result.trim();
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { IPage } from '../../types.js';
6
- import { render } from '../template.js';
6
+ import { render, normalizeEvaluateSource } from '../template.js';
7
7
  import { generateInterceptorJs, generateReadInterceptedJs } from '../../interceptor.js';
8
8
 
9
9
  export async function stepIntercept(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any> {
@@ -24,7 +24,6 @@ export async function stepIntercept(page: IPage | null, params: any, data: any,
24
24
  await page!.goto(String(url));
25
25
  } else if (trigger.startsWith('evaluate:')) {
26
26
  const js = trigger.slice('evaluate:'.length);
27
- const { normalizeEvaluateSource } = await import('../template.js');
28
27
  await page!.evaluate(normalizeEvaluateSource(render(js, { args, data }) as string));
29
28
  } else if (trigger.startsWith('click:')) {
30
29
  const ref = render(trigger.slice('click:'.length), { args, data });
package/src/setup.ts CHANGED
@@ -1,205 +1,69 @@
1
1
  /**
2
- * setup.ts — Interactive Playwright MCP token setup
2
+ * setup.ts — Interactive browser setup for opencli
3
3
  *
4
- * Discovers the extension token, shows an interactive checkbox
5
- * for selecting which config files to update, and applies changes.
4
+ * Simplified for daemon-based architecture. No more token management.
5
+ * Just verifies daemon + extension connectivity.
6
6
  */
7
- import * as fs from 'node:fs';
7
+
8
8
  import chalk from 'chalk';
9
- import { createInterface } from 'node:readline/promises';
10
- import { stdin as input, stdout as output } from 'node:process';
11
- import {
12
- type DoctorReport,
13
- PLAYWRIGHT_TOKEN_ENV,
14
- checkExtensionInstalled,
15
- checkTokenConnectivity,
16
- discoverExtensionToken,
17
- fileExists,
18
- getDefaultShellRcPath,
19
- runBrowserDoctor,
20
- shortenPath,
21
- toolName,
22
- upsertJsonConfigToken,
23
- upsertShellToken,
24
- upsertTomlConfigToken,
25
- writeFileWithMkdir,
26
- } from './doctor.js';
27
- import { getTokenFingerprint } from './browser/index.js';
28
- import { type CheckboxItem, checkboxPrompt } from './tui.js';
9
+ import { checkDaemonStatus } from './browser/discover.js';
10
+ import { checkConnectivity } from './doctor.js';
11
+ import { PlaywrightMCP } from './browser/index.js';
29
12
 
30
13
  export async function runSetup(opts: { cliVersion?: string; token?: string } = {}) {
31
14
  console.log();
32
- console.log(chalk.bold(' opencli setup') + chalk.dim(' — Playwright MCP token configuration'));
15
+ console.log(chalk.bold(' opencli setup') + chalk.dim(' — browser bridge configuration'));
33
16
  console.log();
34
17
 
35
- // Step 1: Discover token
36
- let token = opts.token ?? null;
37
-
38
- if (!token) {
39
- const extensionToken = discoverExtensionToken();
40
- const envToken = process.env[PLAYWRIGHT_TOKEN_ENV] ?? null;
18
+ // Step 1: Check daemon
19
+ console.log(chalk.dim(' Checking daemon status...'));
20
+ const status = await checkDaemonStatus();
41
21
 
42
- if (extensionToken && envToken && extensionToken === envToken) {
43
- token = extensionToken;
44
- console.log(` ${chalk.green('✓')} Token auto-discovered from Chrome extension`);
45
- console.log(` Fingerprint: ${chalk.bold(getTokenFingerprint(token) ?? 'unknown')}`);
46
- } else if (extensionToken) {
47
- token = extensionToken;
48
- console.log(` ${chalk.green('✓')} Token discovered from Chrome extension ` +
49
- chalk.dim(`(${getTokenFingerprint(token)})`));
50
- if (envToken && envToken !== extensionToken) {
51
- console.log(` ${chalk.yellow('!')} Environment has different token ` +
52
- chalk.dim(`(${getTokenFingerprint(envToken)})`));
53
- }
54
- } else if (envToken) {
55
- token = envToken;
56
- console.log(` ${chalk.green('✓')} Token from environment variable ` +
57
- chalk.dim(`(${getTokenFingerprint(token)})`));
58
- }
22
+ if (status.running) {
23
+ console.log(` ${chalk.green('✓')} Daemon is running on port 19825`);
59
24
  } else {
60
- console.log(` ${chalk.green('')} Using provided token ` +
61
- chalk.dim(`(${getTokenFingerprint(token)})`));
25
+ console.log(` ${chalk.yellow('!')} Daemon is not running`);
26
+ console.log(chalk.dim(' The daemon starts automatically when you run a browser command.'));
27
+ console.log(chalk.dim(' Starting daemon now...'));
28
+
29
+ // Try to spawn daemon
30
+ const mcp = new PlaywrightMCP();
31
+ try {
32
+ await mcp.connect({ timeout: 5 });
33
+ await mcp.close();
34
+ console.log(` ${chalk.green('✓')} Daemon started successfully`);
35
+ } catch {
36
+ console.log(` ${chalk.yellow('!')} Could not start daemon automatically`);
37
+ }
62
38
  }
63
39
 
64
- if (!token) {
65
- // Give precise diagnosis of why token scan failed
66
- const extInstall = checkExtensionInstalled();
67
-
68
- console.log(` ${chalk.red('✗')} Browser token scan failed\n`);
69
- if (!extInstall.installed) {
70
- console.log(chalk.dim(' Cause: Playwright MCP Bridge extension is not installed'));
71
- console.log(chalk.dim(' Fix: Install from https://chromewebstore.google.com/detail/'));
72
- console.log(chalk.dim(' playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm'));
73
- } else {
74
- console.log(chalk.dim(` Cause: Extension is installed (${extInstall.browsers.join(', ')}) but token not found in LevelDB`));
75
- console.log(chalk.dim(' Fix: 1) Open the extension popup and verify the token is generated'));
76
- console.log(chalk.dim(' 2) Close Chrome completely, then re-run setup'));
77
- }
40
+ // Step 2: Check extension
41
+ const statusAfter = await checkDaemonStatus();
42
+ if (statusAfter.extensionConnected) {
43
+ console.log(` ${chalk.green('✓')} Chrome extension connected`);
44
+ } else {
45
+ console.log(` ${chalk.red('✗')} Chrome extension not connected`);
78
46
  console.log();
79
- console.log(` You can enter the token manually, or fix the above and re-run ${chalk.bold('opencli setup')}.`);
47
+ console.log(chalk.dim(' To install the opencli Browser Bridge extension:'));
48
+ console.log(chalk.dim(' 1. Download from GitHub Releases'));
49
+ console.log(chalk.dim(' 2. Open chrome://extensions/ → Enable Developer Mode'));
50
+ console.log(chalk.dim(' 3. Click "Load unpacked" → select the extension folder'));
51
+ console.log(chalk.dim(' 4. Make sure Chrome is running'));
80
52
  console.log();
81
- const rl = createInterface({ input, output });
82
- const answer = await rl.question(' Token (press Enter to abort): ');
83
- rl.close();
84
- token = answer.trim();
85
- if (!token) {
86
- console.log(chalk.red('\n No token provided. Aborting.\n'));
87
- return;
88
- }
89
- }
90
-
91
- const fingerprint = getTokenFingerprint(token) ?? 'unknown';
92
- console.log();
93
-
94
- // Step 2: Scan all config locations
95
- const report = await runBrowserDoctor({ token, cliVersion: opts.cliVersion });
96
-
97
- // Step 3: Build checkbox items
98
- const items: CheckboxItem[] = [];
99
-
100
- // Shell file
101
- const shellPath = report.shellFiles[0]?.path ?? getDefaultShellRcPath();
102
- const shellStatus = report.shellFiles[0];
103
- const shellFp = shellStatus?.fingerprint;
104
- const shellOk = shellFp === fingerprint;
105
- const shellTool = toolName(shellPath) || 'Shell';
106
- items.push({
107
- label: padRight(shortenPath(shellPath), 50) + chalk.dim(` [${shellTool}]`),
108
- value: `shell:${shellPath}`,
109
- checked: !shellOk,
110
- status: shellOk ? `configured (${shellFp})` : shellFp ? `mismatch (${shellFp})` : 'missing',
111
- statusColor: shellOk ? 'green' : shellFp ? 'yellow' : 'red',
112
- });
113
-
114
- // Config files
115
- for (const config of report.configs) {
116
- const fp = config.fingerprint;
117
- const ok = fp === fingerprint;
118
- const tool = toolName(config.path);
119
- items.push({
120
- label: padRight(shortenPath(config.path), 50) + chalk.dim(tool ? ` [${tool}]` : ''),
121
- value: `config:${config.path}`,
122
- checked: false, // let user explicitly select which tools to configure
123
- status: ok ? `configured (${fp})` : !config.exists ? 'will create' : fp ? `mismatch (${fp})` : 'missing',
124
- statusColor: ok ? 'green' : 'yellow',
125
- });
126
- }
127
-
128
- // Step 4: Show interactive checkbox
129
- console.clear();
130
- const selected = await checkboxPrompt(items, {
131
- title: ` ${chalk.bold('opencli setup')} — token ${chalk.cyan(fingerprint)}`,
132
- });
133
-
134
- if (selected.length === 0) {
135
- console.log(chalk.dim(' No changes made.\n'));
136
53
  return;
137
54
  }
138
55
 
139
- // Step 5: Apply changes
140
- const written: string[] = [];
141
- let wroteShell = false;
142
-
143
- for (const sel of selected) {
144
- if (sel.startsWith('shell:')) {
145
- const p = sel.slice('shell:'.length);
146
- const before = fileExists(p) ? fs.readFileSync(p, 'utf-8') : '';
147
- writeFileWithMkdir(p, upsertShellToken(before, token, p));
148
- written.push(p);
149
- wroteShell = true;
150
- } else if (sel.startsWith('config:')) {
151
- const p = sel.slice('config:'.length);
152
- const config = report.configs.find(c => c.path === p);
153
- if (config && config.parseError) continue;
154
- const before = fileExists(p) ? fs.readFileSync(p, 'utf-8') : '';
155
- const format = config?.format ?? (p.endsWith('.toml') ? 'toml' : 'json');
156
- const next = format === 'toml' ? upsertTomlConfigToken(before, token) : upsertJsonConfigToken(before, token, p);
157
- writeFileWithMkdir(p, next);
158
- written.push(p);
159
- }
160
- }
161
-
162
- process.env[PLAYWRIGHT_TOKEN_ENV] = token;
163
-
164
- // Step 6: Summary
165
- if (written.length > 0) {
166
- console.log(chalk.green.bold(` ✓ Updated ${written.length} file(s):`));
167
- for (const p of written) {
168
- const tool = toolName(p);
169
- console.log(` ${chalk.dim('•')} ${shortenPath(p)}${tool ? chalk.dim(` [${tool}]`) : ''}`);
170
- }
171
- if (wroteShell) {
172
- console.log();
173
- console.log(chalk.cyan(` 💡 Run ${chalk.bold(`source ${shortenPath(shellPath)}`)} to apply token to current shell.`));
174
- }
175
- } else {
176
- console.log(chalk.yellow(' No files were changed.'));
177
- }
56
+ // Step 3: Test connectivity
178
57
  console.log();
179
-
180
- // Step 7: Auto-verify browser connectivity
181
- console.log(chalk.dim(' Verifying browser connectivity...'));
182
- try {
183
- const result = await checkTokenConnectivity({ timeout: 5 });
184
- if (result.ok) {
185
- console.log(` ${chalk.green('✓')} Browser connected in ${(result.durationMs / 1000).toFixed(1)}s`);
186
- } else {
187
- console.log(` ${chalk.green('')} Token saved successfully.`);
188
- console.log(` ${chalk.yellow('!')} Browser connectivity test failed: ${result.error ?? 'unknown'}`);
189
- console.log(chalk.dim(' Token configuration is complete. To use opencli, make sure Chrome'));
190
- console.log(chalk.dim(' is running with the Playwright MCP Bridge extension enabled.'));
191
- console.log(chalk.dim(` Run ${chalk.bold('opencli doctor --live')} to re-test connectivity.`));
192
- }
193
- } catch {
194
- console.log(` ${chalk.green('✓')} Token saved successfully.`);
195
- console.log(` ${chalk.yellow('!')} Browser connectivity test skipped (Chrome may not be running).`);
196
- console.log(chalk.dim(' Token configuration is complete. Start Chrome to begin using opencli.'));
197
- console.log(chalk.dim(` Run ${chalk.bold('opencli doctor --live')} to re-test connectivity.`));
58
+ console.log(chalk.dim(' Testing browser connectivity...'));
59
+ const conn = await checkConnectivity({ timeout: 5 });
60
+ if (conn.ok) {
61
+ console.log(` ${chalk.green('✓')} Browser connected in ${(conn.durationMs / 1000).toFixed(1)}s`);
62
+ console.log();
63
+ console.log(chalk.green.bold(' ✓ Setup complete! You can now use opencli browser commands.'));
64
+ } else {
65
+ console.log(` ${chalk.yellow('!')} Connectivity test failed: ${conn.error ?? 'unknown'}`);
66
+ console.log(chalk.dim(` Run ${chalk.bold('opencli doctor --live')} to diagnose.`));
198
67
  }
199
68
  console.log();
200
69
  }
201
-
202
- function padRight(s: string, n: number): string {
203
- const visible = s.replace(/\x1b\[[0-9;]*m/g, '');
204
- return visible.length >= n ? s : s + ' '.repeat(n - visible.length);
205
- }
package/src/types.ts CHANGED
@@ -23,4 +23,5 @@ export interface IPage {
23
23
  autoScroll(options?: { times?: number; delayMs?: number }): Promise<void>;
24
24
  installInterceptor(pattern: string): Promise<void>;
25
25
  getInterceptedRequests(): Promise<any[]>;
26
+ screenshot(options?: { format?: 'png' | 'jpeg'; quality?: number; fullPage?: boolean; path?: string }): Promise<string>;
26
27
  }