import { access } from "node:fs/promises"; import { constants } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; export interface RuntimeResolverOptions { env?: NodeJS.ProcessEnv; homeDir?: string; skillDir?: string; } export interface WebAutomationRuntime { scriptsDir: string; checkInstall: { cwd: string; command: string; args: string[]; }; } async function assertFile(path: string, label: string): Promise { try { await access(path, constants.F_OK); } catch { throw new Error(`web-automation runtime is missing ${label}: ${path}`); } } async function assertExecutableOrFile(path: string, label: string): Promise { try { await access(path, constants.X_OK); } catch { await assertFile(path, label); } } function defaultSkillDir(): string { return resolve(dirname(fileURLToPath(import.meta.url)), ".."); } export async function resolveWebAutomationRuntime(options: RuntimeResolverOptions = {}): Promise { const env = options.env ?? process.env; const homeDir = options.homeDir ?? process.env.HOME ?? ""; const skillDir = options.skillDir ?? defaultSkillDir(); const candidates = [ env.AMAZON_SHOPPING_WEB_AUTOMATION_DIR, homeDir ? join(homeDir, ".openclaw", "workspace", "skills", "web-automation", "scripts") : undefined, resolve(skillDir, "..", "web-automation", "scripts") ].filter((candidate): candidate is string => Boolean(candidate)); const errors: string[] = []; for (const scriptsDir of candidates) { try { await assertFile(join(scriptsDir, "check-install.js"), "check-install.js"); await assertFile(join(scriptsDir, "package.json"), "package.json"); await assertExecutableOrFile(join(scriptsDir, "node_modules", ".bin", "tsx"), "node_modules/.bin/tsx"); return { scriptsDir, checkInstall: { cwd: scriptsDir, command: "node", args: ["check-install.js"] } }; } catch (error: unknown) { errors.push(error instanceof Error ? error.message : String(error)); } } throw new Error(`Unable to locate usable web-automation runtime.\n${errors.join("\n")}`); }