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,288 @@
1
+ /**
2
+ * Agent Types Module
3
+ *
4
+ * Type definitions for the Page Agent system.
5
+ * Used for DOM-text based automation with Claude API integration.
6
+ */
7
+
8
+ // ============================================================================
9
+ // DOM Representation Types
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Bounding box for an element's position and dimensions
14
+ */
15
+ export interface BoundingBox {
16
+ x: number;
17
+ y: number;
18
+ width: number;
19
+ height: number;
20
+ }
21
+
22
+ /**
23
+ * Represents a single DOM element extracted from the page
24
+ */
25
+ export interface DOMElement {
26
+ /** Unique identifier for the element within the page */
27
+ id: string;
28
+ /** HTML tag name (e.g., 'button', 'input', 'a') */
29
+ tag: string;
30
+ /** ARIA role if present (e.g., 'button', 'link', 'textbox') */
31
+ role?: string;
32
+ /** ARIA label if present */
33
+ ariaLabel?: string;
34
+ /** Visible text content of the element */
35
+ text: string;
36
+ /** Input type for form elements (e.g., 'text', 'email', 'password') */
37
+ type?: string;
38
+ /** Current value for input elements */
39
+ value?: string;
40
+ /** Whether the element is currently visible */
41
+ visible: boolean;
42
+ /** Whether the element is enabled (not disabled) */
43
+ enabled: boolean;
44
+ /** Bounding box for element location */
45
+ bbox: BoundingBox;
46
+ }
47
+
48
+ /**
49
+ * Page type classification for LinkedIn pages
50
+ */
51
+ export type PageType = 'profile' | 'search' | 'messaging' | 'notifications' | 'feed' | 'unknown';
52
+
53
+ /**
54
+ * Connection state for profile pages
55
+ */
56
+ export type ConnectionState = 'connected' | 'not_connected' | 'pending' | 'unknown';
57
+
58
+ /**
59
+ * Metadata extracted from the page
60
+ */
61
+ export interface PageMetadata {
62
+ /** Type of page detected */
63
+ pageType: PageType;
64
+ /** Name of the profile owner (if on a profile page) */
65
+ profileName?: string;
66
+ /** Title/headline of the profile (if on a profile page) */
67
+ profileTitle?: string;
68
+ /** Connection status with the profile owner */
69
+ connectionState?: ConnectionState;
70
+ /** Status messages or notifications displayed on the page */
71
+ statusMessages?: string[];
72
+ }
73
+
74
+ /**
75
+ * Complete DOM representation of a page
76
+ */
77
+ export interface DOMRepresentation {
78
+ /** Current page URL */
79
+ url: string;
80
+ /** Page title */
81
+ title: string;
82
+ /** Array of interactive elements */
83
+ elements: DOMElement[];
84
+ /** Extracted metadata about the page */
85
+ metadata: PageMetadata;
86
+ }
87
+
88
+ // ============================================================================
89
+ // Action Types
90
+ // ============================================================================
91
+
92
+ /**
93
+ * Base action interface
94
+ */
95
+ export interface BaseAction {
96
+ /** Human-readable description of what this action does */
97
+ description: string;
98
+ }
99
+
100
+ /**
101
+ * Click action - simulates a mouse click on an element
102
+ */
103
+ export interface ClickAction extends BaseAction {
104
+ type: 'click';
105
+ /** ID of the element to click (from DOMElement.id) */
106
+ elementId: string;
107
+ }
108
+
109
+ /**
110
+ * Type action - enters text into an input field
111
+ */
112
+ export interface TypeAction extends BaseAction {
113
+ type: 'type';
114
+ /** ID of the input element (from DOMElement.id) */
115
+ elementId: string;
116
+ /** Text to type into the element */
117
+ text: string;
118
+ }
119
+
120
+ /**
121
+ * Wait action - pauses execution for a specified duration
122
+ */
123
+ export interface WaitAction extends BaseAction {
124
+ type: 'wait';
125
+ /** Duration to wait in milliseconds */
126
+ durationMs: number;
127
+ }
128
+
129
+ /**
130
+ * Navigate action - navigates to a different URL
131
+ */
132
+ export interface NavigateAction extends BaseAction {
133
+ type: 'navigate';
134
+ /** URL to navigate to */
135
+ url: string;
136
+ }
137
+
138
+ /**
139
+ * Union type of all possible actions
140
+ */
141
+ export type Action = ClickAction | TypeAction | WaitAction | NavigateAction;
142
+
143
+ /**
144
+ * Type guard for ClickAction
145
+ */
146
+ export function isClickAction(action: Action): action is ClickAction {
147
+ return action.type === 'click';
148
+ }
149
+
150
+ /**
151
+ * Type guard for TypeAction
152
+ */
153
+ export function isTypeAction(action: Action): action is TypeAction {
154
+ return action.type === 'type';
155
+ }
156
+
157
+ /**
158
+ * Type guard for WaitAction
159
+ */
160
+ export function isWaitAction(action: Action): action is WaitAction {
161
+ return action.type === 'wait';
162
+ }
163
+
164
+ /**
165
+ * Type guard for NavigateAction
166
+ */
167
+ export function isNavigateAction(action: Action): action is NavigateAction {
168
+ return action.type === 'navigate';
169
+ }
170
+
171
+ // ============================================================================
172
+ // Action Planning Types
173
+ // ============================================================================
174
+
175
+ /**
176
+ * Status of an action plan execution
177
+ */
178
+ export type ActionPlanStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
179
+
180
+ /**
181
+ * Action plan generated by Claude for task execution
182
+ */
183
+ export interface ActionPlan {
184
+ /** Reasoning behind the planned actions */
185
+ reasoning: string;
186
+ /** Expected outcome after executing the actions */
187
+ expectedOutcome: string;
188
+ /** Sequence of actions to execute */
189
+ actions: Action[];
190
+ /** Current status of the plan */
191
+ status: ActionPlanStatus;
192
+ /** Error message if plan generation or execution failed */
193
+ error?: string;
194
+ }
195
+
196
+ // ============================================================================
197
+ // Client Configuration Types
198
+ // ============================================================================
199
+
200
+ /**
201
+ * Configuration for Claude API client
202
+ */
203
+ export interface ClaudeClientConfig {
204
+ /** Anthropic API key */
205
+ apiKey: string;
206
+ /** Base URL for API requests (optional, for custom endpoints) */
207
+ baseUrl?: string;
208
+ /** Claude model to use (e.g., 'claude-3-5-sonnet-20241022') */
209
+ model: string;
210
+ }
211
+
212
+ // ============================================================================
213
+ // Task Execution Types
214
+ // ============================================================================
215
+
216
+ /**
217
+ * Task to be executed by the agent
218
+ */
219
+ export interface Task {
220
+ /** High-level goal to accomplish */
221
+ goal: string;
222
+ /** LinkedIn profile URL (if applicable) */
223
+ profileUrl?: string;
224
+ /** Optional note for connection requests */
225
+ note?: string;
226
+ /** Optional message to send */
227
+ message?: string;
228
+ }
229
+
230
+ /**
231
+ * Result of task execution
232
+ */
233
+ export interface TaskResult {
234
+ /** Whether the task was completed successfully */
235
+ success: boolean;
236
+ /** Human-readable message describing the outcome */
237
+ message: string;
238
+ /** List of actions that were executed */
239
+ actionsTaken: Action[];
240
+ /** Final URL after task completion */
241
+ finalUrl?: string;
242
+ }
243
+
244
+ /**
245
+ * Result of a connection request task
246
+ */
247
+ export interface ConnectionResult extends TaskResult {
248
+ /** Whether the connection request was sent */
249
+ sent: boolean;
250
+ /** Whether the request is pending approval */
251
+ pending: boolean;
252
+ /** Whether already connected to this user */
253
+ alreadyConnected?: boolean;
254
+ }
255
+
256
+ /**
257
+ * Result of a message sending task
258
+ */
259
+ export interface MessageResult extends TaskResult {
260
+ /** Whether the message was successfully sent */
261
+ sent: boolean;
262
+ }
263
+
264
+ /**
265
+ * Context for action execution
266
+ */
267
+ export interface ActionContext {
268
+ /** Actions that have already been executed */
269
+ previousActions: ExecutedAction[];
270
+ /** Original task goal */
271
+ goal: string;
272
+ /** Number of retry attempts for the current action */
273
+ retryCount: number;
274
+ }
275
+
276
+ /**
277
+ * Record of an executed action
278
+ */
279
+ export interface ExecutedAction {
280
+ /** The action that was executed */
281
+ action: Action;
282
+ /** Whether the action executed successfully */
283
+ success: boolean;
284
+ /** Timestamp when the action was executed */
285
+ timestamp: Date;
286
+ /** Error message if the action failed */
287
+ error?: string;
288
+ }
@@ -0,0 +1,274 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { PageAgent, PageAgentExtensionClient, getAuthToken } from '../agent';
4
+ import { BrowserController } from '../core/browser';
5
+ import { getConfig } from '../core/config';
6
+ import type { ClaudeClientConfig } from '../agent/types';
7
+
8
+ // Constants
9
+ const DASHSCOPE_API_KEY_ENV = 'DASHSCOPE_API_KEY';
10
+ const LINKEDIN_URL_REGEX = /^https?:\/\/www\.linkedin\.com\/in\/[^\/]+\/?$/;
11
+ const DEFAULT_CLAUDE_CONFIG = {
12
+ baseUrl: 'https://coding.dashscope.aliyuncs.com/apps/anthropic/v1',
13
+ model: 'qwen3.5-plus',
14
+ };
15
+
16
+ /**
17
+ * Check if a CDP-enabled browser is available
18
+ * @returns true if browser is listening on localhost:9222
19
+ */
20
+ async function isCDPBrowserAvailable(port = 9222): Promise<boolean> {
21
+ try {
22
+ const response = await fetch(`http://localhost:${port}/json/version`, {
23
+ signal: AbortSignal.timeout(2000),
24
+ });
25
+ return response.ok;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ // Validation functions
32
+ function validateApiKey(): string | null {
33
+ const apiKey = process.env[DASHSCOPE_API_KEY_ENV];
34
+ if (!apiKey) {
35
+ console.error(chalk.red(`✗ ${DASHSCOPE_API_KEY_ENV} environment variable is required`));
36
+ console.log(chalk.gray('Set it with: export DASHSCOPE_API_KEY=your_key'));
37
+ return null;
38
+ }
39
+ return apiKey;
40
+ }
41
+
42
+ function validateLinkedInUrl(url: string): boolean {
43
+ if (!LINKEDIN_URL_REGEX.test(url)) {
44
+ console.error(chalk.red('✗ Invalid profile URL'));
45
+ console.log(chalk.gray('Expected format: https://www.linkedin.com/in/username/'));
46
+ return false;
47
+ }
48
+ return true;
49
+ }
50
+
51
+ export function registerAgentCommands(program: Command): void {
52
+ // Create agent subcommand
53
+ const agentCmd = program.command('agent').description('AI-powered LinkedIn automation');
54
+
55
+ // Connect command (auto-detects extension, falls back to Playwright)
56
+ agentCmd
57
+ .command('connect <url>')
58
+ .description('Send connection request (auto-detects fastest method)')
59
+ .option('--note <text>', 'Personal note')
60
+ .option('--debug', 'Enable debug output')
61
+ .option('--playwright', 'Force Playwright mode (ignore extension)')
62
+ .action(
63
+ async (url: string, options: { note?: string; debug?: boolean; playwright?: boolean }) => {
64
+ // Validate API key and URL
65
+ const apiKey = validateApiKey();
66
+ if (!apiKey) return;
67
+
68
+ if (!validateLinkedInUrl(url)) return;
69
+
70
+ const note = options.note || 'Hi, I would like to connect with you.';
71
+
72
+ // Check if CDP browser is available
73
+ if (!options.playwright && (await isCDPBrowserAvailable())) {
74
+ // Extension mode (fast)
75
+ console.log(chalk.blue('Using Page Agent Extension (fast mode)...'));
76
+ console.log(chalk.gray('Profile: ' + url));
77
+
78
+ try {
79
+ const extClient = new PageAgentExtensionClient({
80
+ authToken: getAuthToken(),
81
+ apiKey,
82
+ debug: options.debug,
83
+ });
84
+
85
+ const result = await extClient.connect(url, note);
86
+
87
+ if (result.success) {
88
+ console.log(chalk.green('✓ Connection request prepared!'));
89
+ console.log(chalk.gray(`Note: "${note}"`));
90
+ console.log(chalk.yellow('→ Ready for review - Send button NOT clicked'));
91
+ } else {
92
+ console.error(chalk.red('✗ Failed: ' + result.message));
93
+ process.exit(1);
94
+ }
95
+ } catch (error) {
96
+ console.error(
97
+ chalk.red('✗ Extension mode failed:'),
98
+ error instanceof Error ? error.message : error
99
+ );
100
+ process.exit(1);
101
+ }
102
+ } else {
103
+ // Playwright mode (fallback)
104
+ const modeReason = options.playwright
105
+ ? '--playwright flag specified'
106
+ : 'CDP browser not available';
107
+ console.log(chalk.blue(`Using Playwright Agent (${modeReason})...`));
108
+ console.log(chalk.gray('Profile: ' + url));
109
+
110
+ let browser: BrowserController | null = null;
111
+
112
+ try {
113
+ const config = getConfig();
114
+ browser = new BrowserController({
115
+ headless: config.getValue('headless'),
116
+ debug: options.debug,
117
+ });
118
+
119
+ const page = await browser.launch();
120
+
121
+ const claudeConfig: ClaudeClientConfig = {
122
+ apiKey,
123
+ ...DEFAULT_CLAUDE_CONFIG,
124
+ };
125
+
126
+ const agent = new PageAgent({
127
+ claudeConfig,
128
+ debug: options.debug,
129
+ });
130
+
131
+ const result = await agent.connect(url, options.note, page);
132
+
133
+ if (result.sent) {
134
+ console.log(chalk.green('✓ Connection request sent!'));
135
+ } else if (result.alreadyConnected) {
136
+ console.log(chalk.yellow('✓ Already connected'));
137
+ } else {
138
+ console.error(chalk.red('✗ Failed: ' + result.message));
139
+ process.exit(1);
140
+ }
141
+ } catch (error) {
142
+ console.error(
143
+ chalk.red('✗ Playwright mode failed:'),
144
+ error instanceof Error ? error.message : error
145
+ );
146
+ process.exit(1);
147
+ } finally {
148
+ if (browser) {
149
+ await browser.close();
150
+ }
151
+ }
152
+ }
153
+ }
154
+ );
155
+
156
+ // Message command
157
+ agentCmd
158
+ .command('message <url>')
159
+ .description('Send message using AI agent')
160
+ .requiredOption('--message <text>', 'Message to send')
161
+ .option('--debug', 'Enable debug output')
162
+ .action(async (url: string, options: { message: string; debug?: boolean }) => {
163
+ // Validate API key and URL
164
+ const apiKey = validateApiKey();
165
+ if (!apiKey) return;
166
+
167
+ if (!validateLinkedInUrl(url)) return;
168
+
169
+ let browser: BrowserController | null = null;
170
+
171
+ try {
172
+ console.log(chalk.blue(`Preparing to send message to ${url} using AI agent...`));
173
+
174
+ // Launch browser
175
+ const config = getConfig();
176
+ browser = new BrowserController({
177
+ headless: config.getValue('headless'),
178
+ debug: options.debug,
179
+ });
180
+
181
+ const page = await browser.launch();
182
+
183
+ // Create PageAgent with config
184
+ const claudeConfig: ClaudeClientConfig = {
185
+ apiKey,
186
+ ...DEFAULT_CLAUDE_CONFIG,
187
+ };
188
+
189
+ const agent = new PageAgent({
190
+ claudeConfig,
191
+ debug: options.debug,
192
+ });
193
+
194
+ // Call agent.sendMessage
195
+ const result = await agent.sendMessage(url, options.message, page);
196
+
197
+ // Print result
198
+ if (result.sent) {
199
+ console.log(chalk.green('✓ Message sent successfully!'));
200
+ console.log(chalk.gray(`Message: "${options.message}"`));
201
+ } else {
202
+ console.error(chalk.red('✗ Failed to send message'));
203
+ console.error(chalk.gray(result.message));
204
+ }
205
+ } catch (error) {
206
+ console.error(
207
+ chalk.red('✗ Message failed:'),
208
+ error instanceof Error ? error.message : error
209
+ );
210
+ process.exit(1);
211
+ } finally {
212
+ // Ensure browser closes
213
+ if (browser) {
214
+ await browser.close();
215
+ }
216
+ }
217
+ });
218
+
219
+ // Playwright command (explicit Playwright mode)
220
+ agentCmd
221
+ .command('playwright <url>')
222
+ .description('Send connection request using Playwright (slower, explicit)')
223
+ .option('--note <text>', 'Personal note')
224
+ .option('--debug', 'Enable debug output')
225
+ .action(async (url: string, options: { note?: string; debug?: boolean }) => {
226
+ const apiKey = validateApiKey();
227
+ if (!apiKey) return;
228
+
229
+ if (!validateLinkedInUrl(url)) return;
230
+
231
+ let browser: BrowserController | null = null;
232
+
233
+ try {
234
+ console.log(chalk.blue('Using Playwright Agent (explicit mode)...'));
235
+ console.log(chalk.gray('Profile: ' + url));
236
+
237
+ const config = getConfig();
238
+ browser = new BrowserController({
239
+ headless: config.getValue('headless'),
240
+ debug: options.debug,
241
+ });
242
+
243
+ const page = await browser.launch();
244
+
245
+ const claudeConfig: ClaudeClientConfig = {
246
+ apiKey,
247
+ ...DEFAULT_CLAUDE_CONFIG,
248
+ };
249
+
250
+ const agent = new PageAgent({
251
+ claudeConfig,
252
+ debug: options.debug,
253
+ });
254
+
255
+ const result = await agent.connect(url, options.note, page);
256
+
257
+ if (result.sent) {
258
+ console.log(chalk.green('✓ Connection request sent!'));
259
+ } else if (result.alreadyConnected) {
260
+ console.log(chalk.yellow('✓ Already connected'));
261
+ } else {
262
+ console.error(chalk.red('✗ Failed: ' + result.message));
263
+ process.exit(1);
264
+ }
265
+ } catch (error) {
266
+ console.error(chalk.red('✗ Failed:'), error instanceof Error ? error.message : error);
267
+ process.exit(1);
268
+ } finally {
269
+ if (browser) {
270
+ await browser.close();
271
+ }
272
+ }
273
+ });
274
+ }