@actionbookdev/openclaw-plugin 0.1.1

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.
@@ -0,0 +1,2 @@
1
+ export { default } from './plugin.js';
2
+ export { default as actionbookPlugin } from './plugin.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default } from './plugin.js';
2
+ export { default as actionbookPlugin } from './plugin.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACrC,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,12 @@
1
+ import { ApiClient as BaseApiClient } from '@actionbookdev/sdk';
2
+ export interface ApiClientOptions {
3
+ apiKey?: string;
4
+ timeoutMs?: number;
5
+ retry?: {
6
+ maxRetries?: number;
7
+ retryDelay?: number;
8
+ };
9
+ }
10
+ export declare class ApiClient extends BaseApiClient {
11
+ constructor(baseUrl: string, options?: ApiClientOptions);
12
+ }
@@ -0,0 +1,73 @@
1
+ import { ProxyAgent, fetch as undiciFetch } from 'undici';
2
+ import { ApiClient as BaseApiClient } from '@actionbookdev/sdk';
3
+ function getProxyUrl() {
4
+ return (process.env.HTTPS_PROXY ||
5
+ process.env.HTTP_PROXY ||
6
+ process.env.https_proxy ||
7
+ process.env.http_proxy);
8
+ }
9
+ function shouldBypassProxy(url) {
10
+ const noProxy = process.env.NO_PROXY || process.env.no_proxy;
11
+ if (!noProxy)
12
+ return false;
13
+ if (noProxy === '*')
14
+ return true;
15
+ let urlHost;
16
+ let urlPort;
17
+ try {
18
+ const parsed = new URL(url);
19
+ urlHost = parsed.hostname.toLowerCase();
20
+ urlPort = parsed.port || (parsed.protocol === 'https:' ? '443' : '80');
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ const bypassList = noProxy.split(',').map((h) => h.trim().toLowerCase());
26
+ return bypassList.some((bypass) => {
27
+ if (!bypass)
28
+ return false;
29
+ // Handle port-specific entries (e.g. "example.com:8080")
30
+ const [bypassHost, bypassPort] = bypass.includes(':')
31
+ ? [bypass.split(':')[0], bypass.split(':')[1]]
32
+ : [bypass, undefined];
33
+ if (bypassPort && bypassPort !== urlPort)
34
+ return false;
35
+ if (bypassHost.startsWith('.')) {
36
+ return urlHost.endsWith(bypassHost) || urlHost === bypassHost.slice(1);
37
+ }
38
+ return urlHost === bypassHost || urlHost.endsWith('.' + bypassHost);
39
+ });
40
+ }
41
+ function createProxyFetch() {
42
+ const proxyUrl = getProxyUrl();
43
+ if (!proxyUrl) {
44
+ return globalThis.fetch;
45
+ }
46
+ const proxyAgent = new ProxyAgent(proxyUrl);
47
+ const customFetch = async (url, init) => {
48
+ const urlString = typeof url === 'string'
49
+ ? url
50
+ : url instanceof URL
51
+ ? url.toString()
52
+ : url.url;
53
+ const useProxy = proxyAgent && !shouldBypassProxy(urlString);
54
+ const response = await undiciFetch(url, {
55
+ ...init,
56
+ dispatcher: useProxy ? proxyAgent : undefined,
57
+ });
58
+ return response;
59
+ };
60
+ return customFetch;
61
+ }
62
+ export class ApiClient extends BaseApiClient {
63
+ constructor(baseUrl, options = {}) {
64
+ super({
65
+ apiKey: options.apiKey || '',
66
+ baseUrl,
67
+ timeoutMs: options.timeoutMs,
68
+ retry: options.retry,
69
+ fetch: createProxyFetch(),
70
+ });
71
+ }
72
+ }
73
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Actionbook OpenClaw Plugin
3
+ *
4
+ * Registers search_actions and get_action_by_area_id as native OpenClaw agent tools.
5
+ * Provides pre-verified selectors for token-efficient browser automation.
6
+ */
7
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
8
+ declare const actionbookPlugin: {
9
+ id: string;
10
+ name: string;
11
+ description: string;
12
+ register(api: OpenClawPluginApi): void;
13
+ };
14
+ export default actionbookPlugin;
package/dist/plugin.js ADDED
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Actionbook OpenClaw Plugin
3
+ *
4
+ * Registers search_actions and get_action_by_area_id as native OpenClaw agent tools.
5
+ * Provides pre-verified selectors for token-efficient browser automation.
6
+ */
7
+ import { Type } from "@sinclair/typebox";
8
+ import { isActionbookError } from "@actionbookdev/sdk";
9
+ import { ApiClient } from "./lib/api-client.js";
10
+ const DEFAULT_API_URL = "https://api.actionbook.dev";
11
+ const ACTIONBOOK_GUIDANCE = [
12
+ "## Actionbook Browser Automation",
13
+ "When the user needs to interact with any website, call `search_actions` BEFORE taking page snapshots. Use `get_action_by_area_id` to get pre-verified selectors from the results.",
14
+ ].join("\n");
15
+ const SEARCH_ACTIONS_DESCRIPTION = `Search for website action manuals by keyword.
16
+
17
+ Use this tool to find actions, page elements, and their selectors for browser automation.
18
+ Returns area_id identifiers with descriptions and health scores.
19
+
20
+ Example queries:
21
+ - "airbnb search" → find Airbnb search-related actions
22
+ - "google login" → find Google login actions
23
+
24
+ Typical workflow:
25
+ 1. search_actions({ query: "airbnb search" })
26
+ 2. Get area_id from results (e.g., "airbnb.com:/:default")
27
+ 3. get_action_by_area_id({ area_id: "airbnb.com:/:default" })
28
+ 4. Use returned selectors with browser tools`;
29
+ const GET_ACTION_BY_AREA_ID_DESCRIPTION = `Get complete action details by area_id, including DOM selectors.
30
+
31
+ Area ID format: site:path:area (e.g., "airbnb.com:/:default")
32
+
33
+ Returns:
34
+ - Page description and functions
35
+ - Interactive elements with selectors (CSS, XPath, data-testid, role, aria-label)
36
+ - Element types and allowed methods (click, type, etc.)
37
+ - Health score indicating selector reliability
38
+
39
+ Use returned selectors with browser automation tools:
40
+ - data-testid selectors (0.95 confidence) → use with browser eval
41
+ - aria-label selectors (0.88 confidence) → use with browser eval
42
+ - role selectors (0.9 confidence) → use with browser snapshot + click`;
43
+ const PRIVATE_IP_RANGES = [
44
+ /^127\./,
45
+ /^10\./,
46
+ /^172\.(1[6-9]|2\d|3[01])\./,
47
+ /^192\.168\./,
48
+ /^169\.254\./,
49
+ /^0\./,
50
+ /^::1$/,
51
+ /^fe80:/i,
52
+ /^fc00:/i,
53
+ /^fd00:/i,
54
+ ];
55
+ function isPrivateHost(hostname) {
56
+ if (hostname === "localhost" ||
57
+ hostname === "[::1]" ||
58
+ hostname.endsWith(".local")) {
59
+ return true;
60
+ }
61
+ // URL.hostname wraps IPv6 in brackets (e.g. "[fd00::1]") — strip them
62
+ const bare = hostname.startsWith("[") && hostname.endsWith("]")
63
+ ? hostname.slice(1, -1)
64
+ : hostname;
65
+ return PRIVATE_IP_RANGES.some((re) => re.test(bare));
66
+ }
67
+ function resolveApiUrl(value) {
68
+ if (value == null || value === "") {
69
+ return DEFAULT_API_URL;
70
+ }
71
+ if (typeof value !== "string") {
72
+ throw new Error("actionbook: apiUrl must be a string");
73
+ }
74
+ let parsed;
75
+ try {
76
+ parsed = new URL(value);
77
+ }
78
+ catch {
79
+ throw new Error(`actionbook: invalid apiUrl "${value}"`);
80
+ }
81
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
82
+ throw new Error(`actionbook: apiUrl must use http or https, got "${parsed.protocol}"`);
83
+ }
84
+ if (isPrivateHost(parsed.hostname)) {
85
+ throw new Error(`actionbook: apiUrl must not point to private/local addresses, got "${parsed.hostname}"`);
86
+ }
87
+ return parsed.toString().replace(/\/$/, "");
88
+ }
89
+ function formatToolError(action, error) {
90
+ if (isActionbookError(error)) {
91
+ // Upstream SDK errors already have structured info — pass through markdown
92
+ // errors and provide error code in details for agent observability
93
+ const text = error.message.startsWith("## ")
94
+ ? error.message
95
+ : `## Error\n\nFailed to ${action}: ${error.message}`;
96
+ return {
97
+ content: [{ type: "text", text }],
98
+ details: { error: error.message, code: error.code },
99
+ };
100
+ }
101
+ const message = error instanceof Error ? error.message : "Unknown error";
102
+ return {
103
+ content: [
104
+ {
105
+ type: "text",
106
+ text: `## Error\n\nFailed to ${action}: ${message}`,
107
+ },
108
+ ],
109
+ details: { error: message },
110
+ };
111
+ }
112
+ const actionbookPlugin = {
113
+ id: "actionbook",
114
+ name: "Actionbook",
115
+ description: "Token-efficient browser automation with pre-verified selectors from Actionbook",
116
+ register(api) {
117
+ let pluginConfig;
118
+ try {
119
+ const raw = api.pluginConfig ?? {};
120
+ if (typeof raw !== "object" || raw === null) {
121
+ throw new Error("actionbook: pluginConfig must be an object");
122
+ }
123
+ pluginConfig = raw;
124
+ }
125
+ catch (err) {
126
+ api.logger.error(`Actionbook plugin config error: ${err instanceof Error ? err.message : String(err)}`);
127
+ return;
128
+ }
129
+ const apiKey = pluginConfig.apiKey ?? "";
130
+ let apiUrl;
131
+ try {
132
+ apiUrl = resolveApiUrl(pluginConfig.apiUrl);
133
+ }
134
+ catch (err) {
135
+ api.logger.error(`Actionbook plugin URL error: ${err instanceof Error ? err.message : String(err)}`);
136
+ return;
137
+ }
138
+ const client = new ApiClient(apiUrl, {
139
+ apiKey,
140
+ timeoutMs: 30000,
141
+ retry: { maxRetries: 3, retryDelay: 1000 },
142
+ });
143
+ // ========================================================================
144
+ // Lifecycle: inject Actionbook workflow guidance into agent context
145
+ // ========================================================================
146
+ api.on("before_prompt_build", async () => {
147
+ return {
148
+ prependSystemContext: ACTIONBOOK_GUIDANCE,
149
+ };
150
+ });
151
+ // ========================================================================
152
+ // Tool: search_actions
153
+ // ========================================================================
154
+ api.registerTool({
155
+ name: "search_actions",
156
+ label: "Actionbook Search",
157
+ description: SEARCH_ACTIONS_DESCRIPTION,
158
+ parameters: Type.Object({
159
+ query: Type.String({
160
+ minLength: 1,
161
+ maxLength: 200,
162
+ description: "Search keyword (e.g., 'airbnb search', 'login button')",
163
+ }),
164
+ domain: Type.Optional(Type.String({
165
+ description: "Filter by domain (e.g., 'airbnb.com')",
166
+ })),
167
+ background: Type.Optional(Type.String({
168
+ description: "Context for search (improves relevance)",
169
+ })),
170
+ url: Type.Optional(Type.String({ description: "Filter by specific page URL" })),
171
+ page: Type.Optional(Type.Integer({
172
+ minimum: 1,
173
+ description: "Page number (default: 1)",
174
+ })),
175
+ page_size: Type.Optional(Type.Integer({
176
+ minimum: 1,
177
+ maximum: 100,
178
+ description: "Results per page (1-100, default: 10)",
179
+ })),
180
+ }),
181
+ async execute(_toolCallId, params) {
182
+ try {
183
+ const result = await client.searchActions({
184
+ query: params.query,
185
+ domain: params.domain,
186
+ background: params.background,
187
+ url: params.url,
188
+ page: params.page,
189
+ page_size: params.page_size,
190
+ });
191
+ return {
192
+ content: [{ type: "text", text: result }],
193
+ details: { query: params.query, domain: params.domain },
194
+ };
195
+ }
196
+ catch (error) {
197
+ return formatToolError("search actions", error);
198
+ }
199
+ },
200
+ });
201
+ // ========================================================================
202
+ // Tool: get_action_by_area_id
203
+ // ========================================================================
204
+ api.registerTool({
205
+ name: "get_action_by_area_id",
206
+ label: "Actionbook Get Action",
207
+ description: GET_ACTION_BY_AREA_ID_DESCRIPTION,
208
+ parameters: Type.Object({
209
+ area_id: Type.String({
210
+ minLength: 1,
211
+ description: "Area ID from search_actions (e.g., 'airbnb.com:/:default')",
212
+ }),
213
+ }),
214
+ async execute(_toolCallId, params) {
215
+ try {
216
+ const result = await client.getActionByAreaId(params.area_id);
217
+ return {
218
+ content: [{ type: "text", text: result }],
219
+ details: { area_id: params.area_id },
220
+ };
221
+ }
222
+ catch (error) {
223
+ return formatToolError("get action", error);
224
+ }
225
+ },
226
+ });
227
+ },
228
+ };
229
+ export default actionbookPlugin;
230
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ {
2
+ "id": "actionbook",
3
+ "name": "Actionbook",
4
+ "description": "Token-efficient browser automation with pre-verified selectors from Actionbook",
5
+ "skills": ["./skills"],
6
+ "configSchema": {
7
+ "type": "object",
8
+ "properties": {
9
+ "apiKey": {
10
+ "type": "string",
11
+ "description": "Actionbook API key (optional, reduces rate limiting)"
12
+ },
13
+ "apiUrl": {
14
+ "type": "string",
15
+ "description": "Actionbook API base URL (must be a valid URL)"
16
+ }
17
+ },
18
+ "additionalProperties": false
19
+ },
20
+ "uiHints": {
21
+ "apiKey": {
22
+ "label": "API Key",
23
+ "sensitive": true,
24
+ "help": "Optional. Get one at https://actionbook.dev"
25
+ },
26
+ "apiUrl": {
27
+ "label": "API URL",
28
+ "help": "Default: https://api.actionbook.dev"
29
+ }
30
+ }
31
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@actionbookdev/openclaw-plugin",
3
+ "version": "0.1.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Actionbook plugin for OpenClaw - token-efficient browser automation with pre-verified selectors",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/actionbook/actionbook.git"
10
+ },
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "skills",
22
+ "openclaw.plugin.json"
23
+ ],
24
+ "openclaw": {
25
+ "extensions": [
26
+ "./dist/index.js"
27
+ ]
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "prebuild": "pnpm --filter @actionbookdev/sdk build",
34
+ "build": "tsc -p tsconfig.build.json",
35
+ "test": "vitest run",
36
+ "lint": "tsc --noEmit -p tsconfig.json",
37
+ "clean": "rimraf dist",
38
+ "prepublishOnly": "npm run build"
39
+ },
40
+ "dependencies": {
41
+ "@actionbookdev/sdk": "workspace:*",
42
+ "@sinclair/typebox": "^0.34.48",
43
+ "undici": "^6.21.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^20.14.0",
47
+ "rimraf": "^5.0.5",
48
+ "typescript": "^5.6.3",
49
+ "vitest": "^1.6.0"
50
+ },
51
+ "peerDependencies": {
52
+ "openclaw": ">=2026.3.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "openclaw": {
56
+ "optional": true
57
+ }
58
+ },
59
+ "engines": {
60
+ "node": ">=18"
61
+ },
62
+ "license": "Apache-2.0"
63
+ }
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: actionbook
3
+ description: Activate when the user needs to interact with any website — browser automation, web scraping, screenshots, form filling, UI testing, monitoring, or building AI agents. Provides pre-verified page actions with step-by-step instructions and tested selectors.
4
+ user-invocable: false
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Activate when the user:
10
+ - Needs to do anything on a website ("Send a LinkedIn message", "Book an Airbnb", "Search Google for...")
11
+ - Asks how to interact with a site ("How do I post a tweet?", "How to apply on LinkedIn?")
12
+ - Wants to fill out forms, click buttons, navigate, search, filter, or browse on a specific site
13
+ - Wants to take a screenshot of a web page or monitor changes
14
+ - Builds browser-based AI agents, web scrapers, or E2E tests for external websites
15
+ - Automates repetitive web tasks (data entry, form submission, content posting)
16
+
17
+ When performing browser automation tasks, check Actionbook BEFORE taking page snapshots.
18
+
19
+ ## Workflow
20
+
21
+ 1. Before interacting with a website, call `search_actions` to check if Actionbook has pre-verified selectors
22
+ 2. Use `get_action_by_area_id` to get element selectors from the results
23
+ 3. Extract selectors from the returned Elements and use them by priority below
24
+ 4. If no results, skip to Fallback Strategy
25
+
26
+ ## Constructing an Effective Search Query
27
+
28
+ The `query` parameter is the **primary signal** for finding the right action. Pack it with the user's full intent — not just a site name or a vague keyword.
29
+
30
+ **Include in the query:**
31
+ 1. **Target site** — the website name or domain
32
+ 2. **Task verb** — what the user wants to do (search, book, post, filter, login, compose, etc.)
33
+ 3. **Object / context** — what they're acting on (listings, messages, flights, repositories, etc.)
34
+ 4. **Specific details** — any constraints, filters, or parameters the user mentioned
35
+
36
+ **Rule of thumb:** Rewrite the user's request as a single descriptive sentence and use that as the query.
37
+
38
+ | User says | Bad query | Good query |
39
+ |-----------|-----------|------------|
40
+ | "Book an Airbnb in Tokyo for next week" | `"airbnb"` | `"airbnb search listings Tokyo dates check-in check-out guests"` |
41
+ | "Search arXiv for recent NLP papers" | `"arxiv search"` | `"arxiv advanced search papers NLP natural language processing recent"` |
42
+ | "Send a LinkedIn connection request" | `"linkedin"` | `"linkedin send connection request invite someone"` |
43
+ | "Post a tweet with an image" | `"twitter post"` | `"twitter compose new tweet post with image media attachment"` |
44
+ | "Filter GitHub issues by label" | `"github issues"` | `"github repository issues filter by label search issues"` |
45
+
46
+ When `domain` or `url` is known, always add them — they narrow results and improve precision.
47
+
48
+ ## Response Structure
49
+
50
+ ### search_actions response
51
+
52
+ Returns a list of matching actions. Each result includes:
53
+
54
+ - **ID** — unique identifier, use with `get_action_by_area_id` (e.g., `arxiv.org:/search/stat:default`)
55
+ - **Type** — `page` (full page) or `area` (page section)
56
+ - **Description** — page overview with URL, query parameters, and a brief summary
57
+ - **URL** — page where this action applies
58
+ - **Health Score** — selector reliability percentage (0–100%)
59
+ - **Updated** — last verified date
60
+
61
+ ### get_action_by_area_id response
62
+
63
+ Returns a structured document describing the page in detail:
64
+
65
+ 1. **Page URL** — exact URL with query/path parameter descriptions
66
+ 2. **Page Overview** — what the page does (definition of the page's purpose)
67
+ 3. **Page Function Summary** — interactive capabilities listed as bullet points (e.g., "Keyword Search", "Field Selection", "Abstract Toggle")
68
+ 4. **Page Structure Summary** — DOM hierarchy description with **CSS selectors inline** in the text
69
+
70
+ Extract CSS selectors from the Page Structure Summary. Selectors appear embedded in the description, e.g.:
71
+ ```
72
+ Search Form (form[method="GET"]): Large search input field with "All fields" dropdown selector and search button
73
+ Header (<header>): Contains branding, logo, and a mini-search form with query input
74
+ ```
75
+
76
+ ## Selector Priority
77
+
78
+ When Actionbook returns multiple selector types for an element, prefer them in this order:
79
+
80
+ 1. **data-testid** (confidence: 0.95) — e.g., `[data-testid="search-input"]`
81
+ 2. **aria-label** (confidence: 0.88) — e.g., `[aria-label="Notifications"]`
82
+ 3. **CSS selector** — e.g., `button.Search`, `input[type="text"]`
83
+ 4. **role selector** (confidence: 0.9) — e.g., `getByRole('link', { name: 'Notifications' })`
84
+
85
+ Use the returned selectors with the agent's available browser tools (click, fill, evaluate, etc.).
86
+
87
+ ## Example
88
+
89
+ User request: "Search arXiv for papers about Neural Networks, search in titles only"
90
+
91
+ ```
92
+ 1. search_actions({ query: "arxiv advanced search papers neural network title field", domain: "arxiv.org" })
93
+ → Returns area_id: "arxiv.org:/search/advanced:default"
94
+
95
+ 2. get_action_by_area_id({ area_id: "arxiv.org:/search/advanced:default" })
96
+ → Returns page structure with selectors: input[type="text"], select[name="searchtype"], button.Search
97
+
98
+ 3. Use browser tools with returned selectors:
99
+ - Navigate to https://arxiv.org/search/advanced
100
+ - Fill input[type="text"] with "Neural Network"
101
+ - Select select[name="searchtype"] → "title"
102
+ - Click button.Search
103
+ - Wait for navigation
104
+ - Read results
105
+ ```
106
+
107
+ ## Fallback Strategy
108
+
109
+ Actionbook stores page data captured at indexing time. Websites evolve, so selectors may become outdated.
110
+
111
+ - **No Actionbook results**: Use the agent's own browser tools to observe and interact with the page directly
112
+ - **Selector execution fails at runtime**: Fall back to the agent's browser tools to re-observe the live page and retry with updated selectors
113
+
114
+ Selectors should come from Actionbook or live page observation in the current session — not from prior knowledge or memory.
115
+
116
+
117
+ ## Important Notes
118
+
119
+ - Do NOT modify selectors returned from Actionbook
120
+ - Check `Allow Methods` field — it indicates supported operations (click/type/read) per element
121
+ - `region_high_filter_page` entries indicate some elements lack unique selectors — use snapshot fallback for those