@bctrl/sdk 1.0.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.
Files changed (260) hide show
  1. package/README.md +75 -0
  2. package/dist/agents/browser-use/index.d.ts +1 -0
  3. package/dist/agents/browser-use/index.js +9 -0
  4. package/dist/agents/browser-use/namespace.d.ts +39 -0
  5. package/dist/agents/browser-use/namespace.js +93 -0
  6. package/dist/agents/index.d.ts +2 -0
  7. package/dist/agents/index.js +17 -0
  8. package/dist/agents/stagehand/core.d.ts +93 -0
  9. package/dist/agents/stagehand/core.js +144 -0
  10. package/dist/agents/stagehand/index.d.ts +3 -0
  11. package/dist/agents/stagehand/index.js +24 -0
  12. package/dist/agents/stagehand/namespace.d.ts +51 -0
  13. package/dist/agents/stagehand/namespace.js +65 -0
  14. package/dist/ai-credentials/client.d.ts +12 -0
  15. package/dist/ai-credentials/client.js +70 -0
  16. package/dist/ai-credentials/index.d.ts +1 -0
  17. package/dist/ai-credentials/index.js +1 -0
  18. package/dist/base/event-binding.d.ts +7 -0
  19. package/dist/base/event-binding.js +23 -0
  20. package/dist/base/types.d.ts +109 -0
  21. package/dist/base/types.js +4 -0
  22. package/dist/bctrl.d.ts +37 -0
  23. package/dist/bctrl.js +77 -0
  24. package/dist/browser-extensions/client.d.ts +15 -0
  25. package/dist/browser-extensions/client.js +72 -0
  26. package/dist/browser-extensions/index.d.ts +2 -0
  27. package/dist/browser-extensions/index.js +1 -0
  28. package/dist/browser-profiles/client.d.ts +11 -0
  29. package/dist/browser-profiles/client.js +63 -0
  30. package/dist/browser-profiles/index.d.ts +1 -0
  31. package/dist/browser-profiles/index.js +1 -0
  32. package/dist/captcha/index.d.ts +1 -0
  33. package/dist/captcha/index.js +1 -0
  34. package/dist/captcha/namespace.d.ts +34 -0
  35. package/dist/captcha/namespace.js +41 -0
  36. package/dist/client/index.d.ts +1 -0
  37. package/dist/client/index.js +1 -0
  38. package/dist/client/rpc.d.ts +83 -0
  39. package/dist/client/rpc.js +354 -0
  40. package/dist/config.d.ts +5 -0
  41. package/dist/config.js +28 -0
  42. package/dist/constants/browser.d.ts +2 -0
  43. package/dist/constants/browser.js +1 -0
  44. package/dist/contracts/agent-tools.d.ts +45 -0
  45. package/dist/contracts/agent-tools.js +31 -0
  46. package/dist/contracts/automation.d.ts +265 -0
  47. package/dist/contracts/automation.js +109 -0
  48. package/dist/contracts/browser-management.d.ts +179 -0
  49. package/dist/contracts/browser-management.js +95 -0
  50. package/dist/contracts/browser-use.d.ts +136 -0
  51. package/dist/contracts/browser-use.js +93 -0
  52. package/dist/contracts/captcha.d.ts +114 -0
  53. package/dist/contracts/captcha.js +40 -0
  54. package/dist/contracts/desktop.d.ts +223 -0
  55. package/dist/contracts/desktop.js +121 -0
  56. package/dist/contracts/drivers/playwright.d.ts +2008 -0
  57. package/dist/contracts/drivers/playwright.js +2033 -0
  58. package/dist/contracts/drivers/puppeteer.d.ts +1221 -0
  59. package/dist/contracts/drivers/puppeteer.js +941 -0
  60. package/dist/contracts/drivers/selenium.d.ts +156 -0
  61. package/dist/contracts/drivers/selenium.js +61 -0
  62. package/dist/contracts/drivers/stagehand.d.ts +155 -0
  63. package/dist/contracts/drivers/stagehand.js +7 -0
  64. package/dist/contracts/extensions.d.ts +13 -0
  65. package/dist/contracts/extensions.js +9 -0
  66. package/dist/contracts/index.d.ts +13 -0
  67. package/dist/contracts/index.js +13 -0
  68. package/dist/contracts/public-api.d.ts +360 -0
  69. package/dist/contracts/public-api.js +17 -0
  70. package/dist/contracts/runtime.d.ts +81 -0
  71. package/dist/contracts/runtime.js +16 -0
  72. package/dist/contracts/stagehand.d.ts +253 -0
  73. package/dist/contracts/stagehand.js +145 -0
  74. package/dist/contracts/storage.d.ts +51 -0
  75. package/dist/contracts/storage.js +56 -0
  76. package/dist/contracts/vault.d.ts +119 -0
  77. package/dist/contracts/vault.js +112 -0
  78. package/dist/contracts/version.d.ts +3 -0
  79. package/dist/contracts/version.js +16 -0
  80. package/dist/desktop.d.ts +2 -0
  81. package/dist/desktop.js +3 -0
  82. package/dist/drivers/desktop/index.d.ts +65 -0
  83. package/dist/drivers/desktop/index.js +68 -0
  84. package/dist/drivers/desktop/session.d.ts +313 -0
  85. package/dist/drivers/desktop/session.js +432 -0
  86. package/dist/drivers/playwright/event-emitter.d.ts +160 -0
  87. package/dist/drivers/playwright/event-emitter.js +297 -0
  88. package/dist/drivers/playwright/generated/api-request-context.d.ts +137 -0
  89. package/dist/drivers/playwright/generated/api-request-context.js +154 -0
  90. package/dist/drivers/playwright/generated/api-response.d.ts +119 -0
  91. package/dist/drivers/playwright/generated/api-response.js +123 -0
  92. package/dist/drivers/playwright/generated/browser-context.d.ts +284 -0
  93. package/dist/drivers/playwright/generated/browser-context.js +458 -0
  94. package/dist/drivers/playwright/generated/browser.d.ts +120 -0
  95. package/dist/drivers/playwright/generated/browser.js +151 -0
  96. package/dist/drivers/playwright/generated/clock.d.ts +80 -0
  97. package/dist/drivers/playwright/generated/clock.js +94 -0
  98. package/dist/drivers/playwright/generated/console-message.d.ts +94 -0
  99. package/dist/drivers/playwright/generated/console-message.js +89 -0
  100. package/dist/drivers/playwright/generated/coverage.d.ts +57 -0
  101. package/dist/drivers/playwright/generated/coverage.js +66 -0
  102. package/dist/drivers/playwright/generated/dialog.d.ts +79 -0
  103. package/dist/drivers/playwright/generated/dialog.js +80 -0
  104. package/dist/drivers/playwright/generated/element-handle.d.ts +399 -0
  105. package/dist/drivers/playwright/generated/element-handle.js +501 -0
  106. package/dist/drivers/playwright/generated/frame-locator.d.ts +34 -0
  107. package/dist/drivers/playwright/generated/frame-locator.js +63 -0
  108. package/dist/drivers/playwright/generated/frame.d.ts +557 -0
  109. package/dist/drivers/playwright/generated/frame.js +634 -0
  110. package/dist/drivers/playwright/generated/js-handle.d.ts +72 -0
  111. package/dist/drivers/playwright/generated/js-handle.js +92 -0
  112. package/dist/drivers/playwright/generated/keyboard.d.ts +103 -0
  113. package/dist/drivers/playwright/generated/keyboard.js +113 -0
  114. package/dist/drivers/playwright/generated/locator.d.ts +795 -0
  115. package/dist/drivers/playwright/generated/locator.js +974 -0
  116. package/dist/drivers/playwright/generated/mouse.d.ts +97 -0
  117. package/dist/drivers/playwright/generated/mouse.js +109 -0
  118. package/dist/drivers/playwright/generated/page.d.ts +762 -0
  119. package/dist/drivers/playwright/generated/page.js +988 -0
  120. package/dist/drivers/playwright/generated/touchscreen.d.ts +34 -0
  121. package/dist/drivers/playwright/generated/touchscreen.js +37 -0
  122. package/dist/drivers/playwright/generated/tracing.d.ts +78 -0
  123. package/dist/drivers/playwright/generated/tracing.js +80 -0
  124. package/dist/drivers/playwright/generated/worker.d.ts +53 -0
  125. package/dist/drivers/playwright/generated/worker.js +59 -0
  126. package/dist/drivers/playwright/index.d.ts +19 -0
  127. package/dist/drivers/playwright/index.js +20 -0
  128. package/dist/drivers/playwright/remote-base.d.ts +62 -0
  129. package/dist/drivers/playwright/remote-base.js +86 -0
  130. package/dist/drivers/playwright/types.d.ts +148 -0
  131. package/dist/drivers/playwright/types.js +8 -0
  132. package/dist/drivers/puppeteer/errors.d.ts +50 -0
  133. package/dist/drivers/puppeteer/errors.js +71 -0
  134. package/dist/drivers/puppeteer/event-emitter.d.ts +145 -0
  135. package/dist/drivers/puppeteer/event-emitter.js +259 -0
  136. package/dist/drivers/puppeteer/generated/accessibility.d.ts +77 -0
  137. package/dist/drivers/puppeteer/generated/accessibility.js +74 -0
  138. package/dist/drivers/puppeteer/generated/browser-context.d.ts +116 -0
  139. package/dist/drivers/puppeteer/generated/browser-context.js +168 -0
  140. package/dist/drivers/puppeteer/generated/browser.d.ts +169 -0
  141. package/dist/drivers/puppeteer/generated/browser.js +246 -0
  142. package/dist/drivers/puppeteer/generated/console-message.d.ts +54 -0
  143. package/dist/drivers/puppeteer/generated/console-message.js +69 -0
  144. package/dist/drivers/puppeteer/generated/coverage.d.ts +49 -0
  145. package/dist/drivers/puppeteer/generated/coverage.js +57 -0
  146. package/dist/drivers/puppeteer/generated/dialog.d.ts +46 -0
  147. package/dist/drivers/puppeteer/generated/dialog.js +60 -0
  148. package/dist/drivers/puppeteer/generated/element-handle.d.ts +261 -0
  149. package/dist/drivers/puppeteer/generated/element-handle.js +341 -0
  150. package/dist/drivers/puppeteer/generated/file-chooser.d.ts +34 -0
  151. package/dist/drivers/puppeteer/generated/file-chooser.js +43 -0
  152. package/dist/drivers/puppeteer/generated/frame.d.ts +221 -0
  153. package/dist/drivers/puppeteer/generated/frame.js +302 -0
  154. package/dist/drivers/puppeteer/generated/http-request.d.ts +195 -0
  155. package/dist/drivers/puppeteer/generated/http-request.js +243 -0
  156. package/dist/drivers/puppeteer/generated/http-response.d.ts +142 -0
  157. package/dist/drivers/puppeteer/generated/http-response.js +169 -0
  158. package/dist/drivers/puppeteer/generated/js-handle.d.ts +104 -0
  159. package/dist/drivers/puppeteer/generated/js-handle.js +125 -0
  160. package/dist/drivers/puppeteer/generated/keyboard.d.ts +79 -0
  161. package/dist/drivers/puppeteer/generated/keyboard.js +89 -0
  162. package/dist/drivers/puppeteer/generated/locator.d.ts +141 -0
  163. package/dist/drivers/puppeteer/generated/locator.js +164 -0
  164. package/dist/drivers/puppeteer/generated/mouse.d.ts +74 -0
  165. package/dist/drivers/puppeteer/generated/mouse.js +94 -0
  166. package/dist/drivers/puppeteer/generated/page.d.ts +604 -0
  167. package/dist/drivers/puppeteer/generated/page.js +776 -0
  168. package/dist/drivers/puppeteer/generated/target.d.ts +105 -0
  169. package/dist/drivers/puppeteer/generated/target.js +123 -0
  170. package/dist/drivers/puppeteer/generated/touchscreen.d.ts +87 -0
  171. package/dist/drivers/puppeteer/generated/touchscreen.js +103 -0
  172. package/dist/drivers/puppeteer/generated/tracing.d.ts +38 -0
  173. package/dist/drivers/puppeteer/generated/tracing.js +43 -0
  174. package/dist/drivers/puppeteer/generated/web-worker.d.ts +63 -0
  175. package/dist/drivers/puppeteer/generated/web-worker.js +73 -0
  176. package/dist/drivers/puppeteer/index.d.ts +21 -0
  177. package/dist/drivers/puppeteer/index.js +23 -0
  178. package/dist/drivers/puppeteer/remote-base.d.ts +57 -0
  179. package/dist/drivers/puppeteer/remote-base.js +79 -0
  180. package/dist/drivers/puppeteer/types.d.ts +178 -0
  181. package/dist/drivers/puppeteer/types.js +8 -0
  182. package/dist/drivers/selenium/driver.d.ts +28 -0
  183. package/dist/drivers/selenium/driver.js +169 -0
  184. package/dist/drivers/selenium/element.d.ts +34 -0
  185. package/dist/drivers/selenium/element.js +73 -0
  186. package/dist/drivers/selenium/index.d.ts +3 -0
  187. package/dist/drivers/selenium/index.js +5 -0
  188. package/dist/drivers/selenium/types.d.ts +2 -0
  189. package/dist/drivers/selenium/types.js +12 -0
  190. package/dist/drivers/stagehand/generated/context.d.ts +127 -0
  191. package/dist/drivers/stagehand/generated/context.js +153 -0
  192. package/dist/drivers/stagehand/generated/locator.d.ts +324 -0
  193. package/dist/drivers/stagehand/generated/locator.js +368 -0
  194. package/dist/drivers/stagehand/generated/page.d.ts +377 -0
  195. package/dist/drivers/stagehand/generated/page.js +439 -0
  196. package/dist/drivers/stagehand/generated/response.d.ts +197 -0
  197. package/dist/drivers/stagehand/generated/response.js +232 -0
  198. package/dist/drivers/stagehand/index.d.ts +5 -0
  199. package/dist/drivers/stagehand/index.js +8 -0
  200. package/dist/drivers/stagehand/types.d.ts +1 -0
  201. package/dist/drivers/stagehand/types.js +7 -0
  202. package/dist/errors.d.ts +47 -0
  203. package/dist/errors.js +157 -0
  204. package/dist/extensions/client.d.ts +47 -0
  205. package/dist/extensions/client.js +154 -0
  206. package/dist/extensions/index.d.ts +1 -0
  207. package/dist/extensions/index.js +1 -0
  208. package/dist/index.d.ts +12 -0
  209. package/dist/index.js +23 -0
  210. package/dist/internal/dev-client.d.ts +5 -0
  211. package/dist/internal/dev-client.js +9 -0
  212. package/dist/internal/rpc-targets.d.ts +17 -0
  213. package/dist/internal/rpc-targets.js +58 -0
  214. package/dist/internal/serialization.d.ts +32 -0
  215. package/dist/internal/serialization.js +42 -0
  216. package/dist/internal/transport.d.ts +24 -0
  217. package/dist/internal/transport.js +29 -0
  218. package/dist/playwright.d.ts +1 -0
  219. package/dist/playwright.js +2 -0
  220. package/dist/puppeteer.d.ts +1 -0
  221. package/dist/puppeteer.js +2 -0
  222. package/dist/selenium.d.ts +1 -0
  223. package/dist/selenium.js +2 -0
  224. package/dist/stagehand.d.ts +1 -0
  225. package/dist/stagehand.js +2 -0
  226. package/dist/storage/client.d.ts +151 -0
  227. package/dist/storage/client.js +329 -0
  228. package/dist/storage/index.d.ts +2 -0
  229. package/dist/storage/index.js +4 -0
  230. package/dist/telemetry.d.ts +18 -0
  231. package/dist/telemetry.js +93 -0
  232. package/dist/updates/client.d.ts +8 -0
  233. package/dist/updates/client.js +128 -0
  234. package/dist/updates/index.d.ts +1 -0
  235. package/dist/updates/index.js +1 -0
  236. package/dist/utils/http.d.ts +39 -0
  237. package/dist/utils/http.js +88 -0
  238. package/dist/utils/index.d.ts +4 -0
  239. package/dist/utils/index.js +4 -0
  240. package/dist/utils/logger.d.ts +27 -0
  241. package/dist/utils/logger.js +74 -0
  242. package/dist/utils/schema.d.ts +17 -0
  243. package/dist/utils/schema.js +31 -0
  244. package/dist/utils/url.d.ts +5 -0
  245. package/dist/utils/url.js +7 -0
  246. package/dist/vault/client.d.ts +43 -0
  247. package/dist/vault/client.js +123 -0
  248. package/dist/vault/index.d.ts +1 -0
  249. package/dist/vault/index.js +1 -0
  250. package/dist/version.d.ts +1 -0
  251. package/dist/version.js +4 -0
  252. package/dist/workspaces/browser-runtime.d.ts +251 -0
  253. package/dist/workspaces/browser-runtime.js +1025 -0
  254. package/dist/workspaces/client.d.ts +48 -0
  255. package/dist/workspaces/client.js +222 -0
  256. package/dist/workspaces/index.d.ts +2 -0
  257. package/dist/workspaces/index.js +2 -0
  258. package/dist/workspaces/runtime-event-pump.d.ts +65 -0
  259. package/dist/workspaces/runtime-event-pump.js +716 -0
  260. package/package.json +56 -0
@@ -0,0 +1,329 @@
1
+ // ============================================================================
2
+ // Storage Client - SDK client for R2 storage operations
3
+ // ============================================================================
4
+ import { StorageBrowseResponseSchema, StorageReadResponseSchema, } from '../contracts/storage.js';
5
+ import { SDK_VERSION } from '../version.js';
6
+ import { fetchWithTimeout, parseJsonResponse, LONG_TIMEOUT_MS } from '../utils/http.js';
7
+ import { stripTrailingSlash } from '../utils/url.js';
8
+ // ============================================================================
9
+ // Storage Client
10
+ // ============================================================================
11
+ /**
12
+ * Client for interacting with workspace storage.
13
+ */
14
+ export class StorageClient {
15
+ baseUrl;
16
+ workspace;
17
+ apiKey;
18
+ sessionId;
19
+ constructor(baseUrl, workspace, apiKey, sessionId) {
20
+ this.baseUrl = baseUrl;
21
+ this.workspace = workspace;
22
+ this.apiKey = apiKey;
23
+ this.sessionId = sessionId;
24
+ }
25
+ /**
26
+ * Get headers for API requests.
27
+ */
28
+ getHeaders(contentType) {
29
+ const headers = {
30
+ 'x-sdk-version': SDK_VERSION,
31
+ };
32
+ if (this.apiKey) {
33
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
34
+ }
35
+ if (this.sessionId) {
36
+ headers['x-session-id'] = this.sessionId;
37
+ }
38
+ if (contentType) {
39
+ headers['Content-Type'] = contentType;
40
+ }
41
+ return headers;
42
+ }
43
+ /**
44
+ * Clone this client for another workspace while preserving auth/session binding.
45
+ */
46
+ forWorkspace(workspace) {
47
+ return new StorageClient(this.baseUrl, workspace, this.apiKey, this.sessionId);
48
+ }
49
+ /**
50
+ * Build URL for object-catalog endpoints.
51
+ */
52
+ buildObjectsUrl(path = '') {
53
+ const base = stripTrailingSlash(this.baseUrl);
54
+ return `${base}/storage/objects${path}`;
55
+ }
56
+ resolveWorkspace(workspace) {
57
+ const value = (workspace || this.workspace).trim();
58
+ if (!value) {
59
+ throw new Error('Workspace is required');
60
+ }
61
+ return value;
62
+ }
63
+ encodePath(filePath) {
64
+ return filePath
65
+ .split('/')
66
+ .filter((segment) => segment.length > 0)
67
+ .map((segment) => encodeURIComponent(segment))
68
+ .join('/');
69
+ }
70
+ buildWorkspaceUrl(path, workspace, params = {}) {
71
+ const query = new URLSearchParams({ workspace });
72
+ for (const [key, value] of Object.entries(params)) {
73
+ if (value === undefined || value === null || value === '')
74
+ continue;
75
+ query.set(key, String(value));
76
+ }
77
+ return this.buildObjectsUrl(`${path}?${query.toString()}`);
78
+ }
79
+ toBlob(data, contentType) {
80
+ if (data instanceof Blob)
81
+ return data;
82
+ const copy = new Uint8Array(data);
83
+ return new Blob([copy], contentType ? { type: contentType } : undefined);
84
+ }
85
+ /**
86
+ * Upload a file to workspace storage.
87
+ *
88
+ * @param filePath - Logical path in storage (e.g., "uploads/document.pdf")
89
+ * @param data - File data as Buffer, Uint8Array, or Blob
90
+ * @param filename - Optional filename (defaults to last part of filePath)
91
+ */
92
+ async upload(filePath, data, filename) {
93
+ const workspace = this.resolveWorkspace();
94
+ const uploaded = await this.uploadObject(data, {
95
+ workspace,
96
+ path: filePath,
97
+ source: 'upload',
98
+ filename,
99
+ });
100
+ return {
101
+ success: true,
102
+ key: uploaded.objectKey,
103
+ filename: uploaded.filename,
104
+ };
105
+ }
106
+ /**
107
+ * Upload a file and register it in the storage object catalog.
108
+ *
109
+ * @param data - File data as Buffer, Uint8Array, or Blob
110
+ * @param options - Metadata and upload options
111
+ */
112
+ async uploadObject(data, options = {}) {
113
+ const formData = new FormData();
114
+ const workspace = this.resolveWorkspace(options.workspace);
115
+ const logicalPath = options.path?.trim() || undefined;
116
+ const source = options.source?.trim() || 'upload';
117
+ const filename = options.filename?.trim() ||
118
+ (logicalPath ? logicalPath.split('/').pop() : null) ||
119
+ 'file';
120
+ // Multipart parsing is sequential; send metadata fields before file.
121
+ formData.append('workspace', workspace);
122
+ formData.append('source', source);
123
+ if (logicalPath)
124
+ formData.append('path', logicalPath);
125
+ if (options.runtimeId)
126
+ formData.append('runtimeId', options.runtimeId);
127
+ if (options.driver)
128
+ formData.append('driver', options.driver);
129
+ if (options.target)
130
+ formData.append('target', options.target);
131
+ formData.append('file', this.toBlob(data), filename);
132
+ const response = await fetchWithTimeout(this.buildObjectsUrl(''), {
133
+ method: 'POST',
134
+ headers: this.getHeaders(),
135
+ body: formData,
136
+ }, LONG_TIMEOUT_MS);
137
+ if (!response.ok) {
138
+ const error = await response.json().catch(() => ({ error: response.statusText }));
139
+ throw new Error(`Upload object failed: ${error.error || response.statusText}`);
140
+ }
141
+ return parseJsonResponse(response, 'Upload storage object');
142
+ }
143
+ /**
144
+ * Get a file by logical path as raw bytes.
145
+ */
146
+ async get(filePath) {
147
+ const workspace = this.resolveWorkspace();
148
+ const encodedPath = this.encodePath(filePath);
149
+ const response = await fetchWithTimeout(this.buildWorkspaceUrl(`/by-path/get/${encodedPath}`, workspace), {
150
+ method: 'GET',
151
+ headers: this.getHeaders(),
152
+ }, LONG_TIMEOUT_MS);
153
+ if (!response.ok) {
154
+ throw new Error(`Get failed: ${response.statusText}`);
155
+ }
156
+ const arrayBuffer = await response.arrayBuffer();
157
+ return Buffer.from(arrayBuffer);
158
+ }
159
+ /**
160
+ * Get a presigned URL for direct download by logical path.
161
+ */
162
+ async getDownloadUrl(filePath) {
163
+ const workspace = this.resolveWorkspace();
164
+ const encodedPath = this.encodePath(filePath);
165
+ const response = await fetchWithTimeout(this.buildWorkspaceUrl(`/by-path/presign-download/${encodedPath}`, workspace), {
166
+ method: 'GET',
167
+ headers: this.getHeaders(),
168
+ });
169
+ if (!response.ok) {
170
+ throw new Error(`Failed to get download URL: ${response.statusText}`);
171
+ }
172
+ const { url } = await parseJsonResponse(response, 'Get download URL');
173
+ return url;
174
+ }
175
+ /**
176
+ * Browse files/folders by logical prefix (hierarchical listing with cursor pagination).
177
+ */
178
+ async browse(options = {}) {
179
+ const workspace = this.resolveWorkspace();
180
+ const response = await fetchWithTimeout(this.buildWorkspaceUrl('/by-path/browse', workspace, {
181
+ prefix: options.prefix,
182
+ cursor: options.cursor,
183
+ limit: options.limit,
184
+ }), {
185
+ method: 'GET',
186
+ headers: this.getHeaders(),
187
+ });
188
+ if (!response.ok) {
189
+ throw new Error(`Browse failed: ${response.statusText}`);
190
+ }
191
+ const data = await parseJsonResponse(response, 'Browse files');
192
+ return StorageBrowseResponseSchema.parse(data);
193
+ }
194
+ /**
195
+ * Read a UTF-8 text preview by logical path.
196
+ */
197
+ async read(filePath, maxBytes) {
198
+ const workspace = this.resolveWorkspace();
199
+ const encodedPath = this.encodePath(filePath);
200
+ const response = await fetchWithTimeout(this.buildWorkspaceUrl(`/by-path/read/${encodedPath}`, workspace, {
201
+ maxBytes,
202
+ }), {
203
+ method: 'GET',
204
+ headers: this.getHeaders(),
205
+ });
206
+ if (!response.ok) {
207
+ throw new Error(`Read failed: ${response.statusText}`);
208
+ }
209
+ const data = await parseJsonResponse(response, 'Read file text preview');
210
+ return StorageReadResponseSchema.parse(data);
211
+ }
212
+ /**
213
+ * List object metadata from the storage catalog.
214
+ */
215
+ async listObjects(options = {}) {
216
+ const workspace = this.resolveWorkspace(options.workspace);
217
+ const params = new URLSearchParams({ workspace });
218
+ if (options.runtimeId)
219
+ params.set('runtimeId', options.runtimeId);
220
+ if (options.source)
221
+ params.set('source', options.source);
222
+ if (options.driver)
223
+ params.set('driver', options.driver);
224
+ if (options.target)
225
+ params.set('target', options.target);
226
+ if (options.pathPrefix)
227
+ params.set('pathPrefix', options.pathPrefix);
228
+ if (typeof options.limit === 'number')
229
+ params.set('limit', String(options.limit));
230
+ if (typeof options.offset === 'number')
231
+ params.set('offset', String(options.offset));
232
+ const response = await fetchWithTimeout(this.buildObjectsUrl(`?${params.toString()}`), {
233
+ method: 'GET',
234
+ headers: this.getHeaders(),
235
+ });
236
+ if (!response.ok) {
237
+ throw new Error(`List objects failed: ${response.statusText}`);
238
+ }
239
+ const { objects } = await parseJsonResponse(response, 'List storage objects');
240
+ return objects;
241
+ }
242
+ /**
243
+ * Download an object by catalog ID.
244
+ */
245
+ async downloadObject(id) {
246
+ const response = await fetchWithTimeout(this.buildObjectsUrl(`/${encodeURIComponent(id)}/download`), {
247
+ method: 'GET',
248
+ headers: this.getHeaders(),
249
+ }, LONG_TIMEOUT_MS);
250
+ if (!response.ok) {
251
+ throw new Error(`Object download failed: ${response.statusText}`);
252
+ }
253
+ const arrayBuffer = await response.arrayBuffer();
254
+ return Buffer.from(arrayBuffer);
255
+ }
256
+ /**
257
+ * Get a presigned URL for direct download by object ID.
258
+ */
259
+ async getObjectDownloadUrl(id) {
260
+ const response = await fetchWithTimeout(this.buildObjectsUrl(`/${encodeURIComponent(id)}/presign-download`), {
261
+ method: 'GET',
262
+ headers: this.getHeaders(),
263
+ });
264
+ if (!response.ok) {
265
+ throw new Error(`Failed to get object download URL: ${response.statusText}`);
266
+ }
267
+ const { url } = await parseJsonResponse(response, 'Get object download URL');
268
+ return url;
269
+ }
270
+ /**
271
+ * Get all downloads as a ZIP file.
272
+ */
273
+ async getDownloadsZip() {
274
+ const workspace = this.resolveWorkspace();
275
+ const response = await fetchWithTimeout(this.buildWorkspaceUrl('/downloads', workspace), {
276
+ method: 'GET',
277
+ headers: this.getHeaders(),
278
+ }, LONG_TIMEOUT_MS);
279
+ if (!response.ok) {
280
+ if (response.status === 404) {
281
+ throw new Error('No downloads found');
282
+ }
283
+ throw new Error(`Failed to get downloads: ${response.statusText}`);
284
+ }
285
+ const arrayBuffer = await response.arrayBuffer();
286
+ return Buffer.from(arrayBuffer);
287
+ }
288
+ /**
289
+ * Delete a file by logical path.
290
+ */
291
+ async delete(filePath) {
292
+ const workspace = this.resolveWorkspace();
293
+ const encodedPath = this.encodePath(filePath);
294
+ const response = await fetchWithTimeout(this.buildWorkspaceUrl(`/by-path/${encodedPath}`, workspace), {
295
+ method: 'DELETE',
296
+ headers: this.getHeaders(),
297
+ });
298
+ if (!response.ok) {
299
+ throw new Error(`Delete failed: ${response.statusText}`);
300
+ }
301
+ }
302
+ /**
303
+ * Delete an object by catalog ID.
304
+ */
305
+ async deleteObject(id) {
306
+ const response = await fetchWithTimeout(this.buildObjectsUrl(`/${encodeURIComponent(id)}`), {
307
+ method: 'DELETE',
308
+ headers: this.getHeaders(),
309
+ });
310
+ if (!response.ok) {
311
+ throw new Error(`Delete object failed: ${response.statusText}`);
312
+ }
313
+ }
314
+ /**
315
+ * Delete all files for this workspace.
316
+ */
317
+ async deleteAll() {
318
+ const workspace = this.resolveWorkspace();
319
+ const response = await fetchWithTimeout(this.buildWorkspaceUrl('', workspace), {
320
+ method: 'DELETE',
321
+ headers: this.getHeaders(),
322
+ });
323
+ if (!response.ok) {
324
+ throw new Error(`Delete all failed: ${response.statusText}`);
325
+ }
326
+ const result = await parseJsonResponse(response, 'Delete all');
327
+ return result.deletedCount;
328
+ }
329
+ }
@@ -0,0 +1,2 @@
1
+ export { StorageClient } from './client.js';
2
+ export type { StorageBrowseOptions, StorageBrowseFile, StorageBrowseResult, StorageReadResult, UploadResult, PresignedUrlResult, StorageObject, ListObjectsOptions, ListObjectsResult, UploadObjectOptions, UploadObjectResult, } from './client.js';
@@ -0,0 +1,4 @@
1
+ // ============================================================================
2
+ // Storage Module - SDK storage client exports
3
+ // ============================================================================
4
+ export { StorageClient } from './client.js';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Initialize the isolated Sentry client for SDK telemetry.
3
+ * Idempotent — safe to call multiple times (e.g. from multiple Bctrl instances).
4
+ * Uses a separate client instance to avoid conflicts with user's Sentry.
5
+ */
6
+ export declare function init(): void;
7
+ /**
8
+ * Disable telemetry. Once called, no events will be sent.
9
+ */
10
+ export declare function disable(): void;
11
+ /**
12
+ * Capture an error and send it to Sentry (if telemetry is enabled).
13
+ */
14
+ export declare function captureError(err: Error, context?: Record<string, unknown>): void;
15
+ /**
16
+ * Check if telemetry is currently enabled.
17
+ */
18
+ export declare function isEnabled(): boolean;
@@ -0,0 +1,93 @@
1
+ // ============================================================================
2
+ // SDK Telemetry - Isolated Sentry client for error tracking
3
+ // ============================================================================
4
+ //
5
+ // Uses a dedicated Sentry client that won't conflict with the user's own Sentry.
6
+ // Respects the `telemetry: false` opt-out in BctrlOptions.
7
+ // Strips auth headers and sensitive data before sending events.
8
+ // ============================================================================
9
+ import { NodeClient, Scope, makeNodeTransport, defaultStackParser, } from '@sentry/node';
10
+ import { SDK_VERSION } from './version.js';
11
+ let sentryClient;
12
+ let enabled = true;
13
+ function isRemoteTelemetryEnabled() {
14
+ return process.env.NODE_ENV !== 'development' || process.env.OBS_REMOTE === 'true';
15
+ }
16
+ /**
17
+ * Initialize the isolated Sentry client for SDK telemetry.
18
+ * Idempotent — safe to call multiple times (e.g. from multiple Bctrl instances).
19
+ * Uses a separate client instance to avoid conflicts with user's Sentry.
20
+ */
21
+ export function init() {
22
+ if (sentryClient)
23
+ return;
24
+ const dsn = process.env.SENTRY_DSN_SDK;
25
+ if (!dsn || !enabled || !isRemoteTelemetryEnabled())
26
+ return;
27
+ sentryClient = new NodeClient({
28
+ dsn,
29
+ release: `bctrl-sdk@${SDK_VERSION}`,
30
+ environment: process.env.NODE_ENV || 'production',
31
+ transport: makeNodeTransport,
32
+ stackParser: defaultStackParser,
33
+ integrations: [],
34
+ beforeSend(event) {
35
+ // Strip sensitive headers and data
36
+ if (event.request?.headers) {
37
+ delete event.request.headers['authorization'];
38
+ delete event.request.headers['x-internal-secret'];
39
+ delete event.request.headers['cookie'];
40
+ }
41
+ // Strip any sensitive data from breadcrumbs
42
+ if (event.breadcrumbs) {
43
+ for (const breadcrumb of event.breadcrumbs) {
44
+ if (breadcrumb.data) {
45
+ delete breadcrumb.data['authorization'];
46
+ delete breadcrumb.data['apiKey'];
47
+ delete breadcrumb.data['password'];
48
+ }
49
+ }
50
+ }
51
+ // Tag every event with SDK version
52
+ event.tags = { ...event.tags, sdkVersion: SDK_VERSION };
53
+ return event;
54
+ },
55
+ });
56
+ }
57
+ /**
58
+ * Disable telemetry. Once called, no events will be sent.
59
+ */
60
+ export function disable() {
61
+ enabled = false;
62
+ if (sentryClient) {
63
+ sentryClient.close();
64
+ sentryClient = undefined;
65
+ }
66
+ }
67
+ /**
68
+ * Capture an error and send it to Sentry (if telemetry is enabled).
69
+ */
70
+ export function captureError(err, context) {
71
+ if (!enabled || !sentryClient)
72
+ return;
73
+ const scope = new Scope();
74
+ scope.setClient(sentryClient);
75
+ if (context) {
76
+ scope.setContext('bctrl', context);
77
+ if (context.endpoint)
78
+ scope.setTag('endpoint', String(context.endpoint));
79
+ if (context.method)
80
+ scope.setTag('method', String(context.method));
81
+ if (context.statusCode)
82
+ scope.setTag('statusCode', String(context.statusCode));
83
+ if (context.errorCode)
84
+ scope.setTag('errorCode', String(context.errorCode));
85
+ }
86
+ scope.captureException(err);
87
+ }
88
+ /**
89
+ * Check if telemetry is currently enabled.
90
+ */
91
+ export function isEnabled() {
92
+ return enabled && sentryClient !== undefined;
93
+ }
@@ -0,0 +1,8 @@
1
+ import type { UpdateEvent } from '../contracts/public-api.js';
2
+ export declare class UpdatesClient {
3
+ private readonly baseUrl;
4
+ private readonly apiKey;
5
+ constructor(baseUrl: string, apiKey: string);
6
+ stream(signal?: AbortSignal): AsyncGenerator<UpdateEvent, void, void>;
7
+ private issueToken;
8
+ }
@@ -0,0 +1,128 @@
1
+ import { UpdateEventSchema } from '../contracts/public-api.js';
2
+ import { parseErrorDetails, parseJsonResponse } from '../utils/http.js';
3
+ import { SDK_VERSION } from '../version.js';
4
+ function buildHeaders(apiKey, accept) {
5
+ const headers = {
6
+ 'x-sdk-version': SDK_VERSION,
7
+ Authorization: `Bearer ${apiKey}`,
8
+ };
9
+ if (accept) {
10
+ headers.Accept = accept;
11
+ }
12
+ return headers;
13
+ }
14
+ export class UpdatesClient {
15
+ baseUrl;
16
+ apiKey;
17
+ constructor(baseUrl, apiKey) {
18
+ this.baseUrl = baseUrl;
19
+ this.apiKey = apiKey;
20
+ }
21
+ async *stream(signal) {
22
+ const token = await this.issueToken();
23
+ const response = await fetch(`${this.baseUrl}/updates/stream?token=${encodeURIComponent(token)}`, {
24
+ method: 'GET',
25
+ headers: buildHeaders(this.apiKey, 'text/event-stream'),
26
+ signal,
27
+ });
28
+ if (!response.ok) {
29
+ const details = parseErrorDetails(await response.text().catch(() => ''));
30
+ throw new Error(`Open updates stream: ${details.message}`);
31
+ }
32
+ const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
33
+ if (!contentType.includes('text/event-stream')) {
34
+ throw new Error(`Open updates stream: unsupported content type '${contentType || 'unknown'}'`);
35
+ }
36
+ if (!response.body) {
37
+ throw new Error('Open updates stream: response did not include a body');
38
+ }
39
+ const reader = response.body.getReader();
40
+ const decoder = new TextDecoder();
41
+ let buffer = '';
42
+ let eventName = 'message';
43
+ const dataLines = [];
44
+ const flushEvent = () => {
45
+ if (dataLines.length === 0) {
46
+ eventName = 'message';
47
+ return null;
48
+ }
49
+ const raw = dataLines.join('\n');
50
+ dataLines.length = 0;
51
+ const currentEventName = eventName;
52
+ eventName = 'message';
53
+ let parsed;
54
+ try {
55
+ parsed = JSON.parse(raw);
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ if (!parsed || typeof parsed !== 'object') {
61
+ return null;
62
+ }
63
+ if (currentEventName === 'connected') {
64
+ return null;
65
+ }
66
+ const normalized = UpdateEventSchema.safeParse(parsed);
67
+ return normalized.success ? normalized.data : null;
68
+ };
69
+ try {
70
+ while (!signal?.aborted) {
71
+ const { done, value } = await reader.read();
72
+ if (done)
73
+ break;
74
+ buffer += decoder.decode(value, { stream: true });
75
+ while (true) {
76
+ const newlineIndex = buffer.indexOf('\n');
77
+ if (newlineIndex < 0)
78
+ break;
79
+ let line = buffer.slice(0, newlineIndex);
80
+ buffer = buffer.slice(newlineIndex + 1);
81
+ if (line.endsWith('\r')) {
82
+ line = line.slice(0, -1);
83
+ }
84
+ if (line.length === 0) {
85
+ const update = flushEvent();
86
+ if (update) {
87
+ yield update;
88
+ }
89
+ continue;
90
+ }
91
+ if (line.startsWith(':'))
92
+ continue;
93
+ const separator = line.indexOf(':');
94
+ const field = separator >= 0 ? line.slice(0, separator) : line;
95
+ const rawValue = separator >= 0 ? line.slice(separator + 1) : '';
96
+ const valueText = rawValue.startsWith(' ') ? rawValue.slice(1) : rawValue;
97
+ if (field === 'event') {
98
+ eventName = valueText;
99
+ continue;
100
+ }
101
+ if (field === 'data') {
102
+ dataLines.push(valueText);
103
+ }
104
+ }
105
+ }
106
+ }
107
+ finally {
108
+ try {
109
+ reader.releaseLock();
110
+ }
111
+ catch {
112
+ // no-op
113
+ }
114
+ }
115
+ }
116
+ async issueToken() {
117
+ const response = await fetch(`${this.baseUrl}/updates/token`, {
118
+ method: 'POST',
119
+ headers: buildHeaders(this.apiKey, 'application/json'),
120
+ });
121
+ if (!response.ok) {
122
+ const details = parseErrorDetails(await response.text().catch(() => ''));
123
+ throw new Error(`Create updates token: ${details.message}`);
124
+ }
125
+ const payload = await parseJsonResponse(response, 'Create updates token');
126
+ return payload.token;
127
+ }
128
+ }
@@ -0,0 +1 @@
1
+ export * from './client.js';
@@ -0,0 +1 @@
1
+ export * from './client.js';
@@ -0,0 +1,39 @@
1
+ export declare const DEFAULT_TIMEOUT_MS = 30000;
2
+ export declare const LONG_TIMEOUT_MS = 120000;
3
+ /**
4
+ * Safely parse JSON response from a fetch Response.
5
+ * Throws a descriptive error if parsing fails.
6
+ *
7
+ * @param response - The fetch Response object
8
+ * @param context - Context string for error messages (e.g., "Create session", "RPC browser.newPage")
9
+ * @returns Parsed JSON data
10
+ * @throws Error if JSON parsing fails
11
+ */
12
+ export declare function parseJsonResponse<T>(response: Response, context: string): Promise<T>;
13
+ /**
14
+ * Extract a readable error message from an HTTP response body.
15
+ * Supports plain text and common JSON payload shapes ({ error }, { message }).
16
+ */
17
+ export interface ParsedHttpError {
18
+ message: string;
19
+ code?: string;
20
+ }
21
+ /**
22
+ * Parse a server error body into a normalized message + optional machine code.
23
+ */
24
+ export declare function parseErrorDetails(bodyText: string): ParsedHttpError;
25
+ /**
26
+ * Backward-compatible helper for call sites that only need the message text.
27
+ */
28
+ export declare function parseErrorMessage(bodyText: string): string;
29
+ /**
30
+ * Fetch with automatic timeout using AbortController.
31
+ * Throws a descriptive error if the request times out.
32
+ *
33
+ * @param url - The URL to fetch
34
+ * @param options - Standard fetch options (headers, method, body, etc.)
35
+ * @param timeoutMs - Timeout in milliseconds (default: DEFAULT_TIMEOUT_MS)
36
+ * @returns The fetch Response object
37
+ * @throws Error if request times out or fails
38
+ */
39
+ export declare function fetchWithTimeout(url: string, options?: RequestInit, timeoutMs?: number): Promise<Response>;