@llui/mcp 0.0.22 → 0.0.24
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/dist/cli.js +27 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/dist/tool-registry.d.ts +48 -0
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/tool-registry.js.map +1 -1
- package/dist/tools/cdp.d.ts +3 -0
- package/dist/tools/cdp.d.ts.map +1 -0
- package/dist/tools/cdp.js +130 -0
- package/dist/tools/cdp.js.map +1 -0
- package/dist/tools/compiler.d.ts +3 -0
- package/dist/tools/compiler.d.ts.map +1 -0
- package/dist/tools/compiler.js +60 -0
- package/dist/tools/compiler.js.map +1 -0
- package/dist/tools/debug-api.d.ts.map +1 -1
- package/dist/tools/debug-api.js +36 -41
- package/dist/tools/debug-api.js.map +1 -1
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/source.d.ts +3 -0
- package/dist/tools/source.d.ts.map +1 -0
- package/dist/tools/source.js +152 -0
- package/dist/tools/source.js.map +1 -0
- package/dist/tools/ssr.d.ts +3 -0
- package/dist/tools/ssr.d.ts.map +1 -0
- package/dist/tools/ssr.js +58 -0
- package/dist/tools/ssr.js.map +1 -0
- package/dist/transports/cdp.d.ts +51 -0
- package/dist/transports/cdp.d.ts.map +1 -0
- package/dist/transports/cdp.js +237 -0
- package/dist/transports/cdp.js.map +1 -0
- package/dist/transports/index.d.ts +1 -0
- package/dist/transports/index.d.ts.map +1 -1
- package/dist/transports/index.js +1 -0
- package/dist/transports/index.js.map +1 -1
- package/package.json +13 -4
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { findWorkspaceRoot } from '../index.js';
|
|
5
|
+
export function registerSourceTools(registry) {
|
|
6
|
+
registry.register({
|
|
7
|
+
name: 'llui_find_msg_producers',
|
|
8
|
+
description: 'Find all send({type: "msgType"}) call sites in the project source. Returns file path, line, column, and surrounding context for each hit.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
msgType: { type: 'string', description: 'The Msg variant type string to search for' },
|
|
13
|
+
rootDir: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Root directory to search (defaults to workspace root)',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: ['msgType'],
|
|
19
|
+
},
|
|
20
|
+
}, 'source', async (args, _ctx) => {
|
|
21
|
+
const msgType = args.msgType;
|
|
22
|
+
const rootDir = args.rootDir ?? findWorkspaceRoot();
|
|
23
|
+
const pattern = `send\\(\\{[^}]*type:\\s*['"]${msgType}['"]`;
|
|
24
|
+
const hits = grepHits(pattern, rootDir, ['*.ts', '*.tsx']);
|
|
25
|
+
return { msgType, hits };
|
|
26
|
+
});
|
|
27
|
+
registry.register({
|
|
28
|
+
name: 'llui_find_msg_handlers',
|
|
29
|
+
description: 'Find all update() function branches that handle a specific Msg variant. Returns file, line, column, and context for each case arm.',
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
msgType: { type: 'string', description: 'The Msg variant type string to search for' },
|
|
34
|
+
rootDir: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: 'Root directory to search (defaults to workspace root)',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ['msgType'],
|
|
40
|
+
},
|
|
41
|
+
}, 'source', async (args, _ctx) => {
|
|
42
|
+
const msgType = args.msgType;
|
|
43
|
+
const rootDir = args.rootDir ?? findWorkspaceRoot();
|
|
44
|
+
const pattern = `case\\s+['"]${msgType}['"]\\s*:`;
|
|
45
|
+
const hits = grepHits(pattern, rootDir, ['*.ts', '*.tsx']);
|
|
46
|
+
return { msgType, hits };
|
|
47
|
+
});
|
|
48
|
+
registry.register({
|
|
49
|
+
name: 'llui_run_test',
|
|
50
|
+
description: 'Run a vitest test file (and optionally a specific test name). Returns pass/fail status and captured output.',
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
file: { type: 'string', description: 'Absolute path to the test file' },
|
|
55
|
+
testName: { type: 'string', description: 'Test name pattern to filter (-t flag)' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}, 'source', async (args, _ctx) => {
|
|
59
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
60
|
+
let cmd = `pnpm exec vitest run`;
|
|
61
|
+
if (args.file)
|
|
62
|
+
cmd += ` "${args.file}"`;
|
|
63
|
+
if (args.testName)
|
|
64
|
+
cmd += ` -t "${args.testName}"`;
|
|
65
|
+
try {
|
|
66
|
+
const output = execSync(cmd, {
|
|
67
|
+
cwd: workspaceRoot,
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
timeout: 60_000,
|
|
70
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
|
+
});
|
|
72
|
+
return { passed: true, output: output.slice(-4000) };
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const e = err;
|
|
76
|
+
const output = ((e.stdout ?? '') + (e.stderr ?? '')).slice(-4000);
|
|
77
|
+
return { passed: false, output: output || e.message || 'Test failed' };
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
registry.register({
|
|
81
|
+
name: 'llui_lint_project',
|
|
82
|
+
description: 'Run @llui/eslint-plugin rules across all TypeScript files in a directory. Returns a 0–20 idiomatic score, violation count, and per-file violations.',
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
rootDir: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: 'Directory to lint (defaults to workspace root)',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}, 'source', async (args, _ctx) => {
|
|
93
|
+
const rootDir = args.rootDir ?? findWorkspaceRoot();
|
|
94
|
+
const workspaceRoot = findWorkspaceRoot();
|
|
95
|
+
const target = resolve(rootDir, '**/*.{ts,tsx}');
|
|
96
|
+
const cmd = `pnpm exec eslint --format json "${target}"`;
|
|
97
|
+
try {
|
|
98
|
+
const output = execSync(cmd, {
|
|
99
|
+
cwd: workspaceRoot,
|
|
100
|
+
encoding: 'utf8',
|
|
101
|
+
timeout: 60_000,
|
|
102
|
+
});
|
|
103
|
+
return parseEslintOutput(output);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
const e = err;
|
|
107
|
+
if (e.stdout) {
|
|
108
|
+
try {
|
|
109
|
+
return parseEslintOutput(e.stdout);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// fall through
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { score: 0, violations: [], fileCount: 0, error: 'ESLint failed' };
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function grepHits(pattern, rootDir, globs) {
|
|
120
|
+
if (!existsSync(rootDir))
|
|
121
|
+
return [];
|
|
122
|
+
const globArgs = globs.map((g) => `--include="${g}"`).join(' ');
|
|
123
|
+
try {
|
|
124
|
+
const out = execSync(`grep -rn --color=never -E ${globArgs} "${pattern}" "${rootDir}"`, {
|
|
125
|
+
encoding: 'utf8',
|
|
126
|
+
timeout: 15_000,
|
|
127
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
128
|
+
});
|
|
129
|
+
return out
|
|
130
|
+
.split('\n')
|
|
131
|
+
.filter(Boolean)
|
|
132
|
+
.map((line) => {
|
|
133
|
+
const m = /^(.+?):(\d+):(.+)$/.exec(line);
|
|
134
|
+
if (!m)
|
|
135
|
+
return null;
|
|
136
|
+
return { file: m[1], line: Number(m[2]), column: 1, context: m[3].trim() };
|
|
137
|
+
})
|
|
138
|
+
.filter((x) => x !== null)
|
|
139
|
+
.slice(0, 100);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function parseEslintOutput(output) {
|
|
146
|
+
const results = JSON.parse(output);
|
|
147
|
+
const violations = results.flatMap((r) => r.messages);
|
|
148
|
+
const fileCount = results.length;
|
|
149
|
+
const score = Math.max(0, 20 - violations.length);
|
|
150
|
+
return { score, violations, fileCount };
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/tools/source.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport type { ToolRegistry } from '../tool-registry.js'\nimport { findWorkspaceRoot } from '../index.js'\n\nexport function registerSourceTools(registry: ToolRegistry): void {\n registry.register(\n {\n name: 'llui_find_msg_producers',\n description:\n 'Find all send({type: \"msgType\"}) call sites in the project source. Returns file path, line, column, and surrounding context for each hit.',\n inputSchema: {\n type: 'object',\n properties: {\n msgType: { type: 'string', description: 'The Msg variant type string to search for' },\n rootDir: {\n type: 'string',\n description: 'Root directory to search (defaults to workspace root)',\n },\n },\n required: ['msgType'],\n },\n },\n 'source',\n async (args, _ctx) => {\n const msgType = args.msgType as string\n const rootDir = (args.rootDir as string | undefined) ?? findWorkspaceRoot()\n const pattern = `send\\\\(\\\\{[^}]*type:\\\\s*['\"]${msgType}['\"]`\n const hits = grepHits(pattern, rootDir, ['*.ts', '*.tsx'])\n return { msgType, hits }\n },\n )\n\n registry.register(\n {\n name: 'llui_find_msg_handlers',\n description:\n 'Find all update() function branches that handle a specific Msg variant. Returns file, line, column, and context for each case arm.',\n inputSchema: {\n type: 'object',\n properties: {\n msgType: { type: 'string', description: 'The Msg variant type string to search for' },\n rootDir: {\n type: 'string',\n description: 'Root directory to search (defaults to workspace root)',\n },\n },\n required: ['msgType'],\n },\n },\n 'source',\n async (args, _ctx) => {\n const msgType = args.msgType as string\n const rootDir = (args.rootDir as string | undefined) ?? findWorkspaceRoot()\n const pattern = `case\\\\s+['\"]${msgType}['\"]\\\\s*:`\n const hits = grepHits(pattern, rootDir, ['*.ts', '*.tsx'])\n return { msgType, hits }\n },\n )\n\n registry.register(\n {\n name: 'llui_run_test',\n description:\n 'Run a vitest test file (and optionally a specific test name). Returns pass/fail status and captured output.',\n inputSchema: {\n type: 'object',\n properties: {\n file: { type: 'string', description: 'Absolute path to the test file' },\n testName: { type: 'string', description: 'Test name pattern to filter (-t flag)' },\n },\n },\n },\n 'source',\n async (args, _ctx) => {\n const workspaceRoot = findWorkspaceRoot()\n let cmd = `pnpm exec vitest run`\n if (args.file) cmd += ` \"${args.file as string}\"`\n if (args.testName) cmd += ` -t \"${args.testName as string}\"`\n try {\n const output = execSync(cmd, {\n cwd: workspaceRoot,\n encoding: 'utf8',\n timeout: 60_000,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n return { passed: true, output: output.slice(-4000) }\n } catch (err: unknown) {\n const e = err as { stdout?: string; stderr?: string; message?: string }\n const output = ((e.stdout ?? '') + (e.stderr ?? '')).slice(-4000)\n return { passed: false, output: output || e.message || 'Test failed' }\n }\n },\n )\n\n registry.register(\n {\n name: 'llui_lint_project',\n description:\n 'Run @llui/eslint-plugin rules across all TypeScript files in a directory. Returns a 0–20 idiomatic score, violation count, and per-file violations.',\n inputSchema: {\n type: 'object',\n properties: {\n rootDir: {\n type: 'string',\n description: 'Directory to lint (defaults to workspace root)',\n },\n },\n },\n },\n 'source',\n async (args, _ctx) => {\n const rootDir = (args.rootDir as string | undefined) ?? findWorkspaceRoot()\n const workspaceRoot = findWorkspaceRoot()\n const target = resolve(rootDir, '**/*.{ts,tsx}')\n const cmd = `pnpm exec eslint --format json \"${target}\"`\n try {\n const output = execSync(cmd, {\n cwd: workspaceRoot,\n encoding: 'utf8',\n timeout: 60_000,\n })\n return parseEslintOutput(output)\n } catch (err: unknown) {\n const e = err as { stdout?: string }\n if (e.stdout) {\n try {\n return parseEslintOutput(e.stdout)\n } catch {\n // fall through\n }\n }\n return { score: 0, violations: [], fileCount: 0, error: 'ESLint failed' }\n }\n },\n )\n}\n\ninterface GrepHit {\n file: string\n line: number\n column: number\n context: string\n}\n\nfunction grepHits(pattern: string, rootDir: string, globs: string[]): GrepHit[] {\n if (!existsSync(rootDir)) return []\n const globArgs = globs.map((g) => `--include=\"${g}\"`).join(' ')\n try {\n const out = execSync(`grep -rn --color=never -E ${globArgs} \"${pattern}\" \"${rootDir}\"`, {\n encoding: 'utf8',\n timeout: 15_000,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n return out\n .split('\\n')\n .filter(Boolean)\n .map((line) => {\n const m = /^(.+?):(\\d+):(.+)$/.exec(line)\n if (!m) return null\n return { file: m[1]!, line: Number(m[2]), column: 1, context: m[3]!.trim() }\n })\n .filter((x): x is GrepHit => x !== null)\n .slice(0, 100)\n } catch {\n return []\n }\n}\n\nfunction parseEslintOutput(output: string): {\n score: number\n violations: unknown[]\n fileCount: number\n} {\n const results = JSON.parse(output) as Array<{ filePath: string; messages: unknown[] }>\n const violations = results.flatMap((r) => r.messages)\n const fileCount = results.length\n const score = Math.max(0, 20 - violations.length)\n return { score, violations, fileCount }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../../src/tools/ssr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CA6E7D"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function registerSsrTools(registry) {
|
|
2
|
+
registry.register({
|
|
3
|
+
name: 'llui_hydration_report',
|
|
4
|
+
description: 'Compare the server-rendered HTML (from @llui/vike) against the current client DOM and return divergences. Each divergence includes the DOM path, kind (attribute/text/structural), and the server vs client values. Returns an empty array when hydration is clean.',
|
|
5
|
+
inputSchema: { type: 'object', properties: {} },
|
|
6
|
+
}, 'debug-api', async (_args, ctx) => {
|
|
7
|
+
const divergences = await ctx.relay.call('getHydrationReport', []);
|
|
8
|
+
return { divergences };
|
|
9
|
+
});
|
|
10
|
+
registry.register({
|
|
11
|
+
name: 'llui_ssr_render',
|
|
12
|
+
description: 'Server-render the active component using its current state and return the resulting HTML string. Requires @llui/vike to be installed. Useful for verifying that the server output matches what you expect before hydration.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
state: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
description: 'State override (defaults to current component state)',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}, 'debug-api', async (args, ctx) => {
|
|
23
|
+
const currentState = args.state ?? (await ctx.relay.call('getState', []));
|
|
24
|
+
const componentInfo = (await ctx.relay.call('getComponentInfo', []));
|
|
25
|
+
if (!componentInfo?.file) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
error: 'component_file_unknown',
|
|
29
|
+
hint: 'Component file path not available — ensure @llui/vite-plugin emits __componentMeta in dev mode.',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
// Use Function-based dynamic import to avoid Vite static analysis
|
|
34
|
+
// at transform time. @llui/vike is an optional peer dep — we must
|
|
35
|
+
// not let the bundler try to resolve it unconditionally.
|
|
36
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
37
|
+
const vikeModule = (await dynamicImport('@llui/vike'));
|
|
38
|
+
const pageModule = (await dynamicImport(componentInfo.file));
|
|
39
|
+
const pageContext = {
|
|
40
|
+
Page: pageModule.default,
|
|
41
|
+
pageProps: { initialState: currentState },
|
|
42
|
+
urlOriginal: '/',
|
|
43
|
+
headersOriginal: {},
|
|
44
|
+
};
|
|
45
|
+
const result = await vikeModule.onRenderHtml(pageContext);
|
|
46
|
+
return { ok: true, html: String(result.documentHtml) };
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const e = err;
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
error: 'ssr_render_failed',
|
|
53
|
+
hint: e.message ?? 'Unknown error',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=ssr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr.js","sourceRoot":"","sources":["../../src/tools/ssr.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,QAAsB;IACrD,QAAQ,CAAC,QAAQ,CACf;QACE,IAAI,EAAE,uBAAuB;QAC7B,WAAW,EACT,qQAAqQ;QACvQ,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD,EACD,WAAW,EACX,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,KAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAA;QACnE,OAAO,EAAE,WAAW,EAAE,CAAA;IACxB,CAAC,CACF,CAAA;IAED,QAAQ,CAAC,QAAQ,CACf;QACE,IAAI,EAAE,iBAAiB;QACvB,WAAW,EACT,6NAA6N;QAC/N,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,sDAAsD;iBACpE;aACF;SACF;KACF,EACD,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QAClB,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC,KAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAA;QAC1E,MAAM,aAAa,GAAG,CAAC,MAAM,GAAG,CAAC,KAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAG5D,CAAA;QAER,IAAI,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;YACzB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,wBAAwB;gBAC/B,IAAI,EAAE,iGAAiG;aACxG,CAAA;QACH,CAAC;QAED,IAAI,CAAC;YACH,kEAAkE;YAClE,kEAAkE;YAClE,yDAAyD;YACzD,MAAM,aAAa,GAAG,IAAI,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAErD,CAAA;YACrB,MAAM,UAAU,GAAG,CAAC,MAAM,aAAa,CAAC,YAAY,CAAC,CAEpD,CAAA;YACD,MAAM,UAAU,GAAG,CAAC,MAAM,aAAa,CAAC,aAAa,CAAC,IAAI,CAAC,CAE1D,CAAA;YACD,MAAM,WAAW,GAAG;gBAClB,IAAI,EAAE,UAAU,CAAC,OAAO;gBACxB,SAAS,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE;gBACzC,WAAW,EAAE,GAAG;gBAChB,eAAe,EAAE,EAAE;aACpB,CAAA;YACD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;YACzD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAA;QACxD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,GAA2B,CAAA;YACrC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,mBAAmB;gBAC1B,IAAI,EAAE,CAAC,CAAC,OAAO,IAAI,eAAe;aACnC,CAAA;QACH,CAAC;IACH,CAAC,CACF,CAAA;AACH,CAAC","sourcesContent":["import type { ToolRegistry } from '../tool-registry.js'\n\nexport function registerSsrTools(registry: ToolRegistry): void {\n registry.register(\n {\n name: 'llui_hydration_report',\n description:\n 'Compare the server-rendered HTML (from @llui/vike) against the current client DOM and return divergences. Each divergence includes the DOM path, kind (attribute/text/structural), and the server vs client values. Returns an empty array when hydration is clean.',\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => {\n const divergences = await ctx.relay!.call('getHydrationReport', [])\n return { divergences }\n },\n )\n\n registry.register(\n {\n name: 'llui_ssr_render',\n description:\n 'Server-render the active component using its current state and return the resulting HTML string. Requires @llui/vike to be installed. Useful for verifying that the server output matches what you expect before hydration.',\n inputSchema: {\n type: 'object',\n properties: {\n state: {\n type: 'object',\n description: 'State override (defaults to current component state)',\n },\n },\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const currentState = args.state ?? (await ctx.relay!.call('getState', []))\n const componentInfo = (await ctx.relay!.call('getComponentInfo', [])) as {\n name: string\n file: string | null\n } | null\n\n if (!componentInfo?.file) {\n return {\n ok: false,\n error: 'component_file_unknown',\n hint: 'Component file path not available — ensure @llui/vite-plugin emits __componentMeta in dev mode.',\n }\n }\n\n try {\n // Use Function-based dynamic import to avoid Vite static analysis\n // at transform time. @llui/vike is an optional peer dep — we must\n // not let the bundler try to resolve it unconditionally.\n const dynamicImport = new Function('specifier', 'return import(specifier)') as (\n s: string,\n ) => Promise<unknown>\n const vikeModule = (await dynamicImport('@llui/vike')) as {\n onRenderHtml: (ctx: unknown) => Promise<{ documentHtml: unknown }>\n }\n const pageModule = (await dynamicImport(componentInfo.file)) as {\n default: unknown\n }\n const pageContext = {\n Page: pageModule.default,\n pageProps: { initialState: currentState },\n urlOriginal: '/',\n headersOriginal: {},\n }\n const result = await vikeModule.onRenderHtml(pageContext)\n return { ok: true, html: String(result.documentHtml) }\n } catch (err: unknown) {\n const e = err as { message?: string }\n return {\n ok: false,\n error: 'ssr_render_failed',\n hint: e.message ?? 'Unknown error',\n }\n }\n },\n )\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CdpTransport, ConsoleEntry, NetworkEntry, ErrorEntry } from '../tool-registry.js';
|
|
2
|
+
interface CdpSession {
|
|
3
|
+
mode: 'user-chrome' | 'playwright-owned';
|
|
4
|
+
browser: import('playwright').Browser | null;
|
|
5
|
+
page: import('playwright').Page;
|
|
6
|
+
consoleBuffer: ConsoleEntry[];
|
|
7
|
+
networkBuffer: NetworkEntry[];
|
|
8
|
+
errorBuffer: ErrorEntry[];
|
|
9
|
+
}
|
|
10
|
+
export declare class CdpSessionManager implements CdpTransport {
|
|
11
|
+
private session;
|
|
12
|
+
private devUrl;
|
|
13
|
+
private headed;
|
|
14
|
+
constructor(opts?: {
|
|
15
|
+
devUrl?: string | null;
|
|
16
|
+
headed?: boolean;
|
|
17
|
+
});
|
|
18
|
+
setDevUrl(url: string): void;
|
|
19
|
+
setHeaded(v: boolean): void;
|
|
20
|
+
isAvailable(): boolean;
|
|
21
|
+
call(domain: string, method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
22
|
+
screenshot(opts: {
|
|
23
|
+
selector?: string;
|
|
24
|
+
fullPage?: boolean;
|
|
25
|
+
format?: 'png' | 'jpeg';
|
|
26
|
+
}): Promise<{
|
|
27
|
+
data: string;
|
|
28
|
+
format: string;
|
|
29
|
+
mimeType: string;
|
|
30
|
+
}>;
|
|
31
|
+
accessibilitySnapshot(opts: {
|
|
32
|
+
selector?: string;
|
|
33
|
+
interestingOnly?: boolean;
|
|
34
|
+
}): Promise<unknown>;
|
|
35
|
+
getConsoleBuffer(limit?: number, level?: string): ConsoleEntry[];
|
|
36
|
+
getNetworkBuffer(limit?: number, filter?: {
|
|
37
|
+
urlPattern?: string;
|
|
38
|
+
status?: number;
|
|
39
|
+
}): NetworkEntry[];
|
|
40
|
+
getErrorBuffer(limit?: number): ErrorEntry[];
|
|
41
|
+
closeBrowser(): Promise<{
|
|
42
|
+
closed: boolean;
|
|
43
|
+
reason?: string;
|
|
44
|
+
}>;
|
|
45
|
+
private resolveDevUrl;
|
|
46
|
+
private attachListeners;
|
|
47
|
+
private buildSession;
|
|
48
|
+
ensureSession(): Promise<CdpSession>;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=cdp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp.d.ts","sourceRoot":"","sources":["../../src/transports/cdp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAgB/F,UAAU,UAAU;IAClB,IAAI,EAAE,aAAa,GAAG,kBAAkB,CAAA;IACxC,OAAO,EAAE,OAAO,YAAY,EAAE,OAAO,GAAG,IAAI,CAAA;IAC5C,IAAI,EAAE,OAAO,YAAY,EAAE,IAAI,CAAA;IAC/B,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAOD,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,MAAM,CAAS;gBAEX,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO;IAKnE,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAK5B,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAK3B,WAAW,IAAI,OAAO;IAIhB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAaxF,UAAU,CAAC,IAAI,EAAE;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;KACxB,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAazD,qBAAqB,CAAC,IAAI,EAAE;QAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,eAAe,CAAC,EAAE,OAAO,CAAA;KAC1B,GAAG,OAAO,CAAC,OAAO,CAAC;IAQpB,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE;IAOhE,gBAAgB,CACd,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAChD,YAAY,EAAE;IAajB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAMtC,YAAY,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAUnE,OAAO,CAAC,aAAa;YASP,eAAe;YA0Ef,YAAY;IAiBpB,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC;CAiE3C"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
class CdpError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.name = 'CdpError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function pushBounded(arr, item, max) {
|
|
10
|
+
arr.push(item);
|
|
11
|
+
if (arr.length > max)
|
|
12
|
+
arr.shift();
|
|
13
|
+
}
|
|
14
|
+
export class CdpSessionManager {
|
|
15
|
+
session = null;
|
|
16
|
+
devUrl;
|
|
17
|
+
headed;
|
|
18
|
+
constructor(opts = {}) {
|
|
19
|
+
this.devUrl = opts.devUrl ?? null;
|
|
20
|
+
this.headed = opts.headed ?? false;
|
|
21
|
+
}
|
|
22
|
+
setDevUrl(url) {
|
|
23
|
+
this.devUrl = url;
|
|
24
|
+
this.session = null;
|
|
25
|
+
}
|
|
26
|
+
setHeaded(v) {
|
|
27
|
+
this.headed = v;
|
|
28
|
+
this.session = null;
|
|
29
|
+
}
|
|
30
|
+
isAvailable() {
|
|
31
|
+
return this.session !== null;
|
|
32
|
+
}
|
|
33
|
+
async call(domain, method, params) {
|
|
34
|
+
const s = await this.ensureSession();
|
|
35
|
+
const cdpSession = await s.page.context().newCDPSession(s.page);
|
|
36
|
+
try {
|
|
37
|
+
return await cdpSession.send(`${domain}.${method}`, params);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await cdpSession.detach().catch(() => { });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async screenshot(opts) {
|
|
44
|
+
const s = await this.ensureSession();
|
|
45
|
+
const fmt = opts.format ?? 'png';
|
|
46
|
+
let buf;
|
|
47
|
+
if (opts.selector) {
|
|
48
|
+
const el = s.page.locator(opts.selector);
|
|
49
|
+
buf = await el.screenshot({ type: fmt });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
buf = await s.page.screenshot({ type: fmt, fullPage: opts.fullPage ?? false });
|
|
53
|
+
}
|
|
54
|
+
return { data: buf.toString('base64'), format: fmt, mimeType: `image/${fmt}` };
|
|
55
|
+
}
|
|
56
|
+
async accessibilitySnapshot(opts) {
|
|
57
|
+
const s = await this.ensureSession();
|
|
58
|
+
if (opts.selector) {
|
|
59
|
+
return s.page.locator(opts.selector).ariaSnapshot();
|
|
60
|
+
}
|
|
61
|
+
return s.page.ariaSnapshot();
|
|
62
|
+
}
|
|
63
|
+
getConsoleBuffer(limit, level) {
|
|
64
|
+
if (!this.session)
|
|
65
|
+
return [];
|
|
66
|
+
let items = this.session.consoleBuffer;
|
|
67
|
+
if (level)
|
|
68
|
+
items = items.filter((e) => e.level === level);
|
|
69
|
+
return limit != null ? items.slice(-limit) : items;
|
|
70
|
+
}
|
|
71
|
+
getNetworkBuffer(limit, filter) {
|
|
72
|
+
if (!this.session)
|
|
73
|
+
return [];
|
|
74
|
+
let items = this.session.networkBuffer;
|
|
75
|
+
if (filter?.urlPattern) {
|
|
76
|
+
const re = new RegExp(filter.urlPattern);
|
|
77
|
+
items = items.filter((e) => re.test(e.url));
|
|
78
|
+
}
|
|
79
|
+
if (filter?.status != null) {
|
|
80
|
+
items = items.filter((e) => e.status === filter.status);
|
|
81
|
+
}
|
|
82
|
+
return limit != null ? items.slice(-limit) : items;
|
|
83
|
+
}
|
|
84
|
+
getErrorBuffer(limit) {
|
|
85
|
+
if (!this.session)
|
|
86
|
+
return [];
|
|
87
|
+
const items = this.session.errorBuffer;
|
|
88
|
+
return limit != null ? items.slice(-limit) : items;
|
|
89
|
+
}
|
|
90
|
+
async closeBrowser() {
|
|
91
|
+
if (!this.session)
|
|
92
|
+
return { closed: false, reason: 'no_session' };
|
|
93
|
+
if (this.session.mode === 'user-chrome') {
|
|
94
|
+
return { closed: false, reason: 'user_owns_browser' };
|
|
95
|
+
}
|
|
96
|
+
await this.session.browser.close().catch(() => { });
|
|
97
|
+
this.session = null;
|
|
98
|
+
return { closed: true };
|
|
99
|
+
}
|
|
100
|
+
resolveDevUrl() {
|
|
101
|
+
if (!this.devUrl)
|
|
102
|
+
return null;
|
|
103
|
+
try {
|
|
104
|
+
return new URL(this.devUrl);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async attachListeners(page, session) {
|
|
111
|
+
page.on('console', (msg) => {
|
|
112
|
+
pushBounded(session.consoleBuffer, {
|
|
113
|
+
level: msg.type(),
|
|
114
|
+
text: msg.text(),
|
|
115
|
+
timestamp: Date.now(),
|
|
116
|
+
}, 500);
|
|
117
|
+
});
|
|
118
|
+
page.on('pageerror', (err) => {
|
|
119
|
+
pushBounded(session.errorBuffer, {
|
|
120
|
+
text: err.message,
|
|
121
|
+
stack: err.stack ?? '',
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
}, 200);
|
|
124
|
+
});
|
|
125
|
+
page.on('request', (req) => {
|
|
126
|
+
const entry = {
|
|
127
|
+
requestId: req.url() + Date.now(),
|
|
128
|
+
url: req.url(),
|
|
129
|
+
method: req.method(),
|
|
130
|
+
status: null,
|
|
131
|
+
startTime: Date.now(),
|
|
132
|
+
endTime: null,
|
|
133
|
+
durationMs: null,
|
|
134
|
+
failed: false,
|
|
135
|
+
};
|
|
136
|
+
pushBounded(session.networkBuffer, entry, 500);
|
|
137
|
+
});
|
|
138
|
+
page.on('response', (res) => {
|
|
139
|
+
const buf = session.networkBuffer;
|
|
140
|
+
let last;
|
|
141
|
+
for (let i = buf.length - 1; i >= 0; i--) {
|
|
142
|
+
const e = buf[i];
|
|
143
|
+
if (e !== undefined && e.url === res.url() && e.status === null) {
|
|
144
|
+
last = e;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (last) {
|
|
149
|
+
last.status = res.status();
|
|
150
|
+
last.endTime = Date.now();
|
|
151
|
+
last.durationMs = last.endTime - last.startTime;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
page.on('requestfailed', (req) => {
|
|
155
|
+
const buf = session.networkBuffer;
|
|
156
|
+
let last;
|
|
157
|
+
for (let i = buf.length - 1; i >= 0; i--) {
|
|
158
|
+
const e = buf[i];
|
|
159
|
+
if (e !== undefined && e.url === req.url() && e.status === null) {
|
|
160
|
+
last = e;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (last) {
|
|
165
|
+
last.failed = true;
|
|
166
|
+
last.failureReason = req.failure()?.errorText;
|
|
167
|
+
last.endTime = Date.now();
|
|
168
|
+
last.durationMs = last.endTime - last.startTime;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async buildSession(page, browser, mode) {
|
|
173
|
+
const s = {
|
|
174
|
+
mode,
|
|
175
|
+
browser,
|
|
176
|
+
page,
|
|
177
|
+
consoleBuffer: [],
|
|
178
|
+
networkBuffer: [],
|
|
179
|
+
errorBuffer: [],
|
|
180
|
+
};
|
|
181
|
+
await this.attachListeners(page, s);
|
|
182
|
+
return s;
|
|
183
|
+
}
|
|
184
|
+
async ensureSession() {
|
|
185
|
+
if (this.session)
|
|
186
|
+
return this.session;
|
|
187
|
+
const url = this.resolveDevUrl();
|
|
188
|
+
if (!url)
|
|
189
|
+
throw new CdpError('dev_url_unknown', 'No dev URL. Pass --url <devUrl> or set via Vite plugin.');
|
|
190
|
+
try {
|
|
191
|
+
const ctrl = new AbortController();
|
|
192
|
+
const t = setTimeout(() => ctrl.abort(), 200);
|
|
193
|
+
const resp = await fetch('http://127.0.0.1:9222/json/version', {
|
|
194
|
+
signal: ctrl.signal,
|
|
195
|
+
}).finally(() => clearTimeout(t));
|
|
196
|
+
if (resp.ok) {
|
|
197
|
+
const listResp = await fetch('http://127.0.0.1:9222/json/list');
|
|
198
|
+
const targets = (await listResp.json());
|
|
199
|
+
const target = targets.find((t) => t.url?.includes(url.host));
|
|
200
|
+
if (target) {
|
|
201
|
+
const pw = await import('playwright');
|
|
202
|
+
const browser = await pw.chromium.connectOverCDP('http://127.0.0.1:9222');
|
|
203
|
+
const pages = browser.contexts().flatMap((c) => c.pages());
|
|
204
|
+
const page = pages.find((p) => p.url().includes(url.host)) ?? pages[0];
|
|
205
|
+
if (page) {
|
|
206
|
+
this.session = await this.buildSession(page, browser, 'user-chrome');
|
|
207
|
+
return this.session;
|
|
208
|
+
}
|
|
209
|
+
await browser.close();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// fall through to Playwright fallback
|
|
215
|
+
}
|
|
216
|
+
let pw;
|
|
217
|
+
try {
|
|
218
|
+
pw = await import('playwright');
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
throw new CdpError('cdp_unavailable', 'Playwright not installed. Run: npm install playwright && npx playwright install chromium');
|
|
222
|
+
}
|
|
223
|
+
const browser = await pw.chromium.launch({ headless: !this.headed });
|
|
224
|
+
const page = await browser.newPage();
|
|
225
|
+
await page.goto(url.href);
|
|
226
|
+
try {
|
|
227
|
+
await page.waitForFunction(() => typeof globalThis.__lluiDebug !== 'undefined', { timeout: 10_000 });
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
await browser.close();
|
|
231
|
+
throw new CdpError('attach_timeout', `App not ready at ${url.href} within 10s`);
|
|
232
|
+
}
|
|
233
|
+
this.session = await this.buildSession(page, browser, 'playwright-owned');
|
|
234
|
+
return this.session;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=cdp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp.js","sourceRoot":"","sources":["../../src/transports/cdp.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CdpTransport, ConsoleEntry, NetworkEntry, ErrorEntry } from '../tool-registry.js'\n\nclass CdpError extends Error {\n constructor(\n public readonly code:\n | 'cdp_unavailable'\n | 'dev_url_unknown'\n | 'attach_timeout'\n | 'browser_crashed',\n message: string,\n ) {\n super(message)\n this.name = 'CdpError'\n }\n}\n\ninterface CdpSession {\n mode: 'user-chrome' | 'playwright-owned'\n browser: import('playwright').Browser | null\n page: import('playwright').Page\n consoleBuffer: ConsoleEntry[]\n networkBuffer: NetworkEntry[]\n errorBuffer: ErrorEntry[]\n}\n\nfunction pushBounded<T>(arr: T[], item: T, max: number): void {\n arr.push(item)\n if (arr.length > max) arr.shift()\n}\n\nexport class CdpSessionManager implements CdpTransport {\n private session: CdpSession | null = null\n private devUrl: string | null\n private headed: boolean\n\n constructor(opts: { devUrl?: string | null; headed?: boolean } = {}) {\n this.devUrl = opts.devUrl ?? null\n this.headed = opts.headed ?? false\n }\n\n setDevUrl(url: string): void {\n this.devUrl = url\n this.session = null\n }\n\n setHeaded(v: boolean): void {\n this.headed = v\n this.session = null\n }\n\n isAvailable(): boolean {\n return this.session !== null\n }\n\n async call(domain: string, method: string, params?: Record<string, unknown>): Promise<unknown> {\n const s = await this.ensureSession()\n const cdpSession = await s.page.context().newCDPSession(s.page)\n try {\n return await cdpSession.send(\n `${domain}.${method}` as Parameters<typeof cdpSession.send>[0],\n params,\n )\n } finally {\n await cdpSession.detach().catch(() => {})\n }\n }\n\n async screenshot(opts: {\n selector?: string\n fullPage?: boolean\n format?: 'png' | 'jpeg'\n }): Promise<{ data: string; format: string; mimeType: string }> {\n const s = await this.ensureSession()\n const fmt = opts.format ?? 'png'\n let buf: Buffer\n if (opts.selector) {\n const el = s.page.locator(opts.selector)\n buf = await el.screenshot({ type: fmt })\n } else {\n buf = await s.page.screenshot({ type: fmt, fullPage: opts.fullPage ?? false })\n }\n return { data: buf.toString('base64'), format: fmt, mimeType: `image/${fmt}` }\n }\n\n async accessibilitySnapshot(opts: {\n selector?: string\n interestingOnly?: boolean\n }): Promise<unknown> {\n const s = await this.ensureSession()\n if (opts.selector) {\n return s.page.locator(opts.selector).ariaSnapshot()\n }\n return s.page.ariaSnapshot()\n }\n\n getConsoleBuffer(limit?: number, level?: string): ConsoleEntry[] {\n if (!this.session) return []\n let items = this.session.consoleBuffer\n if (level) items = items.filter((e) => e.level === level)\n return limit != null ? items.slice(-limit) : items\n }\n\n getNetworkBuffer(\n limit?: number,\n filter?: { urlPattern?: string; status?: number },\n ): NetworkEntry[] {\n if (!this.session) return []\n let items = this.session.networkBuffer\n if (filter?.urlPattern) {\n const re = new RegExp(filter.urlPattern)\n items = items.filter((e) => re.test(e.url))\n }\n if (filter?.status != null) {\n items = items.filter((e) => e.status === filter.status)\n }\n return limit != null ? items.slice(-limit) : items\n }\n\n getErrorBuffer(limit?: number): ErrorEntry[] {\n if (!this.session) return []\n const items = this.session.errorBuffer\n return limit != null ? items.slice(-limit) : items\n }\n\n async closeBrowser(): Promise<{ closed: boolean; reason?: string }> {\n if (!this.session) return { closed: false, reason: 'no_session' }\n if (this.session.mode === 'user-chrome') {\n return { closed: false, reason: 'user_owns_browser' }\n }\n await this.session.browser!.close().catch(() => {})\n this.session = null\n return { closed: true }\n }\n\n private resolveDevUrl(): URL | null {\n if (!this.devUrl) return null\n try {\n return new URL(this.devUrl)\n } catch {\n return null\n }\n }\n\n private async attachListeners(\n page: import('playwright').Page,\n session: CdpSession,\n ): Promise<void> {\n page.on('console', (msg) => {\n pushBounded(\n session.consoleBuffer,\n {\n level: msg.type() as ConsoleEntry['level'],\n text: msg.text(),\n timestamp: Date.now(),\n },\n 500,\n )\n })\n page.on('pageerror', (err) => {\n pushBounded(\n session.errorBuffer,\n {\n text: err.message,\n stack: err.stack ?? '',\n timestamp: Date.now(),\n },\n 200,\n )\n })\n page.on('request', (req) => {\n const entry: NetworkEntry = {\n requestId: req.url() + Date.now(),\n url: req.url(),\n method: req.method(),\n status: null,\n startTime: Date.now(),\n endTime: null,\n durationMs: null,\n failed: false,\n }\n pushBounded(session.networkBuffer, entry, 500)\n })\n page.on('response', (res) => {\n const buf = session.networkBuffer\n let last: NetworkEntry | undefined\n for (let i = buf.length - 1; i >= 0; i--) {\n const e = buf[i]\n if (e !== undefined && e.url === res.url() && e.status === null) {\n last = e\n break\n }\n }\n if (last) {\n last.status = res.status()\n last.endTime = Date.now()\n last.durationMs = last.endTime - last.startTime\n }\n })\n page.on('requestfailed', (req) => {\n const buf = session.networkBuffer\n let last: NetworkEntry | undefined\n for (let i = buf.length - 1; i >= 0; i--) {\n const e = buf[i]\n if (e !== undefined && e.url === req.url() && e.status === null) {\n last = e\n break\n }\n }\n if (last) {\n last.failed = true\n last.failureReason = req.failure()?.errorText\n last.endTime = Date.now()\n last.durationMs = last.endTime - last.startTime\n }\n })\n }\n\n private async buildSession(\n page: import('playwright').Page,\n browser: import('playwright').Browser | null,\n mode: CdpSession['mode'],\n ): Promise<CdpSession> {\n const s: CdpSession = {\n mode,\n browser,\n page,\n consoleBuffer: [],\n networkBuffer: [],\n errorBuffer: [],\n }\n await this.attachListeners(page, s)\n return s\n }\n\n async ensureSession(): Promise<CdpSession> {\n if (this.session) return this.session\n\n const url = this.resolveDevUrl()\n if (!url)\n throw new CdpError(\n 'dev_url_unknown',\n 'No dev URL. Pass --url <devUrl> or set via Vite plugin.',\n )\n\n try {\n const ctrl = new AbortController()\n const t = setTimeout(() => ctrl.abort(), 200)\n const resp = await fetch('http://127.0.0.1:9222/json/version', {\n signal: ctrl.signal,\n }).finally(() => clearTimeout(t))\n if (resp.ok) {\n const listResp = await fetch('http://127.0.0.1:9222/json/list')\n const targets = (await listResp.json()) as Array<{\n url?: string\n webSocketDebuggerUrl?: string\n }>\n const target = targets.find((t) => t.url?.includes(url.host))\n if (target) {\n const pw = await import('playwright')\n const browser = await pw.chromium.connectOverCDP('http://127.0.0.1:9222')\n const pages = browser.contexts().flatMap((c) => c.pages())\n const page = pages.find((p) => p.url().includes(url.host)) ?? pages[0]\n if (page) {\n this.session = await this.buildSession(page, browser, 'user-chrome')\n return this.session\n }\n await browser.close()\n }\n }\n } catch {\n // fall through to Playwright fallback\n }\n\n let pw: typeof import('playwright')\n try {\n pw = await import('playwright')\n } catch {\n throw new CdpError(\n 'cdp_unavailable',\n 'Playwright not installed. Run: npm install playwright && npx playwright install chromium',\n )\n }\n\n const browser = await pw.chromium.launch({ headless: !this.headed })\n const page = await browser.newPage()\n await page.goto(url.href)\n try {\n await page.waitForFunction(\n () => typeof (globalThis as Record<string, unknown>).__lluiDebug !== 'undefined',\n { timeout: 10_000 },\n )\n } catch {\n await browser.close()\n throw new CdpError('attach_timeout', `App not ready at ${url.href} within 10s`)\n }\n\n this.session = await this.buildSession(page, browser, 'playwright-owned')\n return this.session\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAC3E,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAC3E,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA"}
|
package/dist/transports/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA","sourcesContent":["export { WebSocketRelayTransport, RelayUnavailableError } from './relay.js'\nexport type { RelayTransportOptions, BridgeDiagnostic } from './relay.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAE3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA","sourcesContent":["export { WebSocketRelayTransport, RelayUnavailableError } from './relay.js'\nexport type { RelayTransportOptions, BridgeDiagnostic } from './relay.js'\nexport { CdpSessionManager } from './cdp.js'\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llui/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"bin": {
|
|
@@ -18,12 +18,21 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
20
20
|
"ws": "^8.18.0",
|
|
21
|
-
"@llui/dom": "0.0.
|
|
22
|
-
"@llui/
|
|
21
|
+
"@llui/dom": "0.0.30",
|
|
22
|
+
"@llui/eslint-plugin": "0.0.14"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"playwright": ">=1.0.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependenciesMeta": {
|
|
28
|
+
"playwright": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
23
31
|
},
|
|
24
32
|
"devDependencies": {
|
|
25
33
|
"@types/node": "^22.0.0",
|
|
26
|
-
"@types/ws": "^8.5.13"
|
|
34
|
+
"@types/ws": "^8.5.13",
|
|
35
|
+
"playwright": "*"
|
|
27
36
|
},
|
|
28
37
|
"description": "LLui MCP server — LLM debug tools via Model Context Protocol",
|
|
29
38
|
"keywords": [
|