diggylab 0.1.32 → 0.1.34

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 (3) hide show
  1. package/README.md +2 -1
  2. package/cli.js +89 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ headless Chromium — the Electron app is not involved.
8
8
 
9
9
  - Node.js 22+
10
10
  - Linux packages: `xvfb`, `x11vnc`, `websockify`, `novnc` (Debian/Ubuntu: `apt install xvfb x11vnc websockify novnc`). The Docker image below bundles them.
11
- - Chromium for Playwright: `npx playwright install --with-deps chromium`
11
+ - Chromium for Playwright: run `diggylab install-browser` once after installing the CLI.
12
12
 
13
13
  ## Install
14
14
 
@@ -61,6 +61,7 @@ npm publish apps/desktop-agent/dist-npm --access public
61
61
  | `login` | Opens the Facebook login page on a virtual screen and prints a noVNC link + QR code. Log in from your phone; the session is stored encrypted on the server. |
62
62
  | `run` | Connects the WebSocket worker, receives assigned scans, scans headlessly, uploads results. |
63
63
  | `status` | Shows the config directory, API origin, paired device, and Facebook session state. |
64
+ | `install-browser` | Downloads the Chromium build Playwright needs (one time, ~150 MB). Run it once after installing the CLI. |
64
65
  | `unpair` | Removes device credentials, the Facebook session, and pending scans. |
65
66
 
66
67
  Useful options: `--api <origin>` (default `https://api.diggylab.com`), `--port` / `--bind` /
package/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/cli/cli.ts
4
4
  import { randomUUID as randomUUID3 } from "node:crypto";
5
+ import { spawn as spawn2 } from "node:child_process";
5
6
  import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "node:fs";
6
7
  import { hostname, homedir } from "node:os";
7
8
  import { dirname as dirname3, join as join4 } from "node:path";
@@ -2504,11 +2505,13 @@ Usage:
2504
2505
  diggylab login [--port 6080] [--bind 0.0.0.0] [--public-host <ip or host>]
2505
2506
  diggylab run
2506
2507
  diggylab status
2508
+ diggylab install-browser
2507
2509
  diggylab unpair
2508
2510
 
2509
2511
  Global options:
2510
2512
  --config <directory> Agent state directory (default ~/.diggylab)
2511
2513
  --api <origin> API origin (default $DIGGYLAB_API_URL or https://api.diggylab.com)`;
2514
+ var MISSING_BROWSER_HINT = "Chromium for Playwright is not installed yet. Run `diggylab install-browser` once.";
2512
2515
  function agentVersion() {
2513
2516
  let directory = fileURLToPath(new URL(".", import.meta.url));
2514
2517
  for (let depth = 0; depth < 4; depth += 1) {
@@ -2576,21 +2579,42 @@ function waitForFacebookLogin(login, timeoutMs) {
2576
2579
  });
2577
2580
  });
2578
2581
  }
2582
+ function pairErrorMessage(error, origin) {
2583
+ const code = error instanceof DesktopAgentClientError ? error.code : null;
2584
+ switch (code) {
2585
+ case "PAIRING_CODE_INVALID":
2586
+ return "The pairing code is invalid, already used, or older than 10 minutes. Codes belong to one environment: check --api and create a new code.";
2587
+ case "PLAN_RESTRICTED":
2588
+ return "This account's plan does not include the Desktop Agent.";
2589
+ case "CREDENTIAL_REVOKED":
2590
+ return "This device was revoked. Create a new pairing code to pair again.";
2591
+ case "NETWORK_ERROR":
2592
+ case "SERVER_UNAVAILABLE":
2593
+ return `Could not reach ${origin}. Check the --api origin and the network.`;
2594
+ default:
2595
+ return error instanceof Error ? error.message : String(error);
2596
+ }
2597
+ }
2579
2598
  async function commandPair(runtime, options, io) {
2580
2599
  const code = options.code?.trim();
2581
2600
  if (!code) {
2582
2601
  io.error("Missing --code <pairing code>. Create one on the Desktop Agent page of the web app.");
2583
2602
  return 2;
2584
2603
  }
2585
- const access = await runtime.manager.pair({
2586
- agentVersion: agentVersion(),
2587
- code,
2588
- name: sanitizeDeviceName(options.name?.trim() || hostname()),
2589
- platform: resolvePlatform()
2590
- });
2591
- io.log(`Paired as "${access.session.device.name}".`);
2592
- io.log("Next: run `diggylab login` to connect a Facebook account.");
2593
- return 0;
2604
+ try {
2605
+ const access = await runtime.manager.pair({
2606
+ agentVersion: agentVersion(),
2607
+ code,
2608
+ name: sanitizeDeviceName(options.name?.trim() || hostname()),
2609
+ platform: resolvePlatform()
2610
+ });
2611
+ io.log(`Paired as "${access.session.device.name}".`);
2612
+ io.log("Next: run `diggylab login` to connect a Facebook account.");
2613
+ return 0;
2614
+ } catch (error) {
2615
+ io.error(pairErrorMessage(error, runtime.endpoint.httpOrigin));
2616
+ return 1;
2617
+ }
2594
2618
  }
2595
2619
  async function commandLogin(runtime, options, io) {
2596
2620
  const login = new FacebookLoginService({
@@ -2633,11 +2657,44 @@ ${display.url}`);
2633
2657
  await waitForFacebookLogin(login, LOGIN_TIMEOUT_MS + 6e4);
2634
2658
  io.log("Facebook session saved.");
2635
2659
  return 0;
2660
+ } catch (error) {
2661
+ const message = error instanceof Error ? error.message : String(error);
2662
+ if (/Executable doesn't exist|playwright install/i.test(message)) {
2663
+ io.error(MISSING_BROWSER_HINT);
2664
+ return 1;
2665
+ }
2666
+ throw error;
2636
2667
  } finally {
2637
2668
  await login.close();
2638
2669
  await display?.stop();
2639
2670
  }
2640
2671
  }
2672
+ function playwrightCliPath() {
2673
+ try {
2674
+ return fileURLToPath(new URL("cli.js", import.meta.resolve("playwright")));
2675
+ } catch {
2676
+ return null;
2677
+ }
2678
+ }
2679
+ async function commandInstallBrowser(io) {
2680
+ const cli = playwrightCliPath();
2681
+ if (!cli) {
2682
+ io.error("Could not locate the bundled Playwright. Reinstall with `npm install -g diggylab@latest`.");
2683
+ return 1;
2684
+ }
2685
+ io.log("Downloading Chromium for Playwright...");
2686
+ const code = await new Promise((resolve, reject) => {
2687
+ const child = spawn2(process.execPath, [cli, "install", "chromium"], { stdio: "inherit" });
2688
+ child.once("error", reject);
2689
+ child.once("exit", (value) => resolve(value ?? 1));
2690
+ });
2691
+ if (code !== 0) {
2692
+ io.error("Chromium download failed. Check the network and try again.");
2693
+ return code;
2694
+ }
2695
+ io.log("Chromium installed.");
2696
+ return 0;
2697
+ }
2641
2698
  async function commandRun(runtime, io) {
2642
2699
  const stored = await runtime.manager.load();
2643
2700
  if (!stored) {
@@ -2651,10 +2708,16 @@ async function commandRun(runtime, io) {
2651
2708
  const session = new DesktopAgentSession({
2652
2709
  agentVersion: agentVersion(),
2653
2710
  clearTimer: (timer) => clearTimeout(timer),
2654
- createSocket: (url, accessToken) => new WebSocket(url, {
2655
- headers: { authorization: `Bearer ${accessToken}` },
2656
- maxPayload: 16 * 1024
2657
- }),
2711
+ createSocket: (url, accessToken) => {
2712
+ const socket = new WebSocket(url, {
2713
+ headers: { authorization: `Bearer ${accessToken}` },
2714
+ maxPayload: 16 * 1024
2715
+ });
2716
+ socket.on("close", (code, reason) => {
2717
+ io.log(`socket closed: ${code}${reason?.length ? ` (${reason.toString()})` : ""}`);
2718
+ });
2719
+ return socket;
2720
+ },
2658
2721
  credentials: runtime.manager,
2659
2722
  getFacebookReady: () => facebookReady,
2660
2723
  platform: resolvePlatform(),
@@ -2707,12 +2770,17 @@ async function commandRun(runtime, io) {
2707
2770
  await scanRunner.recover();
2708
2771
  await session.start();
2709
2772
  io.log(`Worker running as "${stored.device.name}". Press Ctrl+C to stop.`);
2710
- await new Promise((resolve) => {
2711
- process.once("SIGINT", resolve);
2712
- process.once("SIGTERM", resolve);
2713
- });
2714
- session.stop();
2715
- scanRunner.stop();
2773
+ const keepAlive = setInterval(() => void 0, 6e4);
2774
+ try {
2775
+ await new Promise((resolve) => {
2776
+ process.once("SIGINT", resolve);
2777
+ process.once("SIGTERM", resolve);
2778
+ });
2779
+ } finally {
2780
+ clearInterval(keepAlive);
2781
+ session.stop();
2782
+ scanRunner.stop();
2783
+ }
2716
2784
  return 0;
2717
2785
  }
2718
2786
  async function commandStatus(runtime, io) {
@@ -2749,11 +2817,12 @@ async function main(argv, io = console) {
2749
2817
  io.log(USAGE);
2750
2818
  return 0;
2751
2819
  }
2752
- if (!["login", "pair", "run", "status", "unpair"].includes(command)) {
2820
+ if (!["install-browser", "login", "pair", "run", "status", "unpair"].includes(command)) {
2753
2821
  io.error(`Unknown command "${command}".`);
2754
2822
  io.error(USAGE);
2755
2823
  return 2;
2756
2824
  }
2825
+ if (command === "install-browser") return commandInstallBrowser(io);
2757
2826
  const runtime = createRuntime(parsed.options);
2758
2827
  switch (command) {
2759
2828
  case "pair":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diggylab",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "DiggyLab Desktop Agent collects posts and information from Facebook Groups on a paired computer. (headless CLI)",
5
5
  "type": "module",
6
6
  "bin": {