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.
@@ -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
- all?: boolean;
104
- format?: 'tree' | 'html';
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 ![alt](src) 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({ id: '...', action: 'click', selector: ref });
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({ id: '...', action: 'snapshot' });
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({ id: 's1', action: 'snapshot' });
276
+ * await agent.execute({ action: 'snapshot' });
249
277
  *
250
278
  * // Show visual highlights
251
- * await agent.execute({ id: 'h1', action: 'highlight' });
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({ id: 'c1', action: 'click', selector: '@ref:5' });
283
+ * await agent.execute({ action: 'click', selector: '@ref:5' });
256
284
  *
257
285
  * // Clear highlights when done
258
- * await agent.execute({ id: 'ch1', action: 'clearHighlight' });
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({ id: 'ch1', action: 'clearHighlight' });
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({ id: '1', action: 'click', selector: '#submit' });
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({ id: '1', action: 'snapshot' }, { tabId: tab2.id });
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({ id: '1', action: 'snapshot' });
168
+ * await browser.execute({ action: 'snapshot' });
167
169
  * ```
168
170
  *
169
171
  * @example Specific tab
170
172
  * ```typescript
171
- * await browser.execute({ id: '1', action: 'snapshot' }, { tabId: 123 });
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({ id: '1', action: 'click', selector: '#submit' });
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({ id: '1', action: 'snapshot' }, { tabId: tab2.id });
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({ id: '1', action: 'snapshot' });
399
+ * await browser.execute({ action: 'snapshot' });
390
400
  * ```
391
401
  *
392
402
  * @example Specific tab
393
403
  * ```typescript
394
- * await browser.execute({ id: '1', action: 'snapshot' }, { tabId: 123 });
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(command)) {
401
- return this.executeExtensionCommand(command);
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(command, options?.tabId);
417
+ return this.sendToContentAgent(internalCmd, options?.tabId);
405
418
  }
406
419
  catch (error) {
407
420
  return {
408
- id: command.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: command.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, command);
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: command.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: command.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: command.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({ id: '1', action: 'snapshot' });
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
- interactive?: boolean;
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({ id: '1', action: 'snapshot' });
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
- function generateCommandId() {
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: command.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: command.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
- interactive: options?.interactive,
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
- id: string;
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 {