From f5118e250bbe1e062f0810ab3f6bd0742b19f9e5 Mon Sep 17 00:00:00 2001 From: mic Date: Sun, 15 Mar 2026 22:00:28 +0100 Subject: [PATCH] CLAUDE.md file improved and added specifications files in claude-spec --- CLAUDE.md | 89 ++++-- claude-spec/01-architecture.md | 108 +++++++ claude-spec/02-prompts.md | 86 +++++ claude-spec/03-placeholders.md | 78 +++++ claude-spec/04-api-integrations.md | 86 +++++ claude-spec/05-options.md | 113 +++++++ claude-spec/06-localization.md | 91 ++++++ claude-spec/99-thunderbird-team-spec.md | 398 ++++++++++++++++++++++++ 8 files changed, 1017 insertions(+), 32 deletions(-) create mode 100644 claude-spec/01-architecture.md create mode 100644 claude-spec/02-prompts.md create mode 100644 claude-spec/03-placeholders.md create mode 100644 claude-spec/04-api-integrations.md create mode 100644 claude-spec/05-options.md create mode 100644 claude-spec/06-localization.md create mode 100644 claude-spec/99-thunderbird-team-spec.md diff --git a/CLAUDE.md b/CLAUDE.md index 69cf47a3..de33f9b2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,39 +1,64 @@ -# ThunderAI Project Agent Context +# ThunderAI - Claude Code Guide ## Project Overview -ThunderAI is a Mozilla Thunderbird add-on that integrates Large Language Models (LLMs) such as ChatGPT, Gemini, Claude, and Ollama for advanced email management. It allows users to analyze, draft, correct, tag, and create calendar events directly from the email client. +ThunderAI is a **Thunderbird WebExtension (Manifest V2)** that integrates multiple AI providers (ChatGPT Web, OpenAI API, Google Gemini, Claude/Anthropic, Ollama, and OpenAI-compatible APIs) directly into the Thunderbird email client. -## Technical Stack -- **Languages:** JavaScript (ES6+), HTML5, CSS3. -- **Environment:** Thunderbird MailExtension API (based on WebExtensions). -- **Core Logic:** Primarily located in `background.js` and UI-specific scripts. -- **API Integrations:** Specific logic is contained within dedicated API provider files (e.g., `api_openai.js`, `api_google_gemini.js`). +- **Extension ID:** `thunderai@micz.it` +- **Min Thunderbird:** 140.0+ +- **Language:** Plain ES6+ JavaScript modules — no build tools, no transpilation, no npm +- **License:** GPLv3 -## Development Rules & Style Guide -- **Code Style:** Use modern JavaScript. Prefer `async/await` over chained Promises. -- **Naming Conventions:** Use `camelCase` for variables and functions. -- **Security:** - Never hardcode API keys in the source code. - - Use `messenger.storage.local` for data persistence. - - Strictly adhere to CORS policies for external API calls. -- **i18n:** - - The project supports multiple languages via `_locales/`. When adding UI strings, always use `messenger.i18n.getMessage()`. - - When you need to modify language file, modify only the english version. -- **Logging:** - - When possibile use the taLog object to log errors, otherwise use the console.error() method. - - If it's useful to log add debug logs using taLog.log() method. +## Key Rules -## Files & Directories Structure -- `manifest.json`: Extension entry point and permission definitions. -- `api_*.js`: Modules for integration with different AI providers. -- `options/`: Configuration and settings pages. -- `_locales/`: Translation files (JSON format). -- `graphics/`: Visual assets and icons. +1. **Localization:** Modify ONLY `_locales/en/messages.json`. All other locale files are managed via Weblate — never touch them. +2. **No build system:** There is no bundler, compiler, or package manager. All JS files are plain ES6 modules loaded directly by the browser engine. +3. **Module imports:** Use relative paths with `.js` extension (e.g., `import { foo } from '../js/mzta-utils.js'`). +4. **Placeholder format:** Placeholders in prompt text use the `{%placeholder_id%}` syntax (e.g., `{%mail_text_body_or_selected%}`). +5. **No test suite:** There is no automated test framework. Testing is done manually in Thunderbird. +6. **Settings defaults:** All new preferences must be added to `options/mzta-options-default.js` in `prefs_default`. -## Important Commands & Workflow -- **Testing:** Since this is a Thunderbird add-on, testing is performed by loading the extension as a "Temporary Add-on" via Thunderbird's `Debug Add-ons` menu. -- **Build:** The project does not use complex build tools (no Webpack/Vite by default); it is a "pure" MailExtension. +## Directory Map -## Agent Instructions -1. **Context Awareness:** When modifying API-related files, ensure compatibility with the existing placeholder system (e.g., `{%mail_body%}`, `{%additional_text%}`). -2. **Permission Review:** Before modifying `manifest.json`, verify that any new permissions requested are strictly necessary for the feature. -3. **Compatibility:** Ensure code is compatible with the latest Thunderbird ESR (Extended Support Release) versions. \ No newline at end of file +``` +/ +├── mzta-background.js # Background script (main entry point) +├── mzta-background.html # Loads the background script +├── manifest.json # Extension manifest +├── js/ # Core modules +│ ├── api/ # AI API integration modules +│ ├── workers/ # Web Workers (one per API provider) +│ ├── lib/ # Third-party libraries (diff.js) +│ └── mzta-*.js # Core utilities, menus, prompts, placeholders +├── options/ # Settings UI +│ ├── mzta-options.html/.js/.css +│ ├── mzta-options-default.js # ALL default preference values +│ └── mzta-release-notes.html +├── pages/ # Feature-specific settings pages +│ ├── addtags/ +│ ├── customprompts/ +│ ├── customdataplaceholders/ +│ ├── get-calendar-event/ +│ ├── get-task/ +│ ├── spamfilter/ +│ ├── summarize/ +│ └── onboarding/ +├── popup/ # Popup menu (shown on toolbar click) +│ └── mzta-popup.html/.js/.css +├── _locales/ # Localization +│ ├── en/messages.json # ← ONLY THIS FILE is edited directly +│ └── [15 other languages managed by Weblate] +├── images/ # Icons and graphical assets +└── api_webchat/ # Web chat API interface +``` + +## Spec Files + +For detailed documentation see [`claude-spec/`](claude-spec/): + +- [01-architecture.md](claude-spec/01-architecture.md) — Module structure and data flow +- [02-prompts.md](claude-spec/02-prompts.md) — Prompt system (types, actions, properties) +- [03-placeholders.md](claude-spec/03-placeholders.md) — Placeholder system +- [04-api-integrations.md](claude-spec/04-api-integrations.md) — AI provider integrations +- [05-options.md](claude-spec/05-options.md) — Settings and preferences system +- [06-localization.md](claude-spec/06-localization.md) — i18n rules and workflow +- [99-thunderbird-team-spec.md](claude-spec/99-thunderbird-team-spec.md) — Thunderbird WebExtensions development guidelines (API usage, experiments, review requirements) diff --git a/claude-spec/01-architecture.md b/claude-spec/01-architecture.md new file mode 100644 index 00000000..94dcdead --- /dev/null +++ b/claude-spec/01-architecture.md @@ -0,0 +1,108 @@ +# Architecture + +## Extension Structure (Manifest V2) + +ThunderAI runs as a standard Thunderbird WebExtension with three main execution contexts: + +``` +Background Page → mzta-background.html / mzta-background.js +Popup → popup/mzta-popup.html / popup/mzta-popup.js +Options Page → options/mzta-options.html / options/mzta-options.js +Feature Pages → pages/*/ +Content Script → js/lib/diff.js (injected into chatgpt.com) +Web Workers → js/workers/model-worker-*.js (one per API provider) +``` + +## Data Flow: User Action → AI Response + +``` +User clicks popup or presses Ctrl+Alt+A + ↓ +popup/mzta-popup.js (renders prompt list, handles selection) + ↓ (sendMessage to background) +mzta-background.js (orchestrates everything) + ↓ +js/mzta-placeholders.js (resolves {%placeholder%} values from email data) + ↓ +js/mzta-prompts.js (builds final prompt string) + ↓ + ┌─────────────────────────────────────────┐ + │ Based on connection_type: │ + │ chatgpt_web → js/mzta-chatgpt.js │ (opens ChatGPT window) + │ chatgpt_api → Web Worker (openai) │ + │ ollama_api → Web Worker (ollama) │ + │ google_gemini → Web Worker (gemini) │ + │ anthropic → Web Worker (anthropic)│ + │ openai_comp → Web Worker (comp) │ + └─────────────────────────────────────────┘ + ↓ + Result returned to background + ↓ +js/mzta-compose-script.js (inserts text into Thunderbird compose window) +``` + +## Key Modules + +| File | Role | +|------|------| +| `mzta-background.js` | Main orchestrator: listens for messages, coordinates all features | +| `js/mzta-menus.js` | Context menu creation and management | +| `js/mzta-prompts.js` | Prompt definitions (built-in) and custom prompt loading | +| `js/mzta-placeholders.js` | Placeholder definitions and resolution logic | +| `js/mzta-utils.js` | General utilities (email parsing, storage helpers, etc.) | +| `js/mzta-utils-prompt.js` | Prompt-specific utilities (text truncation, lang injection) | +| `js/mzta-compose-script.js` | Injects AI response into Thunderbird compose window | +| `js/mzta-chatgpt.js` | ChatGPT Web integration (opens browser window, reads DOM) | +| `js/mzta-special-commands.js` | Handles special prompt actions (add_tags, calendar, task) | +| `js/mzta-spamreport.js` | Spam filter logic | +| `js/mzta-i18n.js` | i18n helper (wraps `browser.i18n.getMessage`) | +| `js/mzta-logger.js` | Debug logging (gated by `do_debug` pref) | +| `js/mzta-store.js` | Storage abstraction helpers | +| `js/mzta-working-status.js` | Visual status indicator during AI processing | +| `js/mzta-addatags-exclusion-list.js` | Tag exclusion list management | +| `js/mzta-placeholders-autocomplete.js` | Autocomplete for placeholders in prompt editor | + +## API Modules (`js/api/`) + +Each file handles HTTP communication for one provider: + +| File | Provider | +|------|----------| +| `anthropic.js` | Claude (Anthropic) API | +| `google_gemini.js` | Google Gemini API | +| `ollama.js` | Ollama (self-hosted) | +| `openai_comp.js` | OpenAI-compatible APIs | +| `openai_comp_configs.js` | Pre-configured providers (DeepSeek, Grok, Mistral, OpenRouter, Perplexity) | +| `openai_responses.js` | OpenAI Responses API | + +## Web Workers (`js/workers/`) + +Each API provider has a dedicated Web Worker so API calls don't block the UI: + +- `model-worker-anthropic.js` +- `model-worker-google_gemini.js` +- `model-worker-ollama.js` +- `model-worker-openai_comp.js` +- `model-worker-openai_responses.js` + +Workers receive a message with the prompt and settings, make the API call, and post back the result. + +## Feature Pages (`pages/`) + +Each subdirectory is a self-contained settings/UI page for a specific feature: + +| Directory | Feature | +|-----------|---------| +| `addtags/` | Auto-tagging configuration | +| `customprompts/` | Custom prompt editor | +| `customdataplaceholders/` | Custom placeholder editor | +| `get-calendar-event/` | Calendar event extraction settings | +| `get-task/` | Task creation settings | +| `spamfilter/` | Spam filter settings | +| `summarize/` | Email summarization settings | +| `onboarding/` | First-run welcome page | +| `_lib/` | Shared libraries used by pages | + +## Storage + +All preferences are stored via `browser.storage.local`. The keys and default values are defined in `options/mzta-options-default.js` (`prefs_default` export). Custom prompts and custom placeholders are stored separately in storage under their own keys. diff --git a/claude-spec/02-prompts.md b/claude-spec/02-prompts.md new file mode 100644 index 00000000..2e37e189 --- /dev/null +++ b/claude-spec/02-prompts.md @@ -0,0 +1,86 @@ +# Prompts System + +## Overview + +Prompts are the core user-facing feature of ThunderAI. Each prompt defines an AI instruction and how it behaves. There are two kinds: + +- **Built-in prompts** — defined in `js/mzta-prompts.js` +- **Custom prompts** — created by the user and stored in `browser.storage.local` + +## Prompt Properties + +### Base Properties (built-in only) + +| Property | Type | Description | +|----------|------|-------------| +| `id` | string | Unique identifier | +| `name` | string | `__MSG_key__` i18n reference or plain text | +| `prompt` | string | The prompt template text (may contain `{%placeholder%}` tokens) | +| `type` | number | `0` = always visible, `1` = reading email only, `2` = composing only | +| `action` | number | `0` = close, `1` = reply (open compose), `2` = substitute text in-place | +| `need_selected` | number | `0` = use full message body, `1` = requires text selection | +| `need_signature` | number | `0` = no signature, `1` = include signature | +| `need_custom_text` | number | `0` = no custom input, `1` = show custom text input field | +| `define_response_lang` | number | `0` = no language hint, `1` = append response language instruction | +| `use_diff_viewer` | number | `0` = normal output, `1` = show diff viewer (ChatGPT Web only) | + +### User Properties (stored per-prompt in storage) + +| Property | Type | Description | +|----------|------|-------------| +| `enabled` | number | `0` = hidden, `1` = shown in popup | +| `position_display` | number | Sort order in reading view | +| `position_compose` | number | Sort order in compose view | + +### Per-Prompt API Override Properties + +Each prompt can override the global API connection. These mirror the keys in `integration_options_config` and `prefs_default`: + +| Property | Description | +|----------|-------------| +| `connection_type` | Override API type for this prompt | +| `chatgpt_web_model` | Override ChatGPT Web model | +| `chatgpt_web_project` | Override ChatGPT Web project | +| `chatgpt_web_custom_gpt` | Override custom GPT | +| All `chatgpt_*`, `ollama_*`, `openai_comp_*`, `google_gemini_*`, `anthropic_*` keys | Override specific API settings | + +## Special Prompts + +Some prompts trigger additional Thunderbird actions beyond just sending text to the AI. They are identified by their `id`: + +| ID | Feature | +|----|---------| +| `add_tags` | Auto-tag the email after AI response | +| `spamfilter` | Classify as spam and optionally move email | +| `summarize` | Summarize email content | +| `get_calendar_event` | Extract and create a calendar event | +| `get_task` | Extract and create a task | + +These special prompts can have their own dedicated API integration settings (configured in the Options page). The list of these special prompts is in `options/mzta-options-default.js` as `special_prompts_with_integration`. + +## Prompt Types Reference + +``` +type 0 → shown when reading AND composing +type 1 → shown only when reading an email (message display) +type 2 → shown only when composing an email +``` + +## Action Types Reference + +``` +action 0 → no output, just close (e.g. for tag/spam actions handled in background) +action 1 → open a reply compose window with AI response +action 2 → replace selected text (or insert) in compose window +``` + +## Adding a New Built-in Prompt + +1. Add the prompt object to the `defaultPrompts` array in `js/mzta-prompts.js` +2. Add the `name` string key to `_locales/en/messages.json` +3. If the prompt text needs a localized string, add it to `_locales/en/messages.json` as well +4. Reference any needed placeholders using `{%placeholder_id%}` syntax in the `prompt` field + +## Custom Prompts + +Custom prompts are stored in `browser.storage.local` and managed via `pages/customprompts/`. They follow the same property structure as built-in prompts but are created/edited/deleted by the user through the UI. Custom placeholders can also be referenced in custom prompt text. diff --git a/claude-spec/03-placeholders.md b/claude-spec/03-placeholders.md new file mode 100644 index 00000000..17d1c201 --- /dev/null +++ b/claude-spec/03-placeholders.md @@ -0,0 +1,78 @@ +# Placeholders System + +## Overview + +Placeholders are dynamic tokens embedded in prompt text that get replaced with real data at runtime (email content, headers, user input, etc.). + +**Format:** `{%placeholder_id%}` + +Example in a prompt: `"Summarize this email: {%mail_text_body_or_selected%}"` + +## Placeholder Properties + +| Property | Type | Description | +|----------|------|-------------| +| `id` | string | Unique identifier used in `{%id%}` tokens | +| `name` | string | Display name (i18n `__MSG_key__` or plain text) | +| `default_value` | string | Value used if placeholder cannot be resolved | +| `type` | number | `0` = always, `1` = reading only, `2` = composing only | +| `is_default` | string | `"1"` = built-in (not editable/deletable), `"0"` = custom | +| `is_dynamic` | string | `"0"` = fixed value, `"1"` = dynamic (takes a parameter after `:`) | +| `enabled` | number | `0` = disabled, `1` = enabled | +| `text` | string | Content for custom placeholders only | + +## Built-in Placeholders (defined in `js/mzta-placeholders.js`) + +| ID | Description | Type | +|----|-------------|------| +| `mail_text_body` | Full plain text of the email | 0 | +| `mail_html_body` | Full HTML of the email | 0 | +| `mail_typed_text` | Text typed so far in compose window | 2 | +| `mail_text_body_or_selected` | Plain text body, or selected text if any | 1 | +| `mail_html_body_or_selected` | HTML body, or selected HTML if any | 1 | +| `mail_selected_text` | Only the selected text | 1 | +| `mail_selected_html` | Only the selected HTML | 1 | +| `mail_subject` | Email subject line | 0 | +| `mail_date` | Email date | 1 | +| `mail_author` | Email sender | 0 | +| `mail_recipients` | Email recipients | 0 | +| `mail_tags` | Current tags on the email | 1 | +| `mail_available_tags` | All available tags in Thunderbird | 1 | +| `identity_name` | Current identity display name | 0 | +| `identity_email` | Current identity email address | 0 | +| `identity_signature` | Current identity signature | 0 | +| `additional_text[id]` | User input field (dynamic, shows input in popup) | 0 | +| `mail_header:name` | Any email header by name (dynamic) | 1 | + +## Dynamic Placeholders + +Dynamic placeholders use a colon separator to pass a parameter: + +``` +{%additional_text:my_field_id%} → shows an input field labelled "my_field_id" in the popup +{%mail_header:x-spam-score%} → fetches the X-Spam-Score header value +``` + +The `is_dynamic: "1"` property signals this behavior in the placeholder definition. + +## Custom Placeholders + +Users can define their own placeholders via `pages/customdataplaceholders/`. Custom placeholders: +- Have `is_default: "0"` +- Have a `text` property containing the replacement value +- Are stored in `browser.storage.local` +- Are merged with default placeholders at runtime before prompt processing + +## Placeholder Resolution Order + +1. Built-in placeholders are defined in `js/mzta-placeholders.js` +2. Custom placeholders are loaded from storage +3. At runtime, `mzta-background.js` gathers email data (via Thunderbird APIs) +4. Each `{%id%}` token in the prompt string is replaced with the resolved value +5. If a value cannot be resolved, `default_value` is used as fallback + +## Adding a New Built-in Placeholder + +1. Add the object to the `defaultPlaceholders` array in `js/mzta-placeholders.js` +2. Add the `name` i18n key to `_locales/en/messages.json` as `placeholder_` (or choose a descriptive key) +3. Implement the resolution logic in the relevant section of `mzta-background.js` diff --git a/claude-spec/04-api-integrations.md b/claude-spec/04-api-integrations.md new file mode 100644 index 00000000..318eadbf --- /dev/null +++ b/claude-spec/04-api-integrations.md @@ -0,0 +1,86 @@ +# API Integrations + +## Connection Types + +The active AI provider is controlled by the `connection_type` preference. Possible values: + +| `connection_type` value | Provider | +|------------------------|----------| +| `chatgpt_web` | ChatGPT Web (no API key, opens browser window) | +| `chatgpt_api` | OpenAI API (ChatGPT via API key) | +| `ollama_api` | Ollama (self-hosted LLM) | +| `openai_comp_api` | OpenAI-compatible API | +| `google_gemini_api` | Google Gemini API | +| `anthropic_api` | Claude (Anthropic) API | + +The global default is `chatgpt_web`. Each special prompt (`add_tags`, `spamfilter`, etc.) can independently override this via its own `{prefix}_connection_type` pref. + +## Provider Configuration + +Each provider has its own settings block in `integration_options_config` (`options/mzta-options-default.js`): + +### ChatGPT Web +Controlled via `js/mzta-chatgpt.js`. Opens a browser window to `chatgpt.com`, injects the prompt via DOM automation, and reads back the response. Settings: `chatgpt_web_model`, `chatgpt_web_tempchat`, `chatgpt_web_project`, `chatgpt_web_custom_gpt`, `chatgpt_web_load_wait_time`. + +Content script `js/lib/diff.js` is injected into ChatGPT pages for diff-view support. + +### OpenAI API (`chatgpt_api`) +- Module: `js/api/openai_responses.js` +- Worker: `js/workers/model-worker-openai_responses.js` +- Settings keys: `chatgpt_api_key`, `chatgpt_model`, `chatgpt_developer_messages`, `chatgpt_temperature`, `chatgpt_store` + +### Ollama (`ollama_api`) +- Module: `js/api/ollama.js` +- Worker: `js/workers/model-worker-ollama.js` +- Settings keys: `ollama_host`, `ollama_model`, `ollama_num_ctx`, `ollama_temperature`, `ollama_think` +- Requires CORS to be configured on the Ollama server + +### OpenAI-Compatible (`openai_comp_api`) +- Module: `js/api/openai_comp.js` +- Worker: `js/workers/model-worker-openai_comp.js` +- Settings keys: `openai_comp_host`, `openai_comp_model`, `openai_comp_api_key`, `openai_comp_use_v1`, `openai_comp_chat_name`, `openai_comp_temperature` +- Pre-configured providers: `js/api/openai_comp_configs.js` (DeepSeek, Grok, Mistral, OpenRouter, Perplexity) + +### Google Gemini (`google_gemini_api`) +- Module: `js/api/google_gemini.js` +- Worker: `js/workers/model-worker-google_gemini.js` +- Settings keys: `google_gemini_api_key`, `google_gemini_model`, `google_gemini_system_instruction`, `google_gemini_thinking_budget`, `google_gemini_temperature` + +### Anthropic / Claude (`anthropic_api`) +- Module: `js/api/anthropic.js` +- Worker: `js/workers/model-worker-anthropic.js` +- Settings keys: `anthropic_api_key`, `anthropic_model`, `anthropic_version`, `anthropic_max_tokens`, `anthropic_system_prompt`, `anthropic_temperature` + +## Web Worker Pattern + +For all API-based providers (everything except ChatGPT Web), the call goes through a Web Worker: + +``` +mzta-background.js + → creates new Worker('js/workers/model-worker-.js') + → postMessage({ prompt, settings }) + → worker makes HTTP fetch to provider API + → worker postMessage({ result }) back + → background handles result +``` + +This keeps API calls off the main thread and avoids blocking the Thunderbird UI. + +## Optional Permissions + +API calls require host permissions. These are declared as `optional_permissions` in `manifest.json` and requested at runtime: + +- `https://*.chatgpt.com/*` and `https://*.openai.com/*` for ChatGPT +- `https://*.anthropic.com/*` for Claude +- `https://*/*` and `http://*/*` for Ollama and OpenAI-compatible endpoints + +## Adding a New Provider + +1. Create `js/api/.js` with the API call logic +2. Create `js/workers/model-worker-.js` that imports and calls the API module +3. Add a new `connection_type` value constant +4. Add settings keys to `integration_options_config` in `options/mzta-options-default.js` +5. Add UI controls to `options/mzta-options.html` and `options/mzta-options.js` +6. Add the new `connection_type` case to the dispatch logic in `mzta-background.js` +7. Add required host permissions to `manifest.json` optional_permissions +8. Add i18n strings to `_locales/en/messages.json` diff --git a/claude-spec/05-options.md b/claude-spec/05-options.md new file mode 100644 index 00000000..f1dc571c --- /dev/null +++ b/claude-spec/05-options.md @@ -0,0 +1,113 @@ +# Options & Settings System + +## Overview + +All extension preferences are stored in `browser.storage.local`. Defaults and the full list of valid keys are defined in `options/mzta-options-default.js`. + +## Key Exports from `mzta-options-default.js` + +| Export | Description | +|--------|-------------| +| `prefs_default` | All preference keys with their default values | +| `integration_options_config` | Per-provider API settings structure | +| `getDynamicSettingsDefaults(keysFilter)` | Returns per-special-prompt integration defaults | +| `getDynamicSettingValue(prefs, prefix, settingName)` | Reads a prefixed setting for a special prompt | + +## Settings Structure + +### Global Integration Settings + +Stored flat in `prefs_default` with `{provider}_{key}` naming: + +``` +chatgpt_api_key, chatgpt_model, chatgpt_developer_messages, chatgpt_temperature, chatgpt_store +ollama_host, ollama_model, ollama_num_ctx, ollama_temperature, ollama_think +openai_comp_host, openai_comp_model, openai_comp_api_key, openai_comp_use_v1, openai_comp_chat_name, openai_comp_temperature +google_gemini_api_key, google_gemini_model, google_gemini_system_instruction, google_gemini_thinking_budget, google_gemini_temperature +anthropic_api_key, anthropic_model, anthropic_version, anthropic_max_tokens, anthropic_system_prompt, anthropic_temperature +``` + +Plus the global connection selector: +``` +connection_type (default: 'chatgpt_web') +use_specific_integration (default: false) +``` + +### Special Prompt Integration Overrides + +The 5 special prompts (`add_tags`, `spamfilter`, `summarize`, `get_calendar_event`, `get_task`) each get their own `use_specific_integration` and `connection_type` keys: + +``` +{prefix}_use_specific_integration (default: false) +{prefix}_connection_type (default: 'chatgpt_api') +``` + +These are generated programmatically at the bottom of `mzta-options-default.js` using `special_prompts_with_integration` array. + +### UI & Feature Preferences + +| Key | Default | Description | +|-----|---------|-------------| +| `do_debug` | `false` | Enable debug logging | +| `chatgpt_win_height` | `800` | ChatGPT window height | +| `chatgpt_win_width` | `700` | ChatGPT window width | +| `chatgpt_win_top` | `''` | Window top position | +| `chatgpt_win_left` | `''` | Window left position | +| `chatgpt_win_save_position` | `false` | Remember window position | +| `default_chatgpt_lang` | `''` | Force response language | +| `default_sign_name` | `''` | Default signature name | +| `reply_type` | `'reply_all'` | Default reply type | +| `composing_plain_text` | `false` | Use plain text in compose | +| `chatgpt_web_model` | `''` | ChatGPT Web model override | +| `chatgpt_web_tempchat` | `false` | Use temporary chat | +| `chatgpt_web_project` | `''` | ChatGPT Web project | +| `chatgpt_web_custom_gpt` | `''` | Custom GPT URL | +| `chatgpt_web_load_wait_time` | `1000` | Wait time (ms) for ChatGPT page | +| `dynamic_menu_force_enter` | `false` | Force Enter to submit in popup | +| `dynamic_menu_order_alphabet` | `true` | Sort prompts alphabetically | +| `placeholders_use_default_value` | `false` | Use placeholder defaults when empty | +| `max_prompt_length` | `30000` | Max prompt string length | + +### Feature Flags + +| Key | Default | Description | +|-----|---------|-------------| +| `add_tags` | `false` | Enable auto-tagging feature | +| `add_tags_maxnum` | `3` | Max tags to apply | +| `add_tags_hide_exclusions` | `false` | Hide excluded tags from menu | +| `add_tags_exclusions_exact_match` | `false` | Exact match for exclusions | +| `add_tags_first_uppercase` | `true` | Capitalize first letter of tags | +| `add_tags_force_lang` | `true` | Force language for tags | +| `add_tags_auto` | `false` | Auto-tag on message open | +| `add_tags_auto_force_existing` | `false` | Only use existing tags | +| `add_tags_auto_only_inbox` | `true` | Auto-tag only inbox messages | +| `add_tags_auto_uselist` | `false` | Use tag allow-list | +| `add_tags_auto_uselist_list` | `''` | Tag allow-list content | +| `add_tags_enabled_accounts` | `[]` | Accounts where auto-tag is active | +| `get_calendar_event` | `true` | Enable calendar event extraction | +| `get_calendar_event_from_clipboard` | `false` | Enable calendar from clipboard | +| `get_task` | `true` | Enable task creation | +| `calendar_enforce_timezone` | `false` | Force specific timezone | +| `calendar_timezone` | `''` | Timezone to enforce | +| `calendar_no_selection` | `false` | Skip selection prompt | +| `spamfilter` | `false` | Enable spam filter | +| `spamfilter_threshold` | `70` | Spam confidence threshold (%) | +| `spamfilter_enabled_accounts` | `[]` | Accounts where spam filter is active | +| `spamfilter_show_msg_panel` | `true` | Show info panel on spam detection | +| `summarize` | `false` | Enable email summarization | + +## Adding a New Preference + +1. Add the key and default value to `prefs_default` in `options/mzta-options-default.js` +2. Add UI control to `options/mzta-options.html` +3. Add load/save logic to `options/mzta-options.js` +4. Add i18n label to `_locales/en/messages.json` +5. Read the pref in the relevant module via `browser.storage.local.get()` + +## Reading Preferences at Runtime + +```javascript +const prefs = await browser.storage.local.get(prefs_default); +// prefs now contains all keys with defaults for any unset values +const myPref = prefs.my_new_pref; +``` diff --git a/claude-spec/06-localization.md b/claude-spec/06-localization.md new file mode 100644 index 00000000..7578fd25 --- /dev/null +++ b/claude-spec/06-localization.md @@ -0,0 +1,91 @@ +# Localization + +## Golden Rule + +**Only ever modify `_locales/en/messages.json`.** + +All other locale files (`de`, `fr`, `it`, `es`, `zh_Hans`, `zh_Hant`, `pl`, `ru`, `pt-br`, `sv`, `el`, `cs`, `hr`, `ja`, `nb_NO`) are managed by translators through [Weblate](https://hosted.weblate.org/). Never edit them manually. + +## Message File Format + +Each entry in `_locales/en/messages.json` follows the standard WebExtension i18n format: + +```json +"key_name": { + "message": "The English text", + "description": "Context for translators explaining where/how this string is used" +} +``` + +The `description` field is important — it helps Weblate translators understand the context. + +## Using Strings in Code + +### In JavaScript +```javascript +import { i18n } from './mzta-i18n.js'; +const text = i18n('key_name'); +// or directly: +const text = browser.i18n.getMessage('key_name'); +``` + +### In HTML +```html + + +__MSG_key_name__ +``` + +### In manifest.json +```json +"description": "__MSG_extensionDescription__" +``` + +## Naming Conventions + +| Prefix | Usage | +|--------|-------| +| `menu_*` | Context menu and popup menu labels | +| `prompt_*` | Built-in prompt names | +| `placeholder_*` | Placeholder display names | +| `options_*` | Settings page labels | +| `pages_*` | Feature page labels | +| `error_*` | Error messages | +| `info_*` | Informational messages | +| `btn_*` | Button labels | + +## Adding a New String + +1. Open `_locales/en/messages.json` +2. Add the new key in alphabetical order within the file (or near related keys) +3. Include both `message` and `description` fields +4. Use the string in code via `browser.i18n.getMessage('key_name')` or `__MSG_key_name__` + +Example: +```json +"my_new_feature_label": { + "message": "My New Feature", + "description": "Label for the new feature button in the options page" +} +``` + +## Supported Languages (16) + +| Code | Language | +|------|----------| +| `en` | English (source) | +| `de` | German | +| `es` | Spanish | +| `fr` | French | +| `it` | Italian | +| `pl` | Polish | +| `ru` | Russian | +| `pt-br` | Brazilian Portuguese | +| `sv` | Swedish | +| `el` | Greek | +| `cs` | Czech | +| `hr` | Croatian | +| `ja` | Japanese | +| `nb_NO` | Norwegian Bokmål | +| `zh_Hans` | Chinese Simplified | +| `zh_Hant` | Chinese Traditional | diff --git a/claude-spec/99-thunderbird-team-spec.md b/claude-spec/99-thunderbird-team-spec.md new file mode 100644 index 00000000..c3be0040 --- /dev/null +++ b/claude-spec/99-thunderbird-team-spec.md @@ -0,0 +1,398 @@ +# Thunderbird WebExtensions Development Guidelines + +> **Purpose:** This file provides operative guidelines for Claude when helping develop or modify ThunderAI. These rules override general AI assistant behavior and must be followed strictly. + +## ThunderAI-Specific Context + +- ThunderAI uses **Manifest Version 2** — do not suggest or apply any MV3 migration. +- No build tools, no transpilation, no npm — plain ES6 modules loaded directly. +- The manifest already uses `browser_specific_settings` (not `applications`). +- All module imports use relative paths with `.js` extension. +- The `mzta-` prefix is used for all core module filenames. + +--- + +## Important Guidelines for AI Assistants + +### 1. Always use `browser_specific_settings` in manifest.json + +The `applications` manifest entry is deprecated. Always use `browser_specific_settings`: + +```json +{ + "manifest_version": 2, + "name": "ThunderAI", + "browser_specific_settings": { + "gecko": { + "id": "thunderai@micz.it", + "strict_min_version": "140.0" + } + } +} +``` + +### 2. Do not guess APIs by using Try-Catch + +A widespread antipattern in AI-generated Thunderbird extensions: + +```javascript +// WRONG - Never do this! +try { + await browser.someApi.method({ guessedParam: value }); +} catch (e) { + try { + await browser.someApi.method({ differentGuess: value }); + } catch (e2) { + // Giving up silently — this makes debugging impossible + } +} +``` + +**Why this is harmful:** +- Makes code unmaintainable +- Hides real errors from developers +- Makes debugging extremely difficult + +**The correct approach:** +1. Read the API documentation FIRST +2. Use the exact parameter names and types specified +3. Only use try-catch for expected error conditions with proper handling +4. Never suppress errors without logging or handling them + +### 3. Do not use Experiments unnecessarily + +```javascript +// WRONG - Using Experiment when standard API exists +// Don't use Experiment just because you found example code using it + +// RIGHT - Check if standard API can do it first +const folders = await browser.folders.query({ name: "Inbox" }); +``` + +### 4. Handle file storage correctly + +```javascript +// WRONG - Trying to use raw filesystem APIs +const fs = require('fs'); // Not available! + +// RIGHT - Use storage.local with File objects +const file = new File([content], "data.txt", { type: "text/plain" }); +await browser.storage.local.set({ file }); +``` + +### 5. Do not use async listeners for the runtime.onMessage listener + +See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage + +### 6. Parse vCard, vTodo, vEvent and iCal strings using a 3rd party library + +Follow https://webextension-api.thunderbird.net/en/mv2/guides/vcard.html to parse vCard, vEvent and vTodo strings. + +### 7. Parse Mailbox Strings using messengerUtilities + +Extract email addresses from mailbox strings like "John Doe ": + +```javascript +const parsed = await browser.messengerUtilities.parseMailboxString( + "John Doe , Jane " +); + +// Result: +// [ +// { name: "John Doe", email: "john@example.com" }, +// { name: "Jane", email: "jane@example.com" } +// ] + +// Extract just emails: +const emails = parsed.map(p => p.email); +``` + +**Documentation:** https://webextension-api.thunderbird.net/en/mv2/messengerUtilities.html + +**Options:** +- `preserveGroups`: Keep grouped hierarchies +- `expandMailingLists`: Expand Thunderbird mailing lists (requires `addressBook` permission) + +### 8. Set correct `strict_min_version` entry + +Make sure `manifest.json` has a `strict_min_version` entry matching the used functions. If a function added in Thunderbird 137 is used, it must be set to `137.0` or higher. + +### 9. Always use background type "module" + +Always use `type: "module"` for background scripts. This allows use of the `import` directive for ES6 modules, and non-ES6 libraries can still be loaded via the `scripts` array: + +```json +// RIGHT - Always use type: "module" +"background": { + "scripts": ["lib/some-non-ES6-lib.js", "background.js"], + "type": "module" +} +``` + +Then in `background.js`, import libraries explicitly: +```javascript +// Import ES6 module with default export +import ICAL from "./lib/ical.js"; + +// Import ES6 module with named exports +import { someFunction, someConstant } from "./lib/somemodule.js"; +``` + +### 10. Verify API return types — do not assume array access + +Many Thunderbird APIs return wrapped objects, not direct arrays. Always verify the return type in the documentation before accessing the data. + +**Common pitfall — MessageList:** +```javascript +// WRONG - getDisplayedMessages() returns MessageList, not an array +const [message] = await browser.messageDisplay.getDisplayedMessages(tabId); + +// RIGHT - MessageList has a .messages array property +const { messages: [message] } = await browser.messageDisplay.getDisplayedMessages(tabId); +``` + +**Common pitfall — HeadersDictionary:** +```javascript +// WRONG - headers might not exist or might not be an array +let returnPath = headers["Return-Path"]; + +// RIGHT - keys are lowercase, values are always arrays +const returnPathArray = headers["return-path"]; +const returnPath = returnPathArray?.[0] ?? null; +``` + +**APIs that return wrapped objects (NOT direct arrays):** + +| API | Returns | Access Pattern | +|-----|---------|----------------| +| `messageDisplay.getDisplayedMessages()` | `MessageList` | `result.messages[0]` | +| `messages.list()` | `MessageList` | `result.messages[0]` | +| `messages.query()` | `MessageList` | `result.messages[0]` | +| `messages.getHeaders()` | `HeadersDictionary` | `result["header-name"][0]` | +| `messages.getFull()` | `MessagePart` | `result.headers["header-name"][0]` | + +**APIs that return direct arrays:** + +| API | Returns | Access Pattern | +|-----|---------|----------------| +| `tabs.query()` | array of Tab | `result[0]` | +| `mailTabs.query()` | array of MailTab | `result[0]` | +| `addressBooks.list()` | array of AddressBookNode | `result[0]` | +| `contacts.list()` | array of ContactNode | `result[0]` | +| `folders.query()` | array of MailFolder | `result[0]` | + +--- + +## Official API Documentation + +**Primary resource:** https://webextension-api.thunderbird.net/en/mv2/ + +Documentation exists for different channels: +- **Release (mv2):** https://webextension-api.thunderbird.net/en/mv2/ +- **ESR (esr-mv2):** https://webextension-api.thunderbird.net/en/esr-mv2/ + +**Key feature:** Search functionality and cross-references between types and functions. + +--- + +## Understanding Thunderbird Release Channels + +### Standard Release Channel (Monthly) +- Update cadence: ~4 weeks +- A new major version with each release (`147.0`, `148.0`, ...) +- Gets new features and APIs (and bug fixes & security fixes) + +### ESR Channel (Extended Support Release) +- Update cadence: 1 major update per year, with lots of versions "missing" in between (`115.*`, `128.*`, `140.*`, `153.*`, ...) +- Receives bug fixes & security fixes on regular basis alongside the major monthly releases, but as minor releases (`140.1`, `140.2`, ...) +- No new features or APIs + +### For ThunderAI +- Target the Release channel for standard API usage. +- Target the ESR channel for add-ons relying on Experiment APIs. Targeting the Release channel with Experiments is acceptable *only* when the developer can guarantee required monthly updates. + +--- + +## Experiment APIs + +### What Are Experiment APIs? +Experiment APIs allow add-ons to access Thunderbird's core internals directly. They have full access to modify everything in Thunderbird. + +### Critical Rules for Experiments + +**1. Avoid Experiments Unless Absolutely Necessary** +- Standard WebExtension APIs should always be your first choice +- Only use Experiments when standard APIs genuinely cannot accomplish your goal + +**2. Experiments Require Updates for Each Major Version** +- This was manageable with ESR (1x/year) +- Became unsustainable with monthly Release channel (12x/year) + +**3. When Suggesting Experiments** +- Only if standard APIs cannot accomplish the core goal +- Developer must explicitly understand the maintenance burden +- **Target ESR channel specifically** +- Reference `esr-mv2` documentation + +### Available semi-official Experiment APIs + +#### Calendar Experiment API + +Use this instead of creating a custom Experiment for calendar interactions. + +**Use cases:** +- Reading existing event/task items from Thunderbird's calendar +- Listening for item updates +- Creating/updating/deleting items + +**Setup requirements:** +1. Temporarily clone the [webext-experiments](https://github.com/thunderbird/webext-experiments/) repository. +2. Add all `experiment_apis` entries found in `calendar/manifest.json` to the project's `manifest.json`. +3. Copy `calendar/experiments/calendar/` into the project as `experiments/calendar/`. Do not modify these files. + +**Note:** Always request iCal format: + +```javascript +// Always consult schema first, if this example is still correct +browser.calendar.items.onCreated.addListener( + async (calendarItem) => { + if (calendarItem.type === "task") { + console.log("Task in iCal format:", calendarItem.item); + } + }, + { returnFormat: "ical" } +); +``` + +### Other Experiment Repositories + +- https://github.com/thunderbird/webext-support — Helper APIs and modules +- https://github.com/thunderbird/webext-examples — Example extensions (includes some Experiments) + +--- + +## Native File System Access + +### Current Limitations +Native filesystem access is NOT available in Thunderbird WebExtensions. + +### Recommended Approach + +**For data persistence:** +```javascript +await browser.storage.local.set({ myData: someValue }); +const data = await browser.storage.local.get("myData"); +``` + +**For user file input:** +```javascript +const file = new File([content], "filename.txt", { type: "text/plain" }); +await browser.storage.local.set({ file }); + +// Retrieve later +const data = await browser.storage.local.get("file"); +console.log(data.file.name); +``` + +**Important:** File objects can be stored directly in `browser.storage.local` without serialization. + +--- + +## Add-on Review Requirements + +**Review policy:** https://thunderbird.github.io/atn-review-policy/ + +### Key Requirements + +**1. No Build Tools** +- Include 3rd party libraries directly (don't use webpack, rollup, etc.) +- Include a `VENDOR.md` file documenting all 3rd party libraries with links to exact versions (not "latest"). Example: https://webextension-api.thunderbird.net/en/mv2/guides/vcard.html + +**2. Permissions** +- Only request permissions you actually need +- The `tabs` and `activeTab` permissions are almost never needed in Thunderbird +- Unnecessary permissions may cause rejection during ATN review + +--- + +## Example Repositories + +- https://github.com/thunderbird/webext-examples — Official example extensions +- https://github.com/thunderbird/webext-support — Support libraries and helpers + +Use these to see proper code structure, learn common patterns, and understand best practices. + +--- + +## Mandatory Checklist Before Providing Code + +Before providing any code, verify ALL of these: + +- [ ] Consulted official API documentation — do NOT guess methods or parameters +- [ ] NO try-catch blocks for guessing API parameters +- [ ] Used 3rd party libraries or API methods for parsing — minimize manual string parsing or regex +- [ ] Used 3rd party libraries are the most recent stable version +- [ ] Event listeners registered at file scope (NOT inside init function) +- [ ] VENDOR.md includes ALL dependencies with exact version URLs +- [ ] Used `browser_specific_settings` (NOT deprecated `applications`) +- [ ] Included proper error handling +- [ ] Code has comments explaining the approach +- [ ] No hardcoded user-facing strings — use the i18n API (`_locales/en/messages.json` only) +- [ ] Add-on fulfills all requirements in the "Add-on Review Requirements" section +- [ ] All guidelines in "Important Guidelines for AI Assistants" are followed +- [ ] Manifest uses correct `strict_min_version` +- [ ] If using Experiments: manifest has `strict_max_version` targeting current ESR (fetch https://webextension-api.thunderbird.net/en/esr-mv2/ to get the major version, then use format `".*"`) + +If ANY checkbox is unchecked, DO NOT provide the code. Fix it first. + +--- + +## Mandatory 3rd Party Library Audit + +For EACH 3rd party library included in the project: + +- [ ] Inspect the actual file to determine the export type: + - **ES6 default export:** Look for `export default` → use `import LibName from "./lib/file.js"` + - **ES6 named exports:** Look for `export { name1, name2 }` → use `import { name1, name2 } from "./lib/file.js"` + - **UMD/IIFE (no ES6 exports):** Look for `(function(root, factory)` or assignments to `window`/`globalThis` → load via `scripts` array in manifest +- [ ] Always prefer the minified module version +- [ ] Output a library audit table: + +| Library | File | Module Type | Import Statement | +|---------|------|-------------|------------------| +| ical.js | lib/ical.js | ES6 default | `import ICAL from "./lib/ical.js"` | + +- [ ] Update VENDOR.md with the correct file path and version URL + +--- + +## Mandatory API Audit + +Before finalizing any code: + +- [ ] List all used API methods +- [ ] For EACH API method, fetch its documentation page: `https://webextension-api.thunderbird.net/en/mv2/.html` +- [ ] For EACH API method, verify: + - **Parameters:** Correct names and types + - **Return type:** The actual type returned by the Promise + - **Access pattern:** How to extract data from the return value + - **Required permission:** What permission is needed in manifest.json +- [ ] Output an API audit table: + +| API Method | Returns | Access Pattern | Required Permission | +|------------|---------|----------------|---------------------| +| `browser.messageDisplay.getDisplayedMessages()` | MessageList | `result.messages[0]` | messagesRead | +| `browser.messages.getHeaders()` | HeadersDictionary | `result["header-name"][0]` | messagesRead | +| `browser.mailTabs.query()` | array of MailTab | `result[0]` | (none) | +| `browser.storage.local.get` | object | `result.keyName` | storage | +| `browser.i18n.getMessage` | string | direct | (none) | + +- [ ] Update the permissions entry in manifest.json to include ALL required permissions + +--- + +## Getting Help + +- **Developer documentation:** https://developer.thunderbird.net/ +- **Support forum:** https://thunderbird.topicbox.com/groups/addons +- **Matrix chat:** #tb-addon-developers:mozilla.org