dotbrave 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. dotbrave-0.1.0/.github/workflows/ci.yml +32 -0
  2. dotbrave-0.1.0/.github/workflows/release.yml +50 -0
  3. dotbrave-0.1.0/.gitignore +22 -0
  4. dotbrave-0.1.0/CLAUDE.md +121 -0
  5. dotbrave-0.1.0/LICENSE +21 -0
  6. dotbrave-0.1.0/PKG-INFO +277 -0
  7. dotbrave-0.1.0/README.md +252 -0
  8. dotbrave-0.1.0/docs/img/minimal-brave.png +0 -0
  9. dotbrave-0.1.0/examples/all.toml +79 -0
  10. dotbrave-0.1.0/examples/pwa.toml +21 -0
  11. dotbrave-0.1.0/examples/settings.toml +56 -0
  12. dotbrave-0.1.0/examples/shortcuts.toml +22 -0
  13. dotbrave-0.1.0/flake.nix +57 -0
  14. dotbrave-0.1.0/pyproject.toml +41 -0
  15. dotbrave-0.1.0/scripts/generate_brave_command_ids.py +79 -0
  16. dotbrave-0.1.0/src/dotbrave/__init__.py +1 -0
  17. dotbrave-0.1.0/src/dotbrave/__main__.py +4 -0
  18. dotbrave-0.1.0/src/dotbrave/_base/__init__.py +0 -0
  19. dotbrave-0.1.0/src/dotbrave/_base/cdp.py +286 -0
  20. dotbrave-0.1.0/src/dotbrave/_base/live_apply.py +98 -0
  21. dotbrave-0.1.0/src/dotbrave/_base/orchestrator.py +700 -0
  22. dotbrave-0.1.0/src/dotbrave/_base/process.py +344 -0
  23. dotbrave-0.1.0/src/dotbrave/_base/pwa.py +530 -0
  24. dotbrave-0.1.0/src/dotbrave/_base/settings.py +431 -0
  25. dotbrave-0.1.0/src/dotbrave/_base/utils.py +67 -0
  26. dotbrave-0.1.0/src/dotbrave/browser.py +355 -0
  27. dotbrave-0.1.0/src/dotbrave/cli.py +65 -0
  28. dotbrave-0.1.0/src/dotbrave/command_ids.py +549 -0
  29. dotbrave-0.1.0/src/dotbrave/live.py +273 -0
  30. dotbrave-0.1.0/src/dotbrave/pwa.py +131 -0
  31. dotbrave-0.1.0/src/dotbrave/settings.py +42 -0
  32. dotbrave-0.1.0/src/dotbrave/shortcuts.py +289 -0
  33. dotbrave-0.1.0/src/dotbrave/utils.py +103 -0
  34. dotbrave-0.1.0/tests/__init__.py +0 -0
  35. dotbrave-0.1.0/tests/conftest.py +79 -0
  36. dotbrave-0.1.0/tests/test_apply_live.py +186 -0
  37. dotbrave-0.1.0/tests/test_brave_channel.py +94 -0
  38. dotbrave-0.1.0/tests/test_brave_live.py +173 -0
  39. dotbrave-0.1.0/tests/test_cdp.py +47 -0
  40. dotbrave-0.1.0/tests/test_error_messages.py +146 -0
  41. dotbrave-0.1.0/tests/test_export.py +215 -0
  42. dotbrave-0.1.0/tests/test_help.py +84 -0
  43. dotbrave-0.1.0/tests/test_init.py +75 -0
  44. dotbrave-0.1.0/tests/test_live_apply.py +356 -0
  45. dotbrave-0.1.0/tests/test_logic.py +220 -0
  46. dotbrave-0.1.0/tests/test_macos_pwa_daemon.py +178 -0
  47. dotbrave-0.1.0/tests/test_platform.py +657 -0
  48. dotbrave-0.1.0/tests/test_pwa_apply.py +469 -0
  49. dotbrave-0.1.0/tests/test_restore.py +222 -0
  50. dotbrave-0.1.0/tests/test_settings_apply.py +380 -0
  51. dotbrave-0.1.0/tests/test_smoke.py +79 -0
  52. dotbrave-0.1.0/tests/test_unified_apply.py +427 -0
  53. dotbrave-0.1.0/tests/test_url_config.py +139 -0
@@ -0,0 +1,32 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: pytest (${{ matrix.os }} / py${{ matrix.python-version }})
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest, windows-latest]
16
+ python-version: ["3.11", "3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+ cache: pip
25
+
26
+ - name: Install package + test extras
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install -e ".[test]"
30
+
31
+ - name: Run pytest
32
+ run: pytest -q
@@ -0,0 +1,50 @@
1
+ name: Publish to PyPI
2
+
3
+ # Triggered when you push a tag like `v0.2.0`. Workflow:
4
+ # 1. tag locally → git tag v0.2.0
5
+ # 2. push the tag → git push origin v0.2.0
6
+ # 3. this workflow runs → builds sdist + wheel, validates, uploads
7
+ #
8
+ # Setup needed once before the first release:
9
+ # - Generate an API token at https://pypi.org/manage/account/token/
10
+ # (scope it to the dotbrave project after the first upload).
11
+ # - Add it as a GitHub Actions secret named `PYPI_API_TOKEN`
12
+ # (Settings → Secrets and variables → Actions → New repository secret).
13
+ #
14
+ # After dotbrave is published once, prefer migrating to PyPI Trusted
15
+ # Publishing (OIDC, no shared secret) — see https://docs.pypi.org/trusted-publishers/
16
+
17
+ on:
18
+ push:
19
+ tags: ["v*"]
20
+ workflow_dispatch:
21
+
22
+ jobs:
23
+ build-and-publish:
24
+ name: build + upload
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+
29
+ - name: Set up Python
30
+ uses: actions/setup-python@v5
31
+ with:
32
+ python-version: "3.12"
33
+ cache: pip
34
+
35
+ - name: Install build tooling
36
+ run: |
37
+ python -m pip install --upgrade pip
38
+ pip install build twine
39
+
40
+ - name: Build sdist + wheel
41
+ run: python -m build
42
+
43
+ - name: Validate metadata
44
+ run: twine check dist/*
45
+
46
+ - name: Upload to PyPI
47
+ env:
48
+ TWINE_USERNAME: __token__
49
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
50
+ run: twine upload dist/*
@@ -0,0 +1,22 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ .env
11
+ .mypy_cache/
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .tox/
15
+ htmlcov/
16
+ .coverage
17
+ *.bak.*
18
+ .worktrees/
19
+
20
+ # nix
21
+ result
22
+ result-*
@@ -0,0 +1,121 @@
1
+ # CLAUDE.md
2
+
3
+ Guidance for agents changing this repository. User-facing setup, examples,
4
+ and CLI reference belong in `README.md` and runtime `--help`; keep this file
5
+ focused on implementation constraints.
6
+
7
+ ## Project
8
+
9
+ `dotbrave` manages Brave browser customizations from TOML files: managed
10
+ tables `[shortcuts]`, `[settings]`, `[pwa]`, applied live when possible with
11
+ an automatic offline fallback, on Linux/macOS/Windows across the stable,
12
+ beta, and nightly channels. It is a Python 3.11+ stdlib-only CLI package,
13
+ extracted from the multi-browser `xom11/dotbrowser` project. The CLI is
14
+ single-browser: `dotbrave apply`, not `dotbrave brave apply`.
15
+
16
+ ## Commands
17
+
18
+ Run from the repository root:
19
+
20
+ ```bash
21
+ pip install -e ".[test]"
22
+ PYTHONPATH=src python -m dotbrave --help
23
+ PYTHONPATH=src python -m dotbrave apply --help
24
+ pytest -q
25
+
26
+ # Regenerate the command-name mapping from upstream brave-core headers.
27
+ # Requires an authenticated `gh` CLI.
28
+ python scripts/generate_brave_command_ids.py
29
+ ```
30
+
31
+ Useful targeted suites:
32
+
33
+ ```bash
34
+ pytest tests/test_help.py tests/test_smoke.py
35
+ pytest tests/test_unified_apply.py tests/test_settings_apply.py tests/test_pwa_apply.py
36
+ pytest tests/test_live_apply.py tests/test_brave_live.py tests/test_apply_live.py
37
+ pytest tests/test_export.py tests/test_restore.py tests/test_brave_channel.py
38
+ pytest tests/test_platform.py tests/test_macos_pwa_daemon.py
39
+ ```
40
+
41
+ ## Code Map
42
+
43
+ - `src/dotbrave/cli.py`: root parser; mounts actions via
44
+ `browser.register(parser)`.
45
+ - `src/dotbrave/browser.py`: channel/profile-root resolution, plan
46
+ assembly, init template, `cmd_*` handlers, registration.
47
+ - `src/dotbrave/shortcuts.py`, `settings.py`, `pwa.py`: namespace wrappers
48
+ (module-level state kept patchable for tests).
49
+ - `src/dotbrave/live.py`: Brave live-apply routes (settingsPrivate, New
50
+ Tab actions, CommandsService).
51
+ - `src/dotbrave/utils.py`: Brave `BrowserProcess` config per channel.
52
+ - `src/dotbrave/command_ids.py`: generated name<->id mapping.
53
+ - `src/dotbrave/_base/`: engine shared with upstream dotbrowser:
54
+ `orchestrator.py` (config loading, unified apply/init/export/restore,
55
+ argparse wiring), `utils.py` (`Plan`, atomic write), `settings.py`
56
+ (dotted keys + MAC refusal), `pwa.py` (policy storage + macOS daemon),
57
+ `process.py`, `cdp.py`, `live_apply.py`.
58
+ - `examples/*.toml`: valid user-facing config samples.
59
+ - `tests/`: behavior contracts. Add or update tests alongside behavior or
60
+ CLI-help changes.
61
+
62
+ ## Invariants
63
+
64
+ Preserve these contracts unless a change explicitly redesigns them:
65
+
66
+ 1. `apply` uses module `Plan` objects and one orchestrated cycle. Validate
67
+ all selected namespaces before committing profile changes; create at most
68
+ one Preferences backup per offline apply.
69
+ 2. Missing TOML table means "skip this namespace"; an empty table means
70
+ "remove/reset entries previously managed by dotbrave".
71
+ 3. `[settings]` must refuse MAC-protected keys found in either `Preferences`
72
+ or sibling `Secure Preferences`. Never make a write that Brave will
73
+ silently reset on launch.
74
+ 4. `[pwa]` is external managed policy storage. It requires sudo on
75
+ Linux/macOS or Administrator on Windows when changed, writes before the
76
+ Preferences commit, and has no Preferences sidecar.
77
+ On macOS the policy file is kept alive by a root-owned self-healing
78
+ LaunchDaemon (`org.dotbrave.<bundle>.pwa`) installed during the same
79
+ privileged write; an empty `[pwa]` table removes the daemon and its
80
+ support files. Keep `build_heal_script`/`build_launchd_plist` pure and
81
+ `install_self_healing_daemon`/`remove_self_healing_daemon` patchable.
82
+ 5. Plain `apply` manages live apply. Endpoints bind to `127.0.0.1` and
83
+ remain internal; no public endpoint or force-kill switch is exposed.
84
+ Unsupported live settings and removals fall back to a normal close,
85
+ verified offline apply, and relaunch. A diff whose only changes are
86
+ `[pwa]` never touches the running browser: the policy is written
87
+ directly (no endpoint bootstrap) and Brave loads it at next launch.
88
+ 6. `export` intentionally omits `[settings]` (Chromium has no defaults
89
+ table for arbitrary prefs). It emits `[shortcuts]` diffs against
90
+ `brave.default_accelerators` plus `[pwa]`.
91
+ 7. `restore` restores Preferences backups and clears shortcut/settings
92
+ sidecars. If Brave is running, it closes normally and restarts; it does
93
+ not roll back external `[pwa]` policy.
94
+ 8. Profile flags (`--channel`, `-r`, `-p`) are accepted both before and
95
+ after the action name: real defaults live on the root parser; action
96
+ parsers re-declare them with `argparse.SUPPRESS` so the after-action
97
+ form overrides. Profile-reading leaves set `_needs_profile` so
98
+ `_normalize_brave_args` skips profile-root resolution for `init` and
99
+ `shortcuts list`. Keep new action parsers consistent with this scheme.
100
+ 9. Runtime help is part of the capability contract. Do not reintroduce
101
+ manual endpoint-selection or force-kill controls.
102
+
103
+ ## Browser Notes
104
+
105
+ - Shortcut values use Chromium KeyEvent-style bindings. `Meta+` and
106
+ `Command+` are normalized per platform before persistence.
107
+ - `--channel` changes both profile discovery and process handling.
108
+ Non-stable Linux channels require PID filtering so applying Beta/Nightly
109
+ does not close another Brave channel.
110
+ - Keep shared engine logic in `_base/` (it mirrors upstream dotbrowser's
111
+ `_base/`, which eases porting fixes across the two repos); Brave-specific
112
+ behavior belongs in the top-level modules.
113
+ - Preserve testability: policy paths, privilege writers, and process
114
+ callbacks are intentionally patchable in tests.
115
+
116
+ ## Release
117
+
118
+ `src/dotbrave/__init__.py::__version__` is the version source of truth;
119
+ `pyproject.toml` reads it through Hatch. Releases are tag-driven through
120
+ `.github/workflows/release.yml` after tests pass (needs the
121
+ `PYPI_API_TOKEN` repo secret).
dotbrave-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xom11
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,277 @@
1
+ Metadata-Version: 2.4
2
+ Name: dotbrave
3
+ Version: 0.1.0
4
+ Summary: Manage Brave as a dotfile: keyboard shortcuts, general settings (non-MAC keys), and force-installed PWAs from one TOML.
5
+ Project-URL: Homepage, https://github.com/xom11/dotbrave
6
+ Project-URL: Issues, https://github.com/xom11/dotbrave/issues
7
+ Author: xom11
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: brave,browser,config,dotfiles,keybindings,pwa,settings,shortcuts
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: MacOS :: MacOS X
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.11
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest>=7; extra == 'test'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # dotbrave
27
+
28
+ [![CI](https://github.com/xom11/dotbrave/actions/workflows/ci.yml/badge.svg)](https://github.com/xom11/dotbrave/actions/workflows/ci.yml)
29
+ [![PyPI](https://img.shields.io/pypi/v/dotbrave.svg)](https://pypi.org/project/dotbrave/)
30
+ [![Python](https://img.shields.io/pypi/pyversions/dotbrave.svg)](https://pypi.org/project/dotbrave/)
31
+
32
+ Manage Brave as a dotfile. Keep keyboard shortcuts, UI tweaks, and
33
+ force-installed web apps in a single TOML, apply with one command, sync
34
+ across machines — no Brave Sync required.
35
+
36
+ > **Status: alpha.** Works on Linux, macOS, and Windows against Brave
37
+ > stable, beta, and nightly. Extracted from the multi-browser
38
+ > [dotbrowser](https://github.com/xom11/dotbrowser) project as a focused,
39
+ > Brave-only tool with a flatter CLI (`dotbrave apply` instead of
40
+ > `dotbrowser brave apply`). Python 3.11+, standard library only.
41
+
42
+ ## Quick start
43
+
44
+ The repo ships an opinionated example config: vertical tabs collapsed to
45
+ icons, decluttered new tab page, stripped-down toolbar, vim-style hjkl
46
+ shortcuts. [`examples/all.toml`](examples/all.toml) bundles all three
47
+ namespaces; [`shortcuts.toml`](examples/shortcuts.toml),
48
+ [`settings.toml`](examples/settings.toml), and
49
+ [`pwa.toml`](examples/pwa.toml) are single-namespace variants.
50
+
51
+ ![Brave with the minimal config — empty new tab page, vertical tabs collapsed to icons, decluttered toolbar](docs/img/minimal-brave.png)
52
+
53
+ **Scaffold a starter config from scratch:**
54
+
55
+ ```bash
56
+ dotbrave init # write commented template to stdout
57
+ dotbrave init -o brave.toml # ...or to a file
58
+ ```
59
+
60
+ **Or apply the example directly from GitHub** — no clone, no install.
61
+ Fetched payloads are echoed with byte size + SHA-256 so you can see exactly
62
+ what's being applied:
63
+
64
+ ```bash
65
+ uvx dotbrave apply --dry-run \
66
+ https://raw.githubusercontent.com/xom11/dotbrave/main/examples/all.toml
67
+
68
+ # Apply. If Brave is already running, dotbrave uses live apply;
69
+ # first-time live setup closes Brave normally and relaunches it once.
70
+ uvx dotbrave apply \
71
+ https://raw.githubusercontent.com/xom11/dotbrave/main/examples/all.toml
72
+ ```
73
+
74
+ Prefer to inspect / customise locally first? Download then apply:
75
+
76
+ ```bash
77
+ curl -fsSL -o brave.toml https://raw.githubusercontent.com/xom11/dotbrave/main/examples/all.toml
78
+ # edit brave.toml ...
79
+ uvx dotbrave apply brave.toml
80
+ ```
81
+
82
+ Anything you later remove from your config reverts to Brave's default on
83
+ the next `apply` — no orphan entries.
84
+
85
+ ## Install
86
+
87
+ ```bash
88
+ pipx install dotbrave # global, isolated venv
89
+ uvx dotbrave <args> # run on demand, no install step
90
+ pip install dotbrave # into the active environment
91
+ ```
92
+
93
+ Run from a branch: `uvx --from git+https://github.com/xom11/dotbrave dotbrave <args>`.
94
+ Local dev: `pip install -e ".[test]"`. Nix users: the repo ships a flake
95
+ (`nix run github:xom11/dotbrave`).
96
+
97
+ ## Build your own config
98
+
99
+ A single TOML carries `[shortcuts]`, `[settings]` and `[pwa]`. One `apply`
100
+ writes all three in a single backup + write cycle.
101
+
102
+ ```toml
103
+ # brave.toml
104
+ [shortcuts]
105
+ toggle_sidebar = ["Control+Shift+KeyE"]
106
+ toggle_ai_chat = ["Alt+KeyA"]
107
+
108
+ # vim-style hjkl
109
+ back = ["Alt+KeyH"]
110
+ forward = ["Alt+KeyL"]
111
+ select_previous_tab = ["Alt+KeyJ"]
112
+ select_next_tab = ["Alt+KeyK"]
113
+
114
+ # Same chord on all OSes — Meta+ = Cmd on macOS, Super on Linux/Windows (auto-translated)
115
+ new_tab = ["Control+KeyT", "Meta+KeyT"]
116
+ close_tab = ["Control+KeyW", "Meta+KeyW"]
117
+
118
+ [settings]
119
+ "brave.tabs.vertical_tabs_enabled" = true
120
+ "brave.tabs.vertical_tabs_collapsed" = true
121
+ "bookmark_bar.show_on_all_tabs" = false
122
+
123
+ [pwa]
124
+ # Force-installed Progressive Web Apps. Brave fetches each manifest,
125
+ # downloads icons, registers the app in chrome://apps, and emits a
126
+ # launcher (.desktop on Linux, app shim on macOS, Start Menu shortcut on
127
+ # Windows). Removing a URL + re-applying = uninstall.
128
+ urls = [
129
+ "https://squoosh.app/",
130
+ "https://app.element.io/",
131
+ ]
132
+ ```
133
+
134
+ ```bash
135
+ dotbrave apply brave.toml --dry-run # preview the diff
136
+ dotbrave apply brave.toml # live apply if Brave is running
137
+ ```
138
+
139
+ - **Shortcut keys**: Chromium [KeyEvent codes](https://www.w3.org/TR/uievents-code/)
140
+ joined by `+` — `Control+Shift+KeyP`, `Alt+Digit1`, `F11`. `Meta+` is
141
+ auto-translated to `Command+` on macOS, so one config works everywhere.
142
+ - **Setting keys**: dotted paths into the profile `Preferences` JSON.
143
+ MAC-protected keys (`homepage`, default search engine, `pinned_tabs`, …)
144
+ are refused with a clear error — Brave would silently reset them on
145
+ launch. Run `dotbrave settings blocked` to see what's protected.
146
+ - **PWA URLs**: every entry installs with
147
+ `default_launch_container = "window"` and `create_desktop_shortcut = true`.
148
+ `[pwa]` is the only namespace that needs elevated privileges: it writes a
149
+ managed-policy file (sudo on Linux/macOS) or the Windows Registry
150
+ (Administrator). No `[pwa]` diff → no elevation prompt.
151
+ - **Empty header** (e.g. `[settings]` with no entries) wipes everything
152
+ dotbrave previously managed in that namespace. **Missing header** = skip
153
+ the namespace entirely.
154
+
155
+ ## CLI reference
156
+
157
+ Shape: `dotbrave [profile-flags] <action> [action-flags] [args]`.
158
+ Profile flags may be given **before or after** the action name — the
159
+ after-action form wins when both are present. `export` is the inverse of
160
+ `apply`: produce a round-trippable TOML from the current profile state.
161
+
162
+ ### Profile flags
163
+
164
+ | Flag | Default | What it does |
165
+ |---|---|---|
166
+ | `-r, --profile-root PATH` | auto-detected | Brave's root profile directory. |
167
+ | `-p, --profile NAME` | `Default` | Profile directory name inside the root — e.g. `"Profile 1"`. |
168
+ | `--channel {stable,beta,nightly}` | `stable` | Release channel; auto-detects the `Brave-Browser-Beta` / `-Nightly` profile path and targets the matching process. |
169
+
170
+ | Channel | Linux | macOS | Windows |
171
+ |---|---|---|---|
172
+ | stable | `~/.config/BraveSoftware/Brave-Browser` | `~/Library/Application Support/BraveSoftware/Brave-Browser` | `%LOCALAPPDATA%\BraveSoftware\Brave-Browser\User Data` |
173
+ | beta / nightly | same, with `-Beta` / `-Nightly` suffix | same, with suffix | same, with suffix |
174
+
175
+ Snap and Flatpak installs (stable only) are probed automatically on Linux.
176
+
177
+ ```bash
178
+ dotbrave apply -r /custom/path -p "Profile 1" brave.toml
179
+ dotbrave apply --channel beta brave.toml
180
+ ```
181
+
182
+ ### Actions
183
+
184
+ | Action | What it does |
185
+ |---|---|
186
+ | `init [-o FILE]` | Scaffold a commented starter TOML. Refuses to overwrite. |
187
+ | `apply [-n] CONFIG` | Apply `[shortcuts]` + `[settings]` + `[pwa]` from a file or HTTPS URL. `-n/--dry-run` previews the diff. URL fetches print size + SHA-256; pin with `--expect-sha256 HEX`; plain HTTP refused unless `--allow-http`. |
188
+ | `export [-o FILE] [-a]` | Emit `[shortcuts]` (only bindings that differ from Brave defaults; `-a/--all-shortcuts` lifts the filter) plus `[pwa]` as round-trippable TOML. `[settings]` is intentionally omitted — Chromium exposes no defaults table, so diff-vs-default is not computable. |
189
+ | `restore [--list] [--from FILE] [-n]` | Restore Preferences from a backup created by `apply` (most recent by default) and clear dotbrave sidecars. Does not touch `[pwa]` policy. |
190
+ | `shortcuts dump [-a] [-o FILE]` | Emit current bindings as TOML (default: only user-customised ones). |
191
+ | `shortcuts list [FILTER]` | List every bindable command name (substring filter). |
192
+ | `settings dump [KEYS...] [-o FILE]` | Dump managed keys, or explicit dotted paths. |
193
+ | `settings blocked [-o FILE]` | List MAC-protected keys `apply` will refuse, with current values. |
194
+ | `pwa dump [-o FILE]` | Emit currently force-installed PWA URLs as a `[pwa]` table. |
195
+
196
+ Every action has detailed `--help` with safety notes and examples.
197
+
198
+ ## How it works
199
+
200
+ When Brave is closed, `dotbrave` patches the profile `Preferences` JSON
201
+ directly. Each offline apply takes one timestamped backup, writes
202
+ atomically (temp file + rename), and verifies the result by reloading.
203
+
204
+ When Brave is running, plain `apply` uses Brave's privileged UI APIs over
205
+ a private loopback DevTools endpoint: ordinary settings go through
206
+ `chrome.settingsPrivate`, New Tab settings through live New Tab UI
207
+ actions, and shortcuts through the Settings `CommandsService`. Supported
208
+ changes take effect without restarting. A Brave not yet carrying the
209
+ endpoint closes normally and relaunches once; a setting without a live
210
+ route falls back to the same normal-close + verified offline write. A
211
+ config whose only diff is `[pwa]` skips the endpoint entirely — the
212
+ managed policy is written while Brave keeps running, and Brave loads it
213
+ at its next launch. The endpoint binds to `127.0.0.1` only, and there is
214
+ no force-kill switch.
215
+
216
+ `[shortcuts]` and `[settings]` track managed entries in sidecar files
217
+ (`Preferences.dotbrave.{shortcuts,settings}.json`), so removing a key from
218
+ your config restores Brave's default on the next apply. `[pwa]` state
219
+ lives in Chromium's managed-policy storage (Linux JSON file, macOS plist,
220
+ Windows Registry) — the policy *is* the state.
221
+
222
+ On macOS, `/Library/Managed Preferences/` is a system-managed cache that
223
+ gets reclaimed at boot on non-MDM machines, which would otherwise
224
+ uninstall your PWAs. To keep them installed, `apply` installs a small
225
+ root-owned self-healing helper:
226
+
227
+ - `/Library/LaunchDaemons/org.dotbrave.com.brave.Browser.pwa.plist` —
228
+ watches the managed-preferences directory and rewrites the policy if
229
+ macOS removes it.
230
+ - `/Library/Application Support/dotbrave/com.brave.Browser.managed.plist`
231
+ and `com.brave.Browser.heal.sh` — the policy source of truth and the
232
+ rewrite script.
233
+
234
+ Applying an empty `[pwa]` table (`urls = []`) removes all of the above.
235
+
236
+ ### Brave install methods
237
+
238
+ | Install | Auto-detected | `[pwa]` works | Notes |
239
+ |---|---|---|---|
240
+ | `.deb` / `.rpm` / Arch / NixOS | yes | yes | Reference install; full support. |
241
+ | **Snap** | yes | **refused with clear error** — sandbox doesn't read `/etc/brave/policies/managed/` | Use `.deb` for `[pwa]`. |
242
+ | **Flatpak** | yes | **refused with clear error** — same sandbox limitation | Relaunch goes back through `flatpak run`. |
243
+ | **macOS** `.dmg` | yes | yes | Includes cfprefsd cache invalidation + self-healing daemon. |
244
+ | **Windows** installer | yes | yes — writes `HKLM\Software\Policies\BraveSoftware\Brave`; requires Administrator | |
245
+
246
+ ## Coming from dotbrowser?
247
+
248
+ `dotbrave` is a standalone extraction of dotbrowser's Brave support with
249
+ its own state names: sidecars are `Preferences.dotbrave.*.json` (was
250
+ `Preferences.dotbrowser.*.json`) and the macOS daemon is
251
+ `org.dotbrave.<bundle>.pwa` (was `org.dotbrowser.<bundle>.pwa`). Your
252
+ Brave profile itself is untouched by the switch, but entries previously
253
+ managed by dotbrowser are not tracked by dotbrave until you `apply` your
254
+ config once with dotbrave. On macOS, remove dotbrowser's daemon
255
+ (`dotbrowser brave apply` with an empty `[pwa]` table, or the manual
256
+ `launchctl bootout` steps in its README) before letting dotbrave manage
257
+ `[pwa]`, so two daemons don't fight over the same plist.
258
+
259
+ ## Caveats
260
+
261
+ - **Brave Sync** can overwrite `[settings]` entries on its next pulse if
262
+ they fall in a synced category. UI-layout keys like
263
+ `brave.tabs.vertical_tabs_*` are local-only and immune. `apply` prints a
264
+ non-fatal warning when `sync.has_setup_completed=true`.
265
+ - A handful of settings (`homepage`, default search engine, `pinned_tabs`,
266
+ …) are integrity-protected and refused rather than silently reset by
267
+ Brave on next launch. Set those in the Brave UI.
268
+ - **`[pwa]` is force-install** (Chromium's enterprise
269
+ `WebAppInstallForceList`). Apps appear in `chrome://apps` with an
270
+ "Installed by your administrator" label and hidden right-click Remove —
271
+ uninstall by deleting the URL from `[pwa]` and re-applying. That is the
272
+ right semantics for dotfile-style management (the TOML is the source of
273
+ truth), but worth knowing if you also install PWAs by hand.
274
+
275
+ ## License
276
+
277
+ MIT