@jackwener/opencli 0.7.10 → 0.7.11
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/pkg-pr-new.yml +30 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/dist/browser/discover.d.ts +15 -0
- package/dist/browser/discover.js +60 -12
- package/dist/browser/index.d.ts +5 -1
- package/dist/browser/index.js +5 -1
- package/dist/browser/mcp.js +7 -6
- package/dist/browser.test.js +135 -1
- package/dist/cli-manifest.json +163 -0
- package/dist/clis/barchart/flow.d.ts +1 -0
- package/dist/clis/barchart/flow.js +117 -0
- package/dist/clis/barchart/greeks.d.ts +1 -0
- package/dist/clis/barchart/greeks.js +119 -0
- package/dist/clis/barchart/options.d.ts +1 -0
- package/dist/clis/barchart/options.js +106 -0
- package/dist/clis/barchart/quote.d.ts +1 -0
- package/dist/clis/barchart/quote.js +133 -0
- package/package.json +1 -1
- package/src/browser/discover.ts +73 -12
- package/src/browser/index.ts +5 -1
- package/src/browser/mcp.ts +7 -5
- package/src/browser.test.ts +140 -1
- package/src/clis/barchart/flow.ts +121 -0
- package/src/clis/barchart/greeks.ts +123 -0
- package/src/clis/barchart/options.ts +110 -0
- package/src/clis/barchart/quote.ts +137 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Publish Any Commit
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, dev]
|
|
8
|
+
|
|
9
|
+
permissions: {}
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
if: ${{ vars.PKG_PR_NEW_ENABLED == 'true' }}
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: '22'
|
|
21
|
+
cache: 'npm'
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: npm ci
|
|
25
|
+
|
|
26
|
+
- name: Build
|
|
27
|
+
run: npm run build
|
|
28
|
+
|
|
29
|
+
- name: Publish to pkg.pr.new
|
|
30
|
+
run: npx pkg-pr-new publish
|
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ A CLI tool that turns **any website** into a command-line interface — Bilibili
|
|
|
44
44
|
> **⚠️ Important**: Browser commands reuse your Chrome login session. You must be logged into the target website in Chrome before running commands. If you get empty data or errors, check your login status first.
|
|
45
45
|
|
|
46
46
|
OpenCLI connects to your browser through the Playwright MCP Bridge extension.
|
|
47
|
+
It prefers an existing local/global `@playwright/mcp` install and falls back to `npx -y @playwright/mcp@latest` automatically when no local MCP server is found.
|
|
47
48
|
|
|
48
49
|
### Playwright MCP Bridge Extension Setup
|
|
49
50
|
|
package/README.zh-CN.md
CHANGED
|
@@ -43,6 +43,7 @@ OpenCLI 将任何网站变成命令行工具 — B站、知乎、小红书、Twi
|
|
|
43
43
|
> **⚠️ 重要**:大多数命令复用你的 Chrome 登录状态。运行命令前,你必须已在 Chrome 中打开目标网站并完成登录。如果获取到空数据或报错,请先检查你的浏览器登录状态。
|
|
44
44
|
|
|
45
45
|
OpenCLI 通过 Playwright MCP Bridge 扩展与你的浏览器通信。
|
|
46
|
+
它会优先复用本地或全局已安装的 `@playwright/mcp`,如果没有嗅探到可用 MCP server,则会自动回退到 `npx -y @playwright/mcp@latest` 启动。
|
|
46
47
|
|
|
47
48
|
### Playwright MCP Bridge 扩展配置
|
|
48
49
|
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP server path discovery and argument building.
|
|
3
3
|
*/
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
export declare function resetMcpServerPathCache(): void;
|
|
7
|
+
export declare function setMcpDiscoveryTestHooks(input?: {
|
|
8
|
+
existsSync?: typeof fs.existsSync;
|
|
9
|
+
execSync?: typeof execSync;
|
|
10
|
+
}): void;
|
|
4
11
|
export declare function findMcpServerPath(): string | null;
|
|
5
12
|
export declare function buildMcpArgs(input: {
|
|
6
13
|
mcpPath: string;
|
|
7
14
|
executablePath?: string | null;
|
|
8
15
|
}): string[];
|
|
16
|
+
export declare function buildMcpLaunchSpec(input: {
|
|
17
|
+
mcpPath?: string | null;
|
|
18
|
+
executablePath?: string | null;
|
|
19
|
+
}): {
|
|
20
|
+
command: string;
|
|
21
|
+
args: string[];
|
|
22
|
+
usedNpxFallback: boolean;
|
|
23
|
+
};
|
package/dist/browser/discover.js
CHANGED
|
@@ -7,27 +7,57 @@ import * as fs from 'node:fs';
|
|
|
7
7
|
import * as os from 'node:os';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
let _cachedMcpServerPath;
|
|
10
|
+
let _existsSync = fs.existsSync;
|
|
11
|
+
let _execSync = execSync;
|
|
12
|
+
export function resetMcpServerPathCache() {
|
|
13
|
+
_cachedMcpServerPath = undefined;
|
|
14
|
+
}
|
|
15
|
+
export function setMcpDiscoveryTestHooks(input) {
|
|
16
|
+
_existsSync = input?.existsSync ?? fs.existsSync;
|
|
17
|
+
_execSync = input?.execSync ?? execSync;
|
|
18
|
+
}
|
|
10
19
|
export function findMcpServerPath() {
|
|
11
20
|
if (_cachedMcpServerPath !== undefined)
|
|
12
21
|
return _cachedMcpServerPath;
|
|
13
22
|
const envMcp = process.env.OPENCLI_MCP_SERVER_PATH;
|
|
14
|
-
if (envMcp &&
|
|
23
|
+
if (envMcp && _existsSync(envMcp)) {
|
|
15
24
|
_cachedMcpServerPath = envMcp;
|
|
16
25
|
return _cachedMcpServerPath;
|
|
17
26
|
}
|
|
18
27
|
// Check local node_modules first (@playwright/mcp is the modern package)
|
|
19
28
|
const localMcp = path.resolve('node_modules', '@playwright', 'mcp', 'cli.js');
|
|
20
|
-
if (
|
|
29
|
+
if (_existsSync(localMcp)) {
|
|
21
30
|
_cachedMcpServerPath = localMcp;
|
|
22
31
|
return _cachedMcpServerPath;
|
|
23
32
|
}
|
|
24
33
|
// Check project-relative path
|
|
25
34
|
const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
|
|
26
35
|
const projectMcp = path.resolve(__dirname2, '..', '..', 'node_modules', '@playwright', 'mcp', 'cli.js');
|
|
27
|
-
if (
|
|
36
|
+
if (_existsSync(projectMcp)) {
|
|
28
37
|
_cachedMcpServerPath = projectMcp;
|
|
29
38
|
return _cachedMcpServerPath;
|
|
30
39
|
}
|
|
40
|
+
// Check global npm/yarn locations derived from current Node runtime.
|
|
41
|
+
const nodePrefix = path.resolve(path.dirname(process.execPath), '..');
|
|
42
|
+
const globalNodeModules = path.join(nodePrefix, 'lib', 'node_modules');
|
|
43
|
+
const globalMcp = path.join(globalNodeModules, '@playwright', 'mcp', 'cli.js');
|
|
44
|
+
if (_existsSync(globalMcp)) {
|
|
45
|
+
_cachedMcpServerPath = globalMcp;
|
|
46
|
+
return _cachedMcpServerPath;
|
|
47
|
+
}
|
|
48
|
+
// Check npm global root directly.
|
|
49
|
+
try {
|
|
50
|
+
const npmRootGlobal = _execSync('npm root -g 2>/dev/null', {
|
|
51
|
+
encoding: 'utf-8',
|
|
52
|
+
timeout: 5000,
|
|
53
|
+
}).trim();
|
|
54
|
+
const npmGlobalMcp = path.join(npmRootGlobal, '@playwright', 'mcp', 'cli.js');
|
|
55
|
+
if (npmRootGlobal && _existsSync(npmGlobalMcp)) {
|
|
56
|
+
_cachedMcpServerPath = npmGlobalMcp;
|
|
57
|
+
return _cachedMcpServerPath;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch { }
|
|
31
61
|
// Check common locations
|
|
32
62
|
const candidates = [
|
|
33
63
|
path.join(os.homedir(), '.npm', '_npx'),
|
|
@@ -36,8 +66,8 @@ export function findMcpServerPath() {
|
|
|
36
66
|
];
|
|
37
67
|
// Try npx resolution (legacy package name)
|
|
38
68
|
try {
|
|
39
|
-
const result =
|
|
40
|
-
if (result &&
|
|
69
|
+
const result = _execSync('npx -y --package=@playwright/mcp which mcp-server-playwright 2>/dev/null', { encoding: 'utf-8', timeout: 10000 }).trim();
|
|
70
|
+
if (result && _existsSync(result)) {
|
|
41
71
|
_cachedMcpServerPath = result;
|
|
42
72
|
return _cachedMcpServerPath;
|
|
43
73
|
}
|
|
@@ -45,8 +75,8 @@ export function findMcpServerPath() {
|
|
|
45
75
|
catch { }
|
|
46
76
|
// Try which
|
|
47
77
|
try {
|
|
48
|
-
const result =
|
|
49
|
-
if (result &&
|
|
78
|
+
const result = _execSync('which mcp-server-playwright 2>/dev/null', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
79
|
+
if (result && _existsSync(result)) {
|
|
50
80
|
_cachedMcpServerPath = result;
|
|
51
81
|
return _cachedMcpServerPath;
|
|
52
82
|
}
|
|
@@ -54,10 +84,10 @@ export function findMcpServerPath() {
|
|
|
54
84
|
catch { }
|
|
55
85
|
// Search in common npx cache
|
|
56
86
|
for (const base of candidates) {
|
|
57
|
-
if (!
|
|
87
|
+
if (!_existsSync(base))
|
|
58
88
|
continue;
|
|
59
89
|
try {
|
|
60
|
-
const found =
|
|
90
|
+
const found = _execSync(`find "${base}" -name "cli.js" -path "*playwright*mcp*" 2>/dev/null | head -1`, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
61
91
|
if (found) {
|
|
62
92
|
_cachedMcpServerPath = found;
|
|
63
93
|
return _cachedMcpServerPath;
|
|
@@ -68,16 +98,34 @@ export function findMcpServerPath() {
|
|
|
68
98
|
_cachedMcpServerPath = null;
|
|
69
99
|
return _cachedMcpServerPath;
|
|
70
100
|
}
|
|
71
|
-
|
|
72
|
-
const args = [
|
|
101
|
+
function buildRuntimeArgs(input) {
|
|
102
|
+
const args = [];
|
|
73
103
|
if (!process.env.CI) {
|
|
74
104
|
// Local: always connect to user's running Chrome via MCP Bridge extension
|
|
75
105
|
args.push('--extension');
|
|
76
106
|
}
|
|
77
107
|
// CI: standalone mode — @playwright/mcp launches its own browser (headed by default).
|
|
78
108
|
// xvfb provides a virtual display for headed mode in GitHub Actions.
|
|
79
|
-
if (input
|
|
109
|
+
if (input?.executablePath) {
|
|
80
110
|
args.push('--executable-path', input.executablePath);
|
|
81
111
|
}
|
|
82
112
|
return args;
|
|
83
113
|
}
|
|
114
|
+
export function buildMcpArgs(input) {
|
|
115
|
+
return [input.mcpPath, ...buildRuntimeArgs(input)];
|
|
116
|
+
}
|
|
117
|
+
export function buildMcpLaunchSpec(input) {
|
|
118
|
+
const runtimeArgs = buildRuntimeArgs(input);
|
|
119
|
+
if (input.mcpPath) {
|
|
120
|
+
return {
|
|
121
|
+
command: 'node',
|
|
122
|
+
args: [input.mcpPath, ...runtimeArgs],
|
|
123
|
+
usedNpxFallback: false,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
command: 'npx',
|
|
128
|
+
args: ['-y', '@playwright/mcp@latest', ...runtimeArgs],
|
|
129
|
+
usedNpxFallback: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { getTokenFingerprint, formatBrowserConnectError } from './errors.js';
|
|
|
10
10
|
export type { ConnectFailureKind, ConnectFailureInput } from './errors.js';
|
|
11
11
|
import { createJsonRpcRequest } from './mcp.js';
|
|
12
12
|
import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
|
|
13
|
-
import { buildMcpArgs } from './discover.js';
|
|
13
|
+
import { buildMcpArgs, buildMcpLaunchSpec, findMcpServerPath, resetMcpServerPathCache, setMcpDiscoveryTestHooks } from './discover.js';
|
|
14
14
|
import { withTimeoutMs } from '../runtime.js';
|
|
15
15
|
export declare const __test__: {
|
|
16
16
|
createJsonRpcRequest: typeof createJsonRpcRequest;
|
|
@@ -18,5 +18,9 @@ export declare const __test__: {
|
|
|
18
18
|
diffTabIndexes: typeof diffTabIndexes;
|
|
19
19
|
appendLimited: typeof appendLimited;
|
|
20
20
|
buildMcpArgs: typeof buildMcpArgs;
|
|
21
|
+
buildMcpLaunchSpec: typeof buildMcpLaunchSpec;
|
|
22
|
+
findMcpServerPath: typeof findMcpServerPath;
|
|
23
|
+
resetMcpServerPathCache: typeof resetMcpServerPathCache;
|
|
24
|
+
setMcpDiscoveryTestHooks: typeof setMcpDiscoveryTestHooks;
|
|
21
25
|
withTimeoutMs: typeof withTimeoutMs;
|
|
22
26
|
};
|
package/dist/browser/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { getTokenFingerprint, formatBrowserConnectError } from './errors.js';
|
|
|
10
10
|
// Test-only helpers — exposed for unit tests
|
|
11
11
|
import { createJsonRpcRequest } from './mcp.js';
|
|
12
12
|
import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
|
|
13
|
-
import { buildMcpArgs } from './discover.js';
|
|
13
|
+
import { buildMcpArgs, buildMcpLaunchSpec, findMcpServerPath, resetMcpServerPathCache, setMcpDiscoveryTestHooks } from './discover.js';
|
|
14
14
|
import { withTimeoutMs } from '../runtime.js';
|
|
15
15
|
export const __test__ = {
|
|
16
16
|
createJsonRpcRequest,
|
|
@@ -18,5 +18,9 @@ export const __test__ = {
|
|
|
18
18
|
diffTabIndexes,
|
|
19
19
|
appendLimited,
|
|
20
20
|
buildMcpArgs,
|
|
21
|
+
buildMcpLaunchSpec,
|
|
22
|
+
findMcpServerPath,
|
|
23
|
+
resetMcpServerPathCache,
|
|
24
|
+
setMcpDiscoveryTestHooks,
|
|
21
25
|
withTimeoutMs,
|
|
22
26
|
};
|
package/dist/browser/mcp.js
CHANGED
|
@@ -7,7 +7,7 @@ import { withTimeoutMs, DEFAULT_BROWSER_CONNECT_TIMEOUT } from '../runtime.js';
|
|
|
7
7
|
import { PKG_VERSION } from '../version.js';
|
|
8
8
|
import { Page } from './page.js';
|
|
9
9
|
import { getTokenFingerprint, formatBrowserConnectError, inferConnectFailureKind } from './errors.js';
|
|
10
|
-
import { findMcpServerPath,
|
|
10
|
+
import { findMcpServerPath, buildMcpLaunchSpec } from './discover.js';
|
|
11
11
|
import { extractTabIdentities, extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
|
|
12
12
|
const STDERR_BUFFER_LIMIT = 16 * 1024;
|
|
13
13
|
const INITIAL_TABS_TIMEOUT_MS = 1500;
|
|
@@ -102,8 +102,6 @@ export class PlaywrightMCP {
|
|
|
102
102
|
if (this._state === 'closed')
|
|
103
103
|
throw new Error('Playwright MCP session is closed');
|
|
104
104
|
const mcpPath = findMcpServerPath();
|
|
105
|
-
if (!mcpPath)
|
|
106
|
-
throw new Error('Playwright MCP server not found. Install: npm install -D @playwright/mcp');
|
|
107
105
|
PlaywrightMCP._registerGlobalCleanup();
|
|
108
106
|
PlaywrightMCP._activeInsts.add(this);
|
|
109
107
|
this._state = 'connecting';
|
|
@@ -148,7 +146,7 @@ export class PlaywrightMCP {
|
|
|
148
146
|
stderr: stderrBuffer,
|
|
149
147
|
}));
|
|
150
148
|
}, timeout * 1000);
|
|
151
|
-
const
|
|
149
|
+
const launchSpec = buildMcpLaunchSpec({
|
|
152
150
|
mcpPath,
|
|
153
151
|
executablePath: process.env.OPENCLI_BROWSER_EXECUTABLE_PATH,
|
|
154
152
|
});
|
|
@@ -156,9 +154,12 @@ export class PlaywrightMCP {
|
|
|
156
154
|
console.error(`[opencli] Mode: ${useExtension ? 'extension' : 'standalone'}`);
|
|
157
155
|
if (useExtension)
|
|
158
156
|
console.error(`[opencli] Extension token: fingerprint ${tokenFingerprint}`);
|
|
157
|
+
if (launchSpec.usedNpxFallback) {
|
|
158
|
+
console.error('[opencli] Playwright MCP not found locally; bootstrapping via npx @playwright/mcp@latest');
|
|
159
|
+
}
|
|
159
160
|
}
|
|
160
|
-
debugLog(`Spawning
|
|
161
|
-
this._proc = spawn(
|
|
161
|
+
debugLog(`Spawning ${launchSpec.command} ${launchSpec.args.join(' ')}`);
|
|
162
|
+
this._proc = spawn(launchSpec.command, launchSpec.args, {
|
|
162
163
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
163
164
|
env: { ...process.env },
|
|
164
165
|
});
|
package/dist/browser.test.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { afterEach, describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import { PlaywrightMCP, __test__ } from './browser/index.js';
|
|
3
|
+
afterEach(() => {
|
|
4
|
+
__test__.resetMcpServerPathCache();
|
|
5
|
+
__test__.setMcpDiscoveryTestHooks();
|
|
6
|
+
delete process.env.OPENCLI_MCP_SERVER_PATH;
|
|
7
|
+
});
|
|
3
8
|
describe('browser helpers', () => {
|
|
4
9
|
it('creates JSON-RPC requests with unique ids', () => {
|
|
5
10
|
const first = __test__.createJsonRpcRequest('tools/call', { name: 'browser_tabs' });
|
|
@@ -91,9 +96,138 @@ describe('browser helpers', () => {
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
});
|
|
99
|
+
it('builds a direct node launch spec when a local MCP path is available', () => {
|
|
100
|
+
const savedCI = process.env.CI;
|
|
101
|
+
delete process.env.CI;
|
|
102
|
+
try {
|
|
103
|
+
expect(__test__.buildMcpLaunchSpec({
|
|
104
|
+
mcpPath: '/tmp/cli.js',
|
|
105
|
+
executablePath: '/usr/bin/google-chrome',
|
|
106
|
+
})).toEqual({
|
|
107
|
+
command: 'node',
|
|
108
|
+
args: ['/tmp/cli.js', '--extension', '--executable-path', '/usr/bin/google-chrome'],
|
|
109
|
+
usedNpxFallback: false,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
if (savedCI !== undefined) {
|
|
114
|
+
process.env.CI = savedCI;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
delete process.env.CI;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
it('falls back to npx bootstrap when no MCP path is available', () => {
|
|
122
|
+
const savedCI = process.env.CI;
|
|
123
|
+
delete process.env.CI;
|
|
124
|
+
try {
|
|
125
|
+
expect(__test__.buildMcpLaunchSpec({
|
|
126
|
+
mcpPath: null,
|
|
127
|
+
})).toEqual({
|
|
128
|
+
command: 'npx',
|
|
129
|
+
args: ['-y', '@playwright/mcp@latest', '--extension'],
|
|
130
|
+
usedNpxFallback: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
if (savedCI !== undefined) {
|
|
135
|
+
process.env.CI = savedCI;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
delete process.env.CI;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
94
142
|
it('times out slow promises', async () => {
|
|
95
143
|
await expect(__test__.withTimeoutMs(new Promise(() => { }), 10, 'timeout')).rejects.toThrow('timeout');
|
|
96
144
|
});
|
|
145
|
+
it('prefers OPENCLI_MCP_SERVER_PATH over discovered locations', () => {
|
|
146
|
+
process.env.OPENCLI_MCP_SERVER_PATH = '/env/mcp/cli.js';
|
|
147
|
+
const existsSync = vi.fn((candidate) => candidate === '/env/mcp/cli.js');
|
|
148
|
+
const execSync = vi.fn();
|
|
149
|
+
__test__.setMcpDiscoveryTestHooks({ existsSync, execSync: execSync });
|
|
150
|
+
expect(__test__.findMcpServerPath()).toBe('/env/mcp/cli.js');
|
|
151
|
+
expect(execSync).not.toHaveBeenCalled();
|
|
152
|
+
expect(existsSync).toHaveBeenCalledWith('/env/mcp/cli.js');
|
|
153
|
+
});
|
|
154
|
+
it('discovers global @playwright/mcp from the current Node runtime prefix', () => {
|
|
155
|
+
const originalExecPath = process.execPath;
|
|
156
|
+
const runtimeExecPath = '/opt/homebrew/Cellar/node/25.2.1/bin/node';
|
|
157
|
+
const runtimeGlobalMcp = '/opt/homebrew/Cellar/node/25.2.1/lib/node_modules/@playwright/mcp/cli.js';
|
|
158
|
+
Object.defineProperty(process, 'execPath', {
|
|
159
|
+
value: runtimeExecPath,
|
|
160
|
+
configurable: true,
|
|
161
|
+
});
|
|
162
|
+
const existsSync = vi.fn((candidate) => candidate === runtimeGlobalMcp);
|
|
163
|
+
const execSync = vi.fn();
|
|
164
|
+
__test__.setMcpDiscoveryTestHooks({ existsSync, execSync: execSync });
|
|
165
|
+
try {
|
|
166
|
+
expect(__test__.findMcpServerPath()).toBe(runtimeGlobalMcp);
|
|
167
|
+
expect(execSync).not.toHaveBeenCalled();
|
|
168
|
+
expect(existsSync).toHaveBeenCalledWith(runtimeGlobalMcp);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
Object.defineProperty(process, 'execPath', {
|
|
172
|
+
value: originalExecPath,
|
|
173
|
+
configurable: true,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
it('falls back to npm root -g when runtime prefix lookup misses', () => {
|
|
178
|
+
const originalExecPath = process.execPath;
|
|
179
|
+
const runtimeExecPath = '/opt/homebrew/Cellar/node/25.2.1/bin/node';
|
|
180
|
+
const runtimeGlobalMcp = '/opt/homebrew/Cellar/node/25.2.1/lib/node_modules/@playwright/mcp/cli.js';
|
|
181
|
+
const npmRootGlobal = '/Users/jakevin/.nvm/versions/node/v22.14.0/lib/node_modules';
|
|
182
|
+
const npmGlobalMcp = '/Users/jakevin/.nvm/versions/node/v22.14.0/lib/node_modules/@playwright/mcp/cli.js';
|
|
183
|
+
Object.defineProperty(process, 'execPath', {
|
|
184
|
+
value: runtimeExecPath,
|
|
185
|
+
configurable: true,
|
|
186
|
+
});
|
|
187
|
+
const existsSync = vi.fn((candidate) => candidate === npmGlobalMcp);
|
|
188
|
+
const execSync = vi.fn((command) => {
|
|
189
|
+
if (String(command).includes('npm root -g'))
|
|
190
|
+
return `${npmRootGlobal}\n`;
|
|
191
|
+
throw new Error(`unexpected command: ${String(command)}`);
|
|
192
|
+
});
|
|
193
|
+
__test__.setMcpDiscoveryTestHooks({ existsSync, execSync: execSync });
|
|
194
|
+
try {
|
|
195
|
+
expect(__test__.findMcpServerPath()).toBe(npmGlobalMcp);
|
|
196
|
+
expect(execSync).toHaveBeenCalledOnce();
|
|
197
|
+
expect(existsSync).toHaveBeenCalledWith(runtimeGlobalMcp);
|
|
198
|
+
expect(existsSync).toHaveBeenCalledWith(npmGlobalMcp);
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
Object.defineProperty(process, 'execPath', {
|
|
202
|
+
value: originalExecPath,
|
|
203
|
+
configurable: true,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
it('returns null when new global discovery paths are unavailable', () => {
|
|
208
|
+
const originalExecPath = process.execPath;
|
|
209
|
+
const runtimeExecPath = '/opt/homebrew/Cellar/node/25.2.1/bin/node';
|
|
210
|
+
Object.defineProperty(process, 'execPath', {
|
|
211
|
+
value: runtimeExecPath,
|
|
212
|
+
configurable: true,
|
|
213
|
+
});
|
|
214
|
+
const existsSync = vi.fn(() => false);
|
|
215
|
+
const execSync = vi.fn((command) => {
|
|
216
|
+
if (String(command).includes('npm root -g'))
|
|
217
|
+
return '/missing/global/node_modules\n';
|
|
218
|
+
throw new Error(`missing command: ${String(command)}`);
|
|
219
|
+
});
|
|
220
|
+
__test__.setMcpDiscoveryTestHooks({ existsSync, execSync: execSync });
|
|
221
|
+
try {
|
|
222
|
+
expect(__test__.findMcpServerPath()).toBeNull();
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
Object.defineProperty(process, 'execPath', {
|
|
226
|
+
value: originalExecPath,
|
|
227
|
+
configurable: true,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
});
|
|
97
231
|
});
|
|
98
232
|
describe('PlaywrightMCP state', () => {
|
|
99
233
|
it('transitions to closed after close()', async () => {
|
package/dist/cli-manifest.json
CHANGED
|
@@ -1,4 +1,167 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"site": "barchart",
|
|
4
|
+
"name": "flow",
|
|
5
|
+
"description": "Barchart unusual options activity / options flow",
|
|
6
|
+
"strategy": "cookie",
|
|
7
|
+
"browser": true,
|
|
8
|
+
"args": [
|
|
9
|
+
{
|
|
10
|
+
"name": "type",
|
|
11
|
+
"type": "str",
|
|
12
|
+
"default": "all",
|
|
13
|
+
"required": false,
|
|
14
|
+
"help": "Filter: all, call, or put"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "limit",
|
|
18
|
+
"type": "int",
|
|
19
|
+
"default": 20,
|
|
20
|
+
"required": false,
|
|
21
|
+
"help": "Number of results"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"type": "ts",
|
|
25
|
+
"modulePath": "barchart/flow.js",
|
|
26
|
+
"domain": "www.barchart.com",
|
|
27
|
+
"columns": [
|
|
28
|
+
"symbol",
|
|
29
|
+
"type",
|
|
30
|
+
"strike",
|
|
31
|
+
"expiration",
|
|
32
|
+
"last",
|
|
33
|
+
"volume",
|
|
34
|
+
"openInterest",
|
|
35
|
+
"volOiRatio",
|
|
36
|
+
"iv"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"site": "barchart",
|
|
41
|
+
"name": "greeks",
|
|
42
|
+
"description": "Barchart options greeks overview (IV, delta, gamma, theta, vega)",
|
|
43
|
+
"strategy": "cookie",
|
|
44
|
+
"browser": true,
|
|
45
|
+
"args": [
|
|
46
|
+
{
|
|
47
|
+
"name": "symbol",
|
|
48
|
+
"type": "str",
|
|
49
|
+
"required": true,
|
|
50
|
+
"help": "Stock ticker (e.g. AAPL)"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "expiration",
|
|
54
|
+
"type": "str",
|
|
55
|
+
"required": false,
|
|
56
|
+
"help": "Expiration date (YYYY-MM-DD). Defaults to the nearest available expiration."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "limit",
|
|
60
|
+
"type": "int",
|
|
61
|
+
"default": 10,
|
|
62
|
+
"required": false,
|
|
63
|
+
"help": "Number of near-the-money strikes per type"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"type": "ts",
|
|
67
|
+
"modulePath": "barchart/greeks.js",
|
|
68
|
+
"domain": "www.barchart.com",
|
|
69
|
+
"columns": [
|
|
70
|
+
"type",
|
|
71
|
+
"strike",
|
|
72
|
+
"last",
|
|
73
|
+
"iv",
|
|
74
|
+
"delta",
|
|
75
|
+
"gamma",
|
|
76
|
+
"theta",
|
|
77
|
+
"vega",
|
|
78
|
+
"rho",
|
|
79
|
+
"volume",
|
|
80
|
+
"openInterest",
|
|
81
|
+
"expiration"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"site": "barchart",
|
|
86
|
+
"name": "options",
|
|
87
|
+
"description": "Barchart options chain with greeks, IV, volume, and open interest",
|
|
88
|
+
"strategy": "cookie",
|
|
89
|
+
"browser": true,
|
|
90
|
+
"args": [
|
|
91
|
+
{
|
|
92
|
+
"name": "symbol",
|
|
93
|
+
"type": "str",
|
|
94
|
+
"required": true,
|
|
95
|
+
"help": "Stock ticker (e.g. AAPL)"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "type",
|
|
99
|
+
"type": "str",
|
|
100
|
+
"default": "Call",
|
|
101
|
+
"required": false,
|
|
102
|
+
"help": "Option type: Call or Put"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "limit",
|
|
106
|
+
"type": "int",
|
|
107
|
+
"default": 20,
|
|
108
|
+
"required": false,
|
|
109
|
+
"help": "Max number of strikes to return"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"type": "ts",
|
|
113
|
+
"modulePath": "barchart/options.js",
|
|
114
|
+
"domain": "www.barchart.com",
|
|
115
|
+
"columns": [
|
|
116
|
+
"strike",
|
|
117
|
+
"bid",
|
|
118
|
+
"ask",
|
|
119
|
+
"last",
|
|
120
|
+
"change",
|
|
121
|
+
"volume",
|
|
122
|
+
"openInterest",
|
|
123
|
+
"iv",
|
|
124
|
+
"delta",
|
|
125
|
+
"gamma",
|
|
126
|
+
"theta",
|
|
127
|
+
"vega",
|
|
128
|
+
"expiration"
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"site": "barchart",
|
|
133
|
+
"name": "quote",
|
|
134
|
+
"description": "Barchart stock quote with price, volume, and key metrics",
|
|
135
|
+
"strategy": "cookie",
|
|
136
|
+
"browser": true,
|
|
137
|
+
"args": [
|
|
138
|
+
{
|
|
139
|
+
"name": "symbol",
|
|
140
|
+
"type": "str",
|
|
141
|
+
"required": true,
|
|
142
|
+
"help": "Stock ticker (e.g. AAPL, MSFT, TSLA)"
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
"type": "ts",
|
|
146
|
+
"modulePath": "barchart/quote.js",
|
|
147
|
+
"domain": "www.barchart.com",
|
|
148
|
+
"columns": [
|
|
149
|
+
"symbol",
|
|
150
|
+
"name",
|
|
151
|
+
"price",
|
|
152
|
+
"change",
|
|
153
|
+
"changePct",
|
|
154
|
+
"open",
|
|
155
|
+
"high",
|
|
156
|
+
"low",
|
|
157
|
+
"prevClose",
|
|
158
|
+
"volume",
|
|
159
|
+
"avgVolume",
|
|
160
|
+
"marketCap",
|
|
161
|
+
"peRatio",
|
|
162
|
+
"eps"
|
|
163
|
+
]
|
|
164
|
+
},
|
|
2
165
|
{
|
|
3
166
|
"site": "bbc",
|
|
4
167
|
"name": "news",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|