Files
clawdbot/src/cli/browser-cli-extension.ts
2026-01-15 05:05:27 +00:00

117 lines
3.7 KiB
TypeScript

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);
});
}