linkedin-automation-cli 1.0.0

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 (314) hide show
  1. package/.env.example +12 -0
  2. package/.github/workflows/ci.yml +66 -0
  3. package/.github/workflows/publish.yml +48 -0
  4. package/.husky/pre-commit +6 -0
  5. package/.prettierignore +4 -0
  6. package/.prettierrc +10 -0
  7. package/AGENTS.md +294 -0
  8. package/CHANGELOG.md +40 -0
  9. package/GIT_RELEASE.md +167 -0
  10. package/LICENSE +21 -0
  11. package/Makefile +30 -0
  12. package/NPM_PUBLISHING.md +230 -0
  13. package/PYEOF +0 -0
  14. package/README.md +295 -0
  15. package/TESTING-GUIDE.md +151 -0
  16. package/cmd/linkedin/main.go +9 -0
  17. package/dist/agent/action-executor.d.ts +81 -0
  18. package/dist/agent/action-executor.d.ts.map +1 -0
  19. package/dist/agent/action-executor.js +170 -0
  20. package/dist/agent/action-executor.js.map +1 -0
  21. package/dist/agent/action-executor.test.d.ts +2 -0
  22. package/dist/agent/action-executor.test.d.ts.map +1 -0
  23. package/dist/agent/action-executor.test.js +366 -0
  24. package/dist/agent/action-executor.test.js.map +1 -0
  25. package/dist/agent/claude-client.d.ts +74 -0
  26. package/dist/agent/claude-client.d.ts.map +1 -0
  27. package/dist/agent/claude-client.js +314 -0
  28. package/dist/agent/claude-client.js.map +1 -0
  29. package/dist/agent/claude-client.test.d.ts +2 -0
  30. package/dist/agent/claude-client.test.d.ts.map +1 -0
  31. package/dist/agent/claude-client.test.js +590 -0
  32. package/dist/agent/claude-client.test.js.map +1 -0
  33. package/dist/agent/dom-extractor.d.ts +50 -0
  34. package/dist/agent/dom-extractor.d.ts.map +1 -0
  35. package/dist/agent/dom-extractor.js +374 -0
  36. package/dist/agent/dom-extractor.js.map +1 -0
  37. package/dist/agent/dom-extractor.test.d.ts +7 -0
  38. package/dist/agent/dom-extractor.test.d.ts.map +1 -0
  39. package/dist/agent/dom-extractor.test.js +504 -0
  40. package/dist/agent/dom-extractor.test.js.map +1 -0
  41. package/dist/agent/extension-client.d.ts +75 -0
  42. package/dist/agent/extension-client.d.ts.map +1 -0
  43. package/dist/agent/extension-client.js +245 -0
  44. package/dist/agent/extension-client.js.map +1 -0
  45. package/dist/agent/index.d.ts +8 -0
  46. package/dist/agent/index.d.ts.map +1 -0
  47. package/dist/agent/index.js +16 -0
  48. package/dist/agent/index.js.map +1 -0
  49. package/dist/agent/page-agent.d.ts +76 -0
  50. package/dist/agent/page-agent.d.ts.map +1 -0
  51. package/dist/agent/page-agent.js +236 -0
  52. package/dist/agent/page-agent.js.map +1 -0
  53. package/dist/agent/types.d.ts +236 -0
  54. package/dist/agent/types.d.ts.map +1 -0
  55. package/dist/agent/types.js +37 -0
  56. package/dist/agent/types.js.map +1 -0
  57. package/dist/cli/agent-commands.d.ts +3 -0
  58. package/dist/cli/agent-commands.d.ts.map +1 -0
  59. package/dist/cli/agent-commands.js +250 -0
  60. package/dist/cli/agent-commands.js.map +1 -0
  61. package/dist/cli/auth.d.ts +3 -0
  62. package/dist/cli/auth.d.ts.map +1 -0
  63. package/dist/cli/auth.js +288 -0
  64. package/dist/cli/auth.js.map +1 -0
  65. package/dist/cli/company.d.ts +3 -0
  66. package/dist/cli/company.d.ts.map +1 -0
  67. package/dist/cli/company.js +55 -0
  68. package/dist/cli/company.js.map +1 -0
  69. package/dist/cli/connection.d.ts +3 -0
  70. package/dist/cli/connection.d.ts.map +1 -0
  71. package/dist/cli/connection.js +79 -0
  72. package/dist/cli/connection.js.map +1 -0
  73. package/dist/cli/index.d.ts +7 -0
  74. package/dist/cli/index.d.ts.map +1 -0
  75. package/dist/cli/index.js +17 -0
  76. package/dist/cli/index.js.map +1 -0
  77. package/dist/cli/messages.d.ts +3 -0
  78. package/dist/cli/messages.d.ts.map +1 -0
  79. package/dist/cli/messages.js +268 -0
  80. package/dist/cli/messages.js.map +1 -0
  81. package/dist/cli/profile.d.ts +3 -0
  82. package/dist/cli/profile.d.ts.map +1 -0
  83. package/dist/cli/profile.js +81 -0
  84. package/dist/cli/profile.js.map +1 -0
  85. package/dist/cli/profile.test.d.ts +2 -0
  86. package/dist/cli/profile.test.d.ts.map +1 -0
  87. package/dist/cli/profile.test.js +15 -0
  88. package/dist/cli/profile.test.js.map +1 -0
  89. package/dist/cli/reply.d.ts +3 -0
  90. package/dist/cli/reply.d.ts.map +1 -0
  91. package/dist/cli/reply.js +129 -0
  92. package/dist/cli/reply.js.map +1 -0
  93. package/dist/core/audit.d.ts +17 -0
  94. package/dist/core/audit.d.ts.map +1 -0
  95. package/dist/core/audit.js +121 -0
  96. package/dist/core/audit.js.map +1 -0
  97. package/dist/core/audit.test.d.ts +2 -0
  98. package/dist/core/audit.test.d.ts.map +1 -0
  99. package/dist/core/audit.test.js +142 -0
  100. package/dist/core/audit.test.js.map +1 -0
  101. package/dist/core/browser-cookies.d.ts +19 -0
  102. package/dist/core/browser-cookies.d.ts.map +1 -0
  103. package/dist/core/browser-cookies.js +181 -0
  104. package/dist/core/browser-cookies.js.map +1 -0
  105. package/dist/core/browser.d.ts +50 -0
  106. package/dist/core/browser.d.ts.map +1 -0
  107. package/dist/core/browser.js +318 -0
  108. package/dist/core/browser.js.map +1 -0
  109. package/dist/core/config.d.ts +20 -0
  110. package/dist/core/config.d.ts.map +1 -0
  111. package/dist/core/config.js +103 -0
  112. package/dist/core/config.js.map +1 -0
  113. package/dist/core/config.test.d.ts +2 -0
  114. package/dist/core/config.test.d.ts.map +1 -0
  115. package/dist/core/config.test.js +111 -0
  116. package/dist/core/config.test.js.map +1 -0
  117. package/dist/core/storage.d.ts +19 -0
  118. package/dist/core/storage.d.ts.map +1 -0
  119. package/dist/core/storage.js +124 -0
  120. package/dist/core/storage.js.map +1 -0
  121. package/dist/core/storage.test.d.ts +2 -0
  122. package/dist/core/storage.test.d.ts.map +1 -0
  123. package/dist/core/storage.test.js +142 -0
  124. package/dist/core/storage.test.js.map +1 -0
  125. package/dist/index.d.ts +3 -0
  126. package/dist/index.d.ts.map +1 -0
  127. package/dist/index.js +63 -0
  128. package/dist/index.js.map +1 -0
  129. package/dist/linkedin/auth.d.ts +22 -0
  130. package/dist/linkedin/auth.d.ts.map +1 -0
  131. package/dist/linkedin/auth.js +167 -0
  132. package/dist/linkedin/auth.js.map +1 -0
  133. package/dist/linkedin/company-extractor.d.ts +36 -0
  134. package/dist/linkedin/company-extractor.d.ts.map +1 -0
  135. package/dist/linkedin/company-extractor.js +211 -0
  136. package/dist/linkedin/company-extractor.js.map +1 -0
  137. package/dist/linkedin/company-extractor.test.d.ts +2 -0
  138. package/dist/linkedin/company-extractor.test.d.ts.map +1 -0
  139. package/dist/linkedin/company-extractor.test.js +52 -0
  140. package/dist/linkedin/company-extractor.test.js.map +1 -0
  141. package/dist/linkedin/connector.d.ts +45 -0
  142. package/dist/linkedin/connector.d.ts.map +1 -0
  143. package/dist/linkedin/connector.js +245 -0
  144. package/dist/linkedin/connector.js.map +1 -0
  145. package/dist/linkedin/message-sender.d.ts +32 -0
  146. package/dist/linkedin/message-sender.d.ts.map +1 -0
  147. package/dist/linkedin/message-sender.js +112 -0
  148. package/dist/linkedin/message-sender.js.map +1 -0
  149. package/dist/linkedin/messages.d.ts +78 -0
  150. package/dist/linkedin/messages.d.ts.map +1 -0
  151. package/dist/linkedin/messages.js +745 -0
  152. package/dist/linkedin/messages.js.map +1 -0
  153. package/dist/linkedin/profile.d.ts +37 -0
  154. package/dist/linkedin/profile.d.ts.map +1 -0
  155. package/dist/linkedin/profile.js +268 -0
  156. package/dist/linkedin/profile.js.map +1 -0
  157. package/dist/linkedin/profile.test.d.ts +2 -0
  158. package/dist/linkedin/profile.test.d.ts.map +1 -0
  159. package/dist/linkedin/profile.test.js +68 -0
  160. package/dist/linkedin/profile.test.js.map +1 -0
  161. package/dist/linkedin/reply.d.ts +21 -0
  162. package/dist/linkedin/reply.d.ts.map +1 -0
  163. package/dist/linkedin/reply.js +76 -0
  164. package/dist/linkedin/reply.js.map +1 -0
  165. package/dist/linkedin/selector-engine.d.ts +69 -0
  166. package/dist/linkedin/selector-engine.d.ts.map +1 -0
  167. package/dist/linkedin/selector-engine.js +339 -0
  168. package/dist/linkedin/selector-engine.js.map +1 -0
  169. package/dist/linkedin/selector-engine.test.d.ts +2 -0
  170. package/dist/linkedin/selector-engine.test.d.ts.map +1 -0
  171. package/dist/linkedin/selector-engine.test.js +135 -0
  172. package/dist/linkedin/selector-engine.test.js.map +1 -0
  173. package/dist/linkedin/selectors.d.ts +65 -0
  174. package/dist/linkedin/selectors.d.ts.map +1 -0
  175. package/dist/linkedin/selectors.js +261 -0
  176. package/dist/linkedin/selectors.js.map +1 -0
  177. package/dist/templates/engine.d.ts +37 -0
  178. package/dist/templates/engine.d.ts.map +1 -0
  179. package/dist/templates/engine.js +215 -0
  180. package/dist/templates/engine.js.map +1 -0
  181. package/dist/templates/engine.test.d.ts +2 -0
  182. package/dist/templates/engine.test.d.ts.map +1 -0
  183. package/dist/templates/engine.test.js +212 -0
  184. package/dist/templates/engine.test.js.map +1 -0
  185. package/dist/templates/index.d.ts +2 -0
  186. package/dist/templates/index.d.ts.map +1 -0
  187. package/dist/templates/index.js +7 -0
  188. package/dist/templates/index.js.map +1 -0
  189. package/dist/types/index.d.ts +113 -0
  190. package/dist/types/index.d.ts.map +1 -0
  191. package/dist/types/index.js +3 -0
  192. package/dist/types/index.js.map +1 -0
  193. package/dist/types/index.test.d.ts +2 -0
  194. package/dist/types/index.test.d.ts.map +1 -0
  195. package/dist/types/index.test.js +90 -0
  196. package/dist/types/index.test.js.map +1 -0
  197. package/dist/utils/paths.d.ts +8 -0
  198. package/dist/utils/paths.d.ts.map +1 -0
  199. package/dist/utils/paths.js +68 -0
  200. package/dist/utils/paths.js.map +1 -0
  201. package/dist/utils/rate-limiter.d.ts +22 -0
  202. package/dist/utils/rate-limiter.d.ts.map +1 -0
  203. package/dist/utils/rate-limiter.js +57 -0
  204. package/dist/utils/rate-limiter.js.map +1 -0
  205. package/dist/utils/retry.d.ts +18 -0
  206. package/dist/utils/retry.d.ts.map +1 -0
  207. package/dist/utils/retry.js +49 -0
  208. package/dist/utils/retry.js.map +1 -0
  209. package/docs/connection-command.md +52 -0
  210. package/docs/plans/2025-03-03-linkedin-cli-design.md +280 -0
  211. package/docs/plans/2025-03-03-linkedin-cli-implementation-plan.md +2087 -0
  212. package/docs/plans/2025-03-03-linkedin-cli-implementation.md +2420 -0
  213. package/docs/plans/2026-02-19-linkedin-connection-feature.md +596 -0
  214. package/docs/plans/2026-02-28-messages-send-feature.md +480 -0
  215. package/docs/plans/2026-02-28-messages-show-design.md +243 -0
  216. package/docs/plans/2026-03-03-linkedin-cli-oss-publishing-design.md +394 -0
  217. package/docs/plans/2026-03-03-linkedin-cli-oss-publishing-plan.md +1592 -0
  218. package/docs/superpowers/plans/2026-03-13-linkedin-automation-resilience-migration.md +425 -0
  219. package/docs/superpowers/plans/2026-03-13-playwright-fara-migration.md +1112 -0
  220. package/docs/superpowers/plans/2026-03-14-page-agent-plan.md +1598 -0
  221. package/docs/superpowers/plans/2026-03-15-company-profile-extraction.md +591 -0
  222. package/docs/superpowers/plans/2026-03-15-profile-extraction-plan.md +943 -0
  223. package/docs/superpowers/specs/2026-03-14-company-profile-extraction-design.md +371 -0
  224. package/docs/superpowers/specs/2026-03-14-page-agent-design.md +385 -0
  225. package/docs/superpowers/specs/2026-03-15-profile-extraction-design.md +409 -0
  226. package/eslint.config.mjs +58 -0
  227. package/go.mod +9 -0
  228. package/go.sum +10 -0
  229. package/import-cookies.js +376 -0
  230. package/internal/cmd/actions.go +123 -0
  231. package/internal/cmd/auth.go +108 -0
  232. package/internal/cmd/connect.go +42 -0
  233. package/internal/cmd/message.go +44 -0
  234. package/internal/cmd/people.go +454 -0
  235. package/internal/cmd/profiles.go +121 -0
  236. package/internal/cmd/root.go +89 -0
  237. package/internal/cmd/sequence.go +192 -0
  238. package/internal/config/config.go +187 -0
  239. package/internal/config/config_test.go +121 -0
  240. package/internal/config/profile.go +65 -0
  241. package/internal/linkedin/navigator.go +195 -0
  242. package/internal/linkedin/selectors.go +39 -0
  243. package/internal/linkedin/validator.go +69 -0
  244. package/internal/pinchtab/client.go +183 -0
  245. package/internal/pinchtab/client_test.go +67 -0
  246. package/internal/pinchtab/types.go +50 -0
  247. package/internal/ratelimit/limiter.go +115 -0
  248. package/internal/ratelimit/limits.go +32 -0
  249. package/package.json +67 -0
  250. package/release.sh +66 -0
  251. package/scripts/debug-linkedin.js +156 -0
  252. package/scripts/debug-login.js +193 -0
  253. package/scripts/extract-from-edge.js +96 -0
  254. package/scripts/import-cookies.js +101 -0
  255. package/scripts/poc-show-data.js +205 -0
  256. package/scripts/proof-of-access.js +87 -0
  257. package/scripts/prove-connection.js +110 -0
  258. package/scripts/show-linkedin-data.js +173 -0
  259. package/src/agent/action-executor.test.ts +464 -0
  260. package/src/agent/action-executor.ts +203 -0
  261. package/src/agent/claude-client.test.ts +707 -0
  262. package/src/agent/claude-client.ts +422 -0
  263. package/src/agent/dom-extractor.test.ts +574 -0
  264. package/src/agent/dom-extractor.ts +437 -0
  265. package/src/agent/extension-client.ts +306 -0
  266. package/src/agent/index.ts +28 -0
  267. package/src/agent/page-agent.ts +292 -0
  268. package/src/agent/types.ts +288 -0
  269. package/src/cli/agent-commands.ts +274 -0
  270. package/src/cli/auth.ts +343 -0
  271. package/src/cli/company.ts +66 -0
  272. package/src/cli/connection.ts +89 -0
  273. package/src/cli/index.ts +7 -0
  274. package/src/cli/messages.ts +338 -0
  275. package/src/cli/profile.test.ts +14 -0
  276. package/src/cli/profile.ts +95 -0
  277. package/src/cli/reply.ts +110 -0
  278. package/src/core/audit.test.ts +134 -0
  279. package/src/core/audit.ts +98 -0
  280. package/src/core/browser-cookies.ts +203 -0
  281. package/src/core/browser.ts +304 -0
  282. package/src/core/config.test.ts +90 -0
  283. package/src/core/config.ts +81 -0
  284. package/src/core/storage.test.ts +129 -0
  285. package/src/core/storage.ts +100 -0
  286. package/src/index.ts +70 -0
  287. package/src/linkedin/auth.ts +218 -0
  288. package/src/linkedin/company-extractor.test.ts +58 -0
  289. package/src/linkedin/company-extractor.ts +222 -0
  290. package/src/linkedin/connector.ts +336 -0
  291. package/src/linkedin/message-sender.ts +141 -0
  292. package/src/linkedin/messages.ts +894 -0
  293. package/src/linkedin/profile.test.ts +79 -0
  294. package/src/linkedin/profile.ts +314 -0
  295. package/src/linkedin/reply.ts +96 -0
  296. package/src/linkedin/selector-engine.test.ts +167 -0
  297. package/src/linkedin/selector-engine.ts +393 -0
  298. package/src/linkedin/selectors.ts +268 -0
  299. package/src/templates/defaults/followup.txt +14 -0
  300. package/src/templates/defaults/meeting.txt +16 -0
  301. package/src/templates/defaults/welcome.txt +14 -0
  302. package/src/templates/engine.test.ts +228 -0
  303. package/src/templates/engine.ts +208 -0
  304. package/src/templates/index.ts +1 -0
  305. package/src/types/index.test.ts +94 -0
  306. package/src/types/index.ts +143 -0
  307. package/src/types/sql.js.d.ts +23 -0
  308. package/src/utils/paths.ts +33 -0
  309. package/src/utils/rate-limiter.ts +75 -0
  310. package/src/utils/retry.ts +78 -0
  311. package/test-cli.sh +85 -0
  312. package/test-real-data.sh +97 -0
  313. package/tsconfig.json +23 -0
  314. package/vitest.config.ts +35 -0
@@ -0,0 +1,23 @@
1
+ declare module 'sql.js' {
2
+ export interface QueryExecResult {
3
+ columns: string[];
4
+ values: any[][];
5
+ }
6
+
7
+ export interface Database {
8
+ run(sql: string, params?: any[]): Database;
9
+ exec(sql: string): QueryExecResult[];
10
+ close(): void;
11
+ getRowsModified(): number;
12
+ }
13
+
14
+ export interface SqlJsStatic {
15
+ Database: new (data?: ArrayLike<number> | Buffer | null) => Database;
16
+ }
17
+
18
+ export interface InitSqlJsOptions {
19
+ locateFile?: (file: string) => string;
20
+ }
21
+
22
+ export default function initSqlJs(options?: InitSqlJsOptions): Promise<SqlJsStatic>;
23
+ }
@@ -0,0 +1,33 @@
1
+ import * as path from 'path';
2
+ import * as os from 'os';
3
+
4
+ const CONFIG_DIR = '.linkedin-cli';
5
+
6
+ export function getConfigDir(): string {
7
+ const homeDir = os.homedir();
8
+ return path.join(homeDir, CONFIG_DIR);
9
+ }
10
+
11
+ export function getSessionsDir(): string {
12
+ return path.join(getConfigDir(), 'sessions');
13
+ }
14
+
15
+ export function getTemplatesDir(): string {
16
+ return path.join(getConfigDir(), 'templates');
17
+ }
18
+
19
+ export function getDefaultsDir(): string {
20
+ return path.join(getTemplatesDir(), 'defaults');
21
+ }
22
+
23
+ export function getCustomDir(): string {
24
+ return path.join(getTemplatesDir(), 'custom');
25
+ }
26
+
27
+ export function getConfigFile(): string {
28
+ return path.join(getConfigDir(), 'config.json');
29
+ }
30
+
31
+ export function getLogFile(): string {
32
+ return path.join(getConfigDir(), 'audit.log');
33
+ }
@@ -0,0 +1,75 @@
1
+ import { getAuditLogger } from '../core/audit';
2
+
3
+ export interface RateLimiterOptions {
4
+ maxRequests?: number;
5
+ windowMs?: number;
6
+ action?: string;
7
+ }
8
+
9
+ export class RateLimiter {
10
+ private requests: Map<string, number[]> = new Map();
11
+
12
+ constructor() {}
13
+
14
+ /**
15
+ * Check if an action can be performed
16
+ */
17
+ async checkLimit(
18
+ options: RateLimiterOptions = {}
19
+ ): Promise<{ allowed: boolean; retryAfter?: number }> {
20
+ const action = options.action || 'default';
21
+ const maxRequests = options.maxRequests || 20;
22
+ const windowMs = options.windowMs || 60 * 60 * 1000; // 1 hour
23
+
24
+ const now = Date.now();
25
+ const windowStart = now - windowMs;
26
+
27
+ // Get existing requests for this action
28
+ let timestamps = this.requests.get(action) || [];
29
+
30
+ // Filter to only requests within the window
31
+ timestamps = timestamps.filter((ts) => ts > windowStart);
32
+
33
+ // Check if under limit
34
+ if (timestamps.length >= maxRequests) {
35
+ const oldestTimestamp = timestamps[0];
36
+ const retryAfter = oldestTimestamp + windowMs - now;
37
+
38
+ return { allowed: false, retryAfter };
39
+ }
40
+
41
+ // Record this request
42
+ timestamps.push(now);
43
+ this.requests.set(action, timestamps);
44
+
45
+ // Also log to audit
46
+ const logger = getAuditLogger();
47
+ logger.log('rate_limit.check', { action, count: timestamps.length, limit: maxRequests }, true);
48
+
49
+ return { allowed: true };
50
+ }
51
+
52
+ /**
53
+ * Wait for rate limit to reset
54
+ */
55
+ async waitForReset(options: RateLimiterOptions = {}): Promise<void> {
56
+ const result = await this.checkLimit(options);
57
+
58
+ if (!result.allowed && result.retryAfter) {
59
+ const logger = getAuditLogger();
60
+ logger.log('rate_limit.wait', { retryAfter: result.retryAfter }, true);
61
+
62
+ await new Promise((resolve) => setTimeout(resolve, result.retryAfter));
63
+ }
64
+ }
65
+ }
66
+
67
+ // Singleton instance
68
+ let rateLimiter: RateLimiter | null = null;
69
+
70
+ export function getRateLimiter(): RateLimiter {
71
+ if (!rateLimiter) {
72
+ rateLimiter = new RateLimiter();
73
+ }
74
+ return rateLimiter;
75
+ }
@@ -0,0 +1,78 @@
1
+ import { getConfig } from '../core/config';
2
+
3
+ export interface RetryOptions {
4
+ maxAttempts?: number;
5
+ baseDelay?: number;
6
+ maxDelay?: number;
7
+ backoffMultiplier?: number;
8
+ retryableErrors?: string[];
9
+ onRetry?: (attempt: number, error: Error, delay: number) => void;
10
+ }
11
+
12
+ export class RetryableError extends Error {
13
+ constructor(
14
+ message: string,
15
+ public readonly cause?: Error
16
+ ) {
17
+ super(message);
18
+ this.name = 'RetryableError';
19
+ }
20
+ }
21
+
22
+ export async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
23
+ const config = getConfig().get();
24
+
25
+ const {
26
+ maxAttempts = 3,
27
+ baseDelay = config.rateLimit,
28
+ maxDelay = 30000,
29
+ backoffMultiplier = 2,
30
+ retryableErrors = ['network', 'timeout', 'rate', 'limit', '429', '503', '504'],
31
+ onRetry,
32
+ } = options;
33
+
34
+ let lastError: Error | null = null;
35
+
36
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
37
+ try {
38
+ return await fn();
39
+ } catch (error) {
40
+ lastError = error instanceof Error ? error : new Error(String(error));
41
+
42
+ // Check if this is a retryable error
43
+ const isRetryable = retryableErrors.some((pattern) =>
44
+ lastError!.message.toLowerCase().includes(pattern.toLowerCase())
45
+ );
46
+
47
+ if (!isRetryable || attempt === maxAttempts) {
48
+ throw new RetryableError(
49
+ `Failed after ${attempt} attempt(s): ${lastError.message}`,
50
+ lastError
51
+ );
52
+ }
53
+
54
+ // Calculate delay with exponential backoff and jitter
55
+ const delay = Math.min(baseDelay * Math.pow(backoffMultiplier, attempt - 1), maxDelay);
56
+ const jitter = Math.random() * 1000; // Add up to 1s of jitter
57
+ const totalDelay = delay + jitter;
58
+
59
+ if (onRetry) {
60
+ onRetry(attempt, lastError, totalDelay);
61
+ }
62
+
63
+ await new Promise((resolve) => setTimeout(resolve, totalDelay));
64
+ }
65
+ }
66
+
67
+ throw new RetryableError('Unexpected exit from retry loop', lastError!);
68
+ }
69
+
70
+ /**
71
+ * Wrap a function to add retry behavior
72
+ */
73
+ export function withRetryWrapper<T extends (...args: any[]) => Promise<any>>(
74
+ fn: T,
75
+ options: RetryOptions = {}
76
+ ): (...args: Parameters<T>) => Promise<ReturnType<T>> {
77
+ return (...args: Parameters<T>) => withRetry(() => fn(...args), options);
78
+ }
package/test-cli.sh ADDED
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+ # LinkedIn CLI Test Script
3
+ # This script helps you test the LinkedIn Automation CLI
4
+
5
+ set -e
6
+
7
+ # Get the directory where this script is located
8
+ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
9
+ CLI_PATH="$SCRIPT_DIR/dist/index.js"
10
+ CONFIG_DIR="$HOME/.linkedin-cli"
11
+
12
+ echo "========================================="
13
+ echo "LinkedIn CLI Test Suite"
14
+ echo "========================================="
15
+ echo "Working directory: $SCRIPT_DIR"
16
+ echo ""
17
+
18
+ # Color codes
19
+ GREEN='\033[0;32m'
20
+ YELLOW='\033[1;33m'
21
+ RED='\033[0;31m'
22
+ NC='\033[0m' # No Color
23
+
24
+ # Test 1: CLI Help
25
+ echo -e "${YELLOW}Test 1: CLI Help Commands${NC}"
26
+ echo "----------------------------------------"
27
+ node $CLI_PATH --help
28
+ echo ""
29
+
30
+ # Test 2: Auth Commands Help
31
+ echo -e "${YELLOW}Test 2: Auth Commands${NC}"
32
+ echo "----------------------------------------"
33
+ node $CLI_PATH auth --help
34
+ echo ""
35
+
36
+ # Test 3: Message Commands Help
37
+ echo -e "${YELLOW}Test 3: Message Commands${NC}"
38
+ echo "----------------------------------------"
39
+ node $CLI_PATH messages --help
40
+ echo ""
41
+
42
+ # Test 4: Connection Commands Help
43
+ echo -e "${YELLOW}Test 4: Connection Commands${NC}"
44
+ echo "----------------------------------------"
45
+ node $CLI_PATH connect --help
46
+ echo ""
47
+
48
+ # Test 5: Config Commands Help
49
+ echo -e "${YELLOW}Test 5: Config Commands${NC}"
50
+ echo "----------------------------------------"
51
+ node $CLI_PATH config --help
52
+ echo ""
53
+
54
+ # Test 6: Check Config Directory
55
+ echo -e "${YELLOW}Test 6: Configuration Directory${NC}"
56
+ echo "----------------------------------------"
57
+ if [ -d "$CONFIG_DIR" ]; then
58
+ echo -e "${GREEN}✓ Config directory exists: $CONFIG_DIR${NC}"
59
+ ls -la "$CONFIG_DIR" 2>/dev/null || echo "Directory is empty"
60
+ else
61
+ echo -e "${YELLOW}⚠ Config directory does not exist yet (will be created on first run)${NC}"
62
+ fi
63
+ echo ""
64
+
65
+ # Test 7: Build Check
66
+ echo -e "${YELLOW}Test 7: Build Verification${NC}"
67
+ echo "----------------------------------------"
68
+ if [ -f "$CLI_PATH" ]; then
69
+ echo -e "${GREEN}✓ CLI build exists${NC}"
70
+ ls -lh $CLI_PATH
71
+ else
72
+ echo -e "${RED}✗ CLI build not found. Run: npm run build${NC}"
73
+ exit 1
74
+ fi
75
+ echo ""
76
+
77
+ echo "========================================="
78
+ echo -e "${GREEN}All Tests Passed!${NC}"
79
+ echo "========================================="
80
+ echo ""
81
+ echo "Next Steps:"
82
+ echo "1. Test authentication: node $CLI_PATH auth login"
83
+ echo "2. Check auth status: node $CLI_PATH auth status"
84
+ echo "3. List messages: node $CLI_PATH messages list"
85
+ echo ""
@@ -0,0 +1,97 @@
1
+ #!/bin/bash
2
+ # Quick test to show LinkedIn data
3
+
4
+ echo "🧪 Testing LinkedIn CLI with Real Data"
5
+ echo "======================================"
6
+ echo ""
7
+
8
+ cd /Users/thaddeus/projects/linkedin-automation/.worktrees/linkedin-automation-cli
9
+
10
+ # Test 1: Auth status
11
+ echo "1️⃣ Checking Authentication..."
12
+ node ./dist/index.js auth status
13
+ echo ""
14
+
15
+ # Test 2: Try to get messages with visible browser
16
+ echo "2️⃣ Fetching Messages (browser will open)..."
17
+ echo " Press Ctrl+C to stop after you see the data"
18
+ echo ""
19
+
20
+ # Create a test script that uses Playwright directly
21
+ cat > /tmp/test-messages.js << 'EOF'
22
+ const { chromium } = require('playwright');
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+
26
+ async function test() {
27
+ const sessionFile = path.join(require('os').homedir(), '.linkedin-cli/sessions/linkedin-session.json');
28
+ const encrypted = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
29
+
30
+ // Decrypt
31
+ const crypto = require('crypto');
32
+ const machineData = [process.env.USER, process.env.HOME, process.platform].join('|');
33
+ const salt = Buffer.from(encrypted.salt, 'base64');
34
+ const key = crypto.pbkdf2Sync(machineData, salt, 100000, 32, 'sha256');
35
+ const iv = Buffer.from(encrypted.iv, 'base64');
36
+ const encryptedData = Buffer.from(encrypted.encrypted, 'base64');
37
+ const tag = Buffer.from(encrypted.tag, 'base64');
38
+
39
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
40
+ decipher.setAuthTag(tag);
41
+ const session = JSON.parse(Buffer.concat([decipher.update(encryptedData), decipher.final()]).toString());
42
+
43
+ // Use Edge instead of Chromium
44
+ const browser = await chromium.launch({
45
+ headless: false,
46
+ executablePath: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'
47
+ });
48
+
49
+ const context = await browser.newContext();
50
+ await context.addCookies(session.cookies.map(c => ({
51
+ name: c.name,
52
+ value: c.value,
53
+ domain: c.domain,
54
+ path: c.path || '/',
55
+ secure: c.secure,
56
+ httpOnly: false,
57
+ sameSite: 'Lax'
58
+ })));
59
+
60
+ const page = await context.newPage();
61
+
62
+ // Go to messages
63
+ console.log('Opening LinkedIn Messages...');
64
+ await page.goto('https://www.linkedin.com/messaging/', { waitUntil: 'domcontentloaded' });
65
+ await page.waitForTimeout(5000);
66
+
67
+ // Try to find conversations
68
+ const conversations = await page.$$eval('.msg-conversation-card', cards =>
69
+ cards.slice(0, 5).map(card => {
70
+ const name = card.querySelector('.msg-conversation-card__participant-names')?.innerText || 'Unknown';
71
+ const preview = card.querySelector('.msg-conversation-card__message-preview')?.innerText || '';
72
+ return { name, preview };
73
+ })
74
+ );
75
+
76
+ if (conversations.length > 0) {
77
+ console.log('\n✅ FOUND CONVERSATIONS:');
78
+ conversations.forEach((c, i) => {
79
+ console.log(`${i + 1}. ${c.name}`);
80
+ console.log(` Preview: ${c.preview?.substring(0, 60)}...`);
81
+ });
82
+ } else {
83
+ console.log('\n⚠️ No conversations found with standard selectors');
84
+ console.log('Taking screenshot for debugging...');
85
+ await page.screenshot({ path: '/tmp/linkedin-debug.png' });
86
+ console.log('Screenshot saved: /tmp/linkedin-debug.png');
87
+ }
88
+
89
+ console.log('\nBrowser will stay open for 30 seconds...');
90
+ await page.waitForTimeout(30000);
91
+ await browser.close();
92
+ }
93
+
94
+ test().catch(console.error);
95
+ EOF
96
+
97
+ node /tmp/test-messages.js
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "CommonJS",
5
+ "lib": ["ES2022", "DOM"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "noUnusedLocals": true,
17
+ "noUnusedParameters": true,
18
+ "noImplicitReturns": true,
19
+ "noFallthroughCasesInSwitch": true
20
+ },
21
+ "include": ["src/**/*"],
22
+ "exclude": ["node_modules", "dist", "coverage"]
23
+ }
@@ -0,0 +1,35 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['src/**/*.test.ts'],
8
+ coverage: {
9
+ provider: 'v8',
10
+ reporter: ['text', 'json', 'html'],
11
+ include: [
12
+ 'src/core/audit.ts',
13
+ 'src/core/config.ts',
14
+ 'src/core/storage.ts',
15
+ 'src/linkedin/selector-engine.ts',
16
+ 'src/linkedin/selectors.ts',
17
+ 'src/templates/engine.ts',
18
+ ],
19
+ exclude: [
20
+ 'node_modules/',
21
+ 'src/*.test.ts',
22
+ 'dist/',
23
+ 'scripts/',
24
+ ],
25
+ thresholds: {
26
+ global: {
27
+ lines: 80,
28
+ functions: 80,
29
+ branches: 80,
30
+ statements: 80,
31
+ },
32
+ },
33
+ },
34
+ },
35
+ });