Bridge the browser and your host machine. Write userscripts that call local executables.
fumi is a Chrome extension + native messaging host that lets you run JavaScript in any web page and invoke scripts on your machine from it — all managed as plain files in your editor, under version control.
Think Tampermonkey, but your userscripts can shell out to anything on your box.
// ==Fumi Action==
// @match https://github.com/*
// ==/Fumi Action==
document.addEventListener('keydown', async (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
const { stdout } = await fumi.run('save-note.sh', {
title: document.title,
url: location.href,
selection: String(window.getSelection()),
});
console.log('saved:', stdout);
}
});- Files, not a UI. Actions and scripts live in
~/.config/fumi/— edit with your editor, track with git, back up like any other dotfile. - Any language for host scripts.
fumi.run("foo.py", payload)— bash, Python, Go binary, whatever is executable. - Small, auditable surface. The host exposes exactly two operations: list actions, run a script. No remote file I/O, no shell interpolation, no writes from the browser.
- Tampermonkey-style frontmatter.
// @match,// @exclude— familiar and declarative. - Zero state in the browser. The extension is a thin runner; the filesystem is the source of truth.
[Web Page] ⇄ [User Script] ⇄ [Extension SW] ⇄ [fumi-host] ⇄ [your script]
⇣
~/.config/fumi/{actions,scripts}/
chrome.userScriptsruns your action JS on matched pages.fumi.run(name, payload)sends a Native Messaging request tofumi-host.fumi-hostspawns~/.config/fumi/scripts/<name>directly (no shell), pipespayloadas JSON on stdin, and returns{ exitCode, stdout, stderr, durationMs }.
- macOS (Linux / Windows not supported)
- Google Chrome — fumi requires the Allow User Scripts toggle enabled on its details page (
chrome.userScripts) - Go 1.26+ and Node.js 22+ (for building from source)
| Channel | Status |
|---|---|
Homebrew tap (brew install --cask tkuramot/tap/fumi) |
available |
| Chrome Web Store listing | available |
| GitHub Releases (binaries + extension zip) | available |
Install the extension from the Chrome Web Store and the binaries via Homebrew — no need to build from source.
brew install --cask tkuramot/tap/fumiThis installs both fumi and fumi-host to /opt/homebrew/bin (the path the Native Messaging manifest expects by default). See docs/installation.md for other install paths, including building from source.
fumi setupThis places the Native Messaging manifest, creates ~/.config/fumi/{actions,scripts}/ (mode 0700), and drops in a couple of samples.
Install from the Chrome Web Store listing. After installing, open the extension's Details page and toggle Allow User Scripts on — fumi uses chrome.userScripts, which Chrome keeps disabled by default.
fumi doctorShould report a green manifest, matching Extension ID, and a writable store.
$EDITOR ~/.config/fumi/actions/hello.js// ==Fumi Action==
// @match https://example.com/*
// ==/Fumi Action==
const { stdout } = await fumi.run('hello.sh', { url: location.href });
alert(stdout);cat > ~/.config/fumi/scripts/hello.sh <<'EOF'
#!/usr/bin/env bash
read -r payload
echo "hello from $payload"
EOF
chmod +x ~/.config/fumi/scripts/hello.shOpen the extension popup → Reload actions, then visit https://example.com.
| Command | Purpose |
|---|---|
fumi setup |
Install native messaging manifest and initialize the store |
fumi doctor |
Diagnose install / permissions / Extension ID mismatches |
fumi actions list |
List actions in the store |
fumi scripts list |
List scripts in the store |
fumi scripts run <name> [--payload '<json>'] |
Invoke a script from the shell for debugging |
fumi uninstall |
Remove the native messaging manifest (the store is preserved) |
This repo ships a Claude Code plugin with skills that scaffold new actions and scripts directly into ~/.config/fumi/:
fumi-action— create a userscript action with the right frontmatter and@matchpatternsfumi-script— create a host script with the stdin payload / stdout result contract wired up
Install from within Claude Code:
/plugin marketplace add tkuramot/fumi
/plugin install fumi@fumi
The first command registers this repo as a plugin marketplace; the second installs the fumi plugin from it. See the Claude Code plugin docs for details.
Once installed, invoke a skill by describing what you want, e.g. "add a fumi action that copies the PR title on GitHub".
fumi is designed so a compromised page or extension cannot reach beyond your own scripts:
- The host has no write, read, list, or arbitrary-path API — only
actions/listandscripts/run. - Script paths are resolved with
realpath+lstat, rejecting anything outsidescripts/, any symlink, and anything that isn't a regular file. - Scripts are spawned directly (no shell); payloads arrive on stdin only, never as argv.
allowed_originsis pinned to fumi's Extension IDs.
For the full threat model, see docs/security.md.
- Installation — build, setup, extension loading, updates, uninstall
- Authoring actions — frontmatter, match patterns, the
fumi.runAPI - Authoring scripts — stdin/stdout contract, env vars, limits
- CLI reference — every subcommand, flags, exit codes
- Security model — threat model, resolution rules, error codes
- Troubleshooting — common errors and fixes
Early development. Expect breaking changes.