import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import type { Command } from "commander"; import { STATE_DIR_CLAWDBOT } from "../config/paths.js"; import { danger, info } from "../globals.js"; import { defaultRuntime } from "../runtime.js"; import { movePathToTrash } from "../browser/trash.js"; import { formatDocsLink } from "../terminal/links.js"; import { theme } from "../terminal/theme.js"; function bundledExtensionRootDir() { const here = path.dirname(fileURLToPath(import.meta.url)); return path.resolve(here, "../../assets/chrome-extension"); } function installedExtensionRootDir() { return path.join(STATE_DIR_CLAWDBOT, "browser", "chrome-extension"); } function hasManifest(dir: string) { return fs.existsSync(path.join(dir, "manifest.json")); } export async function installChromeExtension(opts?: { stateDir?: string; sourceDir?: string; }): Promise<{ path: string }> { const src = opts?.sourceDir ?? bundledExtensionRootDir(); if (!hasManifest(src)) { throw new Error("Bundled Chrome extension is missing. Reinstall Clawdbot and try again."); } const stateDir = opts?.stateDir ?? STATE_DIR_CLAWDBOT; const dest = path.join(stateDir, "browser", "chrome-extension"); fs.mkdirSync(path.dirname(dest), { recursive: true }); if (fs.existsSync(dest)) { await movePathToTrash(dest).catch(() => { const backup = `${dest}.old-${Date.now()}`; fs.renameSync(dest, backup); }); } await fs.promises.cp(src, dest, { recursive: true }); if (!hasManifest(dest)) { throw new Error("Chrome extension install failed (manifest.json missing). Try again."); } return { path: dest }; } export function registerBrowserExtensionCommands( browser: Command, parentOpts: (cmd: Command) => { json?: boolean }, ) { const ext = browser.command("extension").description("Chrome extension helpers"); ext .command("install") .description("Install the Chrome extension to a stable local path") .action(async (_opts, cmd) => { const parent = parentOpts(cmd); let installed: { path: string }; try { installed = await installChromeExtension(); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } if (parent?.json) { defaultRuntime.log(JSON.stringify({ ok: true, path: installed.path }, null, 2)); return; } defaultRuntime.log(installed.path); defaultRuntime.error( info( [ "Next:", `- Chrome → chrome://extensions → enable “Developer mode”`, `- “Load unpacked” → select: ${installed.path}`, `- Pin “Clawdbot Browser Relay”, then click it on the tab (badge shows ON)`, "", `${theme.muted("Docs:")} ${formatDocsLink("/tools/chrome-extension", "docs.clawd.bot/tools/chrome-extension")}`, ].join("\n"), ), ); }); ext .command("path") .description("Print the path to the installed Chrome extension (load unpacked)") .action((_opts, cmd) => { const parent = parentOpts(cmd); const dir = installedExtensionRootDir(); if (!hasManifest(dir)) { defaultRuntime.error( danger( [ 'Chrome extension is not installed. Run: "clawdbot browser extension install"', `Docs: ${formatDocsLink("/tools/chrome-extension", "docs.clawd.bot/tools/chrome-extension")}`, ].join("\n"), ), ); defaultRuntime.exit(1); } if (parent?.json) { defaultRuntime.log(JSON.stringify({ path: dir }, null, 2)); return; } defaultRuntime.log(dir); }); }