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.
- package/README.md +2 -1
- package/cli.js +89 -20
- 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: `
|
|
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
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
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) =>
|
|
2655
|
-
|
|
2656
|
-
|
|
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
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
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":
|