Skip to content

Latest commit

 

History

98 Commits

Folders and files

Repository files navigation

BashCut plugins

The plugin registry for BashCut. There is no server: BashCut reads registry.json from this repo, downloads plugin archives from this repo's GitHub Releases and checks their SHA-256 before anything runs. Plugins use BashCut's out-of-process plugin API (2–8) (Writing plugins).

Plugin What it does
Antigravity Adds Google's Antigravity CLI as an agent terminal, with BashCut tools, skills, project conversations and its own bypass-permissions setting. Contributed by Luan Tran
Silence Markers Adds a section marker at every quiet stretch in the selected clip
Whisper Captions Captions from speech on Apple Silicon (Whisper large-v3 turbo via MLX, MIT): Vietnamese and about 100 languages, timed per word, split into even lines; captions.transcribe provider
VieNeu TTS Vietnamese voiceover on this Mac (VieNeu-TTS v3 Turbo, Apache-2.0): 25 voices, voice cloning; voice.synthesize provider
AI Editor BashCut's editing agent in the agent dock: chat with Claude, GPT, Gemini and others using your API key; it edits through BashCut's commands. Needs BashCut with plugin API 4; agent.chat provider
Pre-production Agent skills for the work before the edit; scene-prompt turns a plot or scene idea into a cinematic prompt for AI video models (based on cinematic-video-prompt-engineer, MIT)

Layout

registry.json                 catalog BashCut reads — generated, never edited by hand
publishers.json               schemaVersion and publishers (source of registry.json)
plugins/<slug>/versions.json  the plugin's listing at its last release and its published versions (source)
plugins/<slug>/plugin.json    manifest (bashcut.plugin/1)
plugins/<slug>/bin/…          entrypoint and helpers
plugins/<slug>/listing.json   store listing: name and summary ({en, vi, …}), category, platforms, minAppVersion
plugins/<slug>/tests/         tests run by CI (not shipped)
plugins/<slug>/build.sh       optional: builds compiled helpers or bundles (CI and package.py run it; not shipped)
plugins/<slug>/src/           optional: sources for build.sh (not shipped)
samples/<slug>/               examples for plugin authors (never published): samples/terminal-agent, samples/views-example
scripts/new-plugin.py         creates a new plugin from a template (scripts/plugin-templates/)
scripts/plugin_manifest.py    the manifest rules BashCut applies, and the category list
scripts/package.py            zip + SHA-256 + signature; --register records the version in versions.json
scripts/build-registry.py     generates registry.json from the sources (--check in CI)

Why registry.json is generated

Each release or yank writes only its own plugins/<slug>/versions.json, so changes to different plugins never touch the same source file, and BashCut still downloads one registry.json (one request, one consistent snapshot). scripts/build-registry.py rebuilds it deterministically; CI fails when it is out of date. A merge or rebase conflict in registry.json is resolved by running the script again — the release workflow does that itself when two releases race. The listing in versions.json is a snapshot of the released listing.json and manifest, so unreleased changes on main never reach users.

Registry format

{
  "schemaVersion": 1,
  "publishers": {"bashcut": {"name": {"en": "BashCut"}, "keys": [], "verified": true}},
  "plugins": [{
    "id": "bashcut.silence-markers", "name": {"en": "Silence Markers", "vi": "Đánh dấu im lặng"},
    "summary": {"en": "Adds a section marker at every quiet stretch…", "vi": "Thêm mốc tại mọi đoạn im lặng…"},
    "publisher": "bashcut", "author": {"name": "BashCut", "url": "https://github.com/dongnguyenvie"}, "category": "audio",
    "capabilities": [], "actions": ["bashcut.silence-markers.mark"], "hooks": [],
    "versions": [{
      "version": "0.1.0", "apiVersion": 2, "minApiVersion": 2, "minAppVersion": "0.0.1",
      "platforms": ["macos-arm64", "macos-x86_64"],
      "url": "https://github.com/dongnguyenvie/bashcut-plugins/releases/download/silence-markers-v0.1.0/bashcut.silence-markers-0.1.0.zip",
      "sha256": "…", "signature": null, "size": 3352, "downloadBytes": 0, "releasedAt": "2026-10-03"
    }]
  }]
}

Display text (name, summary, and in manifests title, help, confirm) is a language map such as {"en": "Silence Markers", "vi": "Đánh dấu im lặng"}; a plain string means English, and a map with several languages must include en. Adding a language is adding a key — no new fields. package.py rejects the old titleVi/nameVi style.

author ({"name", "url"}, url optional) is who wrote the plugin; package.py copies it from the manifest's author. publisher is who signs and ships it (bashcut for everything in this repo), so a contributed plugin keeps publisher: bashcut and names its contributor as author. BashCut shows By in Plugins; older versions ignore the field.

Each archive holds one folder named after the plugin id. The registry keeps the newest 3 versions of each plugin.

Signatures

signature is ed25519:BASE64 over the 32 raw bytes of the archive's SHA-256. BashCut checks it before downloading and shows Signed by BashCut; a signature that matches no key is refused, an unsigned archive gets a warning. The BashCut public key is compiled into the app (PluginSignature.firstPartyKeys) and mirrored in publishers.bashcut.keys here for scripts/verify-registry.swift; the app ignores registry keys for bashcut.

  • The private key is the BASHCUT_SIGNING_KEY Actions secret (base64 raw 32 bytes). An offline copy is in the maintainer's login keychain as "BashCut plugin signing key (ed25519)".
  • release.yml refuses to publish without it, uploads <archive>.sig next to the zip and verifies the registry.
  • scripts/sign-registry.py signs versions published before signing existed (it re-downloads and checks each archive first); scripts/sign.swift is the signer both use.
  • Rotating: ship the new public key in a BashCut release first, then switch the secret; keep the old key in the app for one release cycle.

Withdrawing a version

scripts/yank.py <id> <version> "<reason>" marks a version "yanked" in its versions.json and regenerates registry.json; commit both and push. BashCut stops offering it, tells users who have it why, and Updates offers the newest good version. --undo restores it. Never re-publish a yanked version number.

Writing a plugin

scripts/new-plugin.py creates a working plugin to start from: the manifest, an entrypoint that already speaks the protocol, smoke tests and a README.

scripts/new-plugin.py my-voice --template capability --capability voice.synthesize --name "My Voice"
scripts/new-plugin.py clip-tools --template action --lang shell
scripts/new-plugin.py my-agent --template chat-agent --lang node --private --out ~/code
Template What you get
capability A provider for voice.synthesize, captions.transcribe, audio.beats, audio.loudness or audio.sync (--capability), returning a valid placeholder result
action A contributes.actions command that proposes a timeline edit (a marker at the playhead)
hook contributes.hooks on export.finished (writes pluginData) and media.imported
options Options of every type, including a secret, read by an action
chat-agent An agent.chat session plugin: a tab in the agent dock that streams events and calls BashCut commands

--lang picks the language: swift (default; build.sh compiles a universal binary, nothing to install for users), shell (sh and macOS built-ins; not for chat-agent), node (plain ES modules; the install recipe downloads Node.js when the Mac has none) or python (standard library; fine for private plugins, but the registry refuses a python3 dependency, see below). Your code goes in one handlers file; the protocol file next to it handles requests, errors, progress, events and host calls.

The plugin goes to plugins/<slug> with a listing.json and an empty versions.json. --category (one of CATEGORIES in scripts/plugin_manifest.py, which BashCut groups plugins by) goes in both plugin.json and listing.json; scripts/build-registry.py --check refuses any other category. --private, or --out outside plugins/, makes a standalone plugin for people who keep it to themselves; link it with scripts/dev-link.sh <folder>. The script checks the manifest against BashCut's rules before writing, refuses an existing folder or a plugin id already in use, and prints the next steps: build, run the tests, dev-link, Trust. scripts/tests/test_new_plugin.py generates every template in every language and runs its tests, so the templates keep up with the API.

Agent skills

A plugin can teach agents how to use it (plugin API 7): --skill adds skills/<slug>/SKILL.md and "contributes": {"skills": [{"path": "skills/<slug>"}]}. BashCut gives the skill to Claude, Codex and its other agents while the plugin is trusted and turned on, as <plugin-id>:<name>. Write it like the agent kit's skills:

  • Front matter name equal to the folder name, and a description: what it is for, "Use when …", then Triggers: with the words users really say (Vietnamese and English).
  • When to use the plugin, and when another tool is better.
  • The exact bashcut commands in order (plugins run <action> --params '{…}', the capability's command such as captions generate, plugins option for the options that matter), and what to check afterwards.
  • Limits: what needs the user (Trust, Install Dependencies…) and what to say when it fails.

Keep it under 64 KB (a folder under 2 MB) and text only. package.py refuses a skill BashCut would leave out. plugins/whisper-captions/skills/whisper-captions is an example.

Panels and views

A plugin can have its own panel in BashCut's left rail (plugin API 8): contributes.container (an SF Symbol icon and a title) and contributes.views. A view is not UI code: BashCut asks view.render, the plugin answers with JSON components (text, lists, buttons, inputs, images, audio…), BashCut draws them natively and sends what the user does back as view.event. The panel also lists the plugin's actions as Tools, its skills, the plugins it requires and the capabilities it uses.

Start from samples/views-example: every component, events and state, and how to reuse other plugins from a view (generate speech with voice.speak through whichever voice plugin the user has, call another plugin's capability with plugins.invoke). scripts/dev-link.sh samples/views-example to try it.

Rules for users who are not developers

Installing a plugin is one click in BashCut; users never open Terminal, install Homebrew or fix a Python. So:

  • A dependency is either a tool every Mac has (package.py keeps the list: sh, curl, afconvert, ditto, osascript…) or has an install recipe that downloads it into BASHCUT_PLUGIN_DATA. python3, git, swift and Homebrew tools are refused: on a fresh Mac they are missing or ask to install the Command Line Tools.
  • Plugins written in TypeScript ship one esbuild bundle and need only Node.js, which their recipe downloads from nodejs.org when the Mac has none (see AI Editor: build.sh runs npm ci with the lockfile; node_modules is never shipped). Their tests use node:test and run with npm test when the folder has a package.json.
  • Small plugins are compiled Swift (see Silence Markers: build.sh makes a universal binary with AVFoundation, no runtime needed). Plugins that need Python bring their own with uv, like VieNeu.
  • Probes must exit 0 when the tool works (afconvert -h exits 2, for example).

Testing locally

Run plugins/<slug>/build.sh first when the plugin has one. scripts/dev-link.sh <slug> symlinks a plugin into ~/Library/Application Support/BashCut/Plugins/. Open Plugins in BashCut and choose Trust once; changes to plugin.json or the entrypoint ask for Trust again, other files can change while you iterate. scripts/dev-link.sh <slug> --remove unlinks it. Plugins with heavy models have a fake mode for tests and CI (for VieNeu, VIENEU_FAKE=1).

Any plugin folder links too: scripts/dev-link.sh ~/code/my-plugin. Samples link the same way: scripts/dev-link.sh samples/terminal-agent adds a sample agent CLI to the agent dock (agent.terminal, plugin API 5). Its README explains how to turn it into a real CLI plugin such as Gemini CLI.

Versions stay 0.0.x while plugins are in beta.

Publishing

  1. Change plugins/<slug>, bump version in plugin.json, merge to main.
  2. Tag and push: git tag silence-markers-v0.2.0 && git push origin silence-markers-v0.2.0.
  3. The Release plugin workflow tests the plugin, zips it, creates the GitHub Release with the archive and its .sha256 and .sig, re-downloads and checks it, then commits the signed version to versions.json and the regenerated registry.json.

A version is never re-published: bump it instead.

Installing

In BashCut, open Plugins › Browse, choose Install, review the source, checksum and dependency plan, and approve. Updates appear under Updates; Installed › Remove uninstalls. Agents can run bashcut plugins search and bashcut plugins install <id>, but only the user approves an install.

By hand: download the zip from Releases, unzip it into ~/Library/Application Support/BashCut/Plugins/, open Plugins and choose Trust.

License

MIT. See LICENSE.

About

Official plugin registry for BashCut: signed plugins such as Whisper Captions, VieNeu TTS (Vietnamese voiceover), Silence Markers and the Director chat agent, served from registry.json and GitHub Releases.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages