Skip to content

Plugins

Install, manage, and write local JavaScript plugins that add abg commands or clean up page Markdown.

  1. Install from a GitHub repository, a git URL, or a local directory. --yes is required because plugin code is arbitrary JavaScript:

    Terminal window
    abg plugin install user/repo --yes
    abg plugin install https://github.com/user/repo.git --yes
    abg plugin install git@github.com:user/private-plugin.git --yes
    abg plugin install ./my-plugin --name my-plugin --yes
  2. Check that the Gateway loaded it:

    Terminal window
    abg plugin list --loaded
  3. Run one of its commands. Plugin commands are abg subcommands named after the plugin:

    Terminal window
    abg my-plugin greet --name "Ada"

Repository installs use your local git command. Private repositories work with your existing SSH keys, git credential helper, or GitHub CLI-backed git authentication. ABG does not ask for or store GitHub tokens, and it rejects HTTPS URLs with embedded credentials.

Terminal window
abg plugin list
abg plugin update
abg plugin update my-plugin
abg plugin disable my-plugin
abg plugin enable my-plugin
abg plugin reload my-plugin
abg plugin uninstall my-plugin
  • update runs git pull --ff-only for git-backed user plugins.
  • disable keeps the plugin directory and stops loading its commands and transforms. The state is stored in plugin-state.json under the active ABG user directory, not in an app database.
  • reload reloads a plugin in the running Gateway. Existing tab shares stay in place.
  1. Create a directory with two files:

    hello-plugin/
    index.js
    plugin.json

    index.js is required. plugin.json is optional, but listings and --help use it.

  2. Describe the plugin in plugin.json:

    {
    "name": "hello-plugin",
    "version": "0.1.0",
    "description": "Minimal ABG command plugin.",
    "commands": [
    {
    "name": "greet",
    "description": "Return a greeting.",
    "args": [
    { "name": "name", "type": "string", "required": false, "default": "ABG" }
    ]
    }
    ]
    }
  3. Register the command in index.js:

    abg.registerCommand("greet", async function (args) {
    const name = args.name || "ABG";
    return { ok: true, message: "Hello, " + name };
    });
  4. Install it and run the command:

    Terminal window
    abg plugin install ./hello-plugin --name hello-plugin --yes
    abg plugin reload
    abg hello-plugin greet --name "Ada"
    {
    "message": "Hello, Ada",
    "ok": true
    }
  5. After each edit, run abg plugin reload hello-plugin. If a reload fails, the previous version stays active.

Commands accept arguments in several forms:

Terminal window
abg hello-plugin greet --name "Ada"
abg hello-plugin greet --tab t1
abg hello-plugin greet --json '{"name":"Ada"}'
printf '{"name":"Ada"}' | abg hello-plugin greet --stdin

--tab, --tab-id, --match-url, --match-title, and --first bind the command to a shared tab and are not passed to the plugin. Command results are JSON. Handler failures return a JSON error with error, message, plugin, and command. The audit log records the plugin name, command name, argument keys, and argument byte length, but not argument values.

Command handlers can operate the bound tab with context.tab.<action>(options). These calls go through the same Gateway path as the CLI, so sharing, approvals, and audit logging still apply.

abg.registerCommand("clear-and-paste", async function (args, context) {
if (context.tabId == null) {
return { ok: false, error: "no_tab_context" };
}
await context.tab.clear({ selector: args.selector });
await context.tab.paste({ selector: args.selector, value: args.value });
return { ok: true };
});

Available methods mirror CLI commands: paste, clear, fill, click, key, read, describe, wait, screenshot, and navigate. Do not shell out from JavaScript to reach the browser another way.

If a command runs without an explicit tab and plugin.json declares domains, ABG binds it to the one shared tab whose URL matches. No match returns no_matching_tab. More than one match returns ambiguous_tab with candidates.

Transforms are synchronous string-to-string functions:

abg.log("loaded " + abg.plugin.name);
abg.registerTransform("gmail-clean-markdown", function (input) {
return String(input).trim();
});

Declare the domains and transform in plugin.json:

{
"name": "gmail-plugin",
"domains": ["https://mail.google.com/*"],
"transforms": ["gmail-clean-markdown"]
}

When a shared tab URL matches domains, abg read --format markdown tries the plugin’s transform first, then falls back to the generic html-to-markdown transform. The transform name must contain markdown.

ABG searches plugins in this order:

  1. Bundled plugins in Agent Browser Gateway.app/Contents/Resources/plugins/.
  2. User-installed plugins under ~/.abg/plugins/.
  3. Local development plugins in the repository checkout, when you run from the checkout.

Development Gateway runs on port 8766 use ~/.abg-dev/plugins/ instead of ~/.abg/plugins/, so production and development plugin state stay separate.

  • markdown-plugin: generic HTML to Markdown.
  • notion-plugin: Markdown for notion.so and notion.site pages.
  • gmail-plugin, slack-plugin, linear-plugin: per-domain Markdown examples. slack-plugin also adds catch-up, pending, and open-channel commands.
  • redaction-plugin: opt-in masking with abg read --format markdown --redact.
  • workflow-plugin: command examples that use context.tab.*.
  • info-plugin: a loader smoke test.

The full authoring guide is docs/PLUGINS.md.

  • CLI: commands plugins build on.
  • Security: why plugin code is your responsibility.