btcp-browser-agent 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -86,11 +86,11 @@ import { createContentAgent } from 'btcp-browser-agent';
86
86
  const agent = createContentAgent();
87
87
 
88
88
  // Take a snapshot
89
- const { data } = await agent.execute({ id: '1', action: 'snapshot' });
89
+ const { data } = await agent.execute({ action: 'snapshot' });
90
90
  console.log(data.tree); // Accessibility tree with refs
91
91
 
92
92
  // Click an element using ref from snapshot
93
- await agent.execute({ id: '2', action: 'click', selector: '@ref:5' });
93
+ await agent.execute({ action: 'click', selector: '@ref:5' });
94
94
  ```
95
95
 
96
96
  **Popup (sending commands via messaging):**
@@ -116,11 +116,11 @@ import { createContentAgent } from 'btcp-browser-agent';
116
116
  const agent = createContentAgent();
117
117
 
118
118
  // Take a snapshot
119
- const { data } = await agent.execute({ id: '1', action: 'snapshot' });
119
+ const { data } = await agent.execute({ action: 'snapshot' });
120
120
 
121
121
  // Interact with elements
122
- await agent.execute({ id: '2', action: 'click', selector: '@ref:5' });
123
- await agent.execute({ id: '3', action: 'fill', selector: '@ref:3', value: 'Hello' });
122
+ await agent.execute({ action: 'click', selector: '@ref:5' });
123
+ await agent.execute({ action: 'fill', selector: '@ref:3', value: 'Hello' });
124
124
  ```
125
125
 
126
126
  ## API Reference
@@ -150,7 +150,7 @@ await agent.reload();
150
150
  const screenshot = await agent.screenshot({ format: 'png' });
151
151
 
152
152
  // Execute commands (routes to ContentAgent for DOM operations)
153
- await agent.execute({ id: '1', action: 'click', selector: '#submit' });
153
+ await agent.execute({ action: 'click', selector: '#submit' });
154
154
  ```
155
155
 
156
156
  #### Multi-Tab Operations
@@ -167,7 +167,7 @@ await githubTab.click('@ref:5');
167
167
 
168
168
  // Method 2: Specify tabId in execute
169
169
  await agent.execute(
170
- { id: '1', action: 'getText', selector: 'h1' },
170
+ { action: 'getText', selector: 'h1' },
171
171
  { tabId: tab2.id }
172
172
  );
173
173
 
@@ -184,10 +184,7 @@ import { createContentAgent } from 'btcp-browser-agent';
184
184
  const agent = createContentAgent();
185
185
 
186
186
  // Execute commands
187
- const response = await agent.execute({
188
- id: 'cmd1',
189
- action: 'snapshot'
190
- });
187
+ const response = await agent.execute({ action: 'snapshot' });
191
188
  ```
192
189
 
193
190
  #### Available Actions
@@ -238,11 +235,11 @@ const response = await agent.execute({
238
235
  The `snapshot` action returns element references for stable selection:
239
236
 
240
237
  ```typescript
241
- const { data } = await agent.execute({ id: '1', action: 'snapshot' });
238
+ const { data } = await agent.execute({ action: 'snapshot' });
242
239
  // data.tree: "BUTTON 'Submit' [@ref:5]\nTEXTBOX 'Email' [@ref:3]"
243
240
 
244
241
  // Use refs in subsequent commands
245
- await agent.execute({ id: '2', action: 'click', selector: '@ref:5' });
242
+ await agent.execute({ action: 'click', selector: '@ref:5' });
246
243
  ```
247
244
 
248
245
  ## Architecture
package/dist/index.d.ts CHANGED
@@ -25,10 +25,10 @@
25
25
  * ```typescript
26
26
  * import { createContentAgent } from '@btcp/browser-agent';
27
27
  * const agent = createContentAgent();
28
- * await agent.execute({ id: '1', action: 'snapshot' });
28
+ * await agent.execute({ action: 'snapshot' });
29
29
  * ```
30
30
  */
31
31
  export { createContentAgent, type ContentAgent, DOMActions, createSnapshot, createRefMap, createSimpleRefMap, type Command, type Response, type SnapshotData, type BoundingBox, type RefMap, type Modifier, } from '../packages/core/dist/index.js';
32
32
  export type { ExtensionMessage, ExtensionResponse, TabInfo, ChromeTab, ExtensionCommand, } from '../packages/extension/dist/index.js';
33
- export { BackgroundAgent, getBackgroundAgent, setupMessageListener, createClient, type Client, } from '../packages/extension/dist/index.js';
33
+ export { BackgroundAgent, getBackgroundAgent, setupMessageListener, createClient, generateCommandId, type Client, BrowserAgent, getBrowserAgent, } from '../packages/extension/dist/index.js';
34
34
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -25,11 +25,13 @@
25
25
  * ```typescript
26
26
  * import { createContentAgent } from '@btcp/browser-agent';
27
27
  * const agent = createContentAgent();
28
- * await agent.execute({ id: '1', action: 'snapshot' });
28
+ * await agent.execute({ action: 'snapshot' });
29
29
  * ```
30
30
  */
31
31
  // Re-export everything from core (for standalone usage)
32
32
  export { createContentAgent, DOMActions, createSnapshot, createRefMap, createSimpleRefMap, } from '../packages/core/dist/index.js';
33
33
  // Re-export extension functions
34
- export { BackgroundAgent, getBackgroundAgent, setupMessageListener, createClient, } from '../packages/extension/dist/index.js';
34
+ export { BackgroundAgent, getBackgroundAgent, setupMessageListener, createClient, generateCommandId,
35
+ // Deprecated aliases for backwards compatibility
36
+ BrowserAgent, getBrowserAgent, } from '../packages/extension/dist/index.js';
35
37
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "btcp-browser-agent",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Give AI agents the power to control browsers. A foundation for building agentic systems with smart DOM snapshots and stable element references.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -4,6 +4,10 @@
4
4
  * Element interaction handlers using native browser APIs.
5
5
  */
6
6
  import type { Command, Response, RefMap } from './types.js';
7
+ /**
8
+ * Generate a unique command ID
9
+ */
10
+ export declare function generateCommandId(): string;
7
11
  /**
8
12
  * DOM Actions executor
9
13
  */
@@ -18,6 +22,8 @@ export declare class DOMActions {
18
22
  constructor(doc: Document, win: Window, refMap: RefMap);
19
23
  /**
20
24
  * Execute a command and return a response
25
+ *
26
+ * The command ID is auto-generated internally - users don't need to provide it.
21
27
  */
22
28
  execute(command: Command): Promise<Response>;
23
29
  private dispatch;
@@ -5,6 +5,14 @@
5
5
  */
6
6
  import { createSnapshot } from './snapshot.js';
7
7
  import { DetailedError, createElementNotFoundError, createElementNotCompatibleError, createTimeoutError, createInvalidParametersError, } from './errors.js';
8
+ // Command ID counter for auto-generated IDs
9
+ let commandIdCounter = 0;
10
+ /**
11
+ * Generate a unique command ID
12
+ */
13
+ export function generateCommandId() {
14
+ return `cmd_${Date.now()}_${commandIdCounter++}`;
15
+ }
8
16
  /**
9
17
  * DOM Actions executor
10
18
  */
@@ -23,18 +31,22 @@ export class DOMActions {
23
31
  }
24
32
  /**
25
33
  * Execute a command and return a response
34
+ *
35
+ * The command ID is auto-generated internally - users don't need to provide it.
26
36
  */
27
37
  async execute(command) {
38
+ // Auto-generate ID if not provided
39
+ const id = command.id || generateCommandId();
28
40
  try {
29
41
  const data = await this.dispatch(command);
30
- return { id: command.id, success: true, data };
42
+ return { id, success: true, data };
31
43
  }
32
44
  catch (error) {
33
45
  const message = error instanceof Error ? error.message : String(error);
34
46
  // Include structured error data if available
35
47
  if (error instanceof DetailedError) {
36
48
  return {
37
- id: command.id,
49
+ id,
38
50
  success: false,
39
51
  error: message,
40
52
  errorCode: error.code,
@@ -42,7 +54,7 @@ export class DOMActions {
42
54
  suggestions: error.suggestions,
43
55
  };
44
56
  }
45
- return { id: command.id, success: false, error: message };
57
+ return { id, success: false, error: message };
46
58
  }
47
59
  }
48
60
  async dispatch(command) {
@@ -11,17 +11,10 @@
11
11
  * const agent = createContentAgent(document, window);
12
12
  *
13
13
  * // Take a snapshot
14
- * const snapshot = await agent.execute({
15
- * id: '1',
16
- * action: 'snapshot'
17
- * });
14
+ * const snapshot = await agent.execute({ action: 'snapshot' });
18
15
  *
19
16
  * // Click an element
20
- * await agent.execute({
21
- * id: '2',
22
- * action: 'click',
23
- * selector: '@ref:5' // From snapshot
24
- * });
17
+ * await agent.execute({ action: 'click', selector: '@ref:5' });
25
18
  * ```
26
19
  */
27
20
  import type { Command, Response, RefMap } from './types.js';
@@ -29,7 +22,7 @@ export * from './types.js';
29
22
  export * from './errors.js';
30
23
  export { createSnapshot } from './snapshot.js';
31
24
  export { createRefMap, createSimpleRefMap } from './ref-map.js';
32
- export { DOMActions } from './actions.js';
25
+ export { DOMActions, generateCommandId } from './actions.js';
33
26
  /**
34
27
  * ContentAgent - DOM automation agent that runs in content script context
35
28
  *
@@ -83,10 +76,10 @@ export interface ContentAgent {
83
76
  * const agent = createContentAgent();
84
77
  *
85
78
  * // Take a snapshot of the page
86
- * const { data } = await agent.execute({ id: '1', action: 'snapshot' });
79
+ * const { data } = await agent.execute({ action: 'snapshot' });
87
80
  *
88
81
  * // Click an element using ref from snapshot
89
- * await agent.execute({ id: '2', action: 'click', selector: '@ref:5' });
82
+ * await agent.execute({ action: 'click', selector: '@ref:5' });
90
83
  * ```
91
84
  */
92
85
  export declare function createContentAgent(doc?: Document, win?: Window): ContentAgent;
@@ -11,17 +11,10 @@
11
11
  * const agent = createContentAgent(document, window);
12
12
  *
13
13
  * // Take a snapshot
14
- * const snapshot = await agent.execute({
15
- * id: '1',
16
- * action: 'snapshot'
17
- * });
14
+ * const snapshot = await agent.execute({ action: 'snapshot' });
18
15
  *
19
16
  * // Click an element
20
- * await agent.execute({
21
- * id: '2',
22
- * action: 'click',
23
- * selector: '@ref:5' // From snapshot
24
- * });
17
+ * await agent.execute({ action: 'click', selector: '@ref:5' });
25
18
  * ```
26
19
  */
27
20
  import { DOMActions } from './actions.js';
@@ -30,7 +23,7 @@ export * from './types.js';
30
23
  export * from './errors.js';
31
24
  export { createSnapshot } from './snapshot.js';
32
25
  export { createRefMap, createSimpleRefMap } from './ref-map.js';
33
- export { DOMActions } from './actions.js';
26
+ export { DOMActions, generateCommandId } from './actions.js';
34
27
  /**
35
28
  * Create a ContentAgent for DOM automation
36
29
  *
@@ -44,10 +37,10 @@ export { DOMActions } from './actions.js';
44
37
  * const agent = createContentAgent();
45
38
  *
46
39
  * // Take a snapshot of the page
47
- * const { data } = await agent.execute({ id: '1', action: 'snapshot' });
40
+ * const { data } = await agent.execute({ action: 'snapshot' });
48
41
  *
49
42
  * // Click an element using ref from snapshot
50
- * await agent.execute({ id: '2', action: 'click', selector: '@ref:5' });
43
+ * await agent.execute({ action: 'click', selector: '@ref:5' });
51
44
  * ```
52
45
  */
53
46
  export function createContentAgent(doc = document, win = window) {
@@ -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 {
@@ -177,7 +183,6 @@ export interface EvaluateCommand extends BaseCommand {
177
183
  * @example Pre-validate before typing
178
184
  * ```typescript
179
185
  * const validation = await agent.execute({
180
- * id: 'v1',
181
186
  * action: 'validateElement',
182
187
  * selector: '#username',
183
188
  * capabilities: ['editable']
@@ -185,7 +190,6 @@ export interface EvaluateCommand extends BaseCommand {
185
190
  *
186
191
  * if (validation.data.compatible) {
187
192
  * await agent.execute({
188
- * id: 'a1',
189
193
  * action: 'type',
190
194
  * selector: '#username',
191
195
  * text: 'user@example.com'
@@ -213,20 +217,19 @@ export interface ValidateElementCommand extends BaseCommand {
213
217
  * @example Check ref validity
214
218
  * ```typescript
215
219
  * const validation = await agent.execute({
216
- * id: 'v1',
217
220
  * action: 'validateRefs',
218
221
  * refs: ['@ref:0', '@ref:1', '@ref:2']
219
222
  * });
220
223
  *
221
224
  * // Use only valid refs
222
225
  * for (const ref of validation.data.valid) {
223
- * await agent.execute({ id: '...', action: 'click', selector: ref });
226
+ * await agent.execute({ action: 'click', selector: ref });
224
227
  * }
225
228
  *
226
229
  * // Handle invalid refs
227
230
  * if (validation.data.invalid.length > 0) {
228
231
  * // Take new snapshot to get fresh refs
229
- * await agent.execute({ id: '...', action: 'snapshot' });
232
+ * await agent.execute({ action: 'snapshot' });
230
233
  * }
231
234
  * ```
232
235
  */
@@ -245,17 +248,17 @@ export interface ValidateRefsCommand extends BaseCommand {
245
248
  * @example Highlight elements after snapshot
246
249
  * ```typescript
247
250
  * // Take a snapshot first
248
- * await agent.execute({ id: 's1', action: 'snapshot' });
251
+ * await agent.execute({ action: 'snapshot' });
249
252
  *
250
253
  * // Show visual highlights
251
- * await agent.execute({ id: 'h1', action: 'highlight' });
254
+ * await agent.execute({ action: 'highlight' });
252
255
  *
253
256
  * // Labels now visible on page with @ref:0, @ref:1, etc.
254
257
  * // Use the refs to interact with elements
255
- * await agent.execute({ id: 'c1', action: 'click', selector: '@ref:5' });
258
+ * await agent.execute({ action: 'click', selector: '@ref:5' });
256
259
  *
257
260
  * // Clear highlights when done
258
- * await agent.execute({ id: 'ch1', action: 'clearHighlight' });
261
+ * await agent.execute({ action: 'clearHighlight' });
259
262
  * ```
260
263
  */
261
264
  export interface HighlightCommand extends BaseCommand {
@@ -268,7 +271,7 @@ export interface HighlightCommand extends BaseCommand {
268
271
  *
269
272
  * @example Clear highlights
270
273
  * ```typescript
271
- * await agent.execute({ id: 'ch1', action: 'clearHighlight' });
274
+ * await agent.execute({ action: 'clearHighlight' });
272
275
  * ```
273
276
  */
274
277
  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:
@@ -202,6 +202,10 @@ export interface Client {
202
202
  reconnected: boolean;
203
203
  }>;
204
204
  }
205
+ /**
206
+ * Generate a unique command ID for BTCP commands
207
+ */
208
+ export declare function generateCommandId(): string;
205
209
  /**
206
210
  * Create a client for communicating with the extension
207
211
  *
@@ -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
  });
@@ -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 {