fastbrowser_cli 1.0.14 → 1.0.16
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/.playwright-mcp/.gitignore +3 -0
- package/README.md +29 -3
- package/dist/fastbrowser_cli/fastbrowser_cli.js +52 -35
- package/dist/fastbrowser_cli/fastbrowser_cli.js.map +1 -1
- package/dist/fastbrowser_cli/libs/http-client.js +3 -7
- package/dist/fastbrowser_cli/libs/http-client.js.map +1 -1
- package/dist/fastbrowser_cli/libs/server-manager.d.ts.map +1 -1
- package/dist/fastbrowser_cli/libs/server-manager.js +30 -28
- package/dist/fastbrowser_cli/libs/server-manager.js.map +1 -1
- package/dist/fastbrowser_httpd/fastbrowser_httpd.js +22 -18
- package/dist/fastbrowser_httpd/fastbrowser_httpd.js.map +1 -1
- package/dist/fastbrowser_httpd/libs/routes.d.ts +2 -2
- package/dist/fastbrowser_httpd/libs/routes.d.ts.map +1 -1
- package/dist/fastbrowser_httpd/libs/routes.js +4 -8
- package/dist/fastbrowser_httpd/libs/routes.js.map +1 -1
- package/dist/fastbrowser_httpd/libs/tool-schemas.js +39 -42
- package/dist/fastbrowser_httpd/libs/tool-schemas.js.map +1 -1
- package/dist/fastbrowser_mcp/fastbrowser_mcp.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/fastbrowser_mcp.js +270 -187
- package/dist/fastbrowser_mcp/fastbrowser_mcp.js.map +1 -1
- package/dist/fastbrowser_mcp/fastbrowser_types.d.ts +5 -0
- package/dist/fastbrowser_mcp/fastbrowser_types.d.ts.map +1 -0
- package/dist/fastbrowser_mcp/fastbrowser_types.js +2 -0
- package/dist/fastbrowser_mcp/fastbrowser_types.js.map +1 -0
- package/dist/{fastweb_mcp/libs/mcp_client.d.ts → fastbrowser_mcp/libs/mcp_client_TOREMOVE.d.ts} +6 -2
- package/dist/fastbrowser_mcp/libs/mcp_client_TOREMOVE.d.ts.map +1 -0
- package/dist/{src/libs/mcp_client.js → fastbrowser_mcp/libs/mcp_client_TOREMOVE.js} +15 -12
- package/dist/fastbrowser_mcp/libs/mcp_client_TOREMOVE.js.map +1 -0
- package/dist/{src/libs/mcp_client.d.ts → fastbrowser_mcp/libs/mcp_my_client.d.ts} +6 -2
- package/dist/fastbrowser_mcp/libs/mcp_my_client.d.ts.map +1 -0
- package/dist/{fastweb_mcp/libs/mcp_client.js → fastbrowser_mcp/libs/mcp_my_client.js} +15 -12
- package/dist/fastbrowser_mcp/libs/mcp_my_client.js.map +1 -0
- package/dist/fastbrowser_mcp/libs/mcp_proxy.d.ts +2 -2
- package/dist/fastbrowser_mcp/libs/mcp_proxy.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_proxy.js +9 -16
- package/dist/fastbrowser_mcp/libs/mcp_proxy.js.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts +35 -0
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts.map +1 -0
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.js +161 -0
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.js.map +1 -0
- package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.d.ts +28 -0
- package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.d.ts.map +1 -0
- package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.js +210 -0
- package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.js.map +1 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.d.ts +10 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.d.ts.map +1 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.js +155 -0
- package/dist/fastbrowser_mcp/libs/response_formatter.js.map +1 -0
- package/dist/fastbrowser_mcp/libs/schemas.js +14 -17
- package/dist/fastbrowser_mcp/libs/schemas.js.map +1 -1
- package/examples/mcp_client_playwright.ts +34 -0
- package/examples/welcometothejungle/wttj-job.ts +180 -0
- package/examples/welcometothejungle/wttj-search.ts +105 -0
- package/outputs/.gitignore +3 -0
- package/package.json +12 -8
- package/src/fastbrowser_cli/fastbrowser_cli.ts +34 -11
- package/src/fastbrowser_cli/libs/server-manager.ts +12 -3
- package/src/fastbrowser_httpd/fastbrowser_httpd.ts +16 -5
- package/src/fastbrowser_httpd/libs/routes.ts +2 -2
- package/src/fastbrowser_mcp/fastbrowser_mcp.ts +324 -150
- package/src/fastbrowser_mcp/fastbrowser_types.ts +4 -0
- package/src/fastbrowser_mcp/libs/{mcp_client.ts → mcp_client_TOREMOVE.ts} +13 -1
- package/src/fastbrowser_mcp/libs/mcp_my_client.ts +128 -0
- package/src/fastbrowser_mcp/libs/mcp_proxy.ts +2 -2
- package/src/fastbrowser_mcp/libs/mcp_target_helper.ts +164 -0
- package/src/fastbrowser_mcp/libs/playwright_a11y_helper.ts +249 -0
- package/src/fastbrowser_mcp/libs/response_formatter.ts +162 -0
- package/src/fastbrowser_mcp/libs/schemas.ts +2 -2
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +10 -22
- package/dist/contrib/fastweb-cli/fastweb-cli.d.ts +0 -3
- package/dist/contrib/fastweb-cli/fastweb-cli.d.ts.map +0 -1
- package/dist/contrib/fastweb-cli/fastweb-cli.js +0 -151
- package/dist/contrib/fastweb-cli/fastweb-cli.js.map +0 -1
- package/dist/contrib/fastweb-cli/http-client.d.ts +0 -7
- package/dist/contrib/fastweb-cli/http-client.d.ts.map +0 -1
- package/dist/contrib/fastweb-cli/http-client.js +0 -51
- package/dist/contrib/fastweb-cli/http-client.js.map +0 -1
- package/dist/contrib/fastweb-http-server/fastweb-http-server.d.ts +0 -3
- package/dist/contrib/fastweb-http-server/fastweb-http-server.d.ts.map +0 -1
- package/dist/contrib/fastweb-http-server/fastweb-http-server.js +0 -82
- package/dist/contrib/fastweb-http-server/fastweb-http-server.js.map +0 -1
- package/dist/contrib/fastweb-http-server/routes.d.ts +0 -6
- package/dist/contrib/fastweb-http-server/routes.d.ts.map +0 -1
- package/dist/contrib/fastweb-http-server/routes.js +0 -41
- package/dist/contrib/fastweb-http-server/routes.js.map +0 -1
- package/dist/contrib/fastweb-http-server/tool-schemas.d.ts +0 -63
- package/dist/contrib/fastweb-http-server/tool-schemas.d.ts.map +0 -1
- package/dist/contrib/fastweb-http-server/tool-schemas.js +0 -61
- package/dist/contrib/fastweb-http-server/tool-schemas.js.map +0 -1
- package/dist/fastbrowser_mcp/libs/mcp_client.d.ts +0 -120
- package/dist/fastbrowser_mcp/libs/mcp_client.d.ts.map +0 -1
- package/dist/fastbrowser_mcp/libs/mcp_client.js +0 -83
- package/dist/fastbrowser_mcp/libs/mcp_client.js.map +0 -1
- package/dist/fastweb_cli/fastweb_cli.d.ts +0 -3
- package/dist/fastweb_cli/fastweb_cli.d.ts.map +0 -1
- package/dist/fastweb_cli/fastweb_cli.js +0 -254
- package/dist/fastweb_cli/fastweb_cli.js.map +0 -1
- package/dist/fastweb_cli/http-client.d.ts +0 -7
- package/dist/fastweb_cli/http-client.d.ts.map +0 -1
- package/dist/fastweb_cli/http-client.js +0 -51
- package/dist/fastweb_cli/http-client.js.map +0 -1
- package/dist/fastweb_cli/libs/http-client.d.ts +0 -7
- package/dist/fastweb_cli/libs/http-client.d.ts.map +0 -1
- package/dist/fastweb_cli/libs/http-client.js +0 -51
- package/dist/fastweb_cli/libs/http-client.js.map +0 -1
- package/dist/fastweb_cli/libs/server-manager.d.ts +0 -12
- package/dist/fastweb_cli/libs/server-manager.d.ts.map +0 -1
- package/dist/fastweb_cli/libs/server-manager.js +0 -194
- package/dist/fastweb_cli/libs/server-manager.js.map +0 -1
- package/dist/fastweb_http_server/fastweb_http_server.d.ts +0 -3
- package/dist/fastweb_http_server/fastweb_http_server.d.ts.map +0 -1
- package/dist/fastweb_http_server/fastweb_http_server.js +0 -82
- package/dist/fastweb_http_server/fastweb_http_server.js.map +0 -1
- package/dist/fastweb_http_server/libs/routes.d.ts +0 -6
- package/dist/fastweb_http_server/libs/routes.d.ts.map +0 -1
- package/dist/fastweb_http_server/libs/routes.js +0 -41
- package/dist/fastweb_http_server/libs/routes.js.map +0 -1
- package/dist/fastweb_http_server/libs/tool-schemas.d.ts +0 -72
- package/dist/fastweb_http_server/libs/tool-schemas.d.ts.map +0 -1
- package/dist/fastweb_http_server/libs/tool-schemas.js +0 -65
- package/dist/fastweb_http_server/libs/tool-schemas.js.map +0 -1
- package/dist/fastweb_http_server/routes.d.ts +0 -6
- package/dist/fastweb_http_server/routes.d.ts.map +0 -1
- package/dist/fastweb_http_server/routes.js +0 -41
- package/dist/fastweb_http_server/routes.js.map +0 -1
- package/dist/fastweb_http_server/tool-schemas.d.ts +0 -63
- package/dist/fastweb_http_server/tool-schemas.d.ts.map +0 -1
- package/dist/fastweb_http_server/tool-schemas.js +0 -61
- package/dist/fastweb_http_server/tool-schemas.js.map +0 -1
- package/dist/fastweb_mcp/fastweb_mcp.d.ts +0 -4
- package/dist/fastweb_mcp/fastweb_mcp.d.ts.map +0 -1
- package/dist/fastweb_mcp/fastweb_mcp.js +0 -417
- package/dist/fastweb_mcp/fastweb_mcp.js.map +0 -1
- package/dist/fastweb_mcp/libs/mcp_client.d.ts.map +0 -1
- package/dist/fastweb_mcp/libs/mcp_client.js.map +0 -1
- package/dist/fastweb_mcp/libs/mcp_proxy.d.ts +0 -10
- package/dist/fastweb_mcp/libs/mcp_proxy.d.ts.map +0 -1
- package/dist/fastweb_mcp/libs/mcp_proxy.js +0 -45
- package/dist/fastweb_mcp/libs/mcp_proxy.js.map +0 -1
- package/dist/fastweb_mcp/libs/schemas.d.ts +0 -28
- package/dist/fastweb_mcp/libs/schemas.d.ts.map +0 -1
- package/dist/fastweb_mcp/libs/schemas.js +0 -38
- package/dist/fastweb_mcp/libs/schemas.js.map +0 -1
- package/dist/src/fastweb_mcp.d.ts +0 -17
- package/dist/src/fastweb_mcp.d.ts.map +0 -1
- package/dist/src/fastweb_mcp.js +0 -342
- package/dist/src/fastweb_mcp.js.map +0 -1
- package/dist/src/libs/mcp_client.d.ts.map +0 -1
- package/dist/src/libs/mcp_client.js.map +0 -1
- package/dist/src/libs/mcp_proxy.d.ts +0 -10
- package/dist/src/libs/mcp_proxy.d.ts.map +0 -1
- package/dist/src/libs/mcp_proxy.js +0 -45
- package/dist/src/libs/mcp_proxy.js.map +0 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Usage: npx tsx extract-wttj-job.ts <job-url>
|
|
3
|
+
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7
|
+
|
|
8
|
+
const ALL_STOPS = [
|
|
9
|
+
'Descriptif du poste',
|
|
10
|
+
'Missions clés',
|
|
11
|
+
'What you will do',
|
|
12
|
+
'Vos missions',
|
|
13
|
+
'Vos principales missions',
|
|
14
|
+
'Responsabilités',
|
|
15
|
+
'Profil recherché',
|
|
16
|
+
'About you',
|
|
17
|
+
'Votre profil',
|
|
18
|
+
'Le profil idéal',
|
|
19
|
+
'Qui êtes-vous ?',
|
|
20
|
+
'Location & Remote',
|
|
21
|
+
'What we offer',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
25
|
+
|
|
26
|
+
class SnapshotParser {
|
|
27
|
+
private lines: string[];
|
|
28
|
+
|
|
29
|
+
constructor(snapshot: string) {
|
|
30
|
+
this.lines = snapshot.split('\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private extractQuotedValue(line: string, token: string): string | null {
|
|
34
|
+
const idx = line.indexOf(`${token} "`);
|
|
35
|
+
if (idx === -1) return null;
|
|
36
|
+
const start = idx + token.length + 2;
|
|
37
|
+
const end = line.indexOf('"', start);
|
|
38
|
+
if (end === -1) return null;
|
|
39
|
+
return line.slice(start, end);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
extractStaticTexts(): string[] {
|
|
43
|
+
return this.lines
|
|
44
|
+
.map(l => this.extractQuotedValue(l, 'StaticText'))
|
|
45
|
+
.filter((v): v is string => v !== null && v.trim() !== '');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
extractAfterHeading(headingText: string): string[] {
|
|
49
|
+
const results: string[] = [];
|
|
50
|
+
let found = false;
|
|
51
|
+
for (const line of this.lines) {
|
|
52
|
+
if (line.includes('heading "')) {
|
|
53
|
+
const val = this.extractQuotedValue(line, 'heading');
|
|
54
|
+
if (found) break;
|
|
55
|
+
if (val === headingText) found = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!found) continue;
|
|
59
|
+
if (line.includes('StaticText "')) {
|
|
60
|
+
const val = this.extractQuotedValue(line, 'StaticText');
|
|
61
|
+
if (val === null || val.trim() === '') continue;
|
|
62
|
+
if (ALL_STOPS.includes(val)) break;
|
|
63
|
+
results.push(val);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
extractAfterStaticText(startLabel: string): string[] {
|
|
70
|
+
const results: string[] = [];
|
|
71
|
+
let found = false;
|
|
72
|
+
for (const line of this.lines) {
|
|
73
|
+
if (line.includes('heading "') && found) break;
|
|
74
|
+
if (!line.includes('StaticText "')) continue;
|
|
75
|
+
const val = this.extractQuotedValue(line, 'StaticText');
|
|
76
|
+
if (val === null || val.trim() === '') continue;
|
|
77
|
+
if (found) {
|
|
78
|
+
if (ALL_STOPS.includes(val)) break;
|
|
79
|
+
results.push(val);
|
|
80
|
+
}
|
|
81
|
+
if (val === startLabel) found = true;
|
|
82
|
+
}
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
findFirstSection(labels: string[]): string[] {
|
|
87
|
+
for (const label of labels) {
|
|
88
|
+
const result = this.extractAfterStaticText(label);
|
|
89
|
+
if (result.length > 0) return result;
|
|
90
|
+
}
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
96
|
+
|
|
97
|
+
class FastBrowser {
|
|
98
|
+
static run(command: string): string {
|
|
99
|
+
return execSync(`npx fastbrowser_cli ${command}`, { encoding: 'utf8' });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static navigatePage(url: string): void {
|
|
103
|
+
FastBrowser.run(`navigate_page --url '${url}'`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static takeSnapshot(): string {
|
|
107
|
+
return FastBrowser.run('take_snapshot');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static querySelectors(selector: string, withAncestors = true): string {
|
|
111
|
+
const flag = withAncestors === false ? ' --no-with-ancestors' : '';
|
|
112
|
+
return FastBrowser.run(`query_selectors --selector '${selector}'${flag}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
117
|
+
|
|
118
|
+
class WttjExtractor {
|
|
119
|
+
static extractTitle(output: string): string {
|
|
120
|
+
const match = output.match(/heading "(.+?)" level/);
|
|
121
|
+
return match !== null ? match[1] : '(non trouvé)';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static printSection(title: string, lines: string[]): void {
|
|
125
|
+
console.log(`\n=== ${title} ===`);
|
|
126
|
+
if (lines.length === 0) {
|
|
127
|
+
console.log('(non trouvé)');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
console.log(lines.join('\n'));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static async run(jobUrl: string): Promise<void> {
|
|
134
|
+
FastBrowser.navigatePage(jobUrl);
|
|
135
|
+
|
|
136
|
+
console.log('=== TITRE ===');
|
|
137
|
+
const selectorOutput = FastBrowser.querySelectors('heading[level="2"]', false);
|
|
138
|
+
console.log(WttjExtractor.extractTitle(selectorOutput));
|
|
139
|
+
|
|
140
|
+
const snapshot = FastBrowser.takeSnapshot();
|
|
141
|
+
const parser = new SnapshotParser(snapshot);
|
|
142
|
+
|
|
143
|
+
let descriptif = parser.extractAfterHeading('Descriptif du poste');
|
|
144
|
+
if (descriptif.length === 0) {
|
|
145
|
+
descriptif = parser.extractAfterStaticText('Descriptif du poste');
|
|
146
|
+
}
|
|
147
|
+
WttjExtractor.printSection('DESCRIPTIF DU POSTE', descriptif);
|
|
148
|
+
|
|
149
|
+
const missions = parser.findFirstSection([
|
|
150
|
+
'Missions clés',
|
|
151
|
+
'What you will do',
|
|
152
|
+
'Vos missions',
|
|
153
|
+
'Vos principales missions',
|
|
154
|
+
'Responsabilités',
|
|
155
|
+
]);
|
|
156
|
+
WttjExtractor.printSection('MISSIONS CLÉS', missions);
|
|
157
|
+
|
|
158
|
+
const profil = parser.findFirstSection([
|
|
159
|
+
'Profil recherché',
|
|
160
|
+
'About you',
|
|
161
|
+
'Votre profil',
|
|
162
|
+
'Le profil idéal',
|
|
163
|
+
'Qui êtes-vous ?',
|
|
164
|
+
]);
|
|
165
|
+
WttjExtractor.printSection('PROFIL RECHERCHÉ', profil);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
170
|
+
|
|
171
|
+
const jobUrl = process.argv[2];
|
|
172
|
+
if (jobUrl === undefined || jobUrl === '') {
|
|
173
|
+
console.error('Usage: npx tsx extract-wttj-job.ts <job-url>');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
WttjExtractor.run(jobUrl).catch((err: unknown) => {
|
|
178
|
+
console.error('Error:', err);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { Command, Option } from 'commander';
|
|
5
|
+
|
|
6
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
7
|
+
|
|
8
|
+
class FastBrowser {
|
|
9
|
+
static run(command: string): string {
|
|
10
|
+
return execSync(`npx fastbrowser_cli ${command}`, { encoding: 'utf8' });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static navigatePage(url: string): void {
|
|
14
|
+
FastBrowser.run(`navigate_page --url '${url}'`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static fillForm(selector: string, value: string): void {
|
|
18
|
+
FastBrowser.run(`fill_form --selector '${selector}' --value '${value}'`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static pressKeys(keys: string): void {
|
|
22
|
+
FastBrowser.run(`press_keys --keys '${keys}'`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static click(selector: string): void {
|
|
26
|
+
FastBrowser.run(`click -s '${selector}'`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static querySelectorsAll(selector: string, limit: number): string {
|
|
30
|
+
return FastBrowser.run(`query_selectors_all --selector '${selector}' --limit ${limit}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
35
|
+
|
|
36
|
+
class WttjSearch {
|
|
37
|
+
static parseJobTitles(output: string): Array<{ title: string; url: string; }> {
|
|
38
|
+
const results: Array<{ title: string; url: string; }> = [];
|
|
39
|
+
const headingRe = /heading "(.+?)" level/g;
|
|
40
|
+
const urlRe = /url="([^"]+)"/;
|
|
41
|
+
for (const line of output.split('\n')) {
|
|
42
|
+
const headingMatch = headingRe.exec(line);
|
|
43
|
+
if (headingMatch === null) continue;
|
|
44
|
+
const urlMatch = urlRe.exec(line);
|
|
45
|
+
results.push({
|
|
46
|
+
title: headingMatch[1],
|
|
47
|
+
url: urlMatch !== null ? urlMatch[1] : '',
|
|
48
|
+
});
|
|
49
|
+
headingRe.lastIndex = 0;
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static outputJson(jobs: Array<{ title: string; url: string; }>): void {
|
|
55
|
+
console.log(JSON.stringify(jobs, null, 2));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static outputMarkdown(jobs: Array<{ title: string; url: string; }>): void {
|
|
59
|
+
console.log('| # | Title | URL |');
|
|
60
|
+
console.log('|---|-------|-----|');
|
|
61
|
+
for (const [i, job] of jobs.entries()) {
|
|
62
|
+
const url = job.url !== '' ? `[link](${job.url})` : '';
|
|
63
|
+
console.log(`| ${i + 1} | ${job.title} | ${url} |`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static run(query: string, limit: number = 1, offset: number = 1, format: 'json' | 'markdown' = 'markdown'): void {
|
|
68
|
+
FastBrowser.navigatePage('https://www.welcometothejungle.com/fr');
|
|
69
|
+
FastBrowser.fillForm('combobox[name*="intitulé de poste"]', query);
|
|
70
|
+
FastBrowser.pressKeys('Enter');
|
|
71
|
+
|
|
72
|
+
let allJobs: Array<{ title: string; url: string; }> = [];
|
|
73
|
+
|
|
74
|
+
for (let page = offset; page < offset + limit; page++) {
|
|
75
|
+
if (page > 1) {
|
|
76
|
+
FastBrowser.click(`link[name="${page}"]`);
|
|
77
|
+
}
|
|
78
|
+
const output = FastBrowser.querySelectorsAll('heading[level="2"]', 30);
|
|
79
|
+
allJobs = allJobs.concat(WttjSearch.parseJobTitles(output));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (format === 'json') {
|
|
83
|
+
WttjSearch.outputJson(allJobs);
|
|
84
|
+
} else {
|
|
85
|
+
WttjSearch.outputMarkdown(allJobs);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
91
|
+
|
|
92
|
+
const program = new Command();
|
|
93
|
+
program
|
|
94
|
+
.argument('[query]', 'job search query', 'machine learning')
|
|
95
|
+
.option('-l, --limit <number>', 'number of pages to fetch', '1')
|
|
96
|
+
.option('-o, --offset <number>', 'first page to scrape', '1')
|
|
97
|
+
.addOption(new Option('-f, --format <format>', 'output format: json or markdown').choices(['json', 'markdown']).default('markdown'))
|
|
98
|
+
.parse();
|
|
99
|
+
|
|
100
|
+
const opts = program.opts();
|
|
101
|
+
const query = program.args[0] ?? 'machine learning';
|
|
102
|
+
const limit = Number(opts['limit']);
|
|
103
|
+
const offset = Number(opts['offset']);
|
|
104
|
+
const format: 'json' | 'markdown' = opts['format'] === 'json' ? 'json' : 'markdown';
|
|
105
|
+
WttjSearch.run(query, limit, offset, format);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastbrowser_cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/fastbrowser_cli/fastbrowser_cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,13 +9,17 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start:fastbrowser_mcp": "npx tsx ./src/fastbrowser_mcp/fastbrowser_mcp.ts",
|
|
12
|
-
"start:
|
|
12
|
+
"start:httpd": "npx tsx ./src/fastbrowser_httpd/fastbrowser_httpd.ts",
|
|
13
13
|
"start:cli": "npx tsx ./src/fastbrowser_cli/fastbrowser_cli.ts",
|
|
14
|
-
"inspect:fastbrowser_mcp": "npx @modelcontextprotocol/inspector npx tsx ./src/fastbrowser_mcp/fastbrowser_mcp.ts mcp_server",
|
|
15
|
-
"inspect:
|
|
16
|
-
"
|
|
14
|
+
"inspect:fastbrowser_mcp:chrome_devtools": "npx @modelcontextprotocol/inspector npx tsx ./src/fastbrowser_mcp/fastbrowser_mcp.ts mcp_server --mcp_target chrome_devtools",
|
|
15
|
+
"inspect:fastbrowser_mcp:playwright": "npx @modelcontextprotocol/inspector npx tsx ./src/fastbrowser_mcp/fastbrowser_mcp.ts mcp_server -v --mcp_target playwright",
|
|
16
|
+
"inspect:chrome_devtools": "npx @modelcontextprotocol/inspector npx chrome-devtools-mcp@latest --autoconnect",
|
|
17
|
+
"inspect:playwright:chrome": "npx @modelcontextprotocol/inspector npx @playwright/mcp@latest --cdp-endpoint=chrome",
|
|
18
|
+
"inspect:playwright:cdp_endpoint": "npx @modelcontextprotocol/inspector npx @playwright/mcp@latest --cdp-endpoint=http://localhost:9222",
|
|
19
|
+
"inspect:playwright:extension": "npx @modelcontextprotocol/inspector npx @playwright/mcp@latest --extension",
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
17
21
|
"publish:all": "npm run build && npm version patch && npm publish --access public",
|
|
18
|
-
"typecheck": "tsc --noEmit"
|
|
22
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
19
23
|
},
|
|
20
24
|
"keywords": [],
|
|
21
25
|
"author": "",
|
|
@@ -25,10 +29,10 @@
|
|
|
25
29
|
"url": "https://github.com/jeromeetienne/skillmd_collecetion.git"
|
|
26
30
|
},
|
|
27
31
|
"homepage": "https://github.com/jeromeetienne/skillmd_collection/packages/fastbrowser_cli#readme",
|
|
28
|
-
"type": "
|
|
32
|
+
"type": "module",
|
|
29
33
|
"dependencies": {
|
|
30
34
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
|
-
"a11y_parse": "
|
|
35
|
+
"a11y_parse": "file:../a11y_parse",
|
|
32
36
|
"commander": "^12.1.0",
|
|
33
37
|
"express": "^4.21.2",
|
|
34
38
|
"string-argv": "^0.3.2",
|
|
@@ -26,18 +26,34 @@ class SilentExitError extends Error {
|
|
|
26
26
|
///////////////////////////////////////////////////////////////////////////////
|
|
27
27
|
|
|
28
28
|
class MainHelper {
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Get the server URL from the command options, environment variable, or default
|
|
31
|
+
* @param cmd
|
|
32
|
+
* @returns
|
|
33
|
+
*/
|
|
34
|
+
static getServerUrlFromCmd(cmd: Command): string {
|
|
30
35
|
const globalOpts = cmd.optsWithGlobals<GlobalOpts>();
|
|
31
36
|
return HttpClient.getServerUrl(globalOpts.server);
|
|
32
37
|
}
|
|
33
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Determine whether to auto-start the server based on command options (default: true)
|
|
41
|
+
* @param cmd
|
|
42
|
+
* @returns
|
|
43
|
+
*/
|
|
34
44
|
static getAutostartFromCmd(cmd: Command): boolean {
|
|
35
45
|
const globalOpts = cmd.optsWithGlobals<GlobalOpts>();
|
|
36
46
|
return globalOpts.autostart !== false;
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Run a tool by making an HTTP request to the server, with optional auto-start
|
|
51
|
+
* @param cmd
|
|
52
|
+
* @param routeName
|
|
53
|
+
* @param body
|
|
54
|
+
*/
|
|
39
55
|
static async runTool(cmd: Command, routeName: string, body: unknown): Promise<void> {
|
|
40
|
-
const server = MainHelper.
|
|
56
|
+
const server = MainHelper.getServerUrlFromCmd(cmd);
|
|
41
57
|
if (MainHelper.getAutostartFromCmd(cmd) === true) {
|
|
42
58
|
await ServerManager.ensureRunning(server);
|
|
43
59
|
}
|
|
@@ -116,7 +132,7 @@ class MainHelper {
|
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
static async runInstall(skillFolder: string): Promise<void> {
|
|
119
|
-
const sourceSkillMd = path.resolve(
|
|
135
|
+
const sourceSkillMd = path.resolve(import.meta.dirname, '../../skills/fastbrowser/SKILL.md');
|
|
120
136
|
const targetDir = path.resolve(skillFolder, 'skills', 'fastbrowser');
|
|
121
137
|
const targetSkillMd = path.join(targetDir, 'SKILL.md');
|
|
122
138
|
try {
|
|
@@ -226,28 +242,35 @@ async function main(): Promise<void> {
|
|
|
226
242
|
.command('start')
|
|
227
243
|
.description('Start the fastbrowser HTTP server as a detached daemon')
|
|
228
244
|
.action(async (_opts, cmd: Command) => {
|
|
229
|
-
const
|
|
230
|
-
await ServerManager.start(
|
|
245
|
+
const serverUrl = MainHelper.getServerUrlFromCmd(cmd);
|
|
246
|
+
await ServerManager.start(serverUrl);
|
|
231
247
|
});
|
|
232
248
|
|
|
233
249
|
serverCmd
|
|
234
250
|
.command('stop')
|
|
235
251
|
.description('Stop the fastbrowser HTTP server')
|
|
236
252
|
.action(async (_opts, cmd: Command) => {
|
|
237
|
-
const
|
|
238
|
-
await ServerManager.stop(
|
|
253
|
+
const serverUrl = MainHelper.getServerUrlFromCmd(cmd);
|
|
254
|
+
await ServerManager.stop(serverUrl);
|
|
239
255
|
});
|
|
240
256
|
|
|
241
257
|
serverCmd
|
|
242
258
|
.command('status')
|
|
243
259
|
.description('Report whether the fastbrowser HTTP server is running')
|
|
244
260
|
.action(async (_opts, cmd: Command) => {
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
console.log(`fastbrowser server at ${
|
|
248
|
-
if (state === 'stopped') throw new SilentExitError();
|
|
261
|
+
const serverUrl = MainHelper.getServerUrlFromCmd(cmd);
|
|
262
|
+
const serverStatus = await ServerManager.status(serverUrl);
|
|
263
|
+
console.log(`fastbrowser server at ${serverUrl}: ${serverStatus}`);
|
|
249
264
|
});
|
|
250
265
|
|
|
266
|
+
serverCmd
|
|
267
|
+
.command('restart')
|
|
268
|
+
.description('Restart the fastbrowser HTTP server')
|
|
269
|
+
.action(async (_opts, cmd: Command) => {
|
|
270
|
+
const serverUrl = MainHelper.getServerUrlFromCmd(cmd);
|
|
271
|
+
await ServerManager.stop(serverUrl);
|
|
272
|
+
await ServerManager.start(serverUrl);
|
|
273
|
+
});
|
|
251
274
|
///////////////////////////////////////////////////////////////////////////////
|
|
252
275
|
///////////////////////////////////////////////////////////////////////////////
|
|
253
276
|
//
|
|
@@ -63,14 +63,23 @@ export class ServerManager {
|
|
|
63
63
|
throw new Error(`Refusing to start: ${serverUrl} is not a local URL`);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// debugger
|
|
67
|
+
let entryPath = Path.resolve(import.meta.dirname, '..', '..', 'fastbrowser_httpd', 'fastbrowser_httpd.js');
|
|
66
68
|
const port = ServerManager.parsePort(serverUrl);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
let spawnCommand = process.execPath;
|
|
70
|
+
let spawnArgs = [entryPath, '--port', String(port)]
|
|
71
|
+
// trick to work without being in `./dist'
|
|
72
|
+
if (entryPath.includes('/dist/') === false) {
|
|
73
|
+
spawnCommand = '/usr/local/bin/npx';
|
|
74
|
+
spawnArgs[0] = spawnArgs[0].replace(/\.js$/, '.ts');
|
|
75
|
+
spawnArgs = ['tsx', ...spawnArgs];
|
|
76
|
+
}
|
|
77
|
+
const packageRoot = Path.resolve(import.meta.dirname, '..', '..', '..');
|
|
69
78
|
|
|
70
79
|
Fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
71
80
|
const logFd = Fs.openSync(LOG_FILE, 'a');
|
|
72
81
|
|
|
73
|
-
const child = spawn(
|
|
82
|
+
const child = spawn(spawnCommand, spawnArgs, {
|
|
74
83
|
detached: true,
|
|
75
84
|
stdio: ['ignore', logFd, logFd],
|
|
76
85
|
cwd: packageRoot,
|
|
@@ -8,9 +8,10 @@ import { Command } from 'commander';
|
|
|
8
8
|
import express from 'express';
|
|
9
9
|
|
|
10
10
|
// local imports
|
|
11
|
-
import {
|
|
11
|
+
import { McpMyClient } from '../fastbrowser_mcp/libs/mcp_my_client.js';
|
|
12
12
|
import { Routes } from './libs/routes.js';
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
///////////////////////////////////////////////////////////////////////////////
|
|
15
16
|
///////////////////////////////////////////////////////////////////////////////
|
|
16
17
|
//
|
|
@@ -26,14 +27,24 @@ class MainHelper {
|
|
|
26
27
|
verbose?: boolean;
|
|
27
28
|
}): Promise<void> {
|
|
28
29
|
// Spawn fastbrowser-mcp as a subprocess and hold a persistent MCP client to it.
|
|
29
|
-
const fastbrowserMcpEntry = Path.resolve(
|
|
30
|
-
|
|
30
|
+
const fastbrowserMcpEntry = Path.resolve(import.meta.dirname, '..', 'fastbrowser_mcp', 'fastbrowser_mcp.js');
|
|
31
|
+
let mcpServerCommand = process.execPath;
|
|
32
|
+
let mcpServerArgs = [fastbrowserMcpEntry, 'mcp_server'];
|
|
33
|
+
// trick to work without being in `./dist'
|
|
34
|
+
if (fastbrowserMcpEntry.includes('/dist/') === false) {
|
|
35
|
+
mcpServerCommand = '/usr/local/bin/npx';
|
|
36
|
+
mcpServerArgs[0] = mcpServerArgs[0].replace(/\.js$/, '.ts');
|
|
37
|
+
mcpServerArgs = ['tsx', ...mcpServerArgs];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const mcpClient = new McpMyClient({
|
|
31
41
|
name: 'fastbrowser-httpd',
|
|
32
42
|
version: '1.0.0',
|
|
43
|
+
mcpTarget: 'chrome_devtools',
|
|
33
44
|
transport: {
|
|
34
45
|
type: 'stdio',
|
|
35
|
-
command:
|
|
36
|
-
args:
|
|
46
|
+
command: mcpServerCommand,
|
|
47
|
+
args: mcpServerArgs,
|
|
37
48
|
},
|
|
38
49
|
});
|
|
39
50
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { Express, Request, Response } from 'express';
|
|
3
3
|
|
|
4
4
|
// local imports
|
|
5
|
-
import {
|
|
5
|
+
import { McpMyClient } from '../../fastbrowser_mcp/libs/mcp_my_client.js';
|
|
6
6
|
import { TOOL_SCHEMAS, ToolResponseSchema } from './tool-schemas.js';
|
|
7
7
|
|
|
8
8
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -12,7 +12,7 @@ import { TOOL_SCHEMAS, ToolResponseSchema } from './tool-schemas.js';
|
|
|
12
12
|
///////////////////////////////////////////////////////////////////////////////
|
|
13
13
|
|
|
14
14
|
export class Routes {
|
|
15
|
-
static register(app: Express, mcpClient:
|
|
15
|
+
static register(app: Express, mcpClient: McpMyClient): void {
|
|
16
16
|
app.get('/health', (_req: Request, res: Response) => {
|
|
17
17
|
res.json({ ok: true });
|
|
18
18
|
});
|