{"owner":"chidiwilliams","repo":"buzz","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":2574,"categories":["root-instruction","claude-rule","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","CLAUDE.md","buzz/plugins/AGENTS.md","buzz/plugins/CLAUDE.md"],"skills":{"AGENTS.md":"- Use uv to run tests and any scripts","CLAUDE.md":"Please see AGENTS.md","buzz/plugins/AGENTS.md":"# Buzz Plugin System — Guide for Agents\n\nThis document explains how the Buzz plugin system works and how to create new\nplugins. Read it fully before adding or modifying a plugin.\n\n## Overview\n\nPlugins extend the transcription pipeline without modifying Buzz's core. A\nplugin is a **folder** containing a `plugin.py` entry module that defines exactly\none subclass of `BuzzPlugin`. Plugins can:\n\n- Process / replace the **source audio** before transcription.\n- Modify or replace the **transcription result** (segments) after transcription.\n- Run **custom code** and import parts of Buzz.\n- Declare **pip dependencies** that get installed into the user cache on load.\n- Expose **configuration** fields (text, multi-line text, checkbox, password)\n  shown in a generated settings dialog.\n- Provide **localized** names/descriptions/labels.\n\n### Where plugins live\n\n- Bundled plugins ship in `buzz/plugins/<plugin_id>/` (this directory). They are\n  copied into the user-writable folder on first launch — see\n  `loader.BUNDLED_PLUGIN_IDS` and `loader.copy_bundled_plugins()`.\n- Installed plugins live in `<user_cache_dir>/Buzz/plugins/<plugin_id>/`.\n- Plugin dependencies install into `<user_cache_dir>/Buzz/plugins_deps/` which is\n  added to `sys.path`.\n\nUsers manage plugins from **Help → Plugins**: add by URL (a `.zip`),\nenable/disable, reorder execution, edit settings.\n\n## Architecture (key files)\n\n- `base.py` — the plugin contract: `BuzzPlugin`, `PluginMetadata`, `ConfigField`,\n  `ConfigFieldType`, `PluginContext`, and `plugin_gettext` (localization).\n- `loader.py` — discovery, zip download/extract, dynamic import, bundled-copy.\n- `manager.py` — `PluginManager`: registry, enable/order/config persistence,\n  dependency install, hook dispatch.\n- `post_processing.py` — runs post-transcription hooks off the UI thread and\n  marshals DB access back to the main thread.\n\n## The plugin contract\n\nA plugin subclasses `BuzzPlugin` and sets a `metadata` class attribute. All hooks\nare optional; defaults are no-ops.\n\n```python\nfrom buzz.plugins.base import (\n    BuzzPlugin, PluginMetadata, ConfigField, ConfigFieldType,\n    PluginContext, plugin_gettext,\n)\n\n_ = plugin_gettext(__file__)  # localized strings, see \"Localization\" below\n\n\nclass MyPlugin(BuzzPlugin):\n    metadata = PluginMetadata(\n        id=\"my_plugin\",                 # stable, unique; matches the folder name\n        name=_(\"My Plugin\"),\n        description=_(\"What this plugin does.\"),\n        version=\"1.0.0\",\n        pip_dependencies=[],            # e.g. [\"python-docx>=1.1\"]\n        config_fields=[\n            ConfigField(key=\"api_key\", label=_(\"API key\"),\n                        type=ConfigFieldType.PASSWORD),\n            ConfigField(key=\"enabled_flag\", label=_(\"Some flag\"),\n                        type=ConfigFieldType.BOOL, default=True),\n        ],\n    )\n\n    def before_transcription(self, task, context: PluginContext):\n        # Runs on the WORKER thread. May process the audio at task.file_path.\n        # Return a new file path to use, or None to leave it unchanged.\n        # Do NOT touch the database or Qt here.\n        return None\n\n    def after_transcription(self, task, segments, context: PluginContext):\n        # Runs on a BACKGROUND thread. Return the (possibly modified) list of\n        # Segment objects. Return `segments` unchanged to do nothing.\n        return segments\n\n    def on_complete(self, transcription_id, task, segments, context: PluginContext):\n        # Runs on a BACKGROUND thread AFTER the transcription is saved.\n        # Use for side effects keyed on the persisted transcription, e.g.\n        # writing notes or exporting a file.\n        return None\n```\n\n### Hooks and when they run\n\n| Hook                   | Thread       | Purpose                                            |\n|------------------------|--------------|----------------------------------------------------|\n| `check_skip`           | worker       | Return `list[Segment]` to skip transcription entirely, or `None` to proceed. Runs after `before_transcription`. DB access via `context.transcription_service` is safe (marshaled to main thread). |\n| `before_transcription` | worker       | Process/replace source audio; return new file path |\n| `after_transcription`  | background   | Modify/replace result `Segment`s before save       |\n| `on_complete`          | background   | Post-save side effects (notes, file export, upload)|\n\nHooks run for **enabled** plugins in the user-defined **order**. Exceptions in a\nhook are logged and never break the pipeline or other plugins.\n\n### `PluginContext`\n\nPassed to every hook. Fields:\n\n- `context.config` — resolved config dict keyed by `ConfigField.key` (includes\n  password values pulled from the keyring; missing values fall back to defaults).\n- `context.transcription_service` — DB access. **Safe to call from background\n  hooks**: calls are automatically marshaled to the main thread (the DB\n  connection is thread-affine). Useful methods:\n  `update_transcription_notes(id, text)`,\n  `replace_transcription_segments(id, segments)`,\n  `get_transcription_segments(transcription_id=id)`.\n- `context.settings` — the Buzz `Settings` object.\n- `context.log` — a `logging.Logger` namespaced to the plugin.\n\n### Data types\n\n- `task` is a `FileTranscriptionTask` (see `buzz/transcriber/transcriber.py`):\n  `file_path`, `original_file_path`, `uid`, `transcription_options`, etc.\n- `segments` is a `list[Segment]`; `Segment(start, end, text, translation=\"\")`\n  with `start`/`end` in **milliseconds**.\n\n## Configuration fields\n\nDeclare config via `ConfigField`. Types (`ConfigFieldType`):\n\n- `TEXT` — single-line input.\n- `TEXTAREA` — multi-line input.\n- `BOOL` — checkbox.\n- `PASSWORD` — masked input with a show/hide toggle. **Stored in the OS keyring**,\n  never in plain settings.\n\nEach field has `key`, `label`, optional `type`, `default`, `description`,\n`placeholder`. The settings dialog is generated automatically from these. Read\nvalues from `context.config[field.key]` in your hooks.\n\n## Dependencies\n\nList pip requirements in `metadata.pip_dependencies`. On first load the manager\ninstalls them into the shared `plugins_deps` cache folder (tracked so they are\nnot reinstalled unnecessarily). Prefer packages already bundled with Buzz (e.g.\n`openai`) and declare nothing when possible — dependency install can fail\noffline. See `buzz/pip_utils.py` for how pip is invoked across environments\n(frozen builds, sandboxes).\n\n## Localization\n\nPlugins cannot use Buzz's compiled `.mo` catalogs. Instead, call\n`plugin_gettext(__file__)` to get a translator (conventionally named `_`) and\nwrap user-facing strings (`name`, `description`, field `label`s, messages).\n\nProvide translations in JSON files in a `locale/` folder next to `plugin.py`,\nnamed by locale (`locale/lv_LV.json`) with a language-only fallback\n(`locale/lv.json`). Each file maps the **English source string** to its\ntranslation:\n\n```json\n{\n  \"My Plugin\": \"Mans spraudnis\",\n  \"API key\": \"API atslēga\"\n}\n```\n\nThe JSON **key must match the English source string exactly** (including\npunctuation, parentheses and whitespace) — it is matched verbatim against the\nstring you passed to `_()`. Multi-line source strings assembled via implicit\nstring concatenation in Python collapse into a single key, so the key has no\nline breaks. Strings without a translation (or when no file matches the active\nlocale) fall through unchanged, so a plugin works with no locale files at all.\nThe active locale comes from Buzz's `UI_LOCALE` setting.\n\n> **Critical:** Never use an empty string `\"\"` as a translation value. The\n> translator treats an empty value as \"no translation\" and falls back to the\n> English source string, but only if the value is falsy. A locale file with\n> `\"My Plugin\": \"\"` will cause the plugin name to appear blank in the UI.\n> Either provide a real translation or omit the key entirely.\n\nBundled plugins ship a translation file for **every locale Buzz supports**. The\ncurrent set (keep it in sync across all bundled plugins) is:\n\n```\nca_ES, da_DK, de_DE, es_ES, fr, it_IT, ja_JP, lv_LV, nl,\npl_PL, pt_BR, ru, uk_UA, zh_CN, zh_TW\n```\n\nWhen adding or changing a user-facing string in a bundled plugin, update the key\nin **all** of these files. See `ai_summary/locale/` or\n`enhanced_language_detection/locale/` for a complete worked example, and copy the\nfile set from an existing plugin so the locale list stays consistent.\n\n> **Cache note:** Bundled plugins are copied to `~/.cache/Buzz/plugins/` on\n> launch. `copy_bundled_plugins()` compares each bundled plugin against its\n> cached copy and refreshes the cache whenever the bundle differs — a fixed\n> `plugin.py`, or an added/updated/removed locale file. So editing a bundled\n> plugin's code or locales and relaunching Buzz is enough; no manual sync is\n> needed. (Build artifacts like `__pycache__`/`*.pyc` are ignored in the\n> comparison.) User-installed plugins are never touched.\n\n## Packaging & distribution\n\nA distributable plugin is a `.zip` whose contents are the plugin folder. The\narchive may have `plugin.py` at the root or inside a single wrapping directory\n(GitHub-style zips work). Users install it via **Help → Plugins → Add by URL**.\n\nLayout:\n\n```\nmy_plugin/\n  plugin.py            # required: defines one BuzzPlugin subclass\n  locale/              # optional: <locale>.json translation files\n    lv_LV.json\n```\n\n## Checklist for creating a plugin\n\n1. Create `buzz/plugins/<plugin_id>/plugin.py` with one `BuzzPlugin` subclass.\n2. Set `metadata` with a unique `id` (match the folder name), `name`,\n   `description`, and any `config_fields`.\n3. Implement only the hooks you need; respect the thread rules above.\n4. Use `plugin_gettext(__file__)` for user-facing strings; add `locale/*.json`\n   (for bundled plugins, provide all locales listed under **Localization** and\n   keep the keys matching the English source strings exactly).\n5. Keep `pip_dependencies` minimal; reuse bundled packages.\n6. To ship it bundled: add the id to `loader.BUNDLED_PLUGIN_IDS` and add the\n   folder to `Buzz.spec` `datas` (e.g. `(\"buzz/plugins/<id>\", \"plugins/<id>\")`).\n7. Add tests under `tests/plugins/` (see `plugin_system_test.py`).\n\n## Reference: the `ai_summary` plugin\n\n`ai_summary/plugin.py` is the shipped reference plugin. It uses `on_complete` to\nsummarize the transcript via an OpenAI-compatible API and writes the result to\nthe Notes field and/or a file. It demonstrates password config, localization, and\nreusing the bundled `openai` client (no extra dependencies).\n","buzz/plugins/CLAUDE.md":"# Plugins\n\nBefore adding or modifying any plugin in this directory, read [AGENTS.md](AGENTS.md)\nin full. It is the authoritative guide for the Buzz plugin system and covers:\n\n- The plugin contract (`BuzzPlugin`, `PluginMetadata`, hooks and their threads).\n- Configuration fields, dependencies, and `PluginContext`.\n- Localization (`plugin_gettext`, `locale/*.json`, the supported locale set).\n- Packaging, bundling, and the checklist for creating a plugin.\n\nFollow the conventions there — including localizing user-facing strings and\nkeeping the `locale/` file set in sync across bundled plugins.\n"},"files":{"AGENTS.md":"- Use uv to run tests and any scripts","CLAUDE.md":"Please see AGENTS.md","buzz/plugins/AGENTS.md":"# Buzz Plugin System — Guide for Agents\n\nThis document explains how the Buzz plugin system works and how to create new\nplugins. Read it fully before adding or modifying a plugin.\n\n## Overview\n\nPlugins extend the transcription pipeline without modifying Buzz's core. A\nplugin is a **folder** containing a `plugin.py` entry module that defines exactly\none subclass of `BuzzPlugin`. Plugins can:\n\n- Process / replace the **source audio** before transcription.\n- Modify or replace the **transcription result** (segments) after transcription.\n- Run **custom code** and import parts of Buzz.\n- Declare **pip dependencies** that get installed into the user cache on load.\n- Expose **configuration** fields (text, multi-line text, checkbox, password)\n  shown in a generated settings dialog.\n- Provide **localized** names/descriptions/labels.\n\n### Where plugins live\n\n- Bundled plugins ship in `buzz/plugins/<plugin_id>/` (this directory). They are\n  copied into the user-writable folder on first launch — see\n  `loader.BUNDLED_PLUGIN_IDS` and `loader.copy_bundled_plugins()`.\n- Installed plugins live in `<user_cache_dir>/Buzz/plugins/<plugin_id>/`.\n- Plugin dependencies install into `<user_cache_dir>/Buzz/plugins_deps/` which is\n  added to `sys.path`.\n\nUsers manage plugins from **Help → Plugins**: add by URL (a `.zip`),\nenable/disable, reorder execution, edit settings.\n\n## Architecture (key files)\n\n- `base.py` — the plugin contract: `BuzzPlugin`, `PluginMetadata`, `ConfigField`,\n  `ConfigFieldType`, `PluginContext`, and `plugin_gettext` (localization).\n- `loader.py` — discovery, zip download/extract, dynamic import, bundled-copy.\n- `manager.py` — `PluginManager`: registry, enable/order/config persistence,\n  dependency install, hook dispatch.\n- `post_processing.py` — runs post-transcription hooks off the UI thread and\n  marshals DB access back to the main thread.\n\n## The plugin contract\n\nA plugin subclasses `BuzzPlugin` and sets a `metadata` class attribute. All hooks\nare optional; defaults are no-ops.\n\n```python\nfrom buzz.plugins.base import (\n    BuzzPlugin, PluginMetadata, ConfigField, ConfigFieldType,\n    PluginContext, plugin_gettext,\n)\n\n_ = plugin_gettext(__file__)  # localized strings, see \"Localization\" below\n\n\nclass MyPlugin(BuzzPlugin):\n    metadata = PluginMetadata(\n        id=\"my_plugin\",                 # stable, unique; matches the folder name\n        name=_(\"My Plugin\"),\n        description=_(\"What this plugin does.\"),\n        version=\"1.0.0\",\n        pip_dependencies=[],            # e.g. [\"python-docx>=1.1\"]\n        config_fields=[\n            ConfigField(key=\"api_key\", label=_(\"API key\"),\n                        type=ConfigFieldType.PASSWORD),\n            ConfigField(key=\"enabled_flag\", label=_(\"Some flag\"),\n                        type=ConfigFieldType.BOOL, default=True),\n        ],\n    )\n\n    def before_transcription(self, task, context: PluginContext):\n        # Runs on the WORKER thread. May process the audio at task.file_path.\n        # Return a new file path to use, or None to leave it unchanged.\n        # Do NOT touch the database or Qt here.\n        return None\n\n    def after_transcription(self, task, segments, context: PluginContext):\n        # Runs on a BACKGROUND thread. Return the (possibly modified) list of\n        # Segment objects. Return `segments` unchanged to do nothing.\n        return segments\n\n    def on_complete(self, transcription_id, task, segments, context: PluginContext):\n        # Runs on a BACKGROUND thread AFTER the transcription is saved.\n        # Use for side effects keyed on the persisted transcription, e.g.\n        # writing notes or exporting a file.\n        return None\n```\n\n### Hooks and when they run\n\n| Hook                   | Thread       | Purpose                                            |\n|------------------------|--------------|----------------------------------------------------|\n| `check_skip`           | worker       | Return `list[Segment]` to skip transcription entirely, or `None` to proceed. Runs after `before_transcription`. DB access via `context.transcription_service` is safe (marshaled to main thread). |\n| `before_transcription` | worker       | Process/replace source audio; return new file path |\n| `after_transcription`  | background   | Modify/replace result `Segment`s before save       |\n| `on_complete`          | background   | Post-save side effects (notes, file export, upload)|\n\nHooks run for **enabled** plugins in the user-defined **order**. Exceptions in a\nhook are logged and never break the pipeline or other plugins.\n\n### `PluginContext`\n\nPassed to every hook. Fields:\n\n- `context.config` — resolved config dict keyed by `ConfigField.key` (includes\n  password values pulled from the keyring; missing values fall back to defaults).\n- `context.transcription_service` — DB access. **Safe to call from background\n  hooks**: calls are automatically marshaled to the main thread (the DB\n  connection is thread-affine). Useful methods:\n  `update_transcription_notes(id, text)`,\n  `replace_transcription_segments(id, segments)`,\n  `get_transcription_segments(transcription_id=id)`.\n- `context.settings` — the Buzz `Settings` object.\n- `context.log` — a `logging.Logger` namespaced to the plugin.\n\n### Data types\n\n- `task` is a `FileTranscriptionTask` (see `buzz/transcriber/transcriber.py`):\n  `file_path`, `original_file_path`, `uid`, `transcription_options`, etc.\n- `segments` is a `list[Segment]`; `Segment(start, end, text, translation=\"\")`\n  with `start`/`end` in **milliseconds**.\n\n## Configuration fields\n\nDeclare config via `ConfigField`. Types (`ConfigFieldType`):\n\n- `TEXT` — single-line input.\n- `TEXTAREA` — multi-line input.\n- `BOOL` — checkbox.\n- `PASSWORD` — masked input with a show/hide toggle. **Stored in the OS keyring**,\n  never in plain settings.\n\nEach field has `key`, `label`, optional `type`, `default`, `description`,\n`placeholder`. The settings dialog is generated automatically from these. Read\nvalues from `context.config[field.key]` in your hooks.\n\n## Dependencies\n\nList pip requirements in `metadata.pip_dependencies`. On first load the manager\ninstalls them into the shared `plugins_deps` cache folder (tracked so they are\nnot reinstalled unnecessarily). Prefer packages already bundled with Buzz (e.g.\n`openai`) and declare nothing when possible — dependency install can fail\noffline. See `buzz/pip_utils.py` for how pip is invoked across environments\n(frozen builds, sandboxes).\n\n## Localization\n\nPlugins cannot use Buzz's compiled `.mo` catalogs. Instead, call\n`plugin_gettext(__file__)` to get a translator (conventionally named `_`) and\nwrap user-facing strings (`name`, `description`, field `label`s, messages).\n\nProvide translations in JSON files in a `locale/` folder next to `plugin.py`,\nnamed by locale (`locale/lv_LV.json`) with a language-only fallback\n(`locale/lv.json`). Each file maps the **English source string** to its\ntranslation:\n\n```json\n{\n  \"My Plugin\": \"Mans spraudnis\",\n  \"API key\": \"API atslēga\"\n}\n```\n\nThe JSON **key must match the English source string exactly** (including\npunctuation, parentheses and whitespace) — it is matched verbatim against the\nstring you passed to `_()`. Multi-line source strings assembled via implicit\nstring concatenation in Python collapse into a single key, so the key has no\nline breaks. Strings without a translation (or when no file matches the active\nlocale) fall through unchanged, so a plugin works with no locale files at all.\nThe active locale comes from Buzz's `UI_LOCALE` setting.\n\n> **Critical:** Never use an empty string `\"\"` as a translation value. The\n> translator treats an empty value as \"no translation\" and falls back to the\n> English source string, but only if the value is falsy. A locale file with\n> `\"My Plugin\": \"\"` will cause the plugin name to appear blank in the UI.\n> Either provide a real translation or omit the key entirely.\n\nBundled plugins ship a translation file for **every locale Buzz supports**. The\ncurrent set (keep it in sync across all bundled plugins) is:\n\n```\nca_ES, da_DK, de_DE, es_ES, fr, it_IT, ja_JP, lv_LV, nl,\npl_PL, pt_BR, ru, uk_UA, zh_CN, zh_TW\n```\n\nWhen adding or changing a user-facing string in a bundled plugin, update the key\nin **all** of these files. See `ai_summary/locale/` or\n`enhanced_language_detection/locale/` for a complete worked example, and copy the\nfile set from an existing plugin so the locale list stays consistent.\n\n> **Cache note:** Bundled plugins are copied to `~/.cache/Buzz/plugins/` on\n> launch. `copy_bundled_plugins()` compares each bundled plugin against its\n> cached copy and refreshes the cache whenever the bundle differs — a fixed\n> `plugin.py`, or an added/updated/removed locale file. So editing a bundled\n> plugin's code or locales and relaunching Buzz is enough; no manual sync is\n> needed. (Build artifacts like `__pycache__`/`*.pyc` are ignored in the\n> comparison.) User-installed plugins are never touched.\n\n## Packaging & distribution\n\nA distributable plugin is a `.zip` whose contents are the plugin folder. The\narchive may have `plugin.py` at the root or inside a single wrapping directory\n(GitHub-style zips work). Users install it via **Help → Plugins → Add by URL**.\n\nLayout:\n\n```\nmy_plugin/\n  plugin.py            # required: defines one BuzzPlugin subclass\n  locale/              # optional: <locale>.json translation files\n    lv_LV.json\n```\n\n## Checklist for creating a plugin\n\n1. Create `buzz/plugins/<plugin_id>/plugin.py` with one `BuzzPlugin` subclass.\n2. Set `metadata` with a unique `id` (match the folder name), `name`,\n   `description`, and any `config_fields`.\n3. Implement only the hooks you need; respect the thread rules above.\n4. Use `plugin_gettext(__file__)` for user-facing strings; add `locale/*.json`\n   (for bundled plugins, provide all locales listed under **Localization** and\n   keep the keys matching the English source strings exactly).\n5. Keep `pip_dependencies` minimal; reuse bundled packages.\n6. To ship it bundled: add the id to `loader.BUNDLED_PLUGIN_IDS` and add the\n   folder to `Buzz.spec` `datas` (e.g. `(\"buzz/plugins/<id>\", \"plugins/<id>\")`).\n7. Add tests under `tests/plugins/` (see `plugin_system_test.py`).\n\n## Reference: the `ai_summary` plugin\n\n`ai_summary/plugin.py` is the shipped reference plugin. It uses `on_complete` to\nsummarize the transcript via an OpenAI-compatible API and writes the result to\nthe Notes field and/or a file. It demonstrates password config, localization, and\nreusing the bundled `openai` client (no extra dependencies).\n","buzz/plugins/CLAUDE.md":"# Plugins\n\nBefore adding or modifying any plugin in this directory, read [AGENTS.md](AGENTS.md)\nin full. It is the authoritative guide for the Buzz plugin system and covers:\n\n- The plugin contract (`BuzzPlugin`, `PluginMetadata`, hooks and their threads).\n- Configuration fields, dependencies, and `PluginContext`.\n- Localization (`plugin_gettext`, `locale/*.json`, the supported locale set).\n- Packaging, bundling, and the checklist for creating a plugin.\n\nFollow the conventions there — including localizing user-facing strings and\nkeeping the `locale/` file set in sync across bundled plugins.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/chidiwilliams/buzz/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"- Use uv to run tests and any scripts","isInternal":false,"tokens":9,"sizeBytes":37},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/chidiwilliams/buzz/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"Please see AGENTS.md","isInternal":false,"tokens":5,"sizeBytes":20},{"name":"AGENTS.md","path":"buzz/plugins/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/chidiwilliams/buzz/HEAD/buzz/plugins/AGENTS.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Buzz Plugin System — Guide for Agents\n\nThis document explains how the Buzz plugin system works and how to create new\nplugins. Read it fully before adding or modifying a plugin.\n\n## Overview\n\nPlugins extend the transcription pipeline without modifying Buzz's core. A\nplugin is a **folder** containing a `plugin.py` entry module that defines exactly\none subclass of `BuzzPlugin`. Plugins can:\n\n- Process / replace the **source audio** before transcription.\n- Modify or replace the **transcription result** (segments) after transcription.\n- Run **custom code** and import parts of Buzz.\n- Declare **pip dependencies** that get installed into the user cache on load.\n- Expose **configuration** fields (text, multi-line text, checkbox, password)\n  shown in a generated settings dialog.\n- Provide **localized** names/descriptions/labels.\n\n### Where plugins live\n\n- Bundled plugins ship in `buzz/plugins/<plugin_id>/` (this directory). They are\n  copied into the user-writable folder on first launch — see\n  `loader.BUNDLED_PLUGIN_IDS` and `loader.copy_bundled_plugins()`.\n- Installed plugins live in `<user_cache_dir>/Buzz/plugins/<plugin_id>/`.\n- Plugin dependencies install into `<user_cache_dir>/Buzz/plugins_deps/` which is\n  added to `sys.path`.\n\nUsers manage plugins from **Help → Plugins**: add by URL (a `.zip`),\nenable/disable, reorder execution, edit settings.\n\n## Architecture (key files)\n\n- `base.py` — the plugin contract: `BuzzPlugin`, `PluginMetadata`, `ConfigField`,\n  `ConfigFieldType`, `PluginContext`, and `plugin_gettext` (localization).\n- `loader.py` — discovery, zip download/extract, dynamic import, bundled-copy.\n- `manager.py` — `PluginManager`: registry, enable/order/config persistence,\n  dependency install, hook dispatch.\n- `post_processing.py` — runs post-transcription hooks off the UI thread and\n  marshals DB access back to the main thread.\n\n## The plugin contract\n\nA plugin subclasses `BuzzPlugin` and sets a `metadata` class attribute. All hooks\nare optional; defaults are no-ops.\n\n```python\nfrom buzz.plugins.base import (\n    BuzzPlugin, PluginMetadata, ConfigField, ConfigFieldType,\n    PluginContext, plugin_gettext,\n)\n\n_ = plugin_gettext(__file__)  # localized strings, see \"Localization\" below\n\n\nclass MyPlugin(BuzzPlugin):\n    metadata = PluginMetadata(\n        id=\"my_plugin\",                 # stable, unique; matches the folder name\n        name=_(\"My Plugin\"),\n        description=_(\"What this plugin does.\"),\n        version=\"1.0.0\",\n        pip_dependencies=[],            # e.g. [\"python-docx>=1.1\"]\n        config_fields=[\n            ConfigField(key=\"api_key\", label=_(\"API key\"),\n                        type=ConfigFieldType.PASSWORD),\n            ConfigField(key=\"enabled_flag\", label=_(\"Some flag\"),\n                        type=ConfigFieldType.BOOL, default=True),\n        ],\n    )\n\n    def before_transcription(self, task, context: PluginContext):\n        # Runs on the WORKER thread. May process the audio at task.file_path.\n        # Return a new file path to use, or None to leave it unchanged.\n        # Do NOT touch the database or Qt here.\n        return None\n\n    def after_transcription(self, task, segments, context: PluginContext):\n        # Runs on a BACKGROUND thread. Return the (possibly modified) list of\n        # Segment objects. Return `segments` unchanged to do nothing.\n        return segments\n\n    def on_complete(self, transcription_id, task, segments, context: PluginContext):\n        # Runs on a BACKGROUND thread AFTER the transcription is saved.\n        # Use for side effects keyed on the persisted transcription, e.g.\n        # writing notes or exporting a file.\n        return None\n```\n\n### Hooks and when they run\n\n| Hook                   | Thread       | Purpose                                            |\n|------------------------|--------------|----------------------------------------------------|\n| `check_skip`           | worker       | Return `list[Segment]` to skip transcription entirely, or `None` to proceed. Runs after `before_transcription`. DB access via `context.transcription_service` is safe (marshaled to main thread). |\n| `before_transcription` | worker       | Process/replace source audio; return new file path |\n| `after_transcription`  | background   | Modify/replace result `Segment`s before save       |\n| `on_complete`          | background   | Post-save side effects (notes, file export, upload)|\n\nHooks run for **enabled** plugins in the user-defined **order**. Exceptions in a\nhook are logged and never break the pipeline or other plugins.\n\n### `PluginContext`\n\nPassed to every hook. Fields:\n\n- `context.config` — resolved config dict keyed by `ConfigField.key` (includes\n  password values pulled from the keyring; missing values fall back to defaults).\n- `context.transcription_service` — DB access. **Safe to call from background\n  hooks**: calls are automatically marshaled to the main thread (the DB\n  connection is thread-affine). Useful methods:\n  `update_transcription_notes(id, text)`,\n  `replace_transcription_segments(id, segments)`,\n  `get_transcription_segments(transcription_id=id)`.\n- `context.settings` — the Buzz `Settings` object.\n- `context.log` — a `logging.Logger` namespaced to the plugin.\n\n### Data types\n\n- `task` is a `FileTranscriptionTask` (see `buzz/transcriber/transcriber.py`):\n  `file_path`, `original_file_path`, `uid`, `transcription_options`, etc.\n- `segments` is a `list[Segment]`; `Segment(start, end, text, translation=\"\")`\n  with `start`/`end` in **milliseconds**.\n\n## Configuration fields\n\nDeclare config via `ConfigField`. Types (`ConfigFieldType`):\n\n- `TEXT` — single-line input.\n- `TEXTAREA` — multi-line input.\n- `BOOL` — checkbox.\n- `PASSWORD` — masked input with a show/hide toggle. **Stored in the OS keyring**,\n  never in plain settings.\n\nEach field has `key`, `label`, optional `type`, `default`, `description`,\n`placeholder`. The settings dialog is generated automatically from these. Read\nvalues from `context.config[field.key]` in your hooks.\n\n## Dependencies\n\nList pip requirements in `metadata.pip_dependencies`. On first load the manager\ninstalls them into the shared `plugins_deps` cache folder (tracked so they are\nnot reinstalled unnecessarily). Prefer packages already bundled with Buzz (e.g.\n`openai`) and declare nothing when possible — dependency install can fail\noffline. See `buzz/pip_utils.py` for how pip is invoked across environments\n(frozen builds, sandboxes).\n\n## Localization\n\nPlugins cannot use Buzz's compiled `.mo` catalogs. Instead, call\n`plugin_gettext(__file__)` to get a translator (conventionally named `_`) and\nwrap user-facing strings (`name`, `description`, field `label`s, messages).\n\nProvide translations in JSON files in a `locale/` folder next to `plugin.py`,\nnamed by locale (`locale/lv_LV.json`) with a language-only fallback\n(`locale/lv.json`). Each file maps the **English source string** to its\ntranslation:\n\n```json\n{\n  \"My Plugin\": \"Mans spraudnis\",\n  \"API key\": \"API atslēga\"\n}\n```\n\nThe JSON **key must match the English source string exactly** (including\npunctuation, parentheses and whitespace) — it is matched verbatim against the\nstring you passed to `_()`. Multi-line source strings assembled via implicit\nstring concatenation in Python collapse into a single key, so the key has no\nline breaks. Strings without a translation (or when no file matches the active\nlocale) fall through unchanged, so a plugin works with no locale files at all.\nThe active locale comes from Buzz's `UI_LOCALE` setting.\n\n> **Critical:** Never use an empty string `\"\"` as a translation value. The\n> translator treats an empty value as \"no translation\" and falls back to the\n> English source string, but only if the value is falsy. A locale file with\n> `\"My Plugin\": \"\"` will cause the plugin name to appear blank in the UI.\n> Either provide a real translation or omit the key entirely.\n\nBundled plugins ship a translation file for **every locale Buzz supports**. The\ncurrent set (keep it in sync across all bundled plugins) is:\n\n```\nca_ES, da_DK, de_DE, es_ES, fr, it_IT, ja_JP, lv_LV, nl,\npl_PL, pt_BR, ru, uk_UA, zh_CN, zh_TW\n```\n\nWhen adding or changing a user-facing string in a bundled plugin, update the key\nin **all** of these files. See `ai_summary/locale/` or\n`enhanced_language_detection/locale/` for a complete worked example, and copy the\nfile set from an existing plugin so the locale list stays consistent.\n\n> **Cache note:** Bundled plugins are copied to `~/.cache/Buzz/plugins/` on\n> launch. `copy_bundled_plugins()` compares each bundled plugin against its\n> cached copy and refreshes the cache whenever the bundle differs — a fixed\n> `plugin.py`, or an added/updated/removed locale file. So editing a bundled\n> plugin's code or locales and relaunching Buzz is enough; no manual sync is\n> needed. (Build artifacts like `__pycache__`/`*.pyc` are ignored in the\n> comparison.) User-installed plugins are never touched.\n\n## Packaging & distribution\n\nA distributable plugin is a `.zip` whose contents are the plugin folder. The\narchive may have `plugin.py` at the root or inside a single wrapping directory\n(GitHub-style zips work). Users install it via **Help → Plugins → Add by URL**.\n\nLayout:\n\n```\nmy_plugin/\n  plugin.py            # required: defines one BuzzPlugin subclass\n  locale/              # optional: <locale>.json translation files\n    lv_LV.json\n```\n\n## Checklist for creating a plugin\n\n1. Create `buzz/plugins/<plugin_id>/plugin.py` with one `BuzzPlugin` subclass.\n2. Set `metadata` with a unique `id` (match the folder name), `name`,\n   `description`, and any `config_fields`.\n3. Implement only the hooks you need; respect the thread rules above.\n4. Use `plugin_gettext(__file__)` for user-facing strings; add `locale/*.json`\n   (for bundled plugins, provide all locales listed under **Localization** and\n   keep the keys matching the English source strings exactly).\n5. Keep `pip_dependencies` minimal; reuse bundled packages.\n6. To ship it bundled: add the id to `loader.BUNDLED_PLUGIN_IDS` and add the\n   folder to `Buzz.spec` `datas` (e.g. `(\"buzz/plugins/<id>\", \"plugins/<id>\")`).\n7. Add tests under `tests/plugins/` (see `plugin_system_test.py`).\n\n## Reference: the `ai_summary` plugin\n\n`ai_summary/plugin.py` is the shipped reference plugin. It uses `on_complete` to\nsummarize the transcript via an OpenAI-compatible API and writes the result to\nthe Notes field and/or a file. It demonstrates password config, localization, and\nreusing the bundled `openai` client (no extra dependencies).\n","isInternal":false,"tokens":2435,"sizeBytes":10556},{"name":"CLAUDE.md","path":"buzz/plugins/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/chidiwilliams/buzz/HEAD/buzz/plugins/CLAUDE.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Plugins\n\nBefore adding or modifying any plugin in this directory, read [AGENTS.md](AGENTS.md)\nin full. It is the authoritative guide for the Buzz plugin system and covers:\n\n- The plugin contract (`BuzzPlugin`, `PluginMetadata`, hooks and their threads).\n- Configuration fields, dependencies, and `PluginContext`.\n- Localization (`plugin_gettext`, `locale/*.json`, the supported locale set).\n- Packaging, bundling, and the checklist for creating a plugin.\n\nFollow the conventions there — including localizing user-facing strings and\nkeeping the `locale/` file set in sync across bundled plugins.\n","isInternal":false,"tokens":125,"sizeBytes":599}],"systemPromptSnippet":"<yakaai_skills repo=\"chidiwilliams/buzz\">\n<!-- File: AGENTS.md (Tokens: ~9 | Category: root-instruction) -->\n- Use uv to run tests and any scripts\n\n<!-- File: CLAUDE.md (Tokens: ~5 | Category: claude-rule) -->\nPlease see AGENTS.md\n\n<!-- File: buzz/plugins/AGENTS.md (Tokens: ~2435 | Category: anthropic-skill) -->\n# Buzz Plugin System — Guide for Agents\n\nThis document explains how the Buzz plugin system works and how to create new\nplugins. Read it fully before adding or modifying a plugin.\n\n## Overview\n\nPlugins extend the transcription pipeline without modifying Buzz's core. A\nplugin is a **folder** containing a `plugin.py` entry module that defines exactly\none subclass of `BuzzPlugin`. Plugins can:\n\n- Process / replace the **source audio** before transcription.\n- Modify or replace the **transcription result** (segments) after transcription.\n- Run **custom code** and import parts of Buzz.\n- Declare **pip dependencies** that get installed into the user cache on load.\n- Expose **configuration** fields (text, multi-line text, checkbox, password)\n  shown in a generated settings dialog.\n- Provide **localized** names/descriptions/labels.\n\n### Where plugins live\n\n- Bundled plugins ship in `buzz/plugins/<plugin_id>/` (this directory). They are\n  copied into the user-writable folder on first launch — see\n  `loader.BUNDLED_PLUGIN_IDS` and `loader.copy_bundled_plugins()`.\n- Installed plugins live in `<user_cache_dir>/Buzz/plugins/<plugin_id>/`.\n- Plugin dependencies install into `<user_cache_dir>/Buzz/plugins_deps/` which is\n  added to `sys.path`.\n\nUsers manage plugins from **Help → Plugins**: add by URL (a `.zip`),\nenable/disable, reorder execution, edit settings.\n\n## Architecture (key files)\n\n- `base.py` — the plugin contract: `BuzzPlugin`, `PluginMetadata`, `ConfigField`,\n  `ConfigFieldType`, `PluginContext`, and `plugin_gettext` (localization).\n- `loader.py` — discovery, zip download/extract, dynamic import, bundled-copy.\n- `manager.py` — `PluginManager`: registry, enable/order/config persistence,\n  dependency install, hook dispatch.\n- `post_processing.py` — runs post-transcription hooks off the UI thread and\n  marshals DB access back to the main thread.\n\n## The plugin contract\n\nA plugin subclasses `BuzzPlugin` and sets a `metadata` class attribute. All hooks\nare optional; defaults are no-ops.\n\n```python\nfrom buzz.plugins.base import (\n    BuzzPlugin, PluginMetadata, ConfigField, ConfigFieldType,\n    PluginContext, plugin_gettext,\n)\n\n_ = plugin_gettext(__file__)  # localized strings, see \"Localization\" below\n\n\nclass MyPlugin(BuzzPlugin):\n    metadata = PluginMetadata(\n        id=\"my_plugin\",                 # stable, unique; matches the folder name\n        name=_(\"My Plugin\"),\n        description=_(\"What this plugin does.\"),\n        version=\"1.0.0\",\n        pip_dependencies=[],            # e.g. [\"python-docx>=1.1\"]\n        config_fields=[\n            ConfigField(key=\"api_key\", label=_(\"API key\"),\n                        type=ConfigFieldType.PASSWORD),\n            ConfigField(key=\"enabled_flag\", label=_(\"Some flag\"),\n                        type=ConfigFieldType.BOOL, default=True),\n        ],\n    )\n\n    def before_transcription(self, task, context: PluginContext):\n        # Runs on the WORKER thread. May process the audio at task.file_path.\n        # Return a new file path to use, or None to leave it unchanged.\n        # Do NOT touch the database or Qt here.\n        return None\n\n    def after_transcription(self, task, segments, context: PluginContext):\n        # Runs on a BACKGROUND thread. Return the (possibly modified) list of\n        # Segment objects. Return `segments` unchanged to do nothing.\n        return segments\n\n    def on_complete(self, transcription_id, task, segments, context: PluginContext):\n        # Runs on a BACKGROUND thread AFTER the transcription is saved.\n        # Use for side effects keyed on the persisted transcription, e.g.\n        # writing notes or exporting a file.\n        return None\n```\n\n### Hooks and when they run\n\n| Hook                   | Thread       | Purpose                                            |\n|------------------------|--------------|----------------------------------------------------|\n| `check_skip`           | worker       | Return `list[Segment]` to skip transcription entirely, or `None` to proceed. Runs after `before_transcription`. DB access via `context.transcription_service` is safe (marshaled to main thread). |\n| `before_transcription` | worker       | Process/replace source audio; return new file path |\n| `after_transcription`  | background   | Modify/replace result `Segment`s before save       |\n| `on_complete`          | background   | Post-save side effects (notes, file export, upload)|\n\nHooks run for **enabled** plugins in the user-defined **order**. Exceptions in a\nhook are logged and never break the pipeline or other plugins.\n\n### `PluginContext`\n\nPassed to every hook. Fields:\n\n- `context.config` — resolved config dict keyed by `ConfigField.key` (includes\n  password values pulled from the keyring; missing values fall back to defaults).\n- `context.transcription_service` — DB access. **Safe to call from background\n  hooks**: calls are automatically marshaled to the main thread (the DB\n  connection is thread-affine). Useful methods:\n  `update_transcription_notes(id, text)`,\n  `replace_transcription_segments(id, segments)`,\n  `get_transcription_segments(transcription_id=id)`.\n- `context.settings` — the Buzz `Settings` object.\n- `context.log` — a `logging.Logger` namespaced to the plugin.\n\n### Data types\n\n- `task` is a `FileTranscriptionTask` (see `buzz/transcriber/transcriber.py`):\n  `file_path`, `original_file_path`, `uid`, `transcription_options`, etc.\n- `segments` is a `list[Segment]`; `Segment(start, end, text, translation=\"\")`\n  with `start`/`end` in **milliseconds**.\n\n## Configuration fields\n\nDeclare config via `ConfigField`. Types (`ConfigFieldType`):\n\n- `TEXT` — single-line input.\n- `TEXTAREA` — multi-line input.\n- `BOOL` — checkbox.\n- `PASSWORD` — masked input with a show/hide toggle. **Stored in the OS keyring**,\n  never in plain settings.\n\nEach field has `key`, `label`, optional `type`, `default`, `description`,\n`placeholder`. The settings dialog is generated automatically from these. Read\nvalues from `context.config[field.key]` in your hooks.\n\n## Dependencies\n\nList pip requirements in `metadata.pip_dependencies`. On first load the manager\ninstalls them into the shared `plugins_deps` cache folder (tracked so they are\nnot reinstalled unnecessarily). Prefer packages already bundled with Buzz (e.g.\n`openai`) and declare nothing when possible — dependency install can fail\noffline. See `buzz/pip_utils.py` for how pip is invoked across environments\n(frozen builds, sandboxes).\n\n## Localization\n\nPlugins cannot use Buzz's compiled `.mo` catalogs. Instead, call\n`plugin_gettext(__file__)` to get a translator (conventionally named `_`) and\nwrap user-facing strings (`name`, `description`, field `label`s, messages).\n\nProvide translations in JSON files in a `locale/` folder next to `plugin.py`,\nnamed by locale (`locale/lv_LV.json`) with a language-only fallback\n(`locale/lv.json`). Each file maps the **English source string** to its\ntranslation:\n\n```json\n{\n  \"My Plugin\": \"Mans spraudnis\",\n  \"API key\": \"API atslēga\"\n}\n```\n\nThe JSON **key must match the English source string exactly** (including\npunctuation, parentheses and whitespace) — it is matched verbatim against the\nstring you passed to `_()`. Multi-line source strings assembled via implicit\nstring concatenation in Python collapse into a single key, so the key has no\nline breaks. Strings without a translation (or when no file matches the active\nlocale) fall through unchanged, so a plugin works with no locale files at all.\nThe active locale comes from Buzz's `UI_LOCALE` setting.\n\n> **Critical:** Never use an empty string `\"\"` as a translation value. The\n> translator treats an empty value as \"no translation\" and falls back to the\n> English source string, but only if the value is falsy. A locale file with\n> `\"My Plugin\": \"\"` will cause the plugin name to appear blank in the UI.\n> Either provide a real translation or omit the key entirely.\n\nBundled plugins ship a translation file for **every locale Buzz supports**. The\ncurrent set (keep it in sync across all bundled plugins) is:\n\n```\nca_ES, da_DK, de_DE, es_ES, fr, it_IT, ja_JP, lv_LV, nl,\npl_PL, pt_BR, ru, uk_UA, zh_CN, zh_TW\n```\n\nWhen adding or changing a user-facing string in a bundled plugin, update the key\nin **all** of these files. See `ai_summary/locale/` or\n`enhanced_language_detection/locale/` for a complete worked example, and copy the\nfile set from an existing plugin so the locale list stays consistent.\n\n> **Cache note:** Bundled plugins are copied to `~/.cache/Buzz/plugins/` on\n> launch. `copy_bundled_plugins()` compares each bundled plugin against its\n> cached copy and refreshes the cache whenever the bundle differs — a fixed\n> `plugin.py`, or an added/updated/removed locale file. So editing a bundled\n> plugin's code or locales and relaunching Buzz is enough; no manual sync is\n> needed. (Build artifacts like `__pycache__`/`*.pyc` are ignored in the\n> comparison.) User-installed plugins are never touched.\n\n## Packaging & distribution\n\nA distributable plugin is a `.zip` whose contents are the plugin folder. The\narchive may have `plugin.py` at the root or inside a single wrapping directory\n(GitHub-style zips work). Users install it via **Help → Plugins → Add by URL**.\n\nLayout:\n\n```\nmy_plugin/\n  plugin.py            # required: defines one BuzzPlugin subclass\n  locale/              # optional: <locale>.json translation files\n    lv_LV.json\n```\n\n## Checklist for creating a plugin\n\n1. Create `buzz/plugins/<plugin_id>/plugin.py` with one `BuzzPlugin` subclass.\n2. Set `metadata` with a unique `id` (match the folder name), `name`,\n   `description`, and any `config_fields`.\n3. Implement only the hooks you need; respect the thread rules above.\n4. Use `plugin_gettext(__file__)` for user-facing strings; add `locale/*.json`\n   (for bundled plugins, provide all locales listed under **Localization** and\n   keep the keys matching the English source strings exactly).\n5. Keep `pip_dependencies` minimal; reuse bundled packages.\n6. To ship it bundled: add the id to `loader.BUNDLED_PLUGIN_IDS` and add the\n   folder to `Buzz.spec` `datas` (e.g. `(\"buzz/plugins/<id>\", \"plugins/<id>\")`).\n7. Add tests under `tests/plugins/` (see `plugin_system_test.py`).\n\n## Reference: the `ai_summary` plugin\n\n`ai_summary/plugin.py` is the shipped reference plugin. It uses `on_complete` to\nsummarize the transcript via an OpenAI-compatible API and writes the result to\nthe Notes field and/or a file. It demonstrates password config, localization, and\nreusing the bundled `openai` client (no extra dependencies).\n\n<!-- File: buzz/plugins/CLAUDE.md (Tokens: ~125 | Category: anthropic-skill) -->\n# Plugins\n\nBefore adding or modifying any plugin in this directory, read [AGENTS.md](AGENTS.md)\nin full. It is the authoritative guide for the Buzz plugin system and covers:\n\n- The plugin contract (`BuzzPlugin`, `PluginMetadata`, hooks and their threads).\n- Configuration fields, dependencies, and `PluginContext`.\n- Localization (`plugin_gettext`, `locale/*.json`, the supported locale set).\n- Packaging, bundling, and the checklist for creating a plugin.\n\nFollow the conventions there — including localizing user-facing strings and\nkeeping the `locale/` file set in sync across bundled plugins.\n</yakaai_skills>"}