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 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":";;;AAqEA,wCAKC;AA1ED,yCAA+C;AAQ/C,MAAa,WAAW;IACd,QAAQ,GAA0B,IAAI,GAAG,EAAE,CAAC;IAEpD,gBAAe,CAAC;IAEhB;;OAEG;IACH,KAAK,CAAC,UAAU,CACd,UAA8B,EAAE;QAEhC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC;QAC3C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,SAAS;QAE9D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,GAAG,GAAG,QAAQ,CAAC;QAEnC,wCAAwC;QACxC,IAAI,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAEjD,4CAA4C;QAC5C,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC;QAEzD,uBAAuB;QACvB,IAAI,UAAU,CAAC,MAAM,IAAI,WAAW,EAAE,CAAC;YACrC,MAAM,eAAe,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YACtC,MAAM,UAAU,GAAG,eAAe,GAAG,QAAQ,GAAG,GAAG,CAAC;YAEpD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;QACxC,CAAC;QAED,sBAAsB;QACtB,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAEtC,oBAAoB;QACpB,MAAM,MAAM,GAAG,IAAA,sBAAc,GAAE,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,IAAI,CAAC,CAAC;QAE/F,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAAC,UAA8B,EAAE;QACjD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAE9C,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,IAAA,sBAAc,GAAE,CAAC;YAChC,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,CAAC;YAEvE,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;CACF;AAxDD,kCAwDC;AAED,qBAAqB;AACrB,IAAI,WAAW,GAAuB,IAAI,CAAC;AAE3C,SAAgB,cAAc;IAC5B,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,18 @@
1
+ export interface RetryOptions {
2
+ maxAttempts?: number;
3
+ baseDelay?: number;
4
+ maxDelay?: number;
5
+ backoffMultiplier?: number;
6
+ retryableErrors?: string[];
7
+ onRetry?: (attempt: number, error: Error, delay: number) => void;
8
+ }
9
+ export declare class RetryableError extends Error {
10
+ readonly cause?: Error | undefined;
11
+ constructor(message: string, cause?: Error | undefined);
12
+ }
13
+ export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
14
+ /**
15
+ * Wrap a function to add retry behavior
16
+ */
17
+ export declare function withRetryWrapper<T extends (...args: any[]) => Promise<any>>(fn: T, options?: RetryOptions): (...args: Parameters<T>) => Promise<ReturnType<T>>;
18
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClE;AAED,qBAAa,cAAe,SAAQ,KAAK;aAGrB,KAAK,CAAC,EAAE,KAAK;gBAD7B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,CAAC,CAAC,CA8C/F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,EACzE,EAAE,EAAE,CAAC,EACL,OAAO,GAAE,YAAiB,GACzB,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAEpD"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RetryableError = void 0;
4
+ exports.withRetry = withRetry;
5
+ exports.withRetryWrapper = withRetryWrapper;
6
+ const config_1 = require("../core/config");
7
+ class RetryableError extends Error {
8
+ cause;
9
+ constructor(message, cause) {
10
+ super(message);
11
+ this.cause = cause;
12
+ this.name = 'RetryableError';
13
+ }
14
+ }
15
+ exports.RetryableError = RetryableError;
16
+ async function withRetry(fn, options = {}) {
17
+ const config = (0, config_1.getConfig)().get();
18
+ const { maxAttempts = 3, baseDelay = config.rateLimit, maxDelay = 30000, backoffMultiplier = 2, retryableErrors = ['network', 'timeout', 'rate', 'limit', '429', '503', '504'], onRetry, } = options;
19
+ let lastError = null;
20
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
21
+ try {
22
+ return await fn();
23
+ }
24
+ catch (error) {
25
+ lastError = error instanceof Error ? error : new Error(String(error));
26
+ // Check if this is a retryable error
27
+ const isRetryable = retryableErrors.some((pattern) => lastError.message.toLowerCase().includes(pattern.toLowerCase()));
28
+ if (!isRetryable || attempt === maxAttempts) {
29
+ throw new RetryableError(`Failed after ${attempt} attempt(s): ${lastError.message}`, lastError);
30
+ }
31
+ // Calculate delay with exponential backoff and jitter
32
+ const delay = Math.min(baseDelay * Math.pow(backoffMultiplier, attempt - 1), maxDelay);
33
+ const jitter = Math.random() * 1000; // Add up to 1s of jitter
34
+ const totalDelay = delay + jitter;
35
+ if (onRetry) {
36
+ onRetry(attempt, lastError, totalDelay);
37
+ }
38
+ await new Promise((resolve) => setTimeout(resolve, totalDelay));
39
+ }
40
+ }
41
+ throw new RetryableError('Unexpected exit from retry loop', lastError);
42
+ }
43
+ /**
44
+ * Wrap a function to add retry behavior
45
+ */
46
+ function withRetryWrapper(fn, options = {}) {
47
+ return (...args) => withRetry(() => fn(...args), options);
48
+ }
49
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":";;;AAqBA,8BA8CC;AAKD,4CAKC;AA7ED,2CAA2C;AAW3C,MAAa,cAAe,SAAQ,KAAK;IAGrB;IAFlB,YACE,OAAe,EACC,KAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,UAAK,GAAL,KAAK,CAAQ;QAG7B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AARD,wCAQC;AAEM,KAAK,UAAU,SAAS,CAAI,EAAoB,EAAE,UAAwB,EAAE;IACjF,MAAM,MAAM,GAAG,IAAA,kBAAS,GAAE,CAAC,GAAG,EAAE,CAAC;IAEjC,MAAM,EACJ,WAAW,GAAG,CAAC,EACf,SAAS,GAAG,MAAM,CAAC,SAAS,EAC5B,QAAQ,GAAG,KAAK,EAChB,iBAAiB,GAAG,CAAC,EACrB,eAAe,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,EAC9E,OAAO,GACR,GAAG,OAAO,CAAC;IAEZ,IAAI,SAAS,GAAiB,IAAI,CAAC;IAEnC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACxD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAEtE,qCAAqC;YACrC,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CACnD,SAAU,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CACjE,CAAC;YAEF,IAAI,CAAC,WAAW,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;gBAC5C,MAAM,IAAI,cAAc,CACtB,gBAAgB,OAAO,gBAAgB,SAAS,CAAC,OAAO,EAAE,EAC1D,SAAS,CACV,CAAC;YACJ,CAAC;YAED,sDAAsD;YACtD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,OAAO,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;YACvF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,yBAAyB;YAC9D,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,CAAC;YAElC,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;YAC1C,CAAC;YAED,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAED,MAAM,IAAI,cAAc,CAAC,iCAAiC,EAAE,SAAU,CAAC,CAAC;AAC1E,CAAC;AAED;;GAEG;AACH,SAAgB,gBAAgB,CAC9B,EAAK,EACL,UAAwB,EAAE;IAE1B,OAAO,CAAC,GAAG,IAAmB,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,52 @@
1
+ # Connection Command
2
+
3
+ Send LinkedIn connection requests via CLI.
4
+
5
+ ## Prerequisites
6
+
7
+ You must be logged in:
8
+ ```bash
9
+ linkedin-cli auth login
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ### Basic connection request
15
+ ```bash
16
+ linkedin-cli connect send "https://www.linkedin.com/in/williamhgates/"
17
+ ```
18
+
19
+ ### With a personalized note
20
+ ```bash
21
+ linkedin-cli connect send "https://www.linkedin.com/in/williamhgates/" \
22
+ --note "Hi Bill, I'd love to connect and discuss technology."
23
+ ```
24
+
25
+ ### Run in headless mode
26
+ ```bash
27
+ linkedin-cli connect send "https://www.linkedin.com/in/williamhgates/" --headless
28
+ ```
29
+
30
+ ## How It Works
31
+
32
+ 1. **Navigates to profile:** Opens the LinkedIn profile URL
33
+ 2. **Finds Connect button:** Tries multiple selectors to locate the Connect button
34
+ 3. **Handles "More" menu:** If Connect is hidden, clicks the "..." (More actions) button and selects Connect from the dropdown
35
+ 4. **Optional note:** If `--note` provided, clicks "Add a note", fills the textarea
36
+ 5. **Sends request:** Clicks "Send invitation"
37
+
38
+ ## Error Handling
39
+
40
+ - **Not logged in:** Prompts to run `linkedin-cli auth login`
41
+ - **Already connected:** Shows "Already connected to this person"
42
+ - **Pending request:** Shows "Connection request already pending"
43
+ - **Button not found:** Reports "LinkedIn UI may have changed" with debug info
44
+
45
+ ## Technical Details
46
+
47
+ The command uses a multi-layer selector system to handle LinkedIn's dynamic UI:
48
+ - Tries `aria-label` selectors first (accessibility-friendly)
49
+ - Falls back to text-based selectors (`:has-text()`)
50
+ - Uses class-based selectors as final fallback
51
+
52
+ This approach makes the tool resilient to LinkedIn's frequent UI updates.
@@ -0,0 +1,280 @@
1
+ # LinkedIn CLI Design Document
2
+
3
+ **Date:** 2025-03-03
4
+ **Status:** Approved
5
+ **Milestone:** 1 (Single user, CLI-only)
6
+
7
+ ## Overview
8
+
9
+ A Go-based CLI tool that provides LinkedIn-specific automation on top of PinchTab. Designed for sales teams, starting with single-user personal use.
10
+
11
+ ## Goals
12
+
13
+ - Send connection requests with personalized notes
14
+ - Send direct messages to existing connections
15
+ - Respect LinkedIn rate limits to avoid account restrictions
16
+ - Simple CLI interface with profile management
17
+ - Single binary deployment (no dependencies)
18
+
19
+ ## Non-Goals (Milestone 1)
20
+
21
+ - Multi-user/multi-tenant support
22
+ - Web UI or API server
23
+ - Database persistence
24
+ - Automated retry logic
25
+ - Complex queue management
26
+
27
+ ## Architecture
28
+
29
+ ### Technology Stack
30
+
31
+ - **Language:** Go 1.21+
32
+ - **Browser Control:** PinchTab (HTTP API)
33
+ - **CLI Framework:** Cobra
34
+ - **Configuration:** JSON files
35
+ - **Rate Limiting:** In-memory with JSON persistence
36
+
37
+ ### Package Structure
38
+
39
+ ```
40
+ linkedin-cli/
41
+ ├── cmd/linkedin/ # Main entry point
42
+ │ └── main.go
43
+ ├── internal/
44
+ │ ├── cmd/ # Cobra commands
45
+ │ │ ├── auth.go # Authentication flow
46
+ │ │ ├── connect.go # Connection requests
47
+ │ │ ├── message.go # Direct messages
48
+ │ │ ├── queue.go # Batch processing
49
+ │ │ ├── profiles.go # Profile management
50
+ │ │ └── root.go # Root command setup
51
+ │ ├── config/ # Configuration management
52
+ │ │ ├── config.go # Global config
53
+ │ │ └── profile.go # Profile storage
54
+ │ ├── pinchtab/ # PinchTab HTTP client
55
+ │ │ ├── client.go # HTTP client
56
+ │ │ ├── types.go # API types
57
+ │ │ └── actions.go # Action builders
58
+ │ ├── linkedin/ # LinkedIn-specific logic
59
+ │ │ ├── selectors.go # DOM selectors
60
+ │ │ ├── navigator.go # Page navigation
61
+ │ │ └── validator.go # URL validation
62
+ │ └── ratelimit/ # Rate limiting
63
+ ├── limiter.go # Limiter implementation
64
+ └── limits.go # Limit definitions
65
+ ```
66
+
67
+ ## CLI Interface
68
+
69
+ ### Commands
70
+
71
+ ```bash
72
+ # Profile management
73
+ linkedin auth --profile <name> # Interactive login
74
+ linkedin profiles list # List profiles
75
+ linkedin profiles remove <name> # Delete profile
76
+
77
+ # Connection requests
78
+ linkedin connect --profile <name> \
79
+ --url <linkedin-url> \
80
+ --message <note>
81
+
82
+ # Direct messages
83
+ linkedin message --profile <name> \
84
+ --url <linkedin-url> \
85
+ --message <text>
86
+
87
+ # Batch processing
88
+ linkedin queue --profile <name> \
89
+ --file <csv-file>
90
+
91
+ # Global flags
92
+ --dry-run # Show what would happen
93
+ --verbose # Detailed logging
94
+ --yes # Skip confirmations
95
+ ```
96
+
97
+ ### Environment Variables
98
+
99
+ ```bash
100
+ LINKEDIN_PROFILE # Default profile name
101
+ PINCHTAB_HOST # PinchTab server (default: localhost:9867)
102
+ ```
103
+
104
+ ### Exit Codes
105
+
106
+ - `0`: Success
107
+ - `1`: General error
108
+ - `2`: Rate limit exceeded
109
+ - `3`: PinchTab not available
110
+ - `4`: LinkedIn restriction detected
111
+ - `5`: Invalid arguments
112
+
113
+ ## Data Flow
114
+
115
+ ### Connection Request Flow
116
+
117
+ 1. Parse CLI arguments and flags
118
+ 2. Load profile configuration
119
+ 3. Check rate limits (connections: 20/day, 100/week)
120
+ 4. Start PinchTab instance with profile
121
+ 5. Navigate to target LinkedIn URL
122
+ 6. Wait 3s for accessibility tree
123
+ 7. Locate "Connect" button via selector
124
+ 8. Execute `humanClick` (with randomization)
125
+ 9. If note modal appears:
126
+ - Find textarea
127
+ - Execute `humanType` with message
128
+ - Click "Send"
129
+ 10. Verify success (confirmation message or absence of error)
130
+ 11. Log action to rate limiter
131
+ 12. Return result
132
+
133
+ ### Direct Message Flow
134
+
135
+ 1. Parse CLI arguments
136
+ 2. Load profile and check rate limits (messages: 50/day)
137
+ 3. Navigate to conversation or profile
138
+ 4. Find message input
139
+ 5. Execute `humanType` with message
140
+ 6. Send (Enter key or click send button)
141
+ 7. Verify delivery
142
+ 8. Log action
143
+
144
+ ## Configuration
145
+
146
+ ### Profile Storage
147
+
148
+ Location: `~/.linkedin-cli/profiles/<name>.json`
149
+
150
+ ```json
151
+ {
152
+ "name": "john",
153
+ "pinchtab_profile": "linkedin-john",
154
+ "created_at": "2025-03-03T10:00:00Z",
155
+ "last_used": "2025-03-03T14:30:00Z"
156
+ }
157
+ ```
158
+
159
+ ### Rate Limit State
160
+
161
+ Location: `~/.linkedin-cli/ratelimit.json`
162
+
163
+ ```json
164
+ {
165
+ "john": {
166
+ "connections": {
167
+ "today": 15,
168
+ "this_week": 87,
169
+ "last_action": "2025-03-03T14:30:00Z"
170
+ },
171
+ "messages": {
172
+ "today": 23,
173
+ "last_action": "2025-03-03T14:25:00Z"
174
+ }
175
+ }
176
+ }
177
+ ```
178
+
179
+ ## Rate Limiting
180
+
181
+ ### Limits (LinkedIn-Safe)
182
+
183
+ | Action | Daily | Weekly | Notes |
184
+ |--------|-------|--------|-------|
185
+ | Connection requests | 20 | 100 | Rolling windows |
186
+ | Messages | 50 | - | Per profile |
187
+
188
+ ### Behavior
189
+
190
+ - Counters reset automatically based on last action timestamp
191
+ - Warnings at 80% of daily limit
192
+ - Hard stop at 100% (exit code 2)
193
+ - Human-like delays: 3-8 seconds between actions
194
+
195
+ ## Error Handling
196
+
197
+ ### Safety Mechanisms
198
+
199
+ 1. **Dry-run mode**: Preview actions without executing
200
+ 2. **Confirmation prompts**: Require explicit confirmation (bypass with `--yes`)
201
+ 3. **Circuit breaker**: Stop after 3 consecutive failures
202
+ 4. **LinkedIn detection**: Detect restriction messages, pause for 48 hours
203
+
204
+ ### Error Types
205
+
206
+ - **PinchTab unavailable**: Clear error, suggest starting PinchTab
207
+ - **Profile not found**: List available profiles
208
+ - **Rate limit exceeded**: Show reset time
209
+ - **Element not found**: Screenshot for debugging, retry once
210
+ - **LinkedIn restriction**: Immediate stop, guidance on next steps
211
+
212
+ ## LinkedIn Selectors
213
+
214
+ Selectors are defined in `internal/linkedin/selectors.go` and should be:
215
+ - Based on accessibility attributes when possible
216
+ - Fallback to stable CSS selectors
217
+ - Documented with expected element text
218
+
219
+ Example:
220
+ ```go
221
+ var ConnectButton = Selector{
222
+ A11y: "role='button' name='Connect'",
223
+ CSS: "button[aria-label*='Connect']",
224
+ Text: "Connect",
225
+ }
226
+ ```
227
+
228
+ ## Testing Strategy
229
+
230
+ ### Unit Tests
231
+
232
+ - `pinchtab/` package: Mock PinchTab server
233
+ - `ratelimit/` package: Test calculations and resets
234
+ - `linkedin/` package: Test URL validation
235
+
236
+ ### Integration Tests (Manual)
237
+
238
+ - Auth flow with real LinkedIn
239
+ - Connect/message on test accounts
240
+ - Document expected behavior
241
+
242
+ ### No Automated E2E
243
+
244
+ - LinkedIn blocks automation (can't run in CI)
245
+ - Requires real accounts
246
+ - `--dry-run` mode for validation
247
+
248
+ ## Future Milestones
249
+
250
+ ### Milestone 2: Multi-User
251
+
252
+ - Multiple profiles with isolated sessions
253
+ - Basic queue persistence
254
+ - Configuration file support
255
+
256
+ ### Milestone 3: Team Features
257
+
258
+ - Shared lead database (SQLite)
259
+ - Campaign templates
260
+ - Basic analytics
261
+
262
+ ### Milestone 4: Scale
263
+
264
+ - Web UI for management
265
+ - API server
266
+ - Queue workers
267
+ - Analytics dashboard
268
+
269
+ ## Dependencies
270
+
271
+ - `github.com/spf13/cobra` - CLI framework
272
+ - Standard library only for core functionality
273
+
274
+ ## Open Questions
275
+
276
+ None at this time.
277
+
278
+ ## Approval
279
+
280
+ Design approved on 2025-03-03.