abelworkflow 0.2.0 → 0.6.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.
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import {
4
+ DEFAULT_HOST,
5
+ formatHttpUrl,
6
+ formatReadinessLines,
7
+ parseEntrypointArgs,
8
+ resolveHostForProbe,
9
+ } from "./entrypoint.js";
10
+ import { preflightStandaloneStartup } from "./startup.js";
11
+
12
+ describe("entrypoint host handling", () => {
13
+ it("defaults standalone host to localhost", () => {
14
+ const args = parseEntrypointArgs([], {});
15
+
16
+ expect(args.host).toBe(DEFAULT_HOST);
17
+ expect(args.host).toBe("localhost");
18
+ });
19
+
20
+ it("formats IPv6 hosts in readiness output", () => {
21
+ expect(formatHttpUrl("::1", 9222)).toBe("http://[::1]:9222");
22
+
23
+ expect(
24
+ formatReadinessLines({
25
+ mode: "standalone",
26
+ host: "::1",
27
+ port: 9222,
28
+ wsEndpoint: "ws://127.0.0.1:9223/devtools/browser/test",
29
+ tmpDir: "/tmp/dev-browser",
30
+ profileDir: "/tmp/dev-browser/profile",
31
+ })
32
+ ).toContain(" HTTP: http://[::1]:9222");
33
+ });
34
+
35
+ it("normalizes wildcard hosts to a reachable probe target", () => {
36
+ expect(resolveHostForProbe("0.0.0.0")).toBe("127.0.0.1");
37
+ expect(resolveHostForProbe("::")).toBe("::1");
38
+ expect(resolveHostForProbe("[::1]")).toBe("::1");
39
+ expect(resolveHostForProbe("localhost")).toBe("localhost");
40
+ });
41
+ });
42
+
43
+ describe("preflightStandaloneStartup", () => {
44
+ it("checks the configured host before starting", async () => {
45
+ const checkServer = vi.fn().mockResolvedValue({ ok: false });
46
+ const isPortInUse = vi.fn().mockResolvedValue(false);
47
+ const log = vi.fn();
48
+
49
+ await expect(
50
+ preflightStandaloneStartup(
51
+ {
52
+ mode: "standalone",
53
+ host: "::1",
54
+ port: 9222,
55
+ cdpPort: 9223,
56
+ headless: false,
57
+ },
58
+ {
59
+ checkServer,
60
+ isPortInUse,
61
+ log,
62
+ }
63
+ )
64
+ ).resolves.toBe(true);
65
+
66
+ expect(checkServer).toHaveBeenCalledWith("::1", 9222);
67
+ });
68
+
69
+ it("short-circuits when the configured host already has a server", async () => {
70
+ const checkServer = vi.fn().mockResolvedValue({ ok: true, info: { wsEndpoint: "ws://test" } });
71
+ const isPortInUse = vi.fn();
72
+ const log = vi.fn();
73
+
74
+ await expect(
75
+ preflightStandaloneStartup(
76
+ {
77
+ mode: "standalone",
78
+ host: "localhost",
79
+ port: 9222,
80
+ cdpPort: 9223,
81
+ headless: false,
82
+ },
83
+ {
84
+ checkServer,
85
+ isPortInUse,
86
+ log,
87
+ }
88
+ )
89
+ ).resolves.toBe(false);
90
+
91
+ expect(checkServer).toHaveBeenCalledWith("localhost", 9222);
92
+ expect(isPortInUse).not.toHaveBeenCalled();
93
+ expect(log).toHaveBeenCalledWith("Server already running on port 9222");
94
+ });
95
+ });
@@ -0,0 +1,153 @@
1
+ import { formatReadinessLines, type EntrypointArgs } from "./entrypoint.js";
2
+ import type { PackageManager } from "./runtime.js";
3
+
4
+ export interface RuntimePaths {
5
+ skillDir: string;
6
+ tmpDir: string;
7
+ profileDir: string;
8
+ browserDataDir: string;
9
+ }
10
+
11
+ interface StandaloneServer {
12
+ port: number;
13
+ wsEndpoint: string;
14
+ stop: () => Promise<void>;
15
+ }
16
+
17
+ interface ExtensionServer {
18
+ port: number;
19
+ wsEndpoint: string;
20
+ stop: () => Promise<void>;
21
+ }
22
+
23
+ export interface RunEntrypointDeps {
24
+ runtimePaths: RuntimePaths;
25
+ mkdir: (path: string, options: { recursive: true }) => void;
26
+ serveStandalone: (options: {
27
+ port: number;
28
+ host: string;
29
+ headless: boolean;
30
+ cdpPort: number;
31
+ profileDir: string;
32
+ }) => Promise<StandaloneServer>;
33
+ serveExtension: (options: { port: number; host: string }) => Promise<ExtensionServer>;
34
+ registerShutdown: (stop: () => Promise<void>) => void;
35
+ keepAlive: () => Promise<void>;
36
+ log: (line: string) => void;
37
+ ensureBrowser?: () => Promise<void>;
38
+ preflightStandalone?: () => Promise<boolean>;
39
+ }
40
+
41
+ export interface EnsurePlaywrightChromiumDeps {
42
+ isInstalled: () => boolean;
43
+ findPackageManager: () => PackageManager | null;
44
+ runCommand: (command: string, args: string[]) => Promise<void>;
45
+ log: (line: string) => void;
46
+ }
47
+
48
+ export interface PreflightStandaloneStartupDeps {
49
+ checkServer: (host: string, port: number) => Promise<{ ok: boolean; info?: { wsEndpoint?: string } }>;
50
+ isPortInUse: (port: number) => Promise<boolean>;
51
+ browserDataDir?: string;
52
+ recoverStaleBrowser?: (options: { cdpPort: number; browserDataDir: string }) => Promise<boolean>;
53
+ log: (line: string) => void;
54
+ }
55
+
56
+ export async function preflightStandaloneStartup(
57
+ args: EntrypointArgs,
58
+ deps: PreflightStandaloneStartupDeps
59
+ ): Promise<boolean> {
60
+ const serverCheck = await deps.checkServer(args.host, args.port);
61
+ if (serverCheck.ok) {
62
+ deps.log(`Server already running on port ${args.port}`);
63
+ return false;
64
+ }
65
+
66
+ if (await deps.isPortInUse(args.cdpPort)) {
67
+ const recovered =
68
+ deps.browserDataDir && deps.recoverStaleBrowser
69
+ ? await deps.recoverStaleBrowser({
70
+ cdpPort: args.cdpPort,
71
+ browserDataDir: deps.browserDataDir,
72
+ })
73
+ : false;
74
+
75
+ if (!recovered && (await deps.isPortInUse(args.cdpPort))) {
76
+ throw new Error(`CDP port ${args.cdpPort} is already in use by another process`);
77
+ }
78
+ }
79
+
80
+ return true;
81
+ }
82
+
83
+ export async function ensurePlaywrightChromium(deps: EnsurePlaywrightChromiumDeps) {
84
+ if (deps.isInstalled()) {
85
+ deps.log("Playwright Chromium already installed.");
86
+ return;
87
+ }
88
+
89
+ deps.log("Playwright Chromium not found. Installing (this may take a minute)...");
90
+ const manager = deps.findPackageManager();
91
+ if (!manager) {
92
+ throw new Error("No package manager found (tried bun, pnpm, npm)");
93
+ }
94
+
95
+ deps.log(`Using ${manager.name} to install Playwright...`);
96
+ await deps.runCommand(manager.command, manager.args);
97
+ deps.log("Chromium installed successfully.");
98
+ }
99
+
100
+ export async function runEntrypoint(args: EntrypointArgs, deps: RunEntrypointDeps) {
101
+ deps.mkdir(deps.runtimePaths.tmpDir, { recursive: true });
102
+
103
+ if (args.mode === "extension") {
104
+ const server = await deps.serveExtension({
105
+ port: args.port,
106
+ host: args.host,
107
+ });
108
+
109
+ for (const line of formatReadinessLines({
110
+ mode: "extension",
111
+ host: args.host,
112
+ port: args.port,
113
+ wsEndpoint: server.wsEndpoint,
114
+ })) {
115
+ deps.log(line);
116
+ }
117
+
118
+ deps.registerShutdown(() => server.stop());
119
+ await deps.keepAlive();
120
+ return;
121
+ }
122
+
123
+ deps.mkdir(deps.runtimePaths.profileDir, { recursive: true });
124
+
125
+ const shouldStart = await deps.preflightStandalone?.();
126
+ if (shouldStart === false) {
127
+ return;
128
+ }
129
+
130
+ await deps.ensureBrowser?.();
131
+
132
+ const server = await deps.serveStandalone({
133
+ port: args.port,
134
+ host: args.host,
135
+ headless: args.headless,
136
+ cdpPort: args.cdpPort,
137
+ profileDir: deps.runtimePaths.profileDir,
138
+ });
139
+
140
+ for (const line of formatReadinessLines({
141
+ mode: "standalone",
142
+ host: args.host,
143
+ port: args.port,
144
+ wsEndpoint: server.wsEndpoint,
145
+ tmpDir: deps.runtimePaths.tmpDir,
146
+ profileDir: deps.runtimePaths.profileDir,
147
+ })) {
148
+ deps.log(line);
149
+ }
150
+
151
+ deps.registerShutdown(() => server.stop());
152
+ await deps.keepAlive();
153
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  export interface ServeOptions {
4
4
  port?: number;
5
+ host?: string;
5
6
  headless?: boolean;
6
7
  cdpPort?: number;
7
8
  /** Directory to store persistent browser profiles (cookies, localStorage, etc.) */
@@ -1,32 +0,0 @@
1
- /**
2
- * Start the CDP relay server for Chrome extension mode
3
- *
4
- * Usage: npm run start-extension
5
- */
6
-
7
- import { serveRelay } from "@/relay.js";
8
-
9
- const PORT = parseInt(process.env.PORT || "9222", 10);
10
- const HOST = process.env.HOST || "127.0.0.1";
11
-
12
- async function main() {
13
- const server = await serveRelay({
14
- port: PORT,
15
- host: HOST,
16
- });
17
-
18
- // Handle shutdown
19
- const shutdown = async () => {
20
- console.log("\nShutting down relay server...");
21
- await server.stop();
22
- process.exit(0);
23
- };
24
-
25
- process.on("SIGINT", shutdown);
26
- process.on("SIGTERM", shutdown);
27
- }
28
-
29
- main().catch((err) => {
30
- console.error("Failed to start relay server:", err);
31
- process.exit(1);
32
- });
@@ -1,117 +0,0 @@
1
- import { serve } from "@/index.js";
2
- import { execSync } from "child_process";
3
- import { mkdirSync, existsSync, readdirSync } from "fs";
4
- import { join, dirname } from "path";
5
- import { fileURLToPath } from "url";
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
- const tmpDir = join(__dirname, "..", "tmp");
9
- const profileDir = join(__dirname, "..", "profiles");
10
-
11
- // Create tmp and profile directories if they don't exist
12
- console.log("Creating tmp directory...");
13
- mkdirSync(tmpDir, { recursive: true });
14
- console.log("Creating profiles directory...");
15
- mkdirSync(profileDir, { recursive: true });
16
-
17
- // Install Playwright browsers if not already installed
18
- console.log("Checking Playwright browser installation...");
19
-
20
- function findPackageManager(): { name: string; command: string } | null {
21
- const managers = [
22
- { name: "bun", command: "bunx playwright install chromium" },
23
- { name: "pnpm", command: "pnpm exec playwright install chromium" },
24
- { name: "npm", command: "npx playwright install chromium" },
25
- ];
26
-
27
- for (const manager of managers) {
28
- try {
29
- execSync(`which ${manager.name}`, { stdio: "ignore" });
30
- return manager;
31
- } catch {
32
- // Package manager not found, try next
33
- }
34
- }
35
- return null;
36
- }
37
-
38
- function isChromiumInstalled(): boolean {
39
- const homeDir = process.env.HOME || process.env.USERPROFILE || "";
40
- const playwrightCacheDir = join(homeDir, ".cache", "ms-playwright");
41
-
42
- if (!existsSync(playwrightCacheDir)) {
43
- return false;
44
- }
45
-
46
- // Check for chromium directories (e.g., chromium-1148, chromium_headless_shell-1148)
47
- try {
48
- const entries = readdirSync(playwrightCacheDir);
49
- return entries.some((entry) => entry.startsWith("chromium"));
50
- } catch {
51
- return false;
52
- }
53
- }
54
-
55
- try {
56
- if (!isChromiumInstalled()) {
57
- console.log("Playwright Chromium not found. Installing (this may take a minute)...");
58
-
59
- const pm = findPackageManager();
60
- if (!pm) {
61
- throw new Error("No package manager found (tried bun, pnpm, npm)");
62
- }
63
-
64
- console.log(`Using ${pm.name} to install Playwright...`);
65
- execSync(pm.command, { stdio: "inherit" });
66
- console.log("Chromium installed successfully.");
67
- } else {
68
- console.log("Playwright Chromium already installed.");
69
- }
70
- } catch (error) {
71
- console.error("Failed to install Playwright browsers:", error);
72
- console.log("You may need to run: npx playwright install chromium");
73
- }
74
-
75
- // Check if server is already running
76
- console.log("Checking for existing servers...");
77
- try {
78
- const res = await fetch("http://localhost:9222", {
79
- signal: AbortSignal.timeout(1000),
80
- });
81
- if (res.ok) {
82
- console.log("Server already running on port 9222");
83
- process.exit(0);
84
- }
85
- } catch {
86
- // Server not running, continue to start
87
- }
88
-
89
- // Clean up stale CDP port if HTTP server isn't running (crash recovery)
90
- // This handles the case where Node crashed but Chrome is still running on 9223
91
- try {
92
- const pid = execSync("lsof -ti:9223", { encoding: "utf-8" }).trim();
93
- if (pid) {
94
- console.log(`Cleaning up stale Chrome process on CDP port 9223 (PID: ${pid})`);
95
- execSync(`kill -9 ${pid}`);
96
- }
97
- } catch {
98
- // No process on CDP port, which is expected
99
- }
100
-
101
- console.log("Starting dev browser server...");
102
- const headless = process.env.HEADLESS === "true";
103
- const server = await serve({
104
- port: 9222,
105
- headless,
106
- profileDir,
107
- });
108
-
109
- console.log(`Dev browser server started`);
110
- console.log(` WebSocket: ${server.wsEndpoint}`);
111
- console.log(` Tmp directory: ${tmpDir}`);
112
- console.log(` Profile directory: ${profileDir}`);
113
- console.log(`\nReady`);
114
- console.log(`\nPress Ctrl+C to stop`);
115
-
116
- // Keep the process running
117
- await new Promise(() => {});
@@ -1,24 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Get the directory where this script is located
4
- SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5
-
6
- # Change to the script directory
7
- cd "$SCRIPT_DIR"
8
-
9
- # Parse command line arguments
10
- HEADLESS=false
11
- while [[ "$#" -gt 0 ]]; do
12
- case $1 in
13
- --headless) HEADLESS=true ;;
14
- *) echo "Unknown parameter: $1"; exit 1 ;;
15
- esac
16
- shift
17
- done
18
-
19
- echo "Installing dependencies..."
20
- npm install
21
-
22
- echo "Starting dev-browser server..."
23
- export HEADLESS=$HEADLESS
24
- npx tsx scripts/start-server.ts