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,422 @@
1
+ /**
2
+ * Claude API Client Module
3
+ *
4
+ * Client for interacting with the Claude API via Dashscope.
5
+ * Sends DOM representations and receives action plans for web automation.
6
+ */
7
+
8
+ import {
9
+ type ActionPlan,
10
+ type ActionPlanStatus,
11
+ type Action,
12
+ type DOMRepresentation,
13
+ type ActionContext,
14
+ type ClaudeClientConfig,
15
+ } from './types';
16
+
17
+ /**
18
+ * Default configuration values
19
+ */
20
+ const DEFAULT_BASE_URL = 'https://coding.dashscope.aliyuncs.com/apps/anthropic/v1';
21
+ const DEFAULT_MODEL = 'qwen3.5-plus';
22
+ const DEFAULT_MAX_TOKENS = 4096;
23
+
24
+ /**
25
+ * Error thrown when the Claude API request fails
26
+ */
27
+ export class ClaudeAPIError extends Error {
28
+ constructor(
29
+ message: string,
30
+ public readonly statusCode?: number,
31
+ public readonly responseBody?: string
32
+ ) {
33
+ super(message);
34
+ this.name = 'ClaudeAPIError';
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Error thrown when the response cannot be parsed
40
+ */
41
+ export class ClaudeParseError extends Error {
42
+ constructor(
43
+ message: string,
44
+ public readonly rawResponse?: string
45
+ ) {
46
+ super(message);
47
+ this.name = 'ClaudeParseError';
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Message structure for Claude API
53
+ */
54
+ interface Message {
55
+ role: 'system' | 'user' | 'assistant';
56
+ content: string;
57
+ }
58
+
59
+ /**
60
+ * Request body for Claude API
61
+ */
62
+ interface ClaudeRequest {
63
+ model: string;
64
+ max_tokens: number;
65
+ messages: Message[];
66
+ }
67
+
68
+ /**
69
+ * Response from Claude API
70
+ */
71
+ interface ClaudeResponse {
72
+ id: string;
73
+ type: string;
74
+ role: string;
75
+ content: Array<{
76
+ type: string;
77
+ text: string;
78
+ }>;
79
+ model: string;
80
+ stop_reason: string | null;
81
+ stop_sequence: string | null;
82
+ usage: {
83
+ input_tokens: number;
84
+ output_tokens: number;
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Raw action structure from JSON parsing
90
+ */
91
+ interface RawAction {
92
+ type: string;
93
+ description?: string;
94
+ elementId?: string;
95
+ text?: string;
96
+ durationMs?: number;
97
+ url?: string;
98
+ }
99
+
100
+ /**
101
+ * Raw action plan structure from JSON parsing
102
+ */
103
+ interface RawActionPlan {
104
+ reasoning?: string;
105
+ expectedOutcome?: string;
106
+ actions?: RawAction[];
107
+ status?: ActionPlanStatus;
108
+ error?: string;
109
+ }
110
+
111
+ /**
112
+ * Client for interacting with the Claude API
113
+ */
114
+ export class ClaudeClient {
115
+ private readonly config: Required<ClaudeClientConfig>;
116
+
117
+ constructor(config: ClaudeClientConfig) {
118
+ this.config = {
119
+ apiKey: config.apiKey,
120
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
121
+ model: config.model || DEFAULT_MODEL,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Generates an action plan based on the current DOM state and context
127
+ */
128
+ async generateActionPlan(dom: DOMRepresentation, context: ActionContext): Promise<ActionPlan> {
129
+ const messages = this.buildMessages(dom, context);
130
+ const response = await this.makeAPICall(messages);
131
+ return this.parseResponse(response);
132
+ }
133
+
134
+ /**
135
+ * Builds the system and user messages for the API request
136
+ */
137
+ private buildMessages(dom: DOMRepresentation, context: ActionContext): Message[] {
138
+ return [
139
+ {
140
+ role: 'system',
141
+ content: this.buildSystemPrompt(),
142
+ },
143
+ {
144
+ role: 'user',
145
+ content: this.buildUserPrompt(dom, context),
146
+ },
147
+ ];
148
+ }
149
+
150
+ /**
151
+ * Builds the system prompt explaining the agent role and JSON format
152
+ */
153
+ private buildSystemPrompt(): string {
154
+ return `You are a web automation agent specialized in LinkedIn interactions.
155
+
156
+ Your task is to analyze a DOM representation of a web page and determine the next action(s) to take to accomplish a goal.
157
+
158
+ RULES:
159
+ 1. Respond ONLY with valid JSON. No markdown, no explanations outside JSON.
160
+ 2. The JSON must match this structure:
161
+ {
162
+ "reasoning": "string explaining your thought process",
163
+ "expectedOutcome": "string describing what should happen after these actions",
164
+ "actions": [
165
+ {
166
+ "type": "click|type|wait|navigate",
167
+ "description": "human-readable description of the action",
168
+ ...action-specific fields
169
+ }
170
+ ]
171
+ }
172
+
173
+ AVAILABLE ACTIONS:
174
+ - click: { type: "click", elementId: "string", description: "string" }
175
+ - type: { type: "type", elementId: "string", text: "string", description: "string" }
176
+ - wait: { type: "wait", durationMs: number, description: "string" }
177
+ - navigate: { type: "navigate", url: "string", description: "string" }
178
+
179
+ GUIDELINES:
180
+ - Use element IDs from the provided DOM. Only reference elements that exist.
181
+ - Break complex tasks into sequential actions.
182
+ - Wait after actions that trigger page changes or loading.
183
+ - If the goal is already achieved, return an empty actions array with appropriate reasoning.
184
+ - If stuck or unable to proceed, explain why in the reasoning field.`;
185
+ }
186
+
187
+ /**
188
+ * Builds the user prompt with goal, DOM, and previous actions
189
+ */
190
+ private buildUserPrompt(dom: DOMRepresentation, context: ActionContext): string {
191
+ const previousActionsText =
192
+ context.previousActions.length > 0
193
+ ? context.previousActions
194
+ .map(
195
+ (action, index) =>
196
+ `${index + 1}. ${action.action.type}: ${action.action.description} (${action.success ? 'success' : 'failed'}${action.error ? ` - ${action.error}` : ''})`
197
+ )
198
+ .join('\n')
199
+ : 'None';
200
+
201
+ return `Goal: ${context.goal}
202
+
203
+ Current page DOM:
204
+ \`\`\`json
205
+ ${JSON.stringify(dom, null, 2)}
206
+ \`\`\`
207
+
208
+ Previous actions taken:
209
+ ${previousActionsText}
210
+
211
+ Retry count: ${context.retryCount}
212
+
213
+ What action(s) should be taken next? Respond with the JSON action plan.`;
214
+ }
215
+
216
+ /**
217
+ * Makes the API call to Claude
218
+ */
219
+ private async makeAPICall(messages: Message[]): Promise<ClaudeResponse> {
220
+ const url = `${this.config.baseUrl}/messages`;
221
+ const body: ClaudeRequest = {
222
+ model: this.config.model,
223
+ max_tokens: DEFAULT_MAX_TOKENS,
224
+ messages,
225
+ };
226
+
227
+ let response: globalThis.Response;
228
+ try {
229
+ response = await fetch(url, {
230
+ method: 'POST',
231
+ headers: {
232
+ 'Content-Type': 'application/json',
233
+ Authorization: `Bearer ${this.config.apiKey}`,
234
+ },
235
+ body: JSON.stringify(body),
236
+ });
237
+ } catch (error) {
238
+ throw new ClaudeAPIError(
239
+ `Failed to connect to Claude API: ${error instanceof Error ? error.message : String(error)}`
240
+ );
241
+ }
242
+
243
+ if (!response.ok) {
244
+ const responseBody = await response.text();
245
+ throw new ClaudeAPIError(
246
+ `Claude API error: ${response.status} ${response.statusText}`,
247
+ response.status,
248
+ responseBody
249
+ );
250
+ }
251
+
252
+ try {
253
+ return (await response.json()) as ClaudeResponse;
254
+ } catch (error) {
255
+ throw new ClaudeAPIError(
256
+ `Failed to parse Claude API response: ${error instanceof Error ? error.message : String(error)}`
257
+ );
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Parses the API response into an ActionPlan
263
+ */
264
+ private parseResponse(response: ClaudeResponse): ActionPlan {
265
+ const content = response.content.find((c) => c.type === 'text');
266
+
267
+ if (!content) {
268
+ return this.createErrorPlan('No text content in Claude response');
269
+ }
270
+
271
+ const jsonText = this.extractJSON(content.text);
272
+
273
+ if (!jsonText) {
274
+ return this.createErrorPlan('Could not extract JSON from response');
275
+ }
276
+
277
+ try {
278
+ const raw = JSON.parse(jsonText) as RawActionPlan;
279
+ return this.validateAndTransformPlan(raw);
280
+ } catch (error) {
281
+ return this.createErrorPlan(
282
+ `Failed to parse action plan JSON: ${error instanceof Error ? error.message : String(error)}`
283
+ );
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Extracts JSON from a string that may contain markdown or extra text
289
+ */
290
+ private extractJSON(text: string): string | null {
291
+ // Try to find JSON in code blocks
292
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
293
+ if (codeBlockMatch) {
294
+ return codeBlockMatch[1].trim();
295
+ }
296
+
297
+ // Try to find JSON between curly braces
298
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
299
+ if (jsonMatch) {
300
+ return jsonMatch[0].trim();
301
+ }
302
+
303
+ // If the text looks like it starts with JSON, return it as-is
304
+ if (text.trim().startsWith('{')) {
305
+ return text.trim();
306
+ }
307
+
308
+ return null;
309
+ }
310
+
311
+ /**
312
+ * Validates and transforms the raw plan into a proper ActionPlan
313
+ */
314
+ private validateAndTransformPlan(raw: RawActionPlan): ActionPlan {
315
+ const actions: Action[] = [];
316
+
317
+ if (raw.actions && Array.isArray(raw.actions)) {
318
+ for (const rawAction of raw.actions) {
319
+ const action = this.transformAction(rawAction);
320
+ if (action) {
321
+ actions.push(action);
322
+ }
323
+ }
324
+ }
325
+
326
+ return {
327
+ reasoning: raw.reasoning || 'No reasoning provided',
328
+ expectedOutcome: raw.expectedOutcome || 'No expected outcome provided',
329
+ actions,
330
+ status: this.validateStatus(raw.status),
331
+ error: raw.error,
332
+ };
333
+ }
334
+
335
+ /**
336
+ * Transforms a raw action into a properly typed Action
337
+ */
338
+ private transformAction(rawAction: RawAction): Action | null {
339
+ const description = rawAction.description || 'No description provided';
340
+
341
+ switch (rawAction.type) {
342
+ case 'click': {
343
+ if (!rawAction.elementId) {
344
+ console.warn('Click action missing elementId');
345
+ return null;
346
+ }
347
+ return {
348
+ type: 'click',
349
+ elementId: rawAction.elementId,
350
+ description,
351
+ };
352
+ }
353
+
354
+ case 'type': {
355
+ if (!rawAction.elementId || rawAction.text === undefined) {
356
+ console.warn('Type action missing elementId or text');
357
+ return null;
358
+ }
359
+ return {
360
+ type: 'type',
361
+ elementId: rawAction.elementId,
362
+ text: rawAction.text,
363
+ description,
364
+ };
365
+ }
366
+
367
+ case 'wait': {
368
+ if (rawAction.durationMs === undefined) {
369
+ console.warn('Wait action missing durationMs, using default 1000ms');
370
+ }
371
+ return {
372
+ type: 'wait',
373
+ durationMs: rawAction.durationMs || 1000,
374
+ description,
375
+ };
376
+ }
377
+
378
+ case 'navigate': {
379
+ if (!rawAction.url) {
380
+ console.warn('Navigate action missing url');
381
+ return null;
382
+ }
383
+ return {
384
+ type: 'navigate',
385
+ url: rawAction.url,
386
+ description,
387
+ };
388
+ }
389
+
390
+ default:
391
+ console.warn(`Unknown action type: ${rawAction.type}`);
392
+ return null;
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Validates and returns a valid status
398
+ */
399
+ private validateStatus(status: ActionPlanStatus | undefined): ActionPlanStatus {
400
+ const validStatuses: ActionPlanStatus[] = [
401
+ 'pending',
402
+ 'in_progress',
403
+ 'completed',
404
+ 'failed',
405
+ 'cancelled',
406
+ ];
407
+ return status && validStatuses.includes(status) ? status : 'pending';
408
+ }
409
+
410
+ /**
411
+ * Creates an error action plan
412
+ */
413
+ private createErrorPlan(message: string): ActionPlan {
414
+ return {
415
+ reasoning: 'Failed to generate action plan',
416
+ expectedOutcome: 'No actions can be taken due to error',
417
+ actions: [],
418
+ status: 'failed',
419
+ error: message,
420
+ };
421
+ }
422
+ }