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.
- package/.env.example +12 -0
- package/.github/workflows/ci.yml +66 -0
- package/.github/workflows/publish.yml +48 -0
- package/.husky/pre-commit +6 -0
- package/.prettierignore +4 -0
- package/.prettierrc +10 -0
- package/AGENTS.md +294 -0
- package/CHANGELOG.md +40 -0
- package/GIT_RELEASE.md +167 -0
- package/LICENSE +21 -0
- package/Makefile +30 -0
- package/NPM_PUBLISHING.md +230 -0
- package/PYEOF +0 -0
- package/README.md +295 -0
- package/TESTING-GUIDE.md +151 -0
- package/cmd/linkedin/main.go +9 -0
- package/dist/agent/action-executor.d.ts +81 -0
- package/dist/agent/action-executor.d.ts.map +1 -0
- package/dist/agent/action-executor.js +170 -0
- package/dist/agent/action-executor.js.map +1 -0
- package/dist/agent/action-executor.test.d.ts +2 -0
- package/dist/agent/action-executor.test.d.ts.map +1 -0
- package/dist/agent/action-executor.test.js +366 -0
- package/dist/agent/action-executor.test.js.map +1 -0
- package/dist/agent/claude-client.d.ts +74 -0
- package/dist/agent/claude-client.d.ts.map +1 -0
- package/dist/agent/claude-client.js +314 -0
- package/dist/agent/claude-client.js.map +1 -0
- package/dist/agent/claude-client.test.d.ts +2 -0
- package/dist/agent/claude-client.test.d.ts.map +1 -0
- package/dist/agent/claude-client.test.js +590 -0
- package/dist/agent/claude-client.test.js.map +1 -0
- package/dist/agent/dom-extractor.d.ts +50 -0
- package/dist/agent/dom-extractor.d.ts.map +1 -0
- package/dist/agent/dom-extractor.js +374 -0
- package/dist/agent/dom-extractor.js.map +1 -0
- package/dist/agent/dom-extractor.test.d.ts +7 -0
- package/dist/agent/dom-extractor.test.d.ts.map +1 -0
- package/dist/agent/dom-extractor.test.js +504 -0
- package/dist/agent/dom-extractor.test.js.map +1 -0
- package/dist/agent/extension-client.d.ts +75 -0
- package/dist/agent/extension-client.d.ts.map +1 -0
- package/dist/agent/extension-client.js +245 -0
- package/dist/agent/extension-client.js.map +1 -0
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +16 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/page-agent.d.ts +76 -0
- package/dist/agent/page-agent.d.ts.map +1 -0
- package/dist/agent/page-agent.js +236 -0
- package/dist/agent/page-agent.js.map +1 -0
- package/dist/agent/types.d.ts +236 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +37 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/agent-commands.d.ts +3 -0
- package/dist/cli/agent-commands.d.ts.map +1 -0
- package/dist/cli/agent-commands.js +250 -0
- package/dist/cli/agent-commands.js.map +1 -0
- package/dist/cli/auth.d.ts +3 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +288 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/company.d.ts +3 -0
- package/dist/cli/company.d.ts.map +1 -0
- package/dist/cli/company.js +55 -0
- package/dist/cli/company.js.map +1 -0
- package/dist/cli/connection.d.ts +3 -0
- package/dist/cli/connection.d.ts.map +1 -0
- package/dist/cli/connection.js +79 -0
- package/dist/cli/connection.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +17 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/messages.d.ts +3 -0
- package/dist/cli/messages.d.ts.map +1 -0
- package/dist/cli/messages.js +268 -0
- package/dist/cli/messages.js.map +1 -0
- package/dist/cli/profile.d.ts +3 -0
- package/dist/cli/profile.d.ts.map +1 -0
- package/dist/cli/profile.js +81 -0
- package/dist/cli/profile.js.map +1 -0
- package/dist/cli/profile.test.d.ts +2 -0
- package/dist/cli/profile.test.d.ts.map +1 -0
- package/dist/cli/profile.test.js +15 -0
- package/dist/cli/profile.test.js.map +1 -0
- package/dist/cli/reply.d.ts +3 -0
- package/dist/cli/reply.d.ts.map +1 -0
- package/dist/cli/reply.js +129 -0
- package/dist/cli/reply.js.map +1 -0
- package/dist/core/audit.d.ts +17 -0
- package/dist/core/audit.d.ts.map +1 -0
- package/dist/core/audit.js +121 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/audit.test.d.ts +2 -0
- package/dist/core/audit.test.d.ts.map +1 -0
- package/dist/core/audit.test.js +142 -0
- package/dist/core/audit.test.js.map +1 -0
- package/dist/core/browser-cookies.d.ts +19 -0
- package/dist/core/browser-cookies.d.ts.map +1 -0
- package/dist/core/browser-cookies.js +181 -0
- package/dist/core/browser-cookies.js.map +1 -0
- package/dist/core/browser.d.ts +50 -0
- package/dist/core/browser.d.ts.map +1 -0
- package/dist/core/browser.js +318 -0
- package/dist/core/browser.js.map +1 -0
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +103 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/config.test.d.ts +2 -0
- package/dist/core/config.test.d.ts.map +1 -0
- package/dist/core/config.test.js +111 -0
- package/dist/core/config.test.js.map +1 -0
- package/dist/core/storage.d.ts +19 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +124 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/storage.test.d.ts +2 -0
- package/dist/core/storage.test.d.ts.map +1 -0
- package/dist/core/storage.test.js +142 -0
- package/dist/core/storage.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/linkedin/auth.d.ts +22 -0
- package/dist/linkedin/auth.d.ts.map +1 -0
- package/dist/linkedin/auth.js +167 -0
- package/dist/linkedin/auth.js.map +1 -0
- package/dist/linkedin/company-extractor.d.ts +36 -0
- package/dist/linkedin/company-extractor.d.ts.map +1 -0
- package/dist/linkedin/company-extractor.js +211 -0
- package/dist/linkedin/company-extractor.js.map +1 -0
- package/dist/linkedin/company-extractor.test.d.ts +2 -0
- package/dist/linkedin/company-extractor.test.d.ts.map +1 -0
- package/dist/linkedin/company-extractor.test.js +52 -0
- package/dist/linkedin/company-extractor.test.js.map +1 -0
- package/dist/linkedin/connector.d.ts +45 -0
- package/dist/linkedin/connector.d.ts.map +1 -0
- package/dist/linkedin/connector.js +245 -0
- package/dist/linkedin/connector.js.map +1 -0
- package/dist/linkedin/message-sender.d.ts +32 -0
- package/dist/linkedin/message-sender.d.ts.map +1 -0
- package/dist/linkedin/message-sender.js +112 -0
- package/dist/linkedin/message-sender.js.map +1 -0
- package/dist/linkedin/messages.d.ts +78 -0
- package/dist/linkedin/messages.d.ts.map +1 -0
- package/dist/linkedin/messages.js +745 -0
- package/dist/linkedin/messages.js.map +1 -0
- package/dist/linkedin/profile.d.ts +37 -0
- package/dist/linkedin/profile.d.ts.map +1 -0
- package/dist/linkedin/profile.js +268 -0
- package/dist/linkedin/profile.js.map +1 -0
- package/dist/linkedin/profile.test.d.ts +2 -0
- package/dist/linkedin/profile.test.d.ts.map +1 -0
- package/dist/linkedin/profile.test.js +68 -0
- package/dist/linkedin/profile.test.js.map +1 -0
- package/dist/linkedin/reply.d.ts +21 -0
- package/dist/linkedin/reply.d.ts.map +1 -0
- package/dist/linkedin/reply.js +76 -0
- package/dist/linkedin/reply.js.map +1 -0
- package/dist/linkedin/selector-engine.d.ts +69 -0
- package/dist/linkedin/selector-engine.d.ts.map +1 -0
- package/dist/linkedin/selector-engine.js +339 -0
- package/dist/linkedin/selector-engine.js.map +1 -0
- package/dist/linkedin/selector-engine.test.d.ts +2 -0
- package/dist/linkedin/selector-engine.test.d.ts.map +1 -0
- package/dist/linkedin/selector-engine.test.js +135 -0
- package/dist/linkedin/selector-engine.test.js.map +1 -0
- package/dist/linkedin/selectors.d.ts +65 -0
- package/dist/linkedin/selectors.d.ts.map +1 -0
- package/dist/linkedin/selectors.js +261 -0
- package/dist/linkedin/selectors.js.map +1 -0
- package/dist/templates/engine.d.ts +37 -0
- package/dist/templates/engine.d.ts.map +1 -0
- package/dist/templates/engine.js +215 -0
- package/dist/templates/engine.js.map +1 -0
- package/dist/templates/engine.test.d.ts +2 -0
- package/dist/templates/engine.test.d.ts.map +1 -0
- package/dist/templates/engine.test.js +212 -0
- package/dist/templates/engine.test.js.map +1 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +7 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/types/index.d.ts +113 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.test.d.ts +2 -0
- package/dist/types/index.test.d.ts.map +1 -0
- package/dist/types/index.test.js +90 -0
- package/dist/types/index.test.js.map +1 -0
- package/dist/utils/paths.d.ts +8 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +68 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +22 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +57 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/retry.d.ts +18 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +49 -0
- package/dist/utils/retry.js.map +1 -0
- package/docs/connection-command.md +52 -0
- package/docs/plans/2025-03-03-linkedin-cli-design.md +280 -0
- package/docs/plans/2025-03-03-linkedin-cli-implementation-plan.md +2087 -0
- package/docs/plans/2025-03-03-linkedin-cli-implementation.md +2420 -0
- package/docs/plans/2026-02-19-linkedin-connection-feature.md +596 -0
- package/docs/plans/2026-02-28-messages-send-feature.md +480 -0
- package/docs/plans/2026-02-28-messages-show-design.md +243 -0
- package/docs/plans/2026-03-03-linkedin-cli-oss-publishing-design.md +394 -0
- package/docs/plans/2026-03-03-linkedin-cli-oss-publishing-plan.md +1592 -0
- package/docs/superpowers/plans/2026-03-13-linkedin-automation-resilience-migration.md +425 -0
- package/docs/superpowers/plans/2026-03-13-playwright-fara-migration.md +1112 -0
- package/docs/superpowers/plans/2026-03-14-page-agent-plan.md +1598 -0
- package/docs/superpowers/plans/2026-03-15-company-profile-extraction.md +591 -0
- package/docs/superpowers/plans/2026-03-15-profile-extraction-plan.md +943 -0
- package/docs/superpowers/specs/2026-03-14-company-profile-extraction-design.md +371 -0
- package/docs/superpowers/specs/2026-03-14-page-agent-design.md +385 -0
- package/docs/superpowers/specs/2026-03-15-profile-extraction-design.md +409 -0
- package/eslint.config.mjs +58 -0
- package/go.mod +9 -0
- package/go.sum +10 -0
- package/import-cookies.js +376 -0
- package/internal/cmd/actions.go +123 -0
- package/internal/cmd/auth.go +108 -0
- package/internal/cmd/connect.go +42 -0
- package/internal/cmd/message.go +44 -0
- package/internal/cmd/people.go +454 -0
- package/internal/cmd/profiles.go +121 -0
- package/internal/cmd/root.go +89 -0
- package/internal/cmd/sequence.go +192 -0
- package/internal/config/config.go +187 -0
- package/internal/config/config_test.go +121 -0
- package/internal/config/profile.go +65 -0
- package/internal/linkedin/navigator.go +195 -0
- package/internal/linkedin/selectors.go +39 -0
- package/internal/linkedin/validator.go +69 -0
- package/internal/pinchtab/client.go +183 -0
- package/internal/pinchtab/client_test.go +67 -0
- package/internal/pinchtab/types.go +50 -0
- package/internal/ratelimit/limiter.go +115 -0
- package/internal/ratelimit/limits.go +32 -0
- package/package.json +67 -0
- package/release.sh +66 -0
- package/scripts/debug-linkedin.js +156 -0
- package/scripts/debug-login.js +193 -0
- package/scripts/extract-from-edge.js +96 -0
- package/scripts/import-cookies.js +101 -0
- package/scripts/poc-show-data.js +205 -0
- package/scripts/proof-of-access.js +87 -0
- package/scripts/prove-connection.js +110 -0
- package/scripts/show-linkedin-data.js +173 -0
- package/src/agent/action-executor.test.ts +464 -0
- package/src/agent/action-executor.ts +203 -0
- package/src/agent/claude-client.test.ts +707 -0
- package/src/agent/claude-client.ts +422 -0
- package/src/agent/dom-extractor.test.ts +574 -0
- package/src/agent/dom-extractor.ts +437 -0
- package/src/agent/extension-client.ts +306 -0
- package/src/agent/index.ts +28 -0
- package/src/agent/page-agent.ts +292 -0
- package/src/agent/types.ts +288 -0
- package/src/cli/agent-commands.ts +274 -0
- package/src/cli/auth.ts +343 -0
- package/src/cli/company.ts +66 -0
- package/src/cli/connection.ts +89 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/messages.ts +338 -0
- package/src/cli/profile.test.ts +14 -0
- package/src/cli/profile.ts +95 -0
- package/src/cli/reply.ts +110 -0
- package/src/core/audit.test.ts +134 -0
- package/src/core/audit.ts +98 -0
- package/src/core/browser-cookies.ts +203 -0
- package/src/core/browser.ts +304 -0
- package/src/core/config.test.ts +90 -0
- package/src/core/config.ts +81 -0
- package/src/core/storage.test.ts +129 -0
- package/src/core/storage.ts +100 -0
- package/src/index.ts +70 -0
- package/src/linkedin/auth.ts +218 -0
- package/src/linkedin/company-extractor.test.ts +58 -0
- package/src/linkedin/company-extractor.ts +222 -0
- package/src/linkedin/connector.ts +336 -0
- package/src/linkedin/message-sender.ts +141 -0
- package/src/linkedin/messages.ts +894 -0
- package/src/linkedin/profile.test.ts +79 -0
- package/src/linkedin/profile.ts +314 -0
- package/src/linkedin/reply.ts +96 -0
- package/src/linkedin/selector-engine.test.ts +167 -0
- package/src/linkedin/selector-engine.ts +393 -0
- package/src/linkedin/selectors.ts +268 -0
- package/src/templates/defaults/followup.txt +14 -0
- package/src/templates/defaults/meeting.txt +16 -0
- package/src/templates/defaults/welcome.txt +14 -0
- package/src/templates/engine.test.ts +228 -0
- package/src/templates/engine.ts +208 -0
- package/src/templates/index.ts +1 -0
- package/src/types/index.test.ts +94 -0
- package/src/types/index.ts +143 -0
- package/src/types/sql.js.d.ts +23 -0
- package/src/utils/paths.ts +33 -0
- package/src/utils/rate-limiter.ts +75 -0
- package/src/utils/retry.ts +78 -0
- package/test-cli.sh +85 -0
- package/test-real-data.sh +97 -0
- package/tsconfig.json +23 -0
- 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.
|