btcp-browser-agent 0.1.4 → 0.1.7
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/LICENSE +21 -21
- package/README.md +306 -309
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -2
- package/package.json +69 -69
- package/packages/core/dist/actions.d.ts +6 -0
- package/packages/core/dist/actions.js +58 -42
- package/packages/core/dist/index.d.ts +5 -12
- package/packages/core/dist/index.js +5 -12
- package/packages/core/dist/snapshot.d.ts +31 -4
- package/packages/core/dist/snapshot.js +633 -5
- package/packages/core/dist/types.d.ts +41 -13
- package/packages/extension/dist/background.d.ts +6 -4
- package/packages/extension/dist/background.js +27 -11
- package/packages/extension/dist/content.js +5 -4
- package/packages/extension/dist/index.d.ts +11 -3
- package/packages/extension/dist/index.js +14 -6
- package/packages/extension/dist/types.d.ts +2 -1
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export type CoreAction = 'click' | 'dblclick' | 'type' | 'fill' | 'clear' | 'check' | 'uncheck' | 'select' | 'focus' | 'blur' | 'hover' | 'scroll' | 'scrollIntoView' | 'snapshot' | 'querySelector' | 'querySelectorAll' | 'getText' | 'getAttribute' | 'getProperty' | 'getBoundingBox' | 'isVisible' | 'isEnabled' | 'isChecked' | 'press' | 'keyDown' | 'keyUp' | 'wait' | 'evaluate' | 'validateElement' | 'validateRefs' | 'highlight' | 'clearHighlight';
|
|
7
7
|
export interface BaseCommand {
|
|
8
|
+
/** Optional command ID. Auto-generated if not provided. */
|
|
9
|
+
id?: string;
|
|
10
|
+
action: CoreAction;
|
|
11
|
+
}
|
|
12
|
+
export interface InternalCommand {
|
|
8
13
|
id: string;
|
|
9
14
|
action: CoreAction;
|
|
15
|
+
[key: string]: unknown;
|
|
10
16
|
}
|
|
11
17
|
export type Selector = string;
|
|
12
18
|
export interface ClickCommand extends BaseCommand {
|
|
@@ -87,12 +93,19 @@ export interface GrepOptions {
|
|
|
87
93
|
/** Treat pattern as fixed string, not regex (grep -F) */
|
|
88
94
|
fixedStrings?: boolean;
|
|
89
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Snapshot mode determines what content to capture
|
|
98
|
+
*/
|
|
99
|
+
export type SnapshotMode = 'interactive' | 'outline' | 'content';
|
|
100
|
+
/**
|
|
101
|
+
* Snapshot output format
|
|
102
|
+
*/
|
|
103
|
+
export type SnapshotFormat = 'tree' | 'html' | 'markdown';
|
|
90
104
|
export interface SnapshotCommand extends BaseCommand {
|
|
91
105
|
action: 'snapshot';
|
|
92
106
|
selector?: Selector;
|
|
93
107
|
maxDepth?: number;
|
|
94
108
|
includeHidden?: boolean;
|
|
95
|
-
interactive?: boolean;
|
|
96
109
|
compact?: boolean;
|
|
97
110
|
minDepth?: number;
|
|
98
111
|
samplingStrategy?: 'importance' | 'balanced' | 'depth-first';
|
|
@@ -100,10 +113,28 @@ export interface SnapshotCommand extends BaseCommand {
|
|
|
100
113
|
landmarks?: boolean;
|
|
101
114
|
incremental?: boolean;
|
|
102
115
|
baseSnapshot?: SnapshotData;
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Snapshot mode:
|
|
118
|
+
* - 'interactive': Find clickable elements (default)
|
|
119
|
+
* - 'outline': Understand page structure with xpaths + metadata
|
|
120
|
+
* - 'content': Extract text content from sections
|
|
121
|
+
*/
|
|
122
|
+
mode?: SnapshotMode;
|
|
123
|
+
/**
|
|
124
|
+
* Output format:
|
|
125
|
+
* - 'tree': Flat accessibility tree (default)
|
|
126
|
+
* - 'html': Raw HTML
|
|
127
|
+
* - 'markdown': Markdown formatted content
|
|
128
|
+
*/
|
|
129
|
+
format?: SnapshotFormat;
|
|
105
130
|
/** Filter output lines - simple string or full grep options */
|
|
106
131
|
grep?: string | GrepOptions;
|
|
132
|
+
/** Max chars per section in content mode */
|
|
133
|
+
maxLength?: number;
|
|
134
|
+
/** Include links as [text](url) in markdown format */
|
|
135
|
+
includeLinks?: boolean;
|
|
136
|
+
/** Include images as  in markdown format */
|
|
137
|
+
includeImages?: boolean;
|
|
107
138
|
}
|
|
108
139
|
export interface QuerySelectorCommand extends BaseCommand {
|
|
109
140
|
action: 'querySelector';
|
|
@@ -177,7 +208,6 @@ export interface EvaluateCommand extends BaseCommand {
|
|
|
177
208
|
* @example Pre-validate before typing
|
|
178
209
|
* ```typescript
|
|
179
210
|
* const validation = await agent.execute({
|
|
180
|
-
* id: 'v1',
|
|
181
211
|
* action: 'validateElement',
|
|
182
212
|
* selector: '#username',
|
|
183
213
|
* capabilities: ['editable']
|
|
@@ -185,7 +215,6 @@ export interface EvaluateCommand extends BaseCommand {
|
|
|
185
215
|
*
|
|
186
216
|
* if (validation.data.compatible) {
|
|
187
217
|
* await agent.execute({
|
|
188
|
-
* id: 'a1',
|
|
189
218
|
* action: 'type',
|
|
190
219
|
* selector: '#username',
|
|
191
220
|
* text: 'user@example.com'
|
|
@@ -213,20 +242,19 @@ export interface ValidateElementCommand extends BaseCommand {
|
|
|
213
242
|
* @example Check ref validity
|
|
214
243
|
* ```typescript
|
|
215
244
|
* const validation = await agent.execute({
|
|
216
|
-
* id: 'v1',
|
|
217
245
|
* action: 'validateRefs',
|
|
218
246
|
* refs: ['@ref:0', '@ref:1', '@ref:2']
|
|
219
247
|
* });
|
|
220
248
|
*
|
|
221
249
|
* // Use only valid refs
|
|
222
250
|
* for (const ref of validation.data.valid) {
|
|
223
|
-
* await agent.execute({
|
|
251
|
+
* await agent.execute({ action: 'click', selector: ref });
|
|
224
252
|
* }
|
|
225
253
|
*
|
|
226
254
|
* // Handle invalid refs
|
|
227
255
|
* if (validation.data.invalid.length > 0) {
|
|
228
256
|
* // Take new snapshot to get fresh refs
|
|
229
|
-
* await agent.execute({
|
|
257
|
+
* await agent.execute({ action: 'snapshot' });
|
|
230
258
|
* }
|
|
231
259
|
* ```
|
|
232
260
|
*/
|
|
@@ -245,17 +273,17 @@ export interface ValidateRefsCommand extends BaseCommand {
|
|
|
245
273
|
* @example Highlight elements after snapshot
|
|
246
274
|
* ```typescript
|
|
247
275
|
* // Take a snapshot first
|
|
248
|
-
* await agent.execute({
|
|
276
|
+
* await agent.execute({ action: 'snapshot' });
|
|
249
277
|
*
|
|
250
278
|
* // Show visual highlights
|
|
251
|
-
* await agent.execute({
|
|
279
|
+
* await agent.execute({ action: 'highlight' });
|
|
252
280
|
*
|
|
253
281
|
* // Labels now visible on page with @ref:0, @ref:1, etc.
|
|
254
282
|
* // Use the refs to interact with elements
|
|
255
|
-
* await agent.execute({
|
|
283
|
+
* await agent.execute({ action: 'click', selector: '@ref:5' });
|
|
256
284
|
*
|
|
257
285
|
* // Clear highlights when done
|
|
258
|
-
* await agent.execute({
|
|
286
|
+
* await agent.execute({ action: 'clearHighlight' });
|
|
259
287
|
* ```
|
|
260
288
|
*/
|
|
261
289
|
export interface HighlightCommand extends BaseCommand {
|
|
@@ -268,7 +296,7 @@ export interface HighlightCommand extends BaseCommand {
|
|
|
268
296
|
*
|
|
269
297
|
* @example Clear highlights
|
|
270
298
|
* ```typescript
|
|
271
|
-
* await agent.execute({
|
|
299
|
+
* await agent.execute({ action: 'clearHighlight' });
|
|
272
300
|
* ```
|
|
273
301
|
*/
|
|
274
302
|
export interface ClearHighlightCommand extends BaseCommand {
|
|
@@ -43,7 +43,7 @@ export interface TabHandle {
|
|
|
43
43
|
* ```typescript
|
|
44
44
|
* const agent = new BackgroundAgent();
|
|
45
45
|
* await agent.navigate('https://example.com');
|
|
46
|
-
* await agent.execute({
|
|
46
|
+
* await agent.execute({ action: 'click', selector: '#submit' });
|
|
47
47
|
* ```
|
|
48
48
|
*
|
|
49
49
|
* @example Multi-tab with explicit tabId
|
|
@@ -59,7 +59,7 @@ export interface TabHandle {
|
|
|
59
59
|
* await agent.tab(tab2.id).snapshot();
|
|
60
60
|
*
|
|
61
61
|
* // Or specify tabId in command
|
|
62
|
-
* await agent.execute({
|
|
62
|
+
* await agent.execute({ action: 'snapshot' }, { tabId: tab2.id });
|
|
63
63
|
* ```
|
|
64
64
|
*/
|
|
65
65
|
export declare class BackgroundAgent {
|
|
@@ -158,17 +158,19 @@ export declare class BackgroundAgent {
|
|
|
158
158
|
* Browser-level commands (navigate, screenshot, tabs) are handled here.
|
|
159
159
|
* DOM-level commands are forwarded to the ContentAgent in the target tab.
|
|
160
160
|
*
|
|
161
|
+
* Command IDs are auto-generated internally - users don't need to provide them.
|
|
162
|
+
*
|
|
161
163
|
* @param command - The command to execute
|
|
162
164
|
* @param options - Optional settings including target tabId
|
|
163
165
|
*
|
|
164
166
|
* @example Default (active tab)
|
|
165
167
|
* ```typescript
|
|
166
|
-
* await browser.execute({
|
|
168
|
+
* await browser.execute({ action: 'snapshot' });
|
|
167
169
|
* ```
|
|
168
170
|
*
|
|
169
171
|
* @example Specific tab
|
|
170
172
|
* ```typescript
|
|
171
|
-
* await browser.execute({
|
|
173
|
+
* await browser.execute({ action: 'snapshot' }, { tabId: 123 });
|
|
172
174
|
* ```
|
|
173
175
|
*/
|
|
174
176
|
execute(command: Command, options?: {
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
* - Routing DOM commands to ContentAgents in target tabs
|
|
13
13
|
*/
|
|
14
14
|
import { SessionManager } from './session-manager.js';
|
|
15
|
+
// Command ID counter for auto-generated IDs
|
|
16
|
+
let bgCommandIdCounter = 0;
|
|
17
|
+
/**
|
|
18
|
+
* Generate a unique command ID for background script
|
|
19
|
+
*/
|
|
20
|
+
function generateBgCommandId() {
|
|
21
|
+
return `bg_${Date.now()}_${bgCommandIdCounter++}`;
|
|
22
|
+
}
|
|
15
23
|
/**
|
|
16
24
|
* BackgroundAgent - High-level browser automation orchestrator
|
|
17
25
|
*
|
|
@@ -23,7 +31,7 @@ import { SessionManager } from './session-manager.js';
|
|
|
23
31
|
* ```typescript
|
|
24
32
|
* const agent = new BackgroundAgent();
|
|
25
33
|
* await agent.navigate('https://example.com');
|
|
26
|
-
* await agent.execute({
|
|
34
|
+
* await agent.execute({ action: 'click', selector: '#submit' });
|
|
27
35
|
* ```
|
|
28
36
|
*
|
|
29
37
|
* @example Multi-tab with explicit tabId
|
|
@@ -39,7 +47,7 @@ import { SessionManager } from './session-manager.js';
|
|
|
39
47
|
* await agent.tab(tab2.id).snapshot();
|
|
40
48
|
*
|
|
41
49
|
* // Or specify tabId in command
|
|
42
|
-
* await agent.execute({
|
|
50
|
+
* await agent.execute({ action: 'snapshot' }, { tabId: tab2.id });
|
|
43
51
|
* ```
|
|
44
52
|
*/
|
|
45
53
|
export class BackgroundAgent {
|
|
@@ -381,31 +389,36 @@ export class BackgroundAgent {
|
|
|
381
389
|
* Browser-level commands (navigate, screenshot, tabs) are handled here.
|
|
382
390
|
* DOM-level commands are forwarded to the ContentAgent in the target tab.
|
|
383
391
|
*
|
|
392
|
+
* Command IDs are auto-generated internally - users don't need to provide them.
|
|
393
|
+
*
|
|
384
394
|
* @param command - The command to execute
|
|
385
395
|
* @param options - Optional settings including target tabId
|
|
386
396
|
*
|
|
387
397
|
* @example Default (active tab)
|
|
388
398
|
* ```typescript
|
|
389
|
-
* await browser.execute({
|
|
399
|
+
* await browser.execute({ action: 'snapshot' });
|
|
390
400
|
* ```
|
|
391
401
|
*
|
|
392
402
|
* @example Specific tab
|
|
393
403
|
* ```typescript
|
|
394
|
-
* await browser.execute({
|
|
404
|
+
* await browser.execute({ action: 'snapshot' }, { tabId: 123 });
|
|
395
405
|
* ```
|
|
396
406
|
*/
|
|
397
407
|
async execute(command, options) {
|
|
408
|
+
// Auto-generate ID if not provided
|
|
409
|
+
const id = command.id || generateBgCommandId();
|
|
410
|
+
const internalCmd = { ...command, id };
|
|
398
411
|
try {
|
|
399
412
|
// Extension commands are handled directly by BrowserAgent
|
|
400
|
-
if (this.isExtensionCommand(
|
|
401
|
-
return this.executeExtensionCommand(
|
|
413
|
+
if (this.isExtensionCommand(internalCmd)) {
|
|
414
|
+
return this.executeExtensionCommand(internalCmd);
|
|
402
415
|
}
|
|
403
416
|
// DOM commands are forwarded to ContentAgent in the target tab
|
|
404
|
-
return this.sendToContentAgent(
|
|
417
|
+
return this.sendToContentAgent(internalCmd, options?.tabId);
|
|
405
418
|
}
|
|
406
419
|
catch (error) {
|
|
407
420
|
return {
|
|
408
|
-
id
|
|
421
|
+
id,
|
|
409
422
|
success: false,
|
|
410
423
|
error: error instanceof Error ? error.message : String(error),
|
|
411
424
|
};
|
|
@@ -415,16 +428,19 @@ export class BackgroundAgent {
|
|
|
415
428
|
* Send a command to the ContentAgent in a specific tab
|
|
416
429
|
*/
|
|
417
430
|
async sendToContentAgent(command, tabId) {
|
|
431
|
+
// Ensure command has an ID for internal use
|
|
432
|
+
const id = command.id || generateBgCommandId();
|
|
433
|
+
const internalCmd = { ...command, id };
|
|
418
434
|
const targetTabId = tabId ?? this.activeTabId ?? (await this.getActiveTab())?.id;
|
|
419
435
|
if (!targetTabId) {
|
|
420
436
|
return {
|
|
421
|
-
id
|
|
437
|
+
id,
|
|
422
438
|
success: false,
|
|
423
439
|
error: 'No active tab for DOM command',
|
|
424
440
|
};
|
|
425
441
|
}
|
|
426
442
|
// Try sending with automatic retry and recovery
|
|
427
|
-
return this.sendMessageWithRetry(targetTabId,
|
|
443
|
+
return this.sendMessageWithRetry(targetTabId, internalCmd);
|
|
428
444
|
}
|
|
429
445
|
/**
|
|
430
446
|
* Send message with automatic content script re-injection on failure
|
|
@@ -717,7 +733,7 @@ export function setupMessageListener() {
|
|
|
717
733
|
sendResponse({
|
|
718
734
|
type: 'btcp:response',
|
|
719
735
|
response: {
|
|
720
|
-
id: msg.command.id,
|
|
736
|
+
id: msg.command.id || 'unknown',
|
|
721
737
|
success: false,
|
|
722
738
|
error: error instanceof Error ? error.message : String(error),
|
|
723
739
|
},
|
|
@@ -40,23 +40,24 @@ async function handleCommand(command) {
|
|
|
40
40
|
return getContentAgent().execute(command);
|
|
41
41
|
}
|
|
42
42
|
// Extension commands that need content script execution
|
|
43
|
+
const id = command.id || 'unknown';
|
|
43
44
|
switch (command.action) {
|
|
44
45
|
case 'getUrl':
|
|
45
46
|
return {
|
|
46
|
-
id
|
|
47
|
+
id,
|
|
47
48
|
success: true,
|
|
48
49
|
data: { url: window.location.href },
|
|
49
50
|
};
|
|
50
51
|
case 'getTitle':
|
|
51
52
|
return {
|
|
52
|
-
id
|
|
53
|
+
id,
|
|
53
54
|
success: true,
|
|
54
55
|
data: { title: document.title },
|
|
55
56
|
};
|
|
56
57
|
default:
|
|
57
58
|
// Forward to background script
|
|
58
59
|
return {
|
|
59
|
-
id
|
|
60
|
+
id,
|
|
60
61
|
success: false,
|
|
61
62
|
error: `Command ${command.action} must be handled by background script`,
|
|
62
63
|
};
|
|
@@ -83,7 +84,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
|
83
84
|
sendResponse({
|
|
84
85
|
type: 'btcp:response',
|
|
85
86
|
response: {
|
|
86
|
-
id: msg.command.id,
|
|
87
|
+
id: msg.command.id || 'unknown',
|
|
87
88
|
success: false,
|
|
88
89
|
error: error instanceof Error ? error.message : String(error),
|
|
89
90
|
},
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* import { createContentAgent } from '@btcp/core';
|
|
27
27
|
*
|
|
28
28
|
* const agent = createContentAgent();
|
|
29
|
-
* await agent.execute({
|
|
29
|
+
* await agent.execute({ action: 'snapshot' });
|
|
30
30
|
* ```
|
|
31
31
|
*
|
|
32
32
|
* @example Popup/external usage:
|
|
@@ -87,9 +87,13 @@ export interface Client {
|
|
|
87
87
|
snapshot(options?: {
|
|
88
88
|
selector?: string;
|
|
89
89
|
maxDepth?: number;
|
|
90
|
-
|
|
90
|
+
mode?: 'interactive' | 'outline' | 'content';
|
|
91
91
|
compact?: boolean;
|
|
92
|
-
format?: 'tree' | 'html';
|
|
92
|
+
format?: 'tree' | 'html' | 'markdown';
|
|
93
|
+
grep?: string;
|
|
94
|
+
maxLength?: number;
|
|
95
|
+
includeLinks?: boolean;
|
|
96
|
+
includeImages?: boolean;
|
|
93
97
|
}): Promise<string>;
|
|
94
98
|
/**
|
|
95
99
|
* Click an element
|
|
@@ -202,6 +206,10 @@ export interface Client {
|
|
|
202
206
|
reconnected: boolean;
|
|
203
207
|
}>;
|
|
204
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Generate a unique command ID for BTCP commands
|
|
211
|
+
*/
|
|
212
|
+
export declare function generateCommandId(): string;
|
|
205
213
|
/**
|
|
206
214
|
* Create a client for communicating with the extension
|
|
207
215
|
*
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* import { createContentAgent } from '@btcp/core';
|
|
27
27
|
*
|
|
28
28
|
* const agent = createContentAgent();
|
|
29
|
-
* await agent.execute({
|
|
29
|
+
* await agent.execute({ action: 'snapshot' });
|
|
30
30
|
* ```
|
|
31
31
|
*
|
|
32
32
|
* @example Popup/external usage:
|
|
@@ -49,7 +49,10 @@ _BrowserAgent as BrowserAgent, _getBrowserAgent as getBrowserAgent, };
|
|
|
49
49
|
// Re-export ContentAgent for content script usage
|
|
50
50
|
export { createContentAgent } from '../../core/dist/index.js';
|
|
51
51
|
let commandIdCounter = 0;
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Generate a unique command ID for BTCP commands
|
|
54
|
+
*/
|
|
55
|
+
export function generateCommandId() {
|
|
53
56
|
return `cmd_${Date.now()}_${commandIdCounter++}`;
|
|
54
57
|
}
|
|
55
58
|
/**
|
|
@@ -99,11 +102,12 @@ export function createClient() {
|
|
|
99
102
|
return getAgent().execute(command);
|
|
100
103
|
}
|
|
101
104
|
// In popup/content context, use message passing
|
|
105
|
+
const id = command.id || generateCommandId();
|
|
102
106
|
return new Promise((resolve) => {
|
|
103
|
-
chrome.runtime.sendMessage({ type: 'btcp:command', command }, (response) => {
|
|
107
|
+
chrome.runtime.sendMessage({ type: 'btcp:command', command: { ...command, id } }, (response) => {
|
|
104
108
|
if (chrome.runtime.lastError) {
|
|
105
109
|
resolve({
|
|
106
|
-
id
|
|
110
|
+
id,
|
|
107
111
|
success: false,
|
|
108
112
|
error: chrome.runtime.lastError.message || 'Unknown error',
|
|
109
113
|
});
|
|
@@ -116,7 +120,7 @@ export function createClient() {
|
|
|
116
120
|
else {
|
|
117
121
|
// Unexpected pong response
|
|
118
122
|
resolve({
|
|
119
|
-
id
|
|
123
|
+
id,
|
|
120
124
|
success: false,
|
|
121
125
|
error: 'Unexpected response type',
|
|
122
126
|
});
|
|
@@ -171,9 +175,13 @@ export function createClient() {
|
|
|
171
175
|
action: 'snapshot',
|
|
172
176
|
selector: options?.selector,
|
|
173
177
|
maxDepth: options?.maxDepth,
|
|
174
|
-
|
|
178
|
+
mode: options?.mode,
|
|
175
179
|
compact: options?.compact,
|
|
176
180
|
format: options?.format,
|
|
181
|
+
grep: options?.grep,
|
|
182
|
+
maxLength: options?.maxLength,
|
|
183
|
+
includeLinks: options?.includeLinks,
|
|
184
|
+
includeImages: options?.includeImages,
|
|
177
185
|
});
|
|
178
186
|
assertSuccess(response);
|
|
179
187
|
return response.data;
|
|
@@ -7,7 +7,8 @@ import type { Command as CoreCommand, Response } from '../../core/dist/index.js'
|
|
|
7
7
|
import type { SessionCommand } from './session-types.js';
|
|
8
8
|
export type ExtensionAction = 'navigate' | 'back' | 'forward' | 'reload' | 'getUrl' | 'getTitle' | 'screenshot' | 'tabNew' | 'tabClose' | 'tabSwitch' | 'tabList' | 'groupCreate' | 'groupUpdate' | 'groupDelete' | 'groupList' | 'groupAddTabs' | 'groupRemoveTabs' | 'groupGet' | 'sessionGetCurrent' | 'popupInitialize';
|
|
9
9
|
export interface ExtensionBaseCommand {
|
|
10
|
-
|
|
10
|
+
/** Optional command ID. Auto-generated if not provided. */
|
|
11
|
+
id?: string;
|
|
11
12
|
action: ExtensionAction;
|
|
12
13
|
}
|
|
13
14
|
export interface NavigateCommand extends ExtensionBaseCommand {
|