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