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,425 @@
1
+ # LinkedIn Automation Resilience Migration Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Build a resilient automation architecture that keeps the Go CLI thin while reducing breakage from LinkedIn UI changes for connect and message workflows.
6
+
7
+ **Architecture:** Keep `internal/cmd` as orchestration-only, move interaction logic behind a provider interface, and add a detector/recovery layer with confidence scoring and fallback strategies. Preserve current PinchTab path first, then add optional AI fallback provider behind feature flags.
8
+
9
+ **Tech Stack:** Go 1.21+, Cobra CLI, PinchTab HTTP client, optional sidecar HTTP provider (Stagehand/AgentQL-compatible contract).
10
+
11
+ ---
12
+
13
+ ## Chunk 1: Core Boundaries (Keep CLI Thin)
14
+
15
+ ### Task 1: Introduce automation contracts
16
+
17
+ **Files:**
18
+ - Create: `internal/automation/types.go`
19
+ - Create: `internal/automation/provider.go`
20
+ - Test: `internal/automation/types_test.go`
21
+
22
+ - [ ] **Step 1: Write failing tests for core types and result contracts**
23
+
24
+ ```go
25
+ func TestConnectOutcomeReasonValues(t *testing.T) {
26
+ if ConnectUnavailableReasonAlreadyConnected != "already_connected" {
27
+ t.Fatalf("unexpected value")
28
+ }
29
+ }
30
+ ```
31
+
32
+ - [ ] **Step 2: Run test to verify it fails**
33
+
34
+ Run: `go test ./internal/automation -run TestConnectOutcomeReasonValues -v`
35
+ Expected: FAIL with undefined type/constant.
36
+
37
+ - [ ] **Step 3: Implement minimal contracts**
38
+
39
+ Add:
40
+ - `type ConnectState string`
41
+ - `type ConnectOutcome struct { Success bool; Reason string; Confidence float64; DebugCandidates []string }`
42
+ - `type MessageOutcome struct { Success bool; Reason string }`
43
+ - `type Provider interface { Navigate(...); Connect(...); Message(...) }`
44
+
45
+ - [ ] **Step 4: Run test to verify it passes**
46
+
47
+ Run: `go test ./internal/automation -run TestConnectOutcomeReasonValues -v`
48
+ Expected: PASS.
49
+
50
+ - [ ] **Step 5: Commit**
51
+
52
+ ```bash
53
+ git add internal/automation/types.go internal/automation/provider.go internal/automation/types_test.go
54
+ git commit -m "feat: add automation provider contracts and outcome types"
55
+ ```
56
+
57
+ ### Task 2: Add PinchTab provider adapter
58
+
59
+ **Files:**
60
+ - Create: `internal/linkedin/provider_pinchtab.go`
61
+ - Modify: `internal/linkedin/navigator.go`
62
+ - Test: `internal/linkedin/provider_pinchtab_test.go`
63
+
64
+ - [ ] **Step 1: Write failing provider adapter tests**
65
+
66
+ ```go
67
+ func TestPinchTabProviderConnectMapsUnavailableReason(t *testing.T) {
68
+ // arrange mock snapshot -> follow_only state
69
+ // act provider.Connect(...)
70
+ // assert outcome.Reason == "follow_only"
71
+ }
72
+ ```
73
+
74
+ - [ ] **Step 2: Run test to verify it fails**
75
+
76
+ Run: `go test ./internal/linkedin -run TestPinchTabProviderConnectMapsUnavailableReason -v`
77
+ Expected: FAIL with missing provider implementation.
78
+
79
+ - [ ] **Step 3: Implement provider adapter**
80
+
81
+ Map current navigator errors to `automation.ConnectOutcome` without changing rate-limit behavior.
82
+
83
+ - [ ] **Step 4: Run tests to verify pass**
84
+
85
+ Run: `go test ./internal/linkedin -v`
86
+ Expected: PASS.
87
+
88
+ - [ ] **Step 5: Commit**
89
+
90
+ ```bash
91
+ git add internal/linkedin/provider_pinchtab.go internal/linkedin/navigator.go internal/linkedin/provider_pinchtab_test.go
92
+ git commit -m "feat: add pinchtab automation provider adapter"
93
+ ```
94
+
95
+ ### Task 3: Refactor command actions to use automation service
96
+
97
+ **Files:**
98
+ - Create: `internal/automation/service.go`
99
+ - Modify: `internal/cmd/actions.go`
100
+ - Test: `internal/automation/service_test.go`
101
+
102
+ - [ ] **Step 1: Write failing service behavior tests**
103
+
104
+ ```go
105
+ func TestServiceConnectPreservesLimiterOrdering(t *testing.T) {
106
+ // assert CheckConnection happens before Provider.Connect
107
+ // assert RecordConnection only on success
108
+ }
109
+ ```
110
+
111
+ - [ ] **Step 2: Run test to verify it fails**
112
+
113
+ Run: `go test ./internal/automation -run TestServiceConnectPreservesLimiterOrdering -v`
114
+ Expected: FAIL with missing service implementation.
115
+
116
+ - [ ] **Step 3: Implement minimal service + wire actions.go**
117
+
118
+ Keep `internal/cmd` thin and delegate to `automation.Service`.
119
+
120
+ - [ ] **Step 4: Run tests to verify pass**
121
+
122
+ Run: `go test ./internal/automation ./internal/cmd -v`
123
+ Expected: PASS.
124
+
125
+ - [ ] **Step 5: Commit**
126
+
127
+ ```bash
128
+ git add internal/automation/service.go internal/automation/service_test.go internal/cmd/actions.go
129
+ git commit -m "refactor: route cmd actions through automation service"
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Chunk 2: Resilience Engine (Detection + Recovery)
135
+
136
+ ### Task 4: Add confidence-based detector strategies
137
+
138
+ **Files:**
139
+ - Create: `internal/linkedin/detector.go`
140
+ - Create: `internal/linkedin/strategy.go`
141
+ - Test: `internal/linkedin/detector_test.go`
142
+
143
+ - [ ] **Step 1: Write failing tests for strategy ordering and confidence**
144
+
145
+ ```go
146
+ func TestDetectorPrefersHighConfidenceRoleMatch(t *testing.T) {
147
+ // ensure aria/role strategy beats weak text-only match
148
+ }
149
+ ```
150
+
151
+ - [ ] **Step 2: Run test to verify it fails**
152
+
153
+ Run: `go test ./internal/linkedin -run TestDetectorPrefersHighConfidenceRoleMatch -v`
154
+ Expected: FAIL.
155
+
156
+ - [ ] **Step 3: Implement detector with strategy chain**
157
+
158
+ Strategies:
159
+ - role+name semantic match
160
+ - localized keyword match
161
+ - more-actions fallback discovery
162
+ - low-confidence rejection
163
+
164
+ - [ ] **Step 4: Run tests to verify pass**
165
+
166
+ Run: `go test ./internal/linkedin -v`
167
+ Expected: PASS.
168
+
169
+ - [ ] **Step 5: Commit**
170
+
171
+ ```bash
172
+ git add internal/linkedin/detector.go internal/linkedin/strategy.go internal/linkedin/detector_test.go
173
+ git commit -m "feat: add confidence-based connect detector strategies"
174
+ ```
175
+
176
+ ### Task 5: Add snapshot fixture regression tests
177
+
178
+ **Files:**
179
+ - Create: `internal/linkedin/fixtures/*.json`
180
+ - Create: `internal/linkedin/fixture_loader_test.go`
181
+ - Modify: `internal/linkedin/navigator_test.go`
182
+
183
+ - [ ] **Step 1: Write failing fixture tests**
184
+
185
+ ```go
186
+ func TestDetectorAgainstRealisticFixtures(t *testing.T) {
187
+ // table: connectable, pending, connected, follow_only, hidden-in-more
188
+ }
189
+ ```
190
+
191
+ - [ ] **Step 2: Run test to verify it fails**
192
+
193
+ Run: `go test ./internal/linkedin -run TestDetectorAgainstRealisticFixtures -v`
194
+ Expected: FAIL until fixtures + loader exist.
195
+
196
+ - [ ] **Step 3: Implement fixture loader and test matrix**
197
+
198
+ Add at least five fixtures to cover common states.
199
+
200
+ - [ ] **Step 4: Run tests to verify pass**
201
+
202
+ Run: `go test ./internal/linkedin -v`
203
+ Expected: PASS.
204
+
205
+ - [ ] **Step 5: Commit**
206
+
207
+ ```bash
208
+ git add internal/linkedin/fixtures internal/linkedin/fixture_loader_test.go internal/linkedin/navigator_test.go
209
+ git commit -m "test: add snapshot fixture regression suite for linkedin states"
210
+ ```
211
+
212
+ ### Task 6: Add observability hooks for failure triage
213
+
214
+ **Files:**
215
+ - Create: `internal/automation/telemetry.go`
216
+ - Modify: `internal/automation/service.go`
217
+ - Test: `internal/automation/telemetry_test.go`
218
+
219
+ - [ ] **Step 1: Write failing telemetry tests**
220
+
221
+ ```go
222
+ func TestServiceRecordsFailureContext(t *testing.T) {
223
+ // assert reason, confidence, and candidate nodes are logged
224
+ }
225
+ ```
226
+
227
+ - [ ] **Step 2: Run test to verify it fails**
228
+
229
+ Run: `go test ./internal/automation -run TestServiceRecordsFailureContext -v`
230
+ Expected: FAIL.
231
+
232
+ - [ ] **Step 3: Implement minimal structured telemetry**
233
+
234
+ Record:
235
+ - action (`connect`/`message`)
236
+ - outcome reason
237
+ - confidence
238
+ - candidate nodes
239
+ - profile + url
240
+
241
+ - [ ] **Step 4: Run tests to verify pass**
242
+
243
+ Run: `go test ./internal/automation -v`
244
+ Expected: PASS.
245
+
246
+ - [ ] **Step 5: Commit**
247
+
248
+ ```bash
249
+ git add internal/automation/telemetry.go internal/automation/telemetry_test.go internal/automation/service.go
250
+ git commit -m "feat: add structured telemetry for automation failures"
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Chunk 3: Optional AI Fallback Provider (Feature-Flagged)
256
+
257
+ ### Task 7: Add fallback provider contract and policy
258
+
259
+ **Files:**
260
+ - Create: `internal/automation/fallback.go`
261
+ - Create: `internal/automation/policy.go`
262
+ - Test: `internal/automation/policy_test.go`
263
+
264
+ - [ ] **Step 1: Write failing policy tests**
265
+
266
+ ```go
267
+ func TestFallbackPolicyTriggersOnlyLowConfidence(t *testing.T) {
268
+ // low confidence -> fallback true
269
+ // high confidence explicit unavailable -> fallback false
270
+ }
271
+ ```
272
+
273
+ - [ ] **Step 2: Run test to verify it fails**
274
+
275
+ Run: `go test ./internal/automation -run TestFallbackPolicyTriggersOnlyLowConfidence -v`
276
+ Expected: FAIL.
277
+
278
+ - [ ] **Step 3: Implement policy with conservative defaults**
279
+
280
+ Defaults:
281
+ - fallback disabled
282
+ - only enabled by explicit config/env flag
283
+
284
+ - [ ] **Step 4: Run tests to verify pass**
285
+
286
+ Run: `go test ./internal/automation -v`
287
+ Expected: PASS.
288
+
289
+ - [ ] **Step 5: Commit**
290
+
291
+ ```bash
292
+ git add internal/automation/fallback.go internal/automation/policy.go internal/automation/policy_test.go
293
+ git commit -m "feat: add conservative fallback policy contracts"
294
+ ```
295
+
296
+ ### Task 8: Implement generic HTTP fallback provider adapter
297
+
298
+ **Files:**
299
+ - Create: `internal/automation/http_provider.go`
300
+ - Create: `internal/automation/http_types.go`
301
+ - Test: `internal/automation/http_provider_test.go`
302
+
303
+ - [ ] **Step 1: Write failing adapter tests**
304
+
305
+ ```go
306
+ func TestHTTPProviderMapsConnectIntentAndResponse(t *testing.T) {
307
+ // mock endpoint; assert request/response mapping
308
+ }
309
+ ```
310
+
311
+ - [ ] **Step 2: Run test to verify it fails**
312
+
313
+ Run: `go test ./internal/automation -run TestHTTPProviderMapsConnectIntentAndResponse -v`
314
+ Expected: FAIL.
315
+
316
+ - [ ] **Step 3: Implement adapter (vendor-agnostic)**
317
+
318
+ No vendor lock-in: define neutral request/response schema.
319
+
320
+ - [ ] **Step 4: Run tests to verify pass**
321
+
322
+ Run: `go test ./internal/automation -v`
323
+ Expected: PASS.
324
+
325
+ - [ ] **Step 5: Commit**
326
+
327
+ ```bash
328
+ git add internal/automation/http_provider.go internal/automation/http_types.go internal/automation/http_provider_test.go
329
+ git commit -m "feat: add vendor-agnostic http fallback provider adapter"
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Chunk 4: Rollout, Verification, and Safety
335
+
336
+ ### Task 9: Keep dry-run parity and command behavior stable
337
+
338
+ **Files:**
339
+ - Modify: `internal/cmd/connect.go`
340
+ - Modify: `internal/cmd/sequence.go`
341
+ - Modify: `internal/cmd/people.go`
342
+ - Test: `internal/cmd/actions_test.go`
343
+
344
+ - [ ] **Step 1: Write failing dry-run parity tests**
345
+
346
+ ```go
347
+ func TestDryRunStillSkipsExternalActions(t *testing.T) {
348
+ // no provider calls when dry-run=true
349
+ }
350
+ ```
351
+
352
+ - [ ] **Step 2: Run test to verify it fails**
353
+
354
+ Run: `go test ./internal/cmd -run TestDryRunStillSkipsExternalActions -v`
355
+ Expected: FAIL.
356
+
357
+ - [ ] **Step 3: Implement minimal parity fixes**
358
+
359
+ Ensure no behavior drift in CLI UX.
360
+
361
+ - [ ] **Step 4: Run tests to verify pass**
362
+
363
+ Run: `go test ./internal/cmd -v`
364
+ Expected: PASS.
365
+
366
+ - [ ] **Step 5: Commit**
367
+
368
+ ```bash
369
+ git add internal/cmd/connect.go internal/cmd/sequence.go internal/cmd/people.go internal/cmd/actions_test.go
370
+ git commit -m "test: preserve dry-run behavior across automation refactor"
371
+ ```
372
+
373
+ ### Task 10: Final verification gate
374
+
375
+ **Files:**
376
+ - Modify: `README.md` (optional short section for provider/fallback config)
377
+
378
+ - [ ] **Step 1: Run focused package tests**
379
+
380
+ Run: `go test ./internal/linkedin ./internal/automation ./internal/cmd -v`
381
+ Expected: PASS.
382
+
383
+ - [ ] **Step 2: Run full test suite**
384
+
385
+ Run: `go test ./...`
386
+ Expected: PASS.
387
+
388
+ - [ ] **Step 3: Build CLI**
389
+
390
+ Run: `go build ./cmd/linkedin`
391
+ Expected: exit 0.
392
+
393
+ - [ ] **Step 4: Validate dry-runs manually**
394
+
395
+ Run:
396
+ - `go run ./cmd/linkedin --profile thaddeus connect --dry-run --url "https://www.linkedin.com/in/example" --message "Hi"`
397
+ - `go run ./cmd/linkedin --profile thaddeus sequence --dry-run --url "https://www.linkedin.com/in/example" --step connect --message "Hi"`
398
+ - `go run ./cmd/linkedin --profile thaddeus people --dry-run --company-url "https://www.linkedin.com/company/example" --mode both --limit 3`
399
+
400
+ Expected: no action execution, only dry-run output.
401
+
402
+ - [ ] **Step 5: Commit**
403
+
404
+ ```bash
405
+ git add README.md
406
+ git commit -m "docs: describe resilient automation architecture and fallback flags"
407
+ ```
408
+
409
+ ---
410
+
411
+ ## Decision Criteria During Execution
412
+
413
+ - Keep `internal/cmd` thin; no detection heuristics inside command handlers.
414
+ - Preserve rate limiter ordering and counters exactly.
415
+ - Feature-flag AI fallback; default disabled.
416
+ - Prefer deterministic strategies first, AI fallback only on low confidence.
417
+ - No provider-specific business logic in CLI layer.
418
+
419
+ ## Expected Deliverables
420
+
421
+ 1. Stable automation service boundary (`internal/automation/*`).
422
+ 2. PinchTab provider compatible with existing behavior.
423
+ 3. Confidence-based detection and fixture regression tests.
424
+ 4. Optional vendor-agnostic HTTP fallback provider.
425
+ 5. Verified test/build/dry-run parity.