fastbrowser_cli 1.0.37 → 1.0.40

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 (98) hide show
  1. package/dist/contribs/_shared/fastbrowser_helper.d.ts +13 -0
  2. package/dist/contribs/_shared/fastbrowser_helper.d.ts.map +1 -0
  3. package/dist/contribs/_shared/fastbrowser_helper.js +44 -0
  4. package/dist/contribs/_shared/fastbrowser_helper.js.map +1 -0
  5. package/dist/contribs/linkedin_cli/src/cli.d.ts +3 -0
  6. package/dist/contribs/linkedin_cli/src/cli.d.ts.map +1 -0
  7. package/dist/contribs/linkedin_cli/src/cli.js +299 -0
  8. package/dist/contribs/linkedin_cli/src/cli.js.map +1 -0
  9. package/dist/contribs/linkedin_cli/src/libs/linkedin_profile_helper.d.ts +73 -0
  10. package/dist/contribs/linkedin_cli/src/libs/linkedin_profile_helper.d.ts.map +1 -0
  11. package/dist/contribs/linkedin_cli/src/libs/linkedin_profile_helper.js +866 -0
  12. package/dist/contribs/linkedin_cli/src/libs/linkedin_profile_helper.js.map +1 -0
  13. package/dist/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.d.ts +61 -0
  14. package/dist/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.d.ts.map +1 -0
  15. package/dist/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.js +885 -0
  16. package/dist/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.js.map +1 -0
  17. package/dist/contribs/linkedin_cli/src/libs/linkedin_thread_helper.d.ts +11 -0
  18. package/dist/contribs/linkedin_cli/src/libs/linkedin_thread_helper.d.ts.map +1 -0
  19. package/dist/contribs/linkedin_cli/src/libs/linkedin_thread_helper.js +145 -0
  20. package/dist/contribs/linkedin_cli/src/libs/linkedin_thread_helper.js.map +1 -0
  21. package/dist/contribs/twitter_cli/src/cli.d.ts +3 -0
  22. package/dist/contribs/twitter_cli/src/cli.d.ts.map +1 -0
  23. package/dist/contribs/twitter_cli/src/cli.js +273 -0
  24. package/dist/contribs/twitter_cli/src/cli.js.map +1 -0
  25. package/dist/contribs/twitter_cli/src/libs/twitter_profile_helper.d.ts +28 -0
  26. package/dist/contribs/twitter_cli/src/libs/twitter_profile_helper.d.ts.map +1 -0
  27. package/dist/contribs/twitter_cli/src/libs/twitter_profile_helper.js +274 -0
  28. package/dist/contribs/twitter_cli/src/libs/twitter_profile_helper.js.map +1 -0
  29. package/dist/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.d.ts +43 -0
  30. package/dist/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.d.ts.map +1 -0
  31. package/dist/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.js +519 -0
  32. package/dist/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.js.map +1 -0
  33. package/dist/contribs/twitter_cli/src/libs/twitter_thread_helper.d.ts +11 -0
  34. package/dist/contribs/twitter_cli/src/libs/twitter_thread_helper.d.ts.map +1 -0
  35. package/dist/contribs/twitter_cli/src/libs/twitter_thread_helper.js +213 -0
  36. package/dist/contribs/twitter_cli/src/libs/twitter_thread_helper.js.map +1 -0
  37. package/dist/fastbrowser_cli/fastbrowser_cli.js +43 -0
  38. package/dist/fastbrowser_cli/fastbrowser_cli.js.map +1 -1
  39. package/dist/fastbrowser_httpd/libs/tool-schemas.d.ts +4 -0
  40. package/dist/fastbrowser_httpd/libs/tool-schemas.d.ts.map +1 -1
  41. package/dist/fastbrowser_httpd/libs/tool-schemas.js +4 -0
  42. package/dist/fastbrowser_httpd/libs/tool-schemas.js.map +1 -1
  43. package/dist/fastbrowser_mcp/fastbrowser_mcp.js +36 -2
  44. package/dist/fastbrowser_mcp/fastbrowser_mcp.js.map +1 -1
  45. package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts +2 -0
  46. package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts.map +1 -1
  47. package/dist/fastbrowser_mcp/libs/mcp_target_helper.js +12 -0
  48. package/dist/fastbrowser_mcp/libs/mcp_target_helper.js.map +1 -1
  49. package/dist/fastbrowser_mcp/libs/response_formatter.d.ts +1 -0
  50. package/dist/fastbrowser_mcp/libs/response_formatter.d.ts.map +1 -1
  51. package/dist/fastbrowser_mcp/libs/response_formatter.js +27 -0
  52. package/dist/fastbrowser_mcp/libs/response_formatter.js.map +1 -1
  53. package/dist/shared/fastbrowser_helper.d.ts +13 -0
  54. package/dist/shared/fastbrowser_helper.d.ts.map +1 -0
  55. package/dist/shared/fastbrowser_helper.js +39 -0
  56. package/dist/shared/fastbrowser_helper.js.map +1 -0
  57. package/examples/linkedin_cli_TOREMOVE/README.md +7 -0
  58. package/examples/{linkedin_cli → linkedin_cli_TOREMOVE}/linkedin_dm.sh +8 -4
  59. package/examples/linkedin_cli_TOREMOVE/linkedin_dm.ts +326 -0
  60. package/examples/linkedin_cli_TOREMOVE/linkedin_dm_messages.ts +279 -0
  61. package/examples/linkedin_cli_TOREMOVE/linkedin_full_cycle.sh +5 -0
  62. package/examples/{linkedin_cli → linkedin_cli_TOREMOVE}/linkedin_post.sh +3 -0
  63. package/examples/linkedin_cli_TOREMOVE/message_thread.a11y.txt +252 -0
  64. package/listitem +4 -0
  65. package/package.json +7 -3
  66. package/skills/fastbrowser/SKILL.md +33 -25
  67. package/src/contribs/_shared/fastbrowser_helper.ts +54 -0
  68. package/src/contribs/linkedin_cli/README.md +80 -0
  69. package/src/contribs/linkedin_cli/data/linkedin_posts_jeromeetienne.a11y.txt +2364 -0
  70. package/src/contribs/linkedin_cli/data/linkedin_posts_jontwigge.a11y.txt +2740 -0
  71. package/src/contribs/linkedin_cli/data/linkedin_posts_julien_guezennec.a11y.txt +2073 -0
  72. package/src/contribs/linkedin_cli/data/linkedin_profile_jeromeetienne.a11y.txt +1863 -0
  73. package/src/contribs/linkedin_cli/data/linkedin_profile_jontwigge.a11y.txt +1738 -0
  74. package/src/contribs/linkedin_cli/data/linkedin_profile_julien_guezennec.a11y.txt +2182 -0
  75. package/src/contribs/linkedin_cli/src/cli.ts +345 -0
  76. package/src/contribs/linkedin_cli/src/libs/linkedin_profile_helper.ts +964 -0
  77. package/src/contribs/linkedin_cli/src/libs/linkedin_recent_posts_helper.ts +982 -0
  78. package/src/contribs/linkedin_cli/src/libs/linkedin_thread_helper.ts +171 -0
  79. package/src/contribs/twitter_cli/README.md +79 -0
  80. package/src/contribs/twitter_cli/data/twitter_chat.a11y.txt +215 -0
  81. package/src/contribs/twitter_cli/data/twitter_home.a11y.txt +467 -0
  82. package/src/contribs/twitter_cli/data/twitter_profile.a11y.txt +418 -0
  83. package/src/contribs/twitter_cli/data/twitter_profile_jontwigge.a11y.txt +484 -0
  84. package/src/contribs/twitter_cli/data/twitter_profile_molokoloco.a11y.txt +483 -0
  85. package/src/contribs/twitter_cli/src/cli.ts +315 -0
  86. package/src/contribs/twitter_cli/src/libs/twitter_profile_helper.ts +328 -0
  87. package/src/contribs/twitter_cli/src/libs/twitter_recent_posts_helper.ts +607 -0
  88. package/src/contribs/twitter_cli/src/libs/twitter_thread_helper.ts +240 -0
  89. package/src/fastbrowser_cli/fastbrowser_cli.ts +51 -0
  90. package/src/fastbrowser_httpd/libs/tool-schemas.ts +6 -0
  91. package/src/fastbrowser_mcp/fastbrowser_mcp.ts +46 -3
  92. package/src/fastbrowser_mcp/libs/mcp_target_helper.ts +11 -0
  93. package/src/fastbrowser_mcp/libs/response_formatter.ts +29 -0
  94. package/src/shared/fastbrowser_helper.ts +49 -0
  95. package/tsconfig.json +1 -1
  96. package/examples/mcp_client_playwright.ts +0 -34
  97. /package/examples/{linkedin_cli → linkedin_cli_TOREMOVE}/linkedin.snapshot.txt +0 -0
  98. /package/examples/{twitter_cli → twitter_cli_TOREMOVE}/twitter_post.sh +0 -0
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env npx tsx
2
+
3
+ // npm imports
4
+ import { Command } from 'commander';
5
+ import { A11yQuery, A11yTree, AxNode } from 'a11y_parse';
6
+
7
+ // local imports
8
+ import { FastBrowserHelper } from '../../_shared/fastbrowser_helper.js';
9
+ import { LinkedinThreadHelper } from './libs/linkedin_thread_helper.js';
10
+ import { LinkedinProfile, LinkedinProfileHelper } from './libs/linkedin_profile_helper.js';
11
+ import { LinkedinPost, LinkedinRecentPostsHelper } from './libs/linkedin_recent_posts_helper.js';
12
+
13
+ ///////////////////////////////////////////////////////////////////////////////
14
+ ///////////////////////////////////////////////////////////////////////////////
15
+ //
16
+ ///////////////////////////////////////////////////////////////////////////////
17
+ ///////////////////////////////////////////////////////////////////////////////
18
+
19
+ class MainHelper {
20
+
21
+ ///////////////////////////////////////////////////////////////////////////////
22
+ ///////////////////////////////////////////////////////////////////////////////
23
+ //
24
+ ///////////////////////////////////////////////////////////////////////////////
25
+ ///////////////////////////////////////////////////////////////////////////////
26
+
27
+ static async gotoPageMessaging(): Promise<void> {
28
+ await FastBrowserHelper.run('check');
29
+ await FastBrowserHelper.navigatePage('https://www.linkedin.com/messaging/');
30
+ }
31
+
32
+ ///////////////////////////////////////////////////////////////////////////////
33
+ ///////////////////////////////////////////////////////////////////////////////
34
+ //
35
+ ///////////////////////////////////////////////////////////////////////////////
36
+ ///////////////////////////////////////////////////////////////////////////////
37
+
38
+ static async gotoPageFeed(): Promise<void> {
39
+ await FastBrowserHelper.run('check');
40
+ await FastBrowserHelper.navigatePage('https://www.linkedin.com/feed/');
41
+ }
42
+
43
+ ///////////////////////////////////////////////////////////////////////////////
44
+ ///////////////////////////////////////////////////////////////////////////////
45
+ //
46
+ ///////////////////////////////////////////////////////////////////////////////
47
+ ///////////////////////////////////////////////////////////////////////////////
48
+
49
+ static async gotoPageProfile(slug: string): Promise<void> {
50
+ if (slug.length === 0) {
51
+ throw new Error('slug is required');
52
+ }
53
+ await FastBrowserHelper.run('check');
54
+ await FastBrowserHelper.navigatePage(`https://www.linkedin.com/in/${slug}/`);
55
+ }
56
+
57
+ ///////////////////////////////////////////////////////////////////////////////
58
+ ///////////////////////////////////////////////////////////////////////////////
59
+ //
60
+ ///////////////////////////////////////////////////////////////////////////////
61
+ ///////////////////////////////////////////////////////////////////////////////
62
+
63
+ static async gotoPageRecentPosts(slug: string): Promise<void> {
64
+ if (slug.length === 0) {
65
+ throw new Error('slug is required');
66
+ }
67
+ await FastBrowserHelper.run('check');
68
+ await FastBrowserHelper.navigatePage(`https://www.linkedin.com/in/${slug}/recent-activity/all/`);
69
+ }
70
+
71
+ ///////////////////////////////////////////////////////////////////////////////
72
+ ///////////////////////////////////////////////////////////////////////////////
73
+ //
74
+ ///////////////////////////////////////////////////////////////////////////////
75
+ ///////////////////////////////////////////////////////////////////////////////
76
+
77
+ static async exportRecentPosts(slug: string, limit: number): Promise<LinkedinPost[]> {
78
+ // Trigger LinkedIn's infinite-scroll inside the window so that more than the initial posts get rendered.
79
+ const scrollFunctionTxt = [
80
+ `() => {`,
81
+ ` const tryCount = 6;`,
82
+ ` const delayMs = 500;`,
83
+ ` (async () => {`,
84
+ ` for (let i = 0; i < tryCount; i++) {`,
85
+ ` window.scrollTo({`,
86
+ ` top: 600000,`,
87
+ ` behavior: 'smooth'`,
88
+ ` });`,
89
+ ` await new Promise(resolve => setTimeout(resolve, delayMs));`,
90
+ ` }`,
91
+ ` })();`,
92
+ `}`,
93
+ ].join('\n');
94
+ await FastBrowserHelper.evaluateScript(scrollFunctionTxt);
95
+
96
+ // take snapshot and parse posts
97
+ const snapshot = await FastBrowserHelper.takeSnapshot();
98
+ let posts: LinkedinPost[] = LinkedinRecentPostsHelper.parsePosts(snapshot, slug);
99
+ // apply limit
100
+ if (limit > 0 && posts.length > limit) {
101
+ posts = posts.slice(0, limit);
102
+ }
103
+ // return posts
104
+ return posts;
105
+ }
106
+
107
+ ///////////////////////////////////////////////////////////////////////////////
108
+ ///////////////////////////////////////////////////////////////////////////////
109
+ //
110
+ ///////////////////////////////////////////////////////////////////////////////
111
+ ///////////////////////////////////////////////////////////////////////////////
112
+
113
+ static async exportProfile(slug: string): Promise<LinkedinProfile> {
114
+ // scroll to the bottom of the page multiple times to load dynamic content (like experience details, recommendations, etc.)
115
+ const functionTxt = [
116
+ `() => {`,
117
+ ` // select the element`,
118
+ ` const workspace = document.querySelector('main#workspace');`,
119
+ ` if (workspace === null) {`,
120
+ ` throw new Error('Workspace element not found');`,
121
+ ` }`,
122
+ ` // scroll to the bottom of the page multiple times to load dynamic content (like experience details, recommendations, etc.)`,
123
+ ` const tryCount = 6;`,
124
+ ` const delayMs = 500;`,
125
+ ` (async () => {`,
126
+ ` for (let i = 0; i < tryCount; i++) {`,
127
+ ` workspace.scrollBy({`,
128
+ ` top: 600000,`,
129
+ ` behavior: 'smooth'`,
130
+ ` });`,
131
+ ` await new Promise(resolve => setTimeout(resolve, delayMs));`,
132
+ ` }`,
133
+ ` resolve(true);`,
134
+ ` })();`,
135
+ `}`,
136
+ ].join('\n');
137
+ const resultEvaluateStr: string = await FastBrowserHelper.evaluateScript(functionTxt);
138
+
139
+ // Take snapshot
140
+ const snapshot = await FastBrowserHelper.takeSnapshot();
141
+ // Parse profile
142
+ const linkedinProfile = LinkedinProfileHelper.parseProfile(snapshot, slug);
143
+ // Return profile
144
+ return linkedinProfile;
145
+ }
146
+
147
+ ///////////////////////////////////////////////////////////////////////////////
148
+ ///////////////////////////////////////////////////////////////////////////////
149
+ //
150
+ ///////////////////////////////////////////////////////////////////////////////
151
+ ///////////////////////////////////////////////////////////////////////////////
152
+
153
+ static async createPost(content: string): Promise<void> {
154
+ await FastBrowserHelper.click('button[name^="Start a post"]');
155
+ await FastBrowserHelper.fillForm('textbox[name^="Text editor"]', content);
156
+ await FastBrowserHelper.click('button[name^="Post"]');
157
+ }
158
+
159
+ ///////////////////////////////////////////////////////////////////////////////
160
+ ///////////////////////////////////////////////////////////////////////////////
161
+ //
162
+ ///////////////////////////////////////////////////////////////////////////////
163
+ ///////////////////////////////////////////////////////////////////////////////
164
+
165
+ static async listConvoNames(): Promise<string[]> {
166
+ const output = await FastBrowserHelper.querySelectorsAll(
167
+ 'list[name="Conversation List"] > listitem heading',
168
+ 0,
169
+ );
170
+ const names: string[] = [];
171
+ for (const line of output.split('\n')) {
172
+ if (/^uid=\S+\s+heading\s+"/.test(line) === false) {
173
+ continue;
174
+ }
175
+ const axNode = A11yTree.parse(line);
176
+ if (axNode.name === undefined) {
177
+ continue;
178
+ }
179
+ names.push(axNode.name);
180
+ }
181
+ return names;
182
+ }
183
+
184
+ ///////////////////////////////////////////////////////////////////////////////
185
+ ///////////////////////////////////////////////////////////////////////////////
186
+ //
187
+ ///////////////////////////////////////////////////////////////////////////////
188
+ ///////////////////////////////////////////////////////////////////////////////
189
+
190
+ static async selectConversation(targetUser: string): Promise<void> {
191
+ const escaped = targetUser.replace(/"/g, '\\"');
192
+ await FastBrowserHelper.click(
193
+ `list[name="Conversation List"] > listitem heading[name^="${escaped}"]`,
194
+ );
195
+ }
196
+
197
+ ///////////////////////////////////////////////////////////////////////////////
198
+ ///////////////////////////////////////////////////////////////////////////////
199
+ //
200
+ ///////////////////////////////////////////////////////////////////////////////
201
+ ///////////////////////////////////////////////////////////////////////////////
202
+
203
+ static async fillAndSendMessage(message: string): Promise<void> {
204
+ await FastBrowserHelper.fillForm('textbox[name^="Write"]', message);
205
+ await FastBrowserHelper.click('button[name^="Send"]');
206
+ }
207
+
208
+ ///////////////////////////////////////////////////////////////////////////////
209
+ ///////////////////////////////////////////////////////////////////////////////
210
+ //
211
+ ///////////////////////////////////////////////////////////////////////////////
212
+ ///////////////////////////////////////////////////////////////////////////////
213
+
214
+ static async getMessagesTranscript(): Promise<string> {
215
+ const output = await FastBrowserHelper.takeSnapshot();
216
+ const axTree = A11yTree.parse(output);
217
+ const threadNode = MainHelper.findThreadNode(axTree);
218
+ return await LinkedinThreadHelper.parseMessagesThread(threadNode);
219
+ }
220
+
221
+ ///////////////////////////////////////////////////////////////////////////////
222
+ ///////////////////////////////////////////////////////////////////////////////
223
+ //
224
+ ///////////////////////////////////////////////////////////////////////////////
225
+ ///////////////////////////////////////////////////////////////////////////////
226
+
227
+ private static findThreadNode(axTree: AxNode): AxNode {
228
+ const convList = A11yQuery.querySelector(axTree, 'list[name="Conversation List"]');
229
+ if (convList === undefined) {
230
+ throw new Error('Could not find conversation list node');
231
+ }
232
+ const convListParent = convList.parent;
233
+ if (convListParent === undefined) {
234
+ throw new Error('Conversation list node has no parent');
235
+ }
236
+ const convDetails = A11yTree.nextSibling(convListParent);
237
+ if (convDetails === undefined) {
238
+ throw new Error('Could not find conversation details node');
239
+ }
240
+ const threadNode = A11yQuery.querySelector(convDetails, 'list');
241
+ if (threadNode === undefined) {
242
+ throw new Error('Could not find thread node');
243
+ }
244
+ return threadNode;
245
+ }
246
+ }
247
+
248
+ ///////////////////////////////////////////////////////////////////////////////
249
+ ///////////////////////////////////////////////////////////////////////////////
250
+ // Entry point
251
+ ///////////////////////////////////////////////////////////////////////////////
252
+ ///////////////////////////////////////////////////////////////////////////////
253
+
254
+ async function main(): Promise<void> {
255
+ const program = new Command();
256
+
257
+ program
258
+ .name('linkedin_cli')
259
+ .description('LinkedIn DM CLI - command line tool to interact with LinkedIn using the FastBrowser CLI');
260
+
261
+ program
262
+ .command('post <content>')
263
+ .description('Create a post on the LinkedIn feed')
264
+ .action(async (content: string) => {
265
+ await MainHelper.gotoPageFeed();
266
+ await MainHelper.createPost(content);
267
+ });
268
+
269
+ program
270
+ .command('dm_page')
271
+ .description('Navigate to the LinkedIn messaging page')
272
+ .action(async () => {
273
+ await MainHelper.gotoPageMessaging();
274
+ });
275
+
276
+ program
277
+ .command('dm_list')
278
+ .description('List the names of people you have conversations with. (assume you did \'dm_page\' first)')
279
+ .action(async () => {
280
+ const names = await MainHelper.listConvoNames();
281
+ for (const name of names) {
282
+ console.log(name);
283
+ }
284
+ });
285
+
286
+ program
287
+ .command('dm_send <target_user> <message>')
288
+ .description('Send a message in an existing conversation. (assume you did \'dm_page\' first)')
289
+ .action(async (targetUser: string, message: string) => {
290
+ await MainHelper.selectConversation(targetUser);
291
+ await MainHelper.fillAndSendMessage(message);
292
+ });
293
+
294
+ program
295
+ .command('dm_thread <target_user>')
296
+ .description('Get the message thread of a conversation. (assume you did \'dm_page\' first)')
297
+ .action(async (targetUser: string) => {
298
+ await MainHelper.selectConversation(targetUser);
299
+ const transcript = await MainHelper.getMessagesTranscript();
300
+ console.log(transcript);
301
+ });
302
+
303
+ program
304
+ .command('profile <slug>')
305
+ .description('Export a LinkedIn profile by slug (path component of /in/<slug>/)')
306
+ .option('-f, --format <format>', 'output format: markdown or json', 'markdown')
307
+ .action(async (slug: string, opts: { format: string; }) => {
308
+ if (opts.format !== 'markdown' && opts.format !== 'json') {
309
+ throw new Error(`unknown format '${opts.format}', expected 'markdown' or 'json'`);
310
+ }
311
+ await MainHelper.gotoPageProfile(slug);
312
+ const profile = await MainHelper.exportProfile(slug);
313
+ if (opts.format === 'json') {
314
+ console.log(JSON.stringify(profile));
315
+ return;
316
+ }
317
+ console.log(LinkedinProfileHelper.formatMarkdown(profile));
318
+ });
319
+
320
+ program
321
+ .command('recent_posts <slug>')
322
+ .description('Export the recent activity (posts) of a LinkedIn profile')
323
+ .option('-f, --format <format>', 'output format: markdown or json', 'markdown')
324
+ .option('-l, --limit <limit>', 'max number of posts to return (0 = all visible)', '0')
325
+ .action(async (slug: string, opts: { format: string; limit: string; }) => {
326
+ if (opts.format !== 'markdown' && opts.format !== 'json') {
327
+ throw new Error(`unknown format '${opts.format}', expected 'markdown' or 'json'`);
328
+ }
329
+ const limit = parseInt(opts.limit, 10);
330
+ if (Number.isNaN(limit) === true || limit < 0) {
331
+ throw new Error(`invalid limit '${opts.limit}', expected a non-negative integer`);
332
+ }
333
+ await MainHelper.gotoPageRecentPosts(slug);
334
+ const posts = await MainHelper.exportRecentPosts(slug, limit);
335
+ if (opts.format === 'json') {
336
+ console.log(JSON.stringify(posts));
337
+ return;
338
+ }
339
+ console.log(LinkedinRecentPostsHelper.formatMarkdown(posts));
340
+ });
341
+
342
+ await program.parseAsync();
343
+ }
344
+
345
+ void main();