fastbrowser_cli 1.0.14 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/.playwright-mcp/.gitignore +3 -0
  2. package/README.md +29 -3
  3. package/dist/fastbrowser_cli/fastbrowser_cli.js +52 -35
  4. package/dist/fastbrowser_cli/fastbrowser_cli.js.map +1 -1
  5. package/dist/fastbrowser_cli/libs/http-client.js +3 -7
  6. package/dist/fastbrowser_cli/libs/http-client.js.map +1 -1
  7. package/dist/fastbrowser_cli/libs/server-manager.d.ts.map +1 -1
  8. package/dist/fastbrowser_cli/libs/server-manager.js +31 -29
  9. package/dist/fastbrowser_cli/libs/server-manager.js.map +1 -1
  10. package/dist/fastbrowser_httpd/fastbrowser_httpd.js +22 -18
  11. package/dist/fastbrowser_httpd/fastbrowser_httpd.js.map +1 -1
  12. package/dist/fastbrowser_httpd/libs/routes.d.ts +2 -2
  13. package/dist/fastbrowser_httpd/libs/routes.d.ts.map +1 -1
  14. package/dist/fastbrowser_httpd/libs/routes.js +4 -8
  15. package/dist/fastbrowser_httpd/libs/routes.js.map +1 -1
  16. package/dist/fastbrowser_httpd/libs/tool-schemas.js +39 -42
  17. package/dist/fastbrowser_httpd/libs/tool-schemas.js.map +1 -1
  18. package/dist/fastbrowser_mcp/fastbrowser_mcp.d.ts.map +1 -1
  19. package/dist/fastbrowser_mcp/fastbrowser_mcp.js +270 -187
  20. package/dist/fastbrowser_mcp/fastbrowser_mcp.js.map +1 -1
  21. package/dist/fastbrowser_mcp/fastbrowser_types.d.ts +5 -0
  22. package/dist/fastbrowser_mcp/fastbrowser_types.d.ts.map +1 -0
  23. package/dist/fastbrowser_mcp/fastbrowser_types.js +2 -0
  24. package/dist/fastbrowser_mcp/fastbrowser_types.js.map +1 -0
  25. package/dist/{fastweb_mcp/libs/mcp_client.d.ts → fastbrowser_mcp/libs/mcp_client_TOREMOVE.d.ts} +6 -2
  26. package/dist/fastbrowser_mcp/libs/mcp_client_TOREMOVE.d.ts.map +1 -0
  27. package/dist/{src/libs/mcp_client.js → fastbrowser_mcp/libs/mcp_client_TOREMOVE.js} +15 -12
  28. package/dist/fastbrowser_mcp/libs/mcp_client_TOREMOVE.js.map +1 -0
  29. package/dist/{src/libs/mcp_client.d.ts → fastbrowser_mcp/libs/mcp_my_client.d.ts} +6 -2
  30. package/dist/fastbrowser_mcp/libs/mcp_my_client.d.ts.map +1 -0
  31. package/dist/{fastweb_mcp/libs/mcp_client.js → fastbrowser_mcp/libs/mcp_my_client.js} +15 -12
  32. package/dist/fastbrowser_mcp/libs/mcp_my_client.js.map +1 -0
  33. package/dist/fastbrowser_mcp/libs/mcp_proxy.d.ts +2 -2
  34. package/dist/fastbrowser_mcp/libs/mcp_proxy.d.ts.map +1 -1
  35. package/dist/fastbrowser_mcp/libs/mcp_proxy.js +9 -16
  36. package/dist/fastbrowser_mcp/libs/mcp_proxy.js.map +1 -1
  37. package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts +35 -0
  38. package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts.map +1 -0
  39. package/dist/fastbrowser_mcp/libs/mcp_target_helper.js +161 -0
  40. package/dist/fastbrowser_mcp/libs/mcp_target_helper.js.map +1 -0
  41. package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.d.ts +28 -0
  42. package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.d.ts.map +1 -0
  43. package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.js +210 -0
  44. package/dist/fastbrowser_mcp/libs/playwright_a11y_helper.js.map +1 -0
  45. package/dist/fastbrowser_mcp/libs/response_formatter.d.ts +10 -0
  46. package/dist/fastbrowser_mcp/libs/response_formatter.d.ts.map +1 -0
  47. package/dist/fastbrowser_mcp/libs/response_formatter.js +155 -0
  48. package/dist/fastbrowser_mcp/libs/response_formatter.js.map +1 -0
  49. package/dist/fastbrowser_mcp/libs/schemas.js +14 -17
  50. package/dist/fastbrowser_mcp/libs/schemas.js.map +1 -1
  51. package/examples/mcp_client_playwright.ts +34 -0
  52. package/examples/welcometothejungle/wttj-job.ts +180 -0
  53. package/examples/welcometothejungle/wttj-search.ts +105 -0
  54. package/outputs/.gitignore +3 -0
  55. package/package.json +12 -8
  56. package/src/fastbrowser_cli/fastbrowser_cli.ts +34 -11
  57. package/src/fastbrowser_cli/libs/server-manager.ts +23 -14
  58. package/src/fastbrowser_httpd/fastbrowser_httpd.ts +16 -5
  59. package/src/fastbrowser_httpd/libs/routes.ts +2 -2
  60. package/src/fastbrowser_mcp/fastbrowser_mcp.ts +324 -150
  61. package/src/fastbrowser_mcp/fastbrowser_types.ts +4 -0
  62. package/src/fastbrowser_mcp/libs/{mcp_client.ts → mcp_client_TOREMOVE.ts} +13 -1
  63. package/src/fastbrowser_mcp/libs/mcp_my_client.ts +128 -0
  64. package/src/fastbrowser_mcp/libs/mcp_proxy.ts +2 -2
  65. package/src/fastbrowser_mcp/libs/mcp_target_helper.ts +164 -0
  66. package/src/fastbrowser_mcp/libs/playwright_a11y_helper.ts +249 -0
  67. package/src/fastbrowser_mcp/libs/response_formatter.ts +162 -0
  68. package/src/fastbrowser_mcp/libs/schemas.ts +2 -2
  69. package/tsconfig.build.json +13 -0
  70. package/tsconfig.json +10 -22
  71. package/dist/contrib/fastweb-cli/fastweb-cli.d.ts +0 -3
  72. package/dist/contrib/fastweb-cli/fastweb-cli.d.ts.map +0 -1
  73. package/dist/contrib/fastweb-cli/fastweb-cli.js +0 -151
  74. package/dist/contrib/fastweb-cli/fastweb-cli.js.map +0 -1
  75. package/dist/contrib/fastweb-cli/http-client.d.ts +0 -7
  76. package/dist/contrib/fastweb-cli/http-client.d.ts.map +0 -1
  77. package/dist/contrib/fastweb-cli/http-client.js +0 -51
  78. package/dist/contrib/fastweb-cli/http-client.js.map +0 -1
  79. package/dist/contrib/fastweb-http-server/fastweb-http-server.d.ts +0 -3
  80. package/dist/contrib/fastweb-http-server/fastweb-http-server.d.ts.map +0 -1
  81. package/dist/contrib/fastweb-http-server/fastweb-http-server.js +0 -82
  82. package/dist/contrib/fastweb-http-server/fastweb-http-server.js.map +0 -1
  83. package/dist/contrib/fastweb-http-server/routes.d.ts +0 -6
  84. package/dist/contrib/fastweb-http-server/routes.d.ts.map +0 -1
  85. package/dist/contrib/fastweb-http-server/routes.js +0 -41
  86. package/dist/contrib/fastweb-http-server/routes.js.map +0 -1
  87. package/dist/contrib/fastweb-http-server/tool-schemas.d.ts +0 -63
  88. package/dist/contrib/fastweb-http-server/tool-schemas.d.ts.map +0 -1
  89. package/dist/contrib/fastweb-http-server/tool-schemas.js +0 -61
  90. package/dist/contrib/fastweb-http-server/tool-schemas.js.map +0 -1
  91. package/dist/fastbrowser_mcp/libs/mcp_client.d.ts +0 -120
  92. package/dist/fastbrowser_mcp/libs/mcp_client.d.ts.map +0 -1
  93. package/dist/fastbrowser_mcp/libs/mcp_client.js +0 -83
  94. package/dist/fastbrowser_mcp/libs/mcp_client.js.map +0 -1
  95. package/dist/fastweb_cli/fastweb_cli.d.ts +0 -3
  96. package/dist/fastweb_cli/fastweb_cli.d.ts.map +0 -1
  97. package/dist/fastweb_cli/fastweb_cli.js +0 -254
  98. package/dist/fastweb_cli/fastweb_cli.js.map +0 -1
  99. package/dist/fastweb_cli/http-client.d.ts +0 -7
  100. package/dist/fastweb_cli/http-client.d.ts.map +0 -1
  101. package/dist/fastweb_cli/http-client.js +0 -51
  102. package/dist/fastweb_cli/http-client.js.map +0 -1
  103. package/dist/fastweb_cli/libs/http-client.d.ts +0 -7
  104. package/dist/fastweb_cli/libs/http-client.d.ts.map +0 -1
  105. package/dist/fastweb_cli/libs/http-client.js +0 -51
  106. package/dist/fastweb_cli/libs/http-client.js.map +0 -1
  107. package/dist/fastweb_cli/libs/server-manager.d.ts +0 -12
  108. package/dist/fastweb_cli/libs/server-manager.d.ts.map +0 -1
  109. package/dist/fastweb_cli/libs/server-manager.js +0 -194
  110. package/dist/fastweb_cli/libs/server-manager.js.map +0 -1
  111. package/dist/fastweb_http_server/fastweb_http_server.d.ts +0 -3
  112. package/dist/fastweb_http_server/fastweb_http_server.d.ts.map +0 -1
  113. package/dist/fastweb_http_server/fastweb_http_server.js +0 -82
  114. package/dist/fastweb_http_server/fastweb_http_server.js.map +0 -1
  115. package/dist/fastweb_http_server/libs/routes.d.ts +0 -6
  116. package/dist/fastweb_http_server/libs/routes.d.ts.map +0 -1
  117. package/dist/fastweb_http_server/libs/routes.js +0 -41
  118. package/dist/fastweb_http_server/libs/routes.js.map +0 -1
  119. package/dist/fastweb_http_server/libs/tool-schemas.d.ts +0 -72
  120. package/dist/fastweb_http_server/libs/tool-schemas.d.ts.map +0 -1
  121. package/dist/fastweb_http_server/libs/tool-schemas.js +0 -65
  122. package/dist/fastweb_http_server/libs/tool-schemas.js.map +0 -1
  123. package/dist/fastweb_http_server/routes.d.ts +0 -6
  124. package/dist/fastweb_http_server/routes.d.ts.map +0 -1
  125. package/dist/fastweb_http_server/routes.js +0 -41
  126. package/dist/fastweb_http_server/routes.js.map +0 -1
  127. package/dist/fastweb_http_server/tool-schemas.d.ts +0 -63
  128. package/dist/fastweb_http_server/tool-schemas.d.ts.map +0 -1
  129. package/dist/fastweb_http_server/tool-schemas.js +0 -61
  130. package/dist/fastweb_http_server/tool-schemas.js.map +0 -1
  131. package/dist/fastweb_mcp/fastweb_mcp.d.ts +0 -4
  132. package/dist/fastweb_mcp/fastweb_mcp.d.ts.map +0 -1
  133. package/dist/fastweb_mcp/fastweb_mcp.js +0 -417
  134. package/dist/fastweb_mcp/fastweb_mcp.js.map +0 -1
  135. package/dist/fastweb_mcp/libs/mcp_client.d.ts.map +0 -1
  136. package/dist/fastweb_mcp/libs/mcp_client.js.map +0 -1
  137. package/dist/fastweb_mcp/libs/mcp_proxy.d.ts +0 -10
  138. package/dist/fastweb_mcp/libs/mcp_proxy.d.ts.map +0 -1
  139. package/dist/fastweb_mcp/libs/mcp_proxy.js +0 -45
  140. package/dist/fastweb_mcp/libs/mcp_proxy.js.map +0 -1
  141. package/dist/fastweb_mcp/libs/schemas.d.ts +0 -28
  142. package/dist/fastweb_mcp/libs/schemas.d.ts.map +0 -1
  143. package/dist/fastweb_mcp/libs/schemas.js +0 -38
  144. package/dist/fastweb_mcp/libs/schemas.js.map +0 -1
  145. package/dist/src/fastweb_mcp.d.ts +0 -17
  146. package/dist/src/fastweb_mcp.d.ts.map +0 -1
  147. package/dist/src/fastweb_mcp.js +0 -342
  148. package/dist/src/fastweb_mcp.js.map +0 -1
  149. package/dist/src/libs/mcp_client.d.ts.map +0 -1
  150. package/dist/src/libs/mcp_client.js.map +0 -1
  151. package/dist/src/libs/mcp_proxy.d.ts +0 -10
  152. package/dist/src/libs/mcp_proxy.d.ts.map +0 -1
  153. package/dist/src/libs/mcp_proxy.js +0 -45
  154. 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);
@@ -0,0 +1,3 @@
1
+ # ignore all but .gitignore
2
+ *
3
+ !.gitignore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastbrowser_cli",
3
- "version": "1.0.14",
3
+ "version": "1.0.18",
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:http-server": "npx tsx ./src/fastbrowser_httpd/fastbrowser_httpd.ts",
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:chrome-devtools": "npx @modelcontextprotocol/inspector npx chrome-devtools-mcp@latest --autoconnect",
16
- "build": "tsc -p tsconfig.json",
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": "commonjs",
32
+ "type": "module",
29
33
  "dependencies": {
30
34
  "@modelcontextprotocol/sdk": "^1.29.0",
31
- "a11y_parse": "^1.0.1",
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
- static getServerFromCmd(cmd: Command): string {
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.getServerFromCmd(cmd);
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(__dirname, '../../skills/fastbrowser/SKILL.md');
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 server = MainHelper.getServerFromCmd(cmd);
230
- await ServerManager.start(server);
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 server = MainHelper.getServerFromCmd(cmd);
238
- await ServerManager.stop(server);
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 server = MainHelper.getServerFromCmd(cmd);
246
- const state = await ServerManager.status(server);
247
- console.log(`fastbrowser server at ${server}: ${state}`);
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
  //
@@ -16,9 +16,9 @@ type PidFile = {
16
16
  startedAt: string;
17
17
  };
18
18
 
19
- const STATE_DIR = Path.join(Os.homedir(), '.fastbrowser_cli');
20
- const PID_FILE = Path.join(STATE_DIR, 'server.json');
21
- const LOG_FILE = Path.join(STATE_DIR, 'server.log');
19
+ const STATE_DIRNAME = Path.join(Os.homedir(), '.fastbrowser_cli');
20
+ const PID_FILENAME = Path.join(STATE_DIRNAME, 'server.json');
21
+ const LOG_FILENAME = Path.join(STATE_DIRNAME, 'server.log');
22
22
 
23
23
  ///////////////////////////////////////////////////////////////////////////////
24
24
  ///////////////////////////////////////////////////////////////////////////////
@@ -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
- const entryPath = Path.resolve(__dirname, '..', '..', 'fastbrowser_httpd', 'fastbrowser_httpd.js');
68
- const packageRoot = Path.resolve(__dirname, '..', '..', '..');
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
- Fs.mkdirSync(STATE_DIR, { recursive: true });
71
- const logFd = Fs.openSync(LOG_FILE, 'a');
79
+ Fs.mkdirSync(STATE_DIRNAME, { recursive: true });
80
+ const logFd = Fs.openSync(LOG_FILENAME, 'a');
72
81
 
73
- const child = spawn(process.execPath, [entryPath, '--port', String(port)], {
82
+ const child = spawn(spawnCommand, spawnArgs, {
74
83
  detached: true,
75
84
  stdio: ['ignore', logFd, logFd],
76
85
  cwd: packageRoot,
@@ -89,7 +98,7 @@ export class ServerManager {
89
98
  port,
90
99
  startedAt: new Date().toISOString(),
91
100
  };
92
- Fs.writeFileSync(PID_FILE, JSON.stringify(pidFile, null, 2));
101
+ Fs.writeFileSync(PID_FILENAME, JSON.stringify(pidFile, null, 2));
93
102
 
94
103
  const deadline = Date.now() + 10_000;
95
104
  while (Date.now() < deadline) {
@@ -108,17 +117,17 @@ export class ServerManager {
108
117
  }
109
118
 
110
119
  static async stop(serverUrl: string): Promise<void> {
111
- if (Fs.existsSync(PID_FILE) === false) {
120
+ if (Fs.existsSync(PID_FILENAME) === false) {
112
121
  console.error('no server pid file; nothing to stop');
113
122
  return;
114
123
  }
115
124
 
116
125
  let pidFile: PidFile;
117
126
  try {
118
- const raw = Fs.readFileSync(PID_FILE, 'utf8');
127
+ const raw = Fs.readFileSync(PID_FILENAME, 'utf8');
119
128
  pidFile = JSON.parse(raw) as PidFile;
120
129
  } catch (err) {
121
- throw new Error(`Failed to read pid file ${PID_FILE}: ${(err as Error).message}`);
130
+ throw new Error(`Failed to read pid file ${PID_FILENAME}: ${(err as Error).message}`);
122
131
  }
123
132
 
124
133
  const pid = pidFile.pid;
@@ -146,7 +155,7 @@ export class ServerManager {
146
155
  }
147
156
 
148
157
  try {
149
- Fs.unlinkSync(PID_FILE);
158
+ Fs.unlinkSync(PID_FILENAME);
150
159
  } catch {
151
160
  // best effort
152
161
  }
@@ -196,7 +205,7 @@ export class ServerManager {
196
205
 
197
206
  private static readLogTail(maxLines: number): string {
198
207
  try {
199
- const content = Fs.readFileSync(LOG_FILE, 'utf8');
208
+ const content = Fs.readFileSync(LOG_FILENAME, 'utf8');
200
209
  const lines = content.split('\n');
201
210
  return lines.slice(Math.max(0, lines.length - maxLines)).join('\n');
202
211
  } catch {
@@ -8,9 +8,10 @@ import { Command } from 'commander';
8
8
  import express from 'express';
9
9
 
10
10
  // local imports
11
- import { McpClient } from '../fastbrowser_mcp/libs/mcp_client.js';
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(__dirname, '..', 'fastbrowser_mcp', 'fastbrowser_mcp.js');
30
- const mcpClient = new McpClient({
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: process.execPath,
36
- args: [fastbrowserMcpEntry, 'mcp_server'],
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 { McpClient } from '../../fastbrowser_mcp/libs/mcp_client.js';
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: McpClient): void {
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
  });