{"id":"7998c382-512f-45b7-bbb1-248448dd1e12","slug":"shuvgeist","kind":"skill","name":"shuvgeist","summary":"A clanker that lives in your browser.","owner":"shuv1337","repoUrl":"https://github.com/shuv1337/shuvgeist","license":"AGPL-3.0","licenseTier":"strong-copyleft","trust":"community","stars":2,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-03-16T00:18:16.000Z","addedAt":"2026-10-22T00:46:01.691Z","logoUrl":"https://avatars.githubusercontent.com/u/523952?u=443c48aae6c47d4b3bc2bc6c5135787dfc02ab76&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["browser-web"],"tags":[],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-26T05:01:12.082Z","sample":false,"url":"https://conduits.dev/tools/shuvgeist","language":"TypeScript","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# Shuvgeist\n\nShuvgeist is a Chrome/Edge sidepanel extension for browser-native AI assistance and browser automation. It gives an LLM a controlled toolset for navigating pages, running page-context JavaScript, extracting structured data, working with attachments and artifacts, and reusing domain-specific skills you teach it over time.\n\nThis repo also ships:\n\n- the `shuvgeist` CLI bridge for terminal-driven browser control\n- a self-hostable CORS proxy for browser-restricted provider flows\n- the static marketing/install site at `site/`\n\nWorks on Chrome 141+ and equivalent Edge builds.\n\n## Screenshots\n\n### Extension sidebar\n\n![Shuvgeist extension sidebar](site/src/frontend/img/preview-sidebar.png)\n\n### CLI bridge\n\n![Shuvgeist CLI bridge help](site/src/frontend/img/preview-cli.png)\n\n## What ships today\n\n### Extension\n\nThe extension lives in the browser sidepanel and persists its data locally in IndexedDB. Current user-facing capabilities include:\n\n- chat sessions with resumable history and renameable titles\n- file attachments and artifact generation\n- page navigation and tab management\n- REPL execution in an extension sandbox with page-context `browserjs()` access\n- domain skills that inject reusable site-specific helpers into page scripts\n- interactive element disambiguation when DOM targeting is unclear\n- image extraction and document extraction tools\n- Kokoro-first text-to-speech with page-bound read-along highlighting and audio-only cloud fallback paths\n- optional debugger-backed tooling for stubborn sites that reject synthetic DOM events\n- daily cost tracking with provider/model breakdowns\n- settings tabs for subscriptions, providers/models, skills, bridge state, costs, and about/theme\n\n### Provider and model support\n\nShuvgeist supports two main auth/config paths:\n\n- Subscription login inside the extension:\n  - Anthropic (Claude Pro/Max)\n  - ChatGPT Plus/Pro via OpenAI Codex OAuth\n  - GitHub Copilot\n  - Google Gemini\n  - xAI SuperGrok (Grok through 4.7)\n- API key and custom-provider setup through the Providers & Models UI\n\nThe repo also includes:\n\n- built-in MiniMax extension model registrations\n- custom provider import/export\n- `provider-presets/proxx.json` for the local `proxx` gateway workflow\n\n### Skills\n\nSkills are stored locally and matched by domain glob. The extension ships with a default skill library and a full skill manager UI:\n\n- search/filter skills\n- edit descriptions, examples, and injected library code\n- import/export skill packs\n- auto-inject matching skill libraries into `browserjs()` execution\n\nAn external coding-agent skill for driving the CLI bridge is included at `skills/shuvgeist/`.\n\n### CLI bridge\n\nThe CLI bridge exposes the browser to terminal tools, scripts, and coding agents. The bridge server is a WebSocket relay between the CLI and the extension background worker.\n\nCurrent CLI surface:\n\n- browser lifecycle: `launch`, `close`, `status`\n- navigation: `navigate`, `tabs`, `tabs close`, `switch`, `windows`\n- page execution: `repl`, `eval`, `screenshot`, `cookies`, `select`\n- page assertions: `assert expr`, `assert text`, `assert selector`, `assert role`, `assert label`, `assert url`\n- deterministic workflows: `workflow run`, `workflow validate`\n- semantic page inspection: `snapshot`, `locate`, `ref`, `frame`\n- debugger-backed diagnostics: `network`, `device`, `perf`\n- video repro capture: `record start`, `record stop`, `record status` using CDP screencast by default, with explicit Chrome tab-capture WebM and optional audio\n- Electron desktop targets: `electron list`, `electron allow`, `electron attach`, `electron launch`, `electron windows`, and `--target electron:...`\n- session control: `session`, `inject`, `new-session`, `set-model`, `artifacts`\n\nRun `shuvgeist --help` for the full command reference.\n\n### Supporting subprojects\n\n- `proxy/`: minimal self-hosted CORS proxy with host allowlisting and filtered headers\n- `site/`: static landing page and install guide\n\n## Architecture\n\nHigh-level flow:\n\n`Chrome/Edge sidepanel UI -> Background coordinator -> Offscreen agent/tool runtime -> Background-authorized active tab(s)`\n\nCLI flow:\n\n`shuvgeist CLI -> Bridge server -> Extension background worker -> tab/session tools`\n\nElectron CLI flow:\n\n`shuvgeist CLI -> Bridge server -> Electron CDP endpoint`\n\nElectron requests are handled bridge-local and do not require a connected Chrome/Edge extension target. Chrome remains the default target when `--target` is omitted.\n\nImportant package boundaries:\n\n- `packages/protocol/`: schema-first commands, wire contracts, targets, and shared protocol metadata\n- `packages/driver/`: target-neutral PageDriver, semantic refs, capture engines, and injected driver artifacts\n- `packages/extension/`: sidepanel, offscreen agent runtime, background coordinator, settings UI, and Chrome adapters\n- `packages/server/`: bridge server, Electron sessions, MCP, Node configuration, and local target execution\n- `packages/cli/`: public `shuvgeist` CLI, discovery, launch/autostart, and direct-CDP runtime\n\nSee [ARCHITECTURE.md](ARCHITECTURE.md) for a deeper code-oriented walkthrough. For the local-first TTS overlay and Kokoro read-along flow, see [docs/tts.md](docs/tts.md).\n\n## Install\n\n### Install from a release\n\n1. Download the latest release from [GitHub Releases](https://github.com/shuv1337/shuvgeist/releases/latest).\n2. Unzip it somewhere stable.\n3. Open `chrome://extensions/` or `edge://extensions/`.\n4. Enable Developer mode.\n5. Click `Load unpacked` and select the extension directory.\n6. In the extension details, enable:\n   - `Allow user scripts`\n   - `Allow access to file URLs`\n7. Set site access to `On all sites`.\n\nOpen the sidepanel with:\n\n- macOS: `Command+Shift+S`\n- Windows/Linux: `Ctrl+Shift+S`\n\nOn first launch, connect at least one provider.\n\n### Build from source\n\nThis repo expects sibling checkouts for linked packages:\n\n```text\nparent/\n  mini-lit/\n  pi-mono/\n  shuvgeist/\n```\n\nInstall and build dependencies first:\n\n```bash\ncd ../mini-lit && npm install && npm run build\ncd ../pi-mono && npm install && npm run build\ncd /path/to/shuvgeist && npm install\n```\n\nBuild outputs:\n\n```bash\nnpm run build       # extension -> dist-chrome/\nnpm run build:cli   # CLI -> dist-cli/shuvgeist.mjs\n```\n\nRecording from the CLI requires `ffmpeg` on PATH. The current recorder is video-only (no audio) and keeps the existing sensitive-browser-access gate.\n\nLoad `dist-chrome/` as an unpacked extension.\n\n## Quick start\n\n### Sidepanel\n\n1. Open the sidepanel.\n2. Connect a subscription or add an API key/custom provider.\n3. Pick a model.\n4. Start with a task that needs a real browser, for example:\n   - \"Open the current site and summarize this page.\"\n   - \"Extract the visible products into a CSV artifact.\"\n   - \"Teach yourself this site and save it as a skill.\"\n\n### CLI bridge\n\nBuild and stage the installable CLI package:\n\n```bash\nnpm run package:cli\npackage_version=\"$(node -p 'require(\"./static/manifest.chrome.json\").version')\"\nnpm install -g \"./shuvgeist-${package_version}.tgz\"\n```\n\nThe source workspace is intentionally not directly packable: `npm pack --workspace shuvgeist` fails closed because its development manifest contains private workspace dependencies. Always use `npm run package:cli`, which emits the dependency-free six-file public package.\n\nBasic examples:\n\n```bash\nshuvgeist doctor\nshuvgeist status\nshuvgeist navigate \"https://example.com\"\nshuvgeist tabs --json\nshuvgeist tabs close 123 --json\nshuvgeist tabs close --title-match shuvplan --dry-run --json\nshuvgeist tabs close --title-match shuvplan --yes --json\nshuvgeist windows --json\nshuvgeist screenshot --out page.png\nshuvgeist record start --out /tmp/example.webm --max-duration 5s\nshuvgeist record start --mode tab-capture --audio --out /tmp/example-with-audio.webm --max-duration 5s\nshuvgeist repl 'return await browserjs(() => document.title)'\nshuvgeist assert text \"Example Domain\" --timeout 10s\nshuvgeist snapshot --json\nbaseline_id=\"$(shuvgeist snapshot store --json | jq -r '.record.id')\"\nshuvgeist snapshot diff \"$baseline_id\" --json\nshuvgeist locate text \"Sign in\" --json\nshuvgeist handoff task-42 session-7 --kind manual --message \"Complete sign-in, then resume\"\nshuvgeist journal --last 25\n```\n\nHuman handoffs are bound to the exact task, session, Chrome tab, frame, and navigation generation. The page overlay must acknowledge the pause before automation can trigger an optional browser-native action, and only the same overlay can resume it. Closing the bridge, cancelling the request, timing out, navigating, or reloading the extension revokes the handoff before the caller regains control; late or duplicate page events are rejected. See [docs/human-handoff.md](docs/human-handoff.md).\n\nEach bridge response also carries a compact `aftermath` summary. The server persists the same bounded record in a per-session journal under `~/.shuvgeist/journals/`; `shuvgeist journal` reads it without requiring an extension connection. JSON CLI output uses `{ \"result\": ..., \"aftermath\": ... }` for journaled commands. See [docs/operation-journal.md](docs/operation-journal.md) for retention and privacy rules.\n\nNetwork capture replaces credentials with stable secret references before results enter normal output paths, omits ambiguous bodies, and gates mutating curl exports behind `--review-mutation`. Electron stores lossless values separately in a mode-0600 local profile; Chrome keeps them only in extension memory. See [docs/network-secret-references.md](docs/network-secret-references.md).\n\nFor tightly bounded use of a page's live authenticated session, `shuvgeist request-json <relative-path>` performs a same-origin, no-redirect, no-store JSON request with timeout, byte, mutation-review, and optional schema gates. See [docs/authenticated-json-requests.md](docs/authenticated-json-requests.md).\n\n### Deterministic e2e smoke\n\nUse `assert` for CI-style page checks instead of embedding assertions in REPL snippets. Assertions run against the page context by default and return structured results in `--json` mode.\n\n```bash\nshuvgeist launch --url http://127.0.0.1:3000 --headless --user-data-dir \"${RUNNER_TEMP:-/tmp}/shuvgeist-profile\" --json\nshuvgeist status --json\nshuvgeist assert text \"Welcome\" --timeout 10s --json\nshuvgeist assert role button --name \"Continue\" --visible --json\nshuvgeist assert selector \"form [type=submit]\" --enabled --json\n```\n\nUse workflow target pinning for multi-step tests so focus changes do not move the target tab:\n\n```json\n{\n  \"target\": { \"mode\": \"new-tab\" },\n  \"steps\": [\n    { \"method\": \"navigate\", \"params\": { \"url\": \"http://127.0.0.1:3000\" } },\n    { \"assert\": { \"kind\": \"text\", \"text\": \"Welcome\" }, \"as\": \"welcome\" }\n  ]\n}\n```\n\nSemantic refs use DOM actions by default. Chrome keeps the legacy `--native` mode; use `--trusted` (alias `--cdp-input`) for renderer-scoped CDP input on either supported target:\n\n```bash\nshuvgeist locate label \"Email\" --json\nshuvgeist ref fill <refId> --value \"user@example.com\" --native\nshuvgeist ref fill <refId> --value \"user@example.com\" --trusted\n```\n\n`--native` and `--trusted` are mutually exclusive. Trusted ref actions never fall back to DOM events. `ref fill` handles text inputs, textareas, selects, and editable elements including empty and `plaintext-only` `contenteditable` values. It preserves explicit ARIA role precedence and dispatches cancelable `beforeinput` followed by `input`; a canceled `beforeinput` is reported as a failed action. For clicks that trigger async same-tab navigation, pass a bounded wait:\n\n```bash\nshuvgeist ref fill <selectRefId> --value \"Low to High\" --json\nshuvgeist ref click <buttonRefId> --timeout 5s --json\n```\n\nBefore treating a machine as CI-ready, verify that `shuvgeist launch --headless --json` produces a connected extension target, `status --json` advertises the `page_assert` capability, and the local fixture assertions pass. See [docs/e2e-ci.md](docs/e2e-ci.md) for the full checklist, assertion flags, workflow target modes, native ref caveats, and exit-code contract.\n\n### Electron targets\n\nElectron support is explicit because it attaches to a local app's Chrome DevTools Protocol endpoint. Allow an app before launching or attaching:\n\n```bash\nshuvgeist electron list --json\nshuvgeist electron allow vscode\n```\n\nKnown app references include `vscode`, `shuvscode`, `codex`, `codex-desktop`, `slack`, `legcord`, `signal`, `signal-desktop`, and `obsidian`.\n\nFor Codex Desktop on Linux, the registry recognizes the packaged launcher at `/usr/bin/codex-desktop` and the Electron runtime at `/opt/codex-desktop/codex-desktop-electron`. Allow, diagnose, and attach by alias:\n\n```bash\nshuvgeist electron allow codex\nshuvgeist electron doctor codex --json\nshuvgeist electron attach codex --json\n```\n\nThe targeted doctor requires an exact Codex Desktop executable match and confirms that process owns its listening `--remote-debugging-port` before probing the loopback endpoint, even when it is outside the configured Shuvgeist launch range.\n\nUse `launch` when Shuvgeist should start a known Electron app with a remote debugging port:\n\n```bash\nshuvgeist electron launch vscode --json\n```\n\nUse `attach` when the app is already running with `--remote-debugging-port=<port>` or when Shuvgeist can discover that port from the app or process:\n\n```bash\nshuvgeist electron attach vscode --json\nshuvgeist electron attach --pid 12345 --json\nshuvgeist electron attach --port 9229 --json\n```\n\nList windows and assign labels when multiple renderers are present:\n\n```bash\nshuvgeist electron windows --json\nshuvgeist electron label e1 w1 main\n```\n\nTarget Electron windows with `--target`. Supported forms include `electron:e1:w1`, `electron:e1:main`, `electron:vscode:w1`, and `electron:e1/w1`.\n\n```bash\nshuvgeist screenshot --target electron:e1:w1 --out /tmp/electron.png\nshuvgeist eval \"document.title\" --target electron:e1:main --json\nshuvgeist snapshot --target electron:e1:w1 --json\nshuvgeist locate role button --name \"Run\" --target electron:e1:w1 --json\nshuvgeist ref click <refId> --trusted --target electron:e1:w1 --json\nshuvgeist record start --target electron:e1:w1 --out /tmp/electron.webm --max-duration 5s\n```\n\nElectron trusted input is renderer-scoped CDP synthesis, not operating-system input. It is fail-closed and must be enabled for the exact app in `~/.shuvgeist/bridge.json`:\n\n```json\n{\n  \"electron\": {\n    \"capabilities\": {\n      \"com.microsoft.VSCode\": { \"cdp_input\": true }\n    }\n  }\n}\n```\n\n`--cdp-input` is an alias for `--trusted`. Electron `--native` is intentionally unsupported because Shuvgeist does not synthesize OS-level mouse or keyboard input.\n\nTarget-scoped JSON results identify the resolved page as either a Chrome tab or an Electron session/window/target and include its `navigationGeneration`. Top-level `tabId` and `frameId` remain optional Chrome compatibility fields; Electron results never use negative tab sentinels.\n\nSecurity notes:\n\n- Only allow apps you intend to inspect. The allowlist is stored in `~/.shuvgeist/bridge.json`.\n- Electron commands operate over local CDP and can read renderer DOM, screenshots, page state, and recording frames.\n- `shuvgeist cookies` remains a Chrome/Edge extension command and is not routed to Electron targets. The parsed Electron `cookies` capability key is retained for configuration compatibility but does not enable Electron cookie access.\n- Renderer input requires the separate per-app `cdp_input` capability and is re-authorized against the live session/window before dispatch.\n- Default CDP and Electron recording require `ffmpeg` on PATH because the CLI encodes received frames into WebM. Explicit Chrome `--mode tab-capture` writes MediaRecorder WebM chunks directly and does not use ffmpeg.\n- Recording JSON distinguishes raw captured-frame bytes (`sourceBytes`) from the final WebM file size (`encodedSizeBytes`); deprecated `sizeBytes`, when present, is the encoded size.\n\nTroubleshooting:\n\n- Run `shuvgeist doctor --json` first for a read-only diagnosis of the CLI, bridge, extension artifact, authentication, protocol compatibility, exact build identity, and recording dependency. It does not auto-start the bridge or modify local configuration. A `BRIDGE_BUILD_MISMATCH` or `EXTENSION_BUILD_MISMATCH` means that process came from different source or lockfile content; rebuild and restart the reported component. The versioned JSON schema and failure-code contract are documented in [docs/doctor.md](docs/doctor.md).\n- Unknown app: run `shuvgeist electron list --json` and use one of the listed IDs or aliases.\n- App is not allowlisted: run `shuvgeist electron allow <app-id-or-alias>`.\n- No CDP port found: restart the app with `--remote-debugging-port=<port>` and pass `--port <port>`.\n- Wrong window: run `shuvgeist electron windows --json`, label the intended window, then target the label.\n- Extension disconnected errors on Electron commands usually mean the command was not given an Electron `--target`; Chrome is the default target.\n\n`shuvgeist status` reports package, protocol, exact build identity, browser-extension connectivity, and server-verified Electron liveness separately. Cached sessions whose CDP endpoint or renderer page has disappeared are reported as stale; a disconnected extension does not block a live Electron session.\n\nThe CLI auto-starts the local bridge when needed. Bridge config is resolved from:\n\n1. command-line flags\n2. environment variables\n3. `~/.shuvgeist/bridge.json`\n4. built-in defaults\n\nSupported env vars:\n\n- `SHUVGEIST_BRIDGE_URL`\n- `SHUVGEIST_BRIDGE_HOST`\n- `SHUVGEIST_BRIDGE_PORT`\n- `SHUVGEIST_BRIDGE_TOKEN`\n- `SHUVGEIST_BRIDGE_CONFIG` (override the bridge config path)\n- `SHUVGEIST_OTEL_ENABLED`\n- `SHUVGEIST_OTEL_INGEST_URL`\n- `SHUVGEIST_OTEL_PRIVATE_INGEST_KEY`\n\nAutomatic startup is deliberately narrower than connection support. It requires the exact `/ws` path with no query or fragment, plain `ws://` transport, and exactly `localhost`, `127.0.0.1`, or `::1`; it binds that approved loopback host and port and runs `<repo>/node_modules/.bin/tsx <repo>/packages/cli/src/cli.ts serve`. Every other endpoint—including TLS, remote, wildcard, alternate loopback, and custom-path URLs—must be started explicitly. A manual `shuvgeist serve` resolves its bind host and port independently from the client `url`, so a persisted remote connection URL is never reused as a local listener.\n\nWhen a flag or environment variable temporarily selects a different local endpoint, provide `--token` or `SHUVGEIST_BRIDGE_TOKEN` for reusable or concurrent access. An automatically generated token is returned only to the process that started that transient endpoint and is never written over credentials for an unrelated persisted URL.\n\nBrowser and extension discovery uses `~/.shuvgeist/config.json` (override with `SHUVGEIST_CONFIG`; `SHUVGEIST_DISCOVERY_CONFIG` is retained as an alias). Missing flag, environment, or file candidates fall through in order; malformed configuration stops discovery with the exact failing path.\n\nExit codes:\n\n- `0`: success\n- `1`: assertion failure or command/runtime error\n- `2`: no extension target connected\n- `3`: auth/configuration/network error\n\n## Bridge details\n\nThe extension stores bridge settings in browser storage and exposes them in `Settings -> Bridge`.\n\nCurrent bridge behavior:\n\n- same-host loopback is the default\n- the background worker owns the bridge connection\n- the Bridge tab can block bridge access entirely\n- sensitive browser data access is separately gated\n- remote/LAN bridge URL and token overrides are supported\n\nSensitive bridge access enables commands such as:\n\n- `shuvgeist eval`\n- `shuvgeist assert expr --world main`\n- `shuvgeist cookies`\n- `shuvgeist network get`\n- `shuvgeist network body`\n- `shuvgeist network curl`\n\n### Bridge management\n\nThe bridge is managed automatically by the extension and CLI. You do not need to install or run a separate systemd unit.\n\n## Proxy\n\nShuvgeist includes a self-hostable proxy in `proxy/` for provider flows that still need CORS help from a browser context.\n\nWhy it exists:\n\n- some provider auth/token flows cannot be called directly from the extension\n- some API integrations are intentionally proxied\n- document extraction can also use the proxy\n\nRun it locally:\n\n```bash\ncd proxy\nnpm install\nnpm run dev\n```\n\nSee [proxy/README.md](proxy/README.md) for deployment, allowlist, and security details.\n\n## Development\n\n### Watch mode\n\nStart the extension, linked-package watchers, and site dev server:\n\n```bash\n./dev.sh\n```\n\nExtension-only watcher:\n\n```bash\nnpm run dev\n```\n\n### Checks and tests\n\nPrimary repo check:\n\n```bash\n./check.sh\n```\n\nThat runs formatting, typechecking, unit tests, integration tests, and the site checks.\n\nAdditional test entry points:\n\n```bash\nnpm run test\nnpm run test:unit\nnpm run test:integration\nnpm run test:component\nnpm run test:e2e\nnpm run test:e2e:extension\nnpm run test:e2e:site\nnpm run test:coverage\n```\n\n### Development rules that matter in practice\n\n- after extension UI/runtime changes, rebuild with `npm run build` so `dist-chrome/` is current\n- after CLI bridge changes, rebuild with `npm run build:cli`\n- linked `../mini-lit` or `../pi-mono` changes must be rebuilt in those repos before rebuilding Shuvgeist\n- preserve the repository's intentional dependency forms and lockstep pins described in [docs/dependencies.md](docs/dependencies.md)\n- the bridge is managed automatically; do not run it as a separate ad-hoc shell process\n\n## Project layout\n\n```text\npackages/\n  protocol/                  shared wire schemas, commands, targets, and version contract\n  driver/                    target-neutral PageDriver and injected driver artifacts\n  extension/                 Chrome extension UI, background, offscreen runtime, and adapters\n  server/                    local bridge server, Electron, MCP, and Node configuration\n  cli/                       public CLI, discovery, autostart, and direct-CDP runtime\nscripts/                     root boundary checks and cross-package artifact generation\nstatic/\n  manifest.chrome.json       extension manifest and version source\nsite/\n  src/frontend/              marketing site and install page\nproxy/\n  src/                       self-hosted CORS proxy\nprovider-presets/\n  proxx.json                 importable custom provider preset\nskills/\n  shuvgeist/                 coding-agent skill for the CLI bridge\ntests/\n  unit/ integration/ component/ e2e/\n```\n\n## Website\n\nThe static site lives in `site/`.\n\nLocal dev:\n\n```bash\ncd site\n./run.sh dev\n```\n\nBuild:\n\n```bash\ncd site\n./run.sh build\n```\n\nDeploy:\n\n```bash\ncd site\nSERVER_DIR=/path/on/server ./run.sh deploy\n```\n\nThe deploy script uses SSH/rsync to `slayer.marioslab.io` by default.\n\n## Release process\n\nThe core release version is driven by `static/manifest.chrome.json` and kept in parity with the private root plus the five packages under `packages/`. The `site/` and `proxy/` workspaces retain independent package versions and nested lockfiles.\n\nTo cut a release:\n\n1. Add notes under `## [Unreleased]` in [CHANGELOG.md](CHANGELOG.md).\n2. Make sure the worktree is clean.\n3. Run one of:\n\n```bash\n./release.sh patch\n./release.sh minor\n./release.sh major\n```\n\nThe script bumps versions, updates the changelog, runs checks, commits, tags, and pushes. GitHub Actions then builds the extension zip and publishes the GitHub release.\n\n## License\n\nAGPL-3.0. See [LICENSE](LICENSE).\n"}