Thank you for your interest in the Say, Pi Browser Extension project. Before considering any contributions, please read this document carefully.
The Say, Pi Browser Extension is a proprietary software project. It is not open source, and we are not currently accepting external code contributions.
The source code for this project is made publicly visible for the following reasons:
- Transparency: To allow users to review the code for security and privacy concerns.
- Educational purposes: To provide insights into how the extension works.
However, public visibility does not imply that the project is open source or that external contributions are accepted.
While we're not accepting code contributions, we value your feedback and suggestions. Here's how you can contribute:
-
Reporting Bugs: If you encounter any bugs or issues, please open an issue in the GitHub repository. Provide as much detail as possible, including steps to reproduce the issue, expected behavior, and actual behavior.
-
Suggesting Enhancements: If you have ideas for new features or improvements, feel free to open an issue to discuss them. We can't guarantee that all suggestions will be implemented, but we appreciate your input.
-
Providing Feedback: Your experience with the Say, Pi Browser Extension is important to us. Feel free to share your thoughts and experiences through the provided contact channels.
Please note that we do not accept pull requests from external contributors at this time. Any pull requests opened by external contributors will be closed without merging.
For developers interested in understanding the project's technical implementation, the following information may be helpful.
This project uses a 4-tier environment management system for secure, convenient secret handling across devices.
npm run setupThis creates .env and .env.production from templates. Update the values before running dev/build.
Environment files are automatically validated before dev/build:
npm run validate:env # Manual validationThe validator checks for:
- Missing required variables
- Malformed URLs and API keys
- Placeholder values (XXXXXXXXXX)
- Incorrect formats
npm run setup # One-command initializationCreates environment files from templates and checks for 1Password CLI integration.
Sync secrets across devices using 1Password CLI:
Prerequisites:
- Install 1Password CLI: https://developer.1password.com/docs/cli/get-started/
- Sign in:
op signin
First-time setup (on your primary device):
# Edit .env and .env.production with real values
npm run env:push # Save to 1PasswordOn other devices:
npm run setup # Create template files
npm run env:pull # Load secrets from 1PasswordWorkflow:
npm run env:push- Save current.envvalues to 1Password vaultnpm run env:pull- Load secrets from 1Password to local.envfiles
Secrets are stored in a 1Password vault named saypi-userscript-dev in an item called SayPi Dev Secrets.
Pre-commit hooks automatically prevent accidental commits of:
.envand.env.productionfiles.key,.pem, and other credential files
The hook is installed automatically via npm install (husky).
For developers who frequently switch between local and remote server configurations:
# Toggle between local and remote (smartest option!)
npm run switch
# Switch to specific configuration
npm run switch local # localhost:3000, 127.0.0.1:5001
npm run switch remote # saypi.ai, api.saypi.ai
# Check current configuration
npm run switch status
# Show help
npm run switch help
npm run switch -- -hThe toggle feature automatically switches to whichever environment you're not currently using, making it fast to switch back and forth without manually editing .env files.
We prune bundled Silero ONNX models during npm run build via a lightweight Python helper built on onnxruntime and onnx. Set up a local Python environment once per machine:
# Optional but recommended: install Python 3.11.12 via pyenv (matches saypi-api)
pyenv install 3.11.12
# Create/refresh the local virtual environment and install tooling deps
npm run setup:pythonThe helper script creates .venv/ in the project root (ignored by Git) and installs the dependencies listed in pyproject.toml—currently onnxruntime==1.17.3, onnx==1.19.0, and numpy<2. Activate it manually with source .venv/bin/activate if you need to run the optimizer yourself.
npm run copy-onnx (and therefore the default build pipeline) automatically invokes npm run prune-onnx. If the Python toolchain is missing, the command exits with an actionable error—re-run npm run setup:python after installing Python 3.11 to resolve it.
The extension can be built for different browsers using our packaging script:
./package-extension.sh <browser>Supported targets: chrome, firefox, edge
Firefox-specific notes:
- Requires
jqutility:brew install jq(macOS) orapt install jq(Ubuntu/Debian) - Builds as Manifest V2 for Firefox compatibility
- Use
npm run build:firefoxfor automated build + package
For transparency, here are the exact build environment details for our Firefox releases:
Build Machine:
- Operating system: Linux Mint 21.3, Kernel: Linux 5.15.0-122-generic
- Shell: GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
- Node version: v20.17.0
- npm version: 10.8.2
Build commands:
# Standard build process
npm install
npm run build
./package-extension.sh firefox
# Output: dist/saypi.firefox.xpiThe Voice Activity Detection (VAD) library @ricky0123/vad-web loads ONNX models by fetching the file and passing the bytes to ONNX Runtime (ORT):
fetch(modelURL) → Response.arrayBuffer() → ort.InferenceSession.create(arrayBuffer)
Firefox Issue:
Firefox runs page scripts and content scripts in different JavaScript realms. If a Response/Blob is rewrapped in the content script, the resulting ArrayBuffer can belong to a different realm. ORT uses instanceof ArrayBuffer guards; cross-realm buffers fail those checks and throw:
TypeError: Unexpected argument[0]: must be 'path' or 'buffer'.
Our Solution:
- We avoid rewrapping fetch responses in src/RequestInterceptor.js. We only rewrite URLs and keep the original
Responseobject. - We add a minimal shim in
RequestInterceptor.jsthat patchesResponse.prototype.arrayBuffer()for our model URLs. If the returned value quacks like an ArrayBuffer but failsinstanceof, we copy it into a same-realmArrayBufferand return that.
Alternative approach (not enabled by default): src/vad/custom-model-fetcher.js provides a modelFetcher that performs the same copy and can be passed to MicVAD.new({ modelFetcher }) if we ever remove the shim.
Technical Notes:
- Correct
Content-Typeis not required for.onnx. For.wasm,Accept: application/wasmaids streaming compile; lackingContent-Type: application/wasmmay only impact performance warnings, not correctness. - Asset paths:
baseAssetPathandonnxWASMBasePathpoint tochrome.runtime.getURL("public/").
This repository uses a unified command-line workflow for translating both UI strings and browser store descriptions. The process handles two types of content:
- UI Strings (
_locales/{locale}/messages.json) - Translated viatranslate-cli - Store Descriptions (
_locales/{locale}/description.txt) - Translated via OpenAI API
-
Install
translate-cli(requires Go):go install github.com/quailyquaily/translate-cli@latest
-
Configure
translate-cli: Create a configuration file at~/.config/translate-cli/config.yamlwith your preferred translation engine credentials. See the translate-cli docs for details. -
Set OpenAI API Key (for store descriptions):
export OPENAI_API_KEY="your-api-key-here"
Recommended: Use the unified script that handles both UI strings and store descriptions:
# Using npm script (recommended)
npm run translate
# Or directly (from repo root)
python3 tools/i18n/i18n-translate-all.pyOptions:
# Skip confirmation prompts
npm run translate -- --yes
# Pass specific options to translate-cli
npm run translate -- --translate-cli-args="-b 20 -e openai"
# Check requirements without running translations
npm run translate:check
# Skip validation after translation
npm run translate -- --skip-validation- Validates requirements: Checks for
translate-cli, OpenAI API key, and required files - Translates UI strings: Runs
tools/i18n/i18n-translate-chrome.shto translatemessages.jsonfiles - Translates store descriptions: Runs
tools/i18n/i18n-translate-release-text.pyto translatedescription.txtfiles - Validates results: Checks that all locales have valid JSON and description files
If you need to run translations separately:
UI strings only:
./tools/i18n/i18n-translate-chrome.sh [LOCALES_DIR] -- [translate-cli flags…]Store descriptions only:
python3 tools/i18n/i18n-translate-release-text.py- Update source content in
_locales/en/messages.jsonand_locales/en/description.txt - Run
npm run translateto translate all content - Review translated files in
_locales/ - Test the extension with different locales
- Run
npm run buildto validate translations
Note: The unified script replaces the need to run individual translation scripts manually, ensuring both UI strings and store descriptions are always kept in sync during release preparation.
The tools/i18n/i18n-translate-release-text.py script provides a convenient alias for copying locale-specific descriptions to your clipboard for easy pasting into Chrome Web Store:
# Set up the alias (run once per terminal session)
alias lcp='f(){ cat "_locales/$1/description.txt" | pbcopy; }; f'
# Then use it to copy any locale's description to clipboard
lcp vi # Copy Vietnamese description
lcp zh_CN # Copy Chinese (Simplified) description
lcp pt_BR # Copy Portuguese (Brazil) descriptionQuick workflow tip: Use terminal history to quickly switch between locales:
- Type
lcp viand press Enter - Press ↑ arrow key to get previous command
- Edit
vitofrand press Enter - Paste into Chrome Web Store and repeat for next locale
If you have any questions about the project or this contribution policy, please contact us using the information provided in the README.
Thank you for your understanding and interest in the Say, Pi Browser Extension.