btcp-browser-agent 0.1.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.
Files changed (117) hide show
  1. package/CLAUDE.md +230 -0
  2. package/LICENSE +21 -0
  3. package/README.md +309 -0
  4. package/SKILL.md +143 -0
  5. package/SNAPSHOT_IMPROVEMENTS.md +302 -0
  6. package/USAGE.md +146 -0
  7. package/dist/index.d.ts +34 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +35 -0
  10. package/dist/index.js.map +1 -0
  11. package/docs/browser-cli-design.md +500 -0
  12. package/examples/chrome-extension/CHANGELOG.md +210 -0
  13. package/examples/chrome-extension/DEBUG.md +231 -0
  14. package/examples/chrome-extension/ERROR_FIXED.md +147 -0
  15. package/examples/chrome-extension/QUICK_TEST.md +189 -0
  16. package/examples/chrome-extension/README.md +149 -0
  17. package/examples/chrome-extension/SESSION_ONLY_MODE.md +305 -0
  18. package/examples/chrome-extension/TEST_WITH_YOUR_TABS.md +97 -0
  19. package/examples/chrome-extension/build.js +43 -0
  20. package/examples/chrome-extension/manifest.json +37 -0
  21. package/examples/chrome-extension/package-lock.json +1063 -0
  22. package/examples/chrome-extension/package.json +21 -0
  23. package/examples/chrome-extension/popup.html +195 -0
  24. package/examples/chrome-extension/src/background.ts +12 -0
  25. package/examples/chrome-extension/src/content.ts +7 -0
  26. package/examples/chrome-extension/src/popup.ts +303 -0
  27. package/examples/chrome-extension/src/scenario-google-github.ts +389 -0
  28. package/examples/chrome-extension/test-page.html +127 -0
  29. package/examples/chrome-extension/tests/README.md +206 -0
  30. package/examples/chrome-extension/tests/scenario-google-to-github-star.ts +380 -0
  31. package/examples/chrome-extension/tsconfig.json +14 -0
  32. package/examples/snapshots/README.md +207 -0
  33. package/examples/snapshots/amazon-com-detail.html +9528 -0
  34. package/examples/snapshots/amazon-com-detail.snapshot.txt +997 -0
  35. package/examples/snapshots/convert-snapshots.ts +97 -0
  36. package/examples/snapshots/edition-cnn-com.html +13292 -0
  37. package/examples/snapshots/edition-cnn-com.snapshot.txt +562 -0
  38. package/examples/snapshots/github-com-microsoft-vscode.html +2916 -0
  39. package/examples/snapshots/github-com-microsoft-vscode.snapshot.txt +455 -0
  40. package/examples/snapshots/google-search.html +20012 -0
  41. package/examples/snapshots/google-search.snapshot.txt +195 -0
  42. package/examples/snapshots/metadata.json +86 -0
  43. package/examples/snapshots/npr-org-templates.html +2031 -0
  44. package/examples/snapshots/npr-org-templates.snapshot.txt +224 -0
  45. package/examples/snapshots/stackoverflow-com.html +5216 -0
  46. package/examples/snapshots/stackoverflow-com.snapshot.txt +2404 -0
  47. package/examples/snapshots/test-all-mode.html +46 -0
  48. package/examples/snapshots/test-all-mode.snapshot.txt +5 -0
  49. package/examples/snapshots/validate.test.ts +296 -0
  50. package/package.json +65 -0
  51. package/packages/cli/package.json +42 -0
  52. package/packages/cli/src/__tests__/cli.test.ts +434 -0
  53. package/packages/cli/src/__tests__/errors.test.ts +226 -0
  54. package/packages/cli/src/__tests__/executor.test.ts +275 -0
  55. package/packages/cli/src/__tests__/formatter.test.ts +260 -0
  56. package/packages/cli/src/__tests__/parser.test.ts +288 -0
  57. package/packages/cli/src/__tests__/suggestions.test.ts +255 -0
  58. package/packages/cli/src/commands/back.ts +22 -0
  59. package/packages/cli/src/commands/check.ts +33 -0
  60. package/packages/cli/src/commands/clear.ts +33 -0
  61. package/packages/cli/src/commands/click.ts +32 -0
  62. package/packages/cli/src/commands/closetab.ts +31 -0
  63. package/packages/cli/src/commands/eval.ts +41 -0
  64. package/packages/cli/src/commands/fill.ts +30 -0
  65. package/packages/cli/src/commands/focus.ts +33 -0
  66. package/packages/cli/src/commands/forward.ts +22 -0
  67. package/packages/cli/src/commands/goto.ts +34 -0
  68. package/packages/cli/src/commands/help.ts +162 -0
  69. package/packages/cli/src/commands/hover.ts +34 -0
  70. package/packages/cli/src/commands/index.ts +129 -0
  71. package/packages/cli/src/commands/newtab.ts +35 -0
  72. package/packages/cli/src/commands/press.ts +40 -0
  73. package/packages/cli/src/commands/reload.ts +25 -0
  74. package/packages/cli/src/commands/screenshot.ts +27 -0
  75. package/packages/cli/src/commands/scroll.ts +64 -0
  76. package/packages/cli/src/commands/select.ts +35 -0
  77. package/packages/cli/src/commands/snapshot.ts +21 -0
  78. package/packages/cli/src/commands/tab.ts +32 -0
  79. package/packages/cli/src/commands/tabs.ts +26 -0
  80. package/packages/cli/src/commands/text.ts +27 -0
  81. package/packages/cli/src/commands/title.ts +17 -0
  82. package/packages/cli/src/commands/type.ts +38 -0
  83. package/packages/cli/src/commands/uncheck.ts +33 -0
  84. package/packages/cli/src/commands/url.ts +17 -0
  85. package/packages/cli/src/commands/wait.ts +54 -0
  86. package/packages/cli/src/errors.ts +164 -0
  87. package/packages/cli/src/executor.ts +68 -0
  88. package/packages/cli/src/formatter.ts +215 -0
  89. package/packages/cli/src/index.ts +257 -0
  90. package/packages/cli/src/parser.ts +195 -0
  91. package/packages/cli/src/suggestions.ts +207 -0
  92. package/packages/cli/src/terminal/Terminal.ts +365 -0
  93. package/packages/cli/src/terminal/index.ts +5 -0
  94. package/packages/cli/src/types.ts +155 -0
  95. package/packages/cli/tsconfig.json +20 -0
  96. package/packages/core/package.json +35 -0
  97. package/packages/core/src/actions.ts +1210 -0
  98. package/packages/core/src/errors.ts +296 -0
  99. package/packages/core/src/index.test.ts +638 -0
  100. package/packages/core/src/index.ts +220 -0
  101. package/packages/core/src/ref-map.ts +107 -0
  102. package/packages/core/src/snapshot.ts +873 -0
  103. package/packages/core/src/types.ts +536 -0
  104. package/packages/core/tsconfig.json +23 -0
  105. package/packages/extension/README.md +129 -0
  106. package/packages/extension/package.json +43 -0
  107. package/packages/extension/src/background.ts +888 -0
  108. package/packages/extension/src/content.ts +172 -0
  109. package/packages/extension/src/index.ts +579 -0
  110. package/packages/extension/src/session-manager.ts +385 -0
  111. package/packages/extension/src/session-types.ts +144 -0
  112. package/packages/extension/src/types.ts +162 -0
  113. package/packages/extension/tsconfig.json +28 -0
  114. package/src/index.ts +64 -0
  115. package/tsconfig.build.json +12 -0
  116. package/tsconfig.json +26 -0
  117. package/vitest.config.ts +13 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @btcp/core
3
+ *
4
+ * Core DOM actions for browser automation.
5
+ * Runs in content script context (web page, extension content script, iframe).
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createContentAgent } from '@btcp/core';
10
+ *
11
+ * const agent = createContentAgent(document, window);
12
+ *
13
+ * // Take a snapshot
14
+ * const snapshot = await agent.execute({
15
+ * id: '1',
16
+ * action: 'snapshot'
17
+ * });
18
+ *
19
+ * // Click an element
20
+ * await agent.execute({
21
+ * id: '2',
22
+ * action: 'click',
23
+ * selector: '@ref:5' // From snapshot
24
+ * });
25
+ * ```
26
+ */
27
+
28
+ import { DOMActions } from './actions.js';
29
+ import { createRefMap, createSimpleRefMap } from './ref-map.js';
30
+ import type { Command, Response, RefMap } from './types.js';
31
+
32
+ export * from './types.js';
33
+ export * from './errors.js';
34
+ export { createSnapshot } from './snapshot.js';
35
+ export { createRefMap, createSimpleRefMap } from './ref-map.js';
36
+ export { DOMActions } from './actions.js';
37
+
38
+ /**
39
+ * ContentAgent - DOM automation agent that runs in content script context
40
+ *
41
+ * This agent handles all DOM-level operations:
42
+ * - Element interaction (click, type, fill, etc.)
43
+ * - DOM queries (snapshot, getText, getAttribute, etc.)
44
+ * - Keyboard/mouse events
45
+ *
46
+ * Use this in content scripts or directly in web pages.
47
+ * For browser-level operations (tabs, navigation, screenshots),
48
+ * use BrowserAgent from @btcp/extension.
49
+ */
50
+ export interface ContentAgent {
51
+ /**
52
+ * Execute a command and return a response
53
+ */
54
+ execute(command: Command): Promise<Response>;
55
+
56
+ /**
57
+ * Execute a command from JSON string
58
+ */
59
+ executeJson(json: string): Promise<string>;
60
+
61
+ /**
62
+ * Get the element reference map
63
+ */
64
+ getRefMap(): RefMap;
65
+
66
+ /**
67
+ * Clear all element references
68
+ */
69
+ clearRefs(): void;
70
+
71
+ /**
72
+ * Message handler for chrome.runtime.onMessage
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const agent = createContentAgent();
77
+ * chrome.runtime.onMessage.addListener(agent.handleMessage);
78
+ * ```
79
+ */
80
+ handleMessage: (
81
+ message: unknown,
82
+ sender: unknown,
83
+ sendResponse: (response: unknown) => void
84
+ ) => boolean;
85
+ }
86
+
87
+ /**
88
+ * Create a ContentAgent for DOM automation
89
+ *
90
+ * @param doc - The document to operate on (defaults to current document)
91
+ * @param win - The window context (defaults to current window)
92
+ * @returns A ContentAgent instance
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * // In content script
97
+ * const agent = createContentAgent();
98
+ *
99
+ * // Take a snapshot of the page
100
+ * const { data } = await agent.execute({ id: '1', action: 'snapshot' });
101
+ *
102
+ * // Click an element using ref from snapshot
103
+ * await agent.execute({ id: '2', action: 'click', selector: '@ref:5' });
104
+ * ```
105
+ */
106
+ export function createContentAgent(doc: Document = document, win: Window = window): ContentAgent {
107
+ // Use WeakRef-based map if available for better memory management
108
+ const refMap = typeof WeakRef !== 'undefined'
109
+ ? createRefMap()
110
+ : createSimpleRefMap();
111
+
112
+ const actions = new DOMActions(doc, win, refMap);
113
+
114
+ const agent: ContentAgent = {
115
+ async execute(command: Command): Promise<Response> {
116
+ return actions.execute(command);
117
+ },
118
+
119
+ async executeJson(json: string): Promise<string> {
120
+ try {
121
+ const command = JSON.parse(json) as Command;
122
+ const response = await actions.execute(command);
123
+ return JSON.stringify(response);
124
+ } catch (error) {
125
+ const message = error instanceof Error ? error.message : String(error);
126
+ return JSON.stringify({
127
+ id: 'unknown',
128
+ success: false,
129
+ error: `Failed to parse command: ${message}`,
130
+ });
131
+ }
132
+ },
133
+
134
+ getRefMap(): RefMap {
135
+ return refMap;
136
+ },
137
+
138
+ clearRefs(): void {
139
+ refMap.clear();
140
+ actions.invalidateSnapshot();
141
+ },
142
+
143
+ handleMessage(
144
+ message: unknown,
145
+ _sender: unknown,
146
+ sendResponse: (response: unknown) => void
147
+ ): boolean {
148
+ const msg = message as { type?: string; command?: Command };
149
+ if (msg?.type !== 'btcp:command') return false;
150
+
151
+ agent.execute(msg.command!).then(response => {
152
+ sendResponse({ type: 'btcp:response', response });
153
+ }).catch(error => {
154
+ sendResponse({
155
+ type: 'btcp:response',
156
+ response: {
157
+ id: msg.command?.id ?? 'unknown',
158
+ success: false,
159
+ error: error instanceof Error ? error.message : String(error),
160
+ },
161
+ });
162
+ });
163
+
164
+ return true; // Keep channel open for async response
165
+ },
166
+ };
167
+
168
+ return agent;
169
+ }
170
+
171
+ /**
172
+ * @deprecated Use createContentAgent instead
173
+ */
174
+ export const createAgent = createContentAgent;
175
+
176
+ /**
177
+ * @deprecated Use ContentAgent instead
178
+ */
179
+ export type Agent = ContentAgent;
180
+
181
+ /**
182
+ * Message types for extension communication
183
+ */
184
+ export interface AgentMessage {
185
+ type: 'aspect:command';
186
+ command: Command;
187
+ }
188
+
189
+ export interface AgentResponse {
190
+ type: 'aspect:response';
191
+ response: Response;
192
+ }
193
+
194
+ /**
195
+ * Install message listener for extension communication
196
+ * Call this in your content script to enable command execution via postMessage
197
+ *
198
+ * @param agent - The agent instance
199
+ * @returns Cleanup function to remove the listener
200
+ */
201
+ export function installMessageListener(agent: Agent): () => void {
202
+ const handler = async (event: MessageEvent) => {
203
+ // Only accept messages from same window
204
+ if (event.source !== window) return;
205
+
206
+ const data = event.data as AgentMessage;
207
+ if (data?.type !== 'aspect:command') return;
208
+
209
+ const response = await agent.execute(data.command);
210
+
211
+ window.postMessage({
212
+ type: 'aspect:response',
213
+ response,
214
+ } satisfies AgentResponse, '*');
215
+ };
216
+
217
+ window.addEventListener('message', handler);
218
+
219
+ return () => window.removeEventListener('message', handler);
220
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @btcp/core - Element Reference Map
3
+ *
4
+ * Maintains a mapping between string refs and DOM elements.
5
+ * Used by the snapshot system to enable @ref:xxx selectors.
6
+ */
7
+
8
+ import type { RefMap } from './types.js';
9
+
10
+ /**
11
+ * Create a new element reference map
12
+ */
13
+ export function createRefMap(): RefMap {
14
+ const map = new Map<string, WeakRef<Element>>();
15
+ let counter = 0;
16
+
17
+ return {
18
+ get(ref: string): Element | null {
19
+ const weakRef = map.get(ref);
20
+ if (!weakRef) return null;
21
+
22
+ const element = weakRef.deref();
23
+ if (!element) {
24
+ // Element was garbage collected
25
+ map.delete(ref);
26
+ return null;
27
+ }
28
+
29
+ // Check if element is still in DOM
30
+ if (!element.isConnected) {
31
+ map.delete(ref);
32
+ return null;
33
+ }
34
+
35
+ return element;
36
+ },
37
+
38
+ set(ref: string, element: Element): void {
39
+ map.set(ref, new WeakRef(element));
40
+ },
41
+
42
+ clear(): void {
43
+ map.clear();
44
+ counter = 0;
45
+ },
46
+
47
+ generateRef(element: Element): string {
48
+ // Check if element already has a ref
49
+ for (const [ref, weakRef] of map.entries()) {
50
+ if (weakRef.deref() === element) {
51
+ return ref;
52
+ }
53
+ }
54
+
55
+ // Generate new ref
56
+ const ref = `@ref:${counter++}`;
57
+ map.set(ref, new WeakRef(element));
58
+ return ref;
59
+ },
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Simple ref map without WeakRef (for environments that don't support it)
65
+ */
66
+ export function createSimpleRefMap(): RefMap {
67
+ const map = new Map<string, Element>();
68
+ let counter = 0;
69
+
70
+ return {
71
+ get(ref: string): Element | null {
72
+ const element = map.get(ref);
73
+ if (!element) return null;
74
+
75
+ // Check if element is still in DOM
76
+ if (!element.isConnected) {
77
+ map.delete(ref);
78
+ return null;
79
+ }
80
+
81
+ return element;
82
+ },
83
+
84
+ set(ref: string, element: Element): void {
85
+ map.set(ref, element);
86
+ },
87
+
88
+ clear(): void {
89
+ map.clear();
90
+ counter = 0;
91
+ },
92
+
93
+ generateRef(element: Element): string {
94
+ // Check if element already has a ref
95
+ for (const [ref, el] of map.entries()) {
96
+ if (el === element) {
97
+ return ref;
98
+ }
99
+ }
100
+
101
+ // Generate new ref
102
+ const ref = `@ref:${counter++}`;
103
+ map.set(ref, element);
104
+ return ref;
105
+ },
106
+ };
107
+ }