@phi-code-admin/browser 1.0.2 → 1.0.4

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/dist/index.d.ts CHANGED
@@ -108,12 +108,6 @@ export declare function screenshot(options: {
108
108
  tabId: string;
109
109
  userId?: string;
110
110
  fullPage?: boolean;
111
- clip?: {
112
- x: number;
113
- y: number;
114
- width: number;
115
- height: number;
116
- };
117
111
  }): Promise<ScreenshotResult>;
118
112
  /**
119
113
  * High-level search macro: opens a new tab, navigates to a search engine
package/dist/index.js CHANGED
@@ -222,6 +222,27 @@ async function request(pathname, options = {}) {
222
222
  body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
223
223
  signal: controller.signal,
224
224
  });
225
+ if (options.responseType === "binary") {
226
+ if (!res.ok) {
227
+ // Even on error, the body is JSON — fall through to error parsing.
228
+ const text = await res.text();
229
+ let parsed = undefined;
230
+ if (text) {
231
+ try {
232
+ parsed = JSON.parse(text);
233
+ }
234
+ catch {
235
+ parsed = text;
236
+ }
237
+ }
238
+ const message = typeof parsed === "object" && parsed && "error" in parsed
239
+ ? String(parsed.error)
240
+ : `HTTP ${res.status}`;
241
+ throw new Error(`${method} ${pathname} → ${message}`);
242
+ }
243
+ const buffer = new Uint8Array(await res.arrayBuffer());
244
+ return buffer;
245
+ }
225
246
  const text = await res.text();
226
247
  let parsed = undefined;
227
248
  if (text) {
@@ -332,29 +353,60 @@ export async function extract(options) {
332
353
  sessionKey: options.sessionKey,
333
354
  });
334
355
  }
335
- const res = await request(`/tabs/${encodeURIComponent(tabId)}/extract`, {
356
+ // The camofox-browser POST /tabs/:tabId/extract endpoint is a
357
+ // *deterministic* extractor that requires a structured `schema` of
358
+ // refs from a prior snapshot — it's not a Readability extractor. Phi
359
+ // callers expect a plain `{title, content, textContent}` blob, so we
360
+ // achieve that via /evaluate, running a small Readability-style script
361
+ // inside the page. This keeps the public API stable regardless of how
362
+ // the camofox-browser server evolves.
363
+ const mode = options.mode ?? "readability";
364
+ const expression = `(() => {
365
+ const limit = 50000;
366
+ const title = document.title || "";
367
+ const url = window.location.href || "";
368
+ if (${JSON.stringify(mode)} === "html") {
369
+ return { title, url, content: document.documentElement.outerHTML.slice(0, limit) };
370
+ }
371
+ if (${JSON.stringify(mode)} === "text") {
372
+ return { title, url, textContent: (document.body && document.body.innerText || "").slice(0, limit) };
373
+ }
374
+ // readability-light: strip nav/footer/header/aside, keep <main>/<article>/body.
375
+ const clone = document.cloneNode(true);
376
+ clone.querySelectorAll("script,style,noscript,iframe,nav,footer,header,aside,svg,form").forEach((el) => el.remove());
377
+ const root = clone.querySelector("main") || clone.querySelector("article") || clone.body || clone;
378
+ const text = (root.innerText || root.textContent || "").replace(/\\n{3,}/g, "\\n\\n").trim();
379
+ const excerpt = text.slice(0, 240);
380
+ return {
381
+ title,
382
+ url,
383
+ content: root.innerHTML ? root.innerHTML.slice(0, limit) : undefined,
384
+ textContent: text.slice(0, limit),
385
+ excerpt,
386
+ length: text.length,
387
+ };
388
+ })()`;
389
+ const evalRes = await request(`/tabs/${encodeURIComponent(tabId)}/evaluate`, {
336
390
  method: "POST",
337
- body: {
338
- userId: options.userId ?? DEFAULT_USER_ID,
339
- sessionKey: options.sessionKey ?? DEFAULT_SESSION_KEY,
340
- mode: options.mode ?? "readability",
341
- },
391
+ body: { userId: options.userId ?? DEFAULT_USER_ID, expression },
342
392
  });
343
- return res;
393
+ return evalRes.result ?? {};
344
394
  }
345
395
  /** Capture a screenshot of the given tab as a base64-encoded PNG. */
346
396
  export async function screenshot(options) {
347
397
  const query = new URLSearchParams();
348
398
  query.set("userId", options.userId ?? DEFAULT_USER_ID);
399
+ // The server expects `fullPage=true` (string match), not `=1`.
349
400
  if (options.fullPage)
350
- query.set("fullPage", "1");
351
- if (options.clip)
352
- query.set("clip", JSON.stringify(options.clip));
353
- const res = await request(`/tabs/${encodeURIComponent(options.tabId)}/screenshot?${query.toString()}`);
401
+ query.set("fullPage", "true");
402
+ // The camofox-browser screenshot endpoint streams a raw `image/png`
403
+ // body, not a JSON envelope. Pull it as a Uint8Array and base64-encode
404
+ // here so the result is JSON-safe for the tool result channel.
405
+ const bytes = await request(`/tabs/${encodeURIComponent(options.tabId)}/screenshot?${query.toString()}`, { responseType: "binary" });
354
406
  return {
355
407
  tabId: options.tabId,
356
- mimeType: res.mimeType ?? "image/png",
357
- bytesBase64: res.image ?? "",
408
+ mimeType: "image/png",
409
+ bytesBase64: Buffer.from(bytes).toString("base64"),
358
410
  };
359
411
  }
360
412
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/browser",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Phi-code browser automation API: lazy-start the bundled Camoufox + camofox-browser server and expose 10 high-level tools (navigate, extract, screenshot, click, type, scroll, snapshot, search, close_tab, list_tabs) as plain ES module functions. Zero external dependencies at runtime.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,8 +38,8 @@
38
38
  "clean": "rimraf dist"
39
39
  },
40
40
  "dependencies": {
41
- "@phi-code-admin/camofox-browser": "1.0.1",
42
- "@phi-code-admin/camoufox-js": "1.0.0"
41
+ "@phi-code-admin/camofox-browser": "1.0.2",
42
+ "@phi-code-admin/camoufox-js": "1.0.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^24.0.0",