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,23 @@
|
|
|
1
|
+
declare module 'sql.js' {
|
|
2
|
+
export interface QueryExecResult {
|
|
3
|
+
columns: string[];
|
|
4
|
+
values: any[][];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface Database {
|
|
8
|
+
run(sql: string, params?: any[]): Database;
|
|
9
|
+
exec(sql: string): QueryExecResult[];
|
|
10
|
+
close(): void;
|
|
11
|
+
getRowsModified(): number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SqlJsStatic {
|
|
15
|
+
Database: new (data?: ArrayLike<number> | Buffer | null) => Database;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface InitSqlJsOptions {
|
|
19
|
+
locateFile?: (file: string) => string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function initSqlJs(options?: InitSqlJsOptions): Promise<SqlJsStatic>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
|
|
4
|
+
const CONFIG_DIR = '.linkedin-cli';
|
|
5
|
+
|
|
6
|
+
export function getConfigDir(): string {
|
|
7
|
+
const homeDir = os.homedir();
|
|
8
|
+
return path.join(homeDir, CONFIG_DIR);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getSessionsDir(): string {
|
|
12
|
+
return path.join(getConfigDir(), 'sessions');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getTemplatesDir(): string {
|
|
16
|
+
return path.join(getConfigDir(), 'templates');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getDefaultsDir(): string {
|
|
20
|
+
return path.join(getTemplatesDir(), 'defaults');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getCustomDir(): string {
|
|
24
|
+
return path.join(getTemplatesDir(), 'custom');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getConfigFile(): string {
|
|
28
|
+
return path.join(getConfigDir(), 'config.json');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getLogFile(): string {
|
|
32
|
+
return path.join(getConfigDir(), 'audit.log');
|
|
33
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getAuditLogger } from '../core/audit';
|
|
2
|
+
|
|
3
|
+
export interface RateLimiterOptions {
|
|
4
|
+
maxRequests?: number;
|
|
5
|
+
windowMs?: number;
|
|
6
|
+
action?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class RateLimiter {
|
|
10
|
+
private requests: Map<string, number[]> = new Map();
|
|
11
|
+
|
|
12
|
+
constructor() {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if an action can be performed
|
|
16
|
+
*/
|
|
17
|
+
async checkLimit(
|
|
18
|
+
options: RateLimiterOptions = {}
|
|
19
|
+
): Promise<{ allowed: boolean; retryAfter?: number }> {
|
|
20
|
+
const action = options.action || 'default';
|
|
21
|
+
const maxRequests = options.maxRequests || 20;
|
|
22
|
+
const windowMs = options.windowMs || 60 * 60 * 1000; // 1 hour
|
|
23
|
+
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const windowStart = now - windowMs;
|
|
26
|
+
|
|
27
|
+
// Get existing requests for this action
|
|
28
|
+
let timestamps = this.requests.get(action) || [];
|
|
29
|
+
|
|
30
|
+
// Filter to only requests within the window
|
|
31
|
+
timestamps = timestamps.filter((ts) => ts > windowStart);
|
|
32
|
+
|
|
33
|
+
// Check if under limit
|
|
34
|
+
if (timestamps.length >= maxRequests) {
|
|
35
|
+
const oldestTimestamp = timestamps[0];
|
|
36
|
+
const retryAfter = oldestTimestamp + windowMs - now;
|
|
37
|
+
|
|
38
|
+
return { allowed: false, retryAfter };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Record this request
|
|
42
|
+
timestamps.push(now);
|
|
43
|
+
this.requests.set(action, timestamps);
|
|
44
|
+
|
|
45
|
+
// Also log to audit
|
|
46
|
+
const logger = getAuditLogger();
|
|
47
|
+
logger.log('rate_limit.check', { action, count: timestamps.length, limit: maxRequests }, true);
|
|
48
|
+
|
|
49
|
+
return { allowed: true };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Wait for rate limit to reset
|
|
54
|
+
*/
|
|
55
|
+
async waitForReset(options: RateLimiterOptions = {}): Promise<void> {
|
|
56
|
+
const result = await this.checkLimit(options);
|
|
57
|
+
|
|
58
|
+
if (!result.allowed && result.retryAfter) {
|
|
59
|
+
const logger = getAuditLogger();
|
|
60
|
+
logger.log('rate_limit.wait', { retryAfter: result.retryAfter }, true);
|
|
61
|
+
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, result.retryAfter));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Singleton instance
|
|
68
|
+
let rateLimiter: RateLimiter | null = null;
|
|
69
|
+
|
|
70
|
+
export function getRateLimiter(): RateLimiter {
|
|
71
|
+
if (!rateLimiter) {
|
|
72
|
+
rateLimiter = new RateLimiter();
|
|
73
|
+
}
|
|
74
|
+
return rateLimiter;
|
|
75
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getConfig } from '../core/config';
|
|
2
|
+
|
|
3
|
+
export interface RetryOptions {
|
|
4
|
+
maxAttempts?: number;
|
|
5
|
+
baseDelay?: number;
|
|
6
|
+
maxDelay?: number;
|
|
7
|
+
backoffMultiplier?: number;
|
|
8
|
+
retryableErrors?: string[];
|
|
9
|
+
onRetry?: (attempt: number, error: Error, delay: number) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class RetryableError extends Error {
|
|
13
|
+
constructor(
|
|
14
|
+
message: string,
|
|
15
|
+
public readonly cause?: Error
|
|
16
|
+
) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'RetryableError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
|
|
23
|
+
const config = getConfig().get();
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
maxAttempts = 3,
|
|
27
|
+
baseDelay = config.rateLimit,
|
|
28
|
+
maxDelay = 30000,
|
|
29
|
+
backoffMultiplier = 2,
|
|
30
|
+
retryableErrors = ['network', 'timeout', 'rate', 'limit', '429', '503', '504'],
|
|
31
|
+
onRetry,
|
|
32
|
+
} = options;
|
|
33
|
+
|
|
34
|
+
let lastError: Error | null = null;
|
|
35
|
+
|
|
36
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
37
|
+
try {
|
|
38
|
+
return await fn();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
41
|
+
|
|
42
|
+
// Check if this is a retryable error
|
|
43
|
+
const isRetryable = retryableErrors.some((pattern) =>
|
|
44
|
+
lastError!.message.toLowerCase().includes(pattern.toLowerCase())
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (!isRetryable || attempt === maxAttempts) {
|
|
48
|
+
throw new RetryableError(
|
|
49
|
+
`Failed after ${attempt} attempt(s): ${lastError.message}`,
|
|
50
|
+
lastError
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Calculate delay with exponential backoff and jitter
|
|
55
|
+
const delay = Math.min(baseDelay * Math.pow(backoffMultiplier, attempt - 1), maxDelay);
|
|
56
|
+
const jitter = Math.random() * 1000; // Add up to 1s of jitter
|
|
57
|
+
const totalDelay = delay + jitter;
|
|
58
|
+
|
|
59
|
+
if (onRetry) {
|
|
60
|
+
onRetry(attempt, lastError, totalDelay);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await new Promise((resolve) => setTimeout(resolve, totalDelay));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new RetryableError('Unexpected exit from retry loop', lastError!);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wrap a function to add retry behavior
|
|
72
|
+
*/
|
|
73
|
+
export function withRetryWrapper<T extends (...args: any[]) => Promise<any>>(
|
|
74
|
+
fn: T,
|
|
75
|
+
options: RetryOptions = {}
|
|
76
|
+
): (...args: Parameters<T>) => Promise<ReturnType<T>> {
|
|
77
|
+
return (...args: Parameters<T>) => withRetry(() => fn(...args), options);
|
|
78
|
+
}
|
package/test-cli.sh
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# LinkedIn CLI Test Script
|
|
3
|
+
# This script helps you test the LinkedIn Automation CLI
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
# Get the directory where this script is located
|
|
8
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
9
|
+
CLI_PATH="$SCRIPT_DIR/dist/index.js"
|
|
10
|
+
CONFIG_DIR="$HOME/.linkedin-cli"
|
|
11
|
+
|
|
12
|
+
echo "========================================="
|
|
13
|
+
echo "LinkedIn CLI Test Suite"
|
|
14
|
+
echo "========================================="
|
|
15
|
+
echo "Working directory: $SCRIPT_DIR"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
# Color codes
|
|
19
|
+
GREEN='\033[0;32m'
|
|
20
|
+
YELLOW='\033[1;33m'
|
|
21
|
+
RED='\033[0;31m'
|
|
22
|
+
NC='\033[0m' # No Color
|
|
23
|
+
|
|
24
|
+
# Test 1: CLI Help
|
|
25
|
+
echo -e "${YELLOW}Test 1: CLI Help Commands${NC}"
|
|
26
|
+
echo "----------------------------------------"
|
|
27
|
+
node $CLI_PATH --help
|
|
28
|
+
echo ""
|
|
29
|
+
|
|
30
|
+
# Test 2: Auth Commands Help
|
|
31
|
+
echo -e "${YELLOW}Test 2: Auth Commands${NC}"
|
|
32
|
+
echo "----------------------------------------"
|
|
33
|
+
node $CLI_PATH auth --help
|
|
34
|
+
echo ""
|
|
35
|
+
|
|
36
|
+
# Test 3: Message Commands Help
|
|
37
|
+
echo -e "${YELLOW}Test 3: Message Commands${NC}"
|
|
38
|
+
echo "----------------------------------------"
|
|
39
|
+
node $CLI_PATH messages --help
|
|
40
|
+
echo ""
|
|
41
|
+
|
|
42
|
+
# Test 4: Connection Commands Help
|
|
43
|
+
echo -e "${YELLOW}Test 4: Connection Commands${NC}"
|
|
44
|
+
echo "----------------------------------------"
|
|
45
|
+
node $CLI_PATH connect --help
|
|
46
|
+
echo ""
|
|
47
|
+
|
|
48
|
+
# Test 5: Config Commands Help
|
|
49
|
+
echo -e "${YELLOW}Test 5: Config Commands${NC}"
|
|
50
|
+
echo "----------------------------------------"
|
|
51
|
+
node $CLI_PATH config --help
|
|
52
|
+
echo ""
|
|
53
|
+
|
|
54
|
+
# Test 6: Check Config Directory
|
|
55
|
+
echo -e "${YELLOW}Test 6: Configuration Directory${NC}"
|
|
56
|
+
echo "----------------------------------------"
|
|
57
|
+
if [ -d "$CONFIG_DIR" ]; then
|
|
58
|
+
echo -e "${GREEN}✓ Config directory exists: $CONFIG_DIR${NC}"
|
|
59
|
+
ls -la "$CONFIG_DIR" 2>/dev/null || echo "Directory is empty"
|
|
60
|
+
else
|
|
61
|
+
echo -e "${YELLOW}⚠ Config directory does not exist yet (will be created on first run)${NC}"
|
|
62
|
+
fi
|
|
63
|
+
echo ""
|
|
64
|
+
|
|
65
|
+
# Test 7: Build Check
|
|
66
|
+
echo -e "${YELLOW}Test 7: Build Verification${NC}"
|
|
67
|
+
echo "----------------------------------------"
|
|
68
|
+
if [ -f "$CLI_PATH" ]; then
|
|
69
|
+
echo -e "${GREEN}✓ CLI build exists${NC}"
|
|
70
|
+
ls -lh $CLI_PATH
|
|
71
|
+
else
|
|
72
|
+
echo -e "${RED}✗ CLI build not found. Run: npm run build${NC}"
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
echo ""
|
|
76
|
+
|
|
77
|
+
echo "========================================="
|
|
78
|
+
echo -e "${GREEN}All Tests Passed!${NC}"
|
|
79
|
+
echo "========================================="
|
|
80
|
+
echo ""
|
|
81
|
+
echo "Next Steps:"
|
|
82
|
+
echo "1. Test authentication: node $CLI_PATH auth login"
|
|
83
|
+
echo "2. Check auth status: node $CLI_PATH auth status"
|
|
84
|
+
echo "3. List messages: node $CLI_PATH messages list"
|
|
85
|
+
echo ""
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Quick test to show LinkedIn data
|
|
3
|
+
|
|
4
|
+
echo "🧪 Testing LinkedIn CLI with Real Data"
|
|
5
|
+
echo "======================================"
|
|
6
|
+
echo ""
|
|
7
|
+
|
|
8
|
+
cd /Users/thaddeus/projects/linkedin-automation/.worktrees/linkedin-automation-cli
|
|
9
|
+
|
|
10
|
+
# Test 1: Auth status
|
|
11
|
+
echo "1️⃣ Checking Authentication..."
|
|
12
|
+
node ./dist/index.js auth status
|
|
13
|
+
echo ""
|
|
14
|
+
|
|
15
|
+
# Test 2: Try to get messages with visible browser
|
|
16
|
+
echo "2️⃣ Fetching Messages (browser will open)..."
|
|
17
|
+
echo " Press Ctrl+C to stop after you see the data"
|
|
18
|
+
echo ""
|
|
19
|
+
|
|
20
|
+
# Create a test script that uses Playwright directly
|
|
21
|
+
cat > /tmp/test-messages.js << 'EOF'
|
|
22
|
+
const { chromium } = require('playwright');
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
async function test() {
|
|
27
|
+
const sessionFile = path.join(require('os').homedir(), '.linkedin-cli/sessions/linkedin-session.json');
|
|
28
|
+
const encrypted = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
|
|
29
|
+
|
|
30
|
+
// Decrypt
|
|
31
|
+
const crypto = require('crypto');
|
|
32
|
+
const machineData = [process.env.USER, process.env.HOME, process.platform].join('|');
|
|
33
|
+
const salt = Buffer.from(encrypted.salt, 'base64');
|
|
34
|
+
const key = crypto.pbkdf2Sync(machineData, salt, 100000, 32, 'sha256');
|
|
35
|
+
const iv = Buffer.from(encrypted.iv, 'base64');
|
|
36
|
+
const encryptedData = Buffer.from(encrypted.encrypted, 'base64');
|
|
37
|
+
const tag = Buffer.from(encrypted.tag, 'base64');
|
|
38
|
+
|
|
39
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
40
|
+
decipher.setAuthTag(tag);
|
|
41
|
+
const session = JSON.parse(Buffer.concat([decipher.update(encryptedData), decipher.final()]).toString());
|
|
42
|
+
|
|
43
|
+
// Use Edge instead of Chromium
|
|
44
|
+
const browser = await chromium.launch({
|
|
45
|
+
headless: false,
|
|
46
|
+
executablePath: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const context = await browser.newContext();
|
|
50
|
+
await context.addCookies(session.cookies.map(c => ({
|
|
51
|
+
name: c.name,
|
|
52
|
+
value: c.value,
|
|
53
|
+
domain: c.domain,
|
|
54
|
+
path: c.path || '/',
|
|
55
|
+
secure: c.secure,
|
|
56
|
+
httpOnly: false,
|
|
57
|
+
sameSite: 'Lax'
|
|
58
|
+
})));
|
|
59
|
+
|
|
60
|
+
const page = await context.newPage();
|
|
61
|
+
|
|
62
|
+
// Go to messages
|
|
63
|
+
console.log('Opening LinkedIn Messages...');
|
|
64
|
+
await page.goto('https://www.linkedin.com/messaging/', { waitUntil: 'domcontentloaded' });
|
|
65
|
+
await page.waitForTimeout(5000);
|
|
66
|
+
|
|
67
|
+
// Try to find conversations
|
|
68
|
+
const conversations = await page.$$eval('.msg-conversation-card', cards =>
|
|
69
|
+
cards.slice(0, 5).map(card => {
|
|
70
|
+
const name = card.querySelector('.msg-conversation-card__participant-names')?.innerText || 'Unknown';
|
|
71
|
+
const preview = card.querySelector('.msg-conversation-card__message-preview')?.innerText || '';
|
|
72
|
+
return { name, preview };
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (conversations.length > 0) {
|
|
77
|
+
console.log('\n✅ FOUND CONVERSATIONS:');
|
|
78
|
+
conversations.forEach((c, i) => {
|
|
79
|
+
console.log(`${i + 1}. ${c.name}`);
|
|
80
|
+
console.log(` Preview: ${c.preview?.substring(0, 60)}...`);
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
console.log('\n⚠️ No conversations found with standard selectors');
|
|
84
|
+
console.log('Taking screenshot for debugging...');
|
|
85
|
+
await page.screenshot({ path: '/tmp/linkedin-debug.png' });
|
|
86
|
+
console.log('Screenshot saved: /tmp/linkedin-debug.png');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log('\nBrowser will stay open for 30 seconds...');
|
|
90
|
+
await page.waitForTimeout(30000);
|
|
91
|
+
await browser.close();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
test().catch(console.error);
|
|
95
|
+
EOF
|
|
96
|
+
|
|
97
|
+
node /tmp/test-messages.js
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"lib": ["ES2022", "DOM"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noImplicitReturns": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["src/**/*"],
|
|
22
|
+
"exclude": ["node_modules", "dist", "coverage"]
|
|
23
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
include: ['src/**/*.test.ts'],
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
reporter: ['text', 'json', 'html'],
|
|
11
|
+
include: [
|
|
12
|
+
'src/core/audit.ts',
|
|
13
|
+
'src/core/config.ts',
|
|
14
|
+
'src/core/storage.ts',
|
|
15
|
+
'src/linkedin/selector-engine.ts',
|
|
16
|
+
'src/linkedin/selectors.ts',
|
|
17
|
+
'src/templates/engine.ts',
|
|
18
|
+
],
|
|
19
|
+
exclude: [
|
|
20
|
+
'node_modules/',
|
|
21
|
+
'src/*.test.ts',
|
|
22
|
+
'dist/',
|
|
23
|
+
'scripts/',
|
|
24
|
+
],
|
|
25
|
+
thresholds: {
|
|
26
|
+
global: {
|
|
27
|
+
lines: 80,
|
|
28
|
+
functions: 80,
|
|
29
|
+
branches: 80,
|
|
30
|
+
statements: 80,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|