braveprefs 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.
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ .venv/
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release.
6
+
7
+ - `export` reads an allowlist of named settings from a browser profile and writes one JSON file.
8
+ - `diff` compares two exported files.
9
+ - `import` writes the restorable settings back, dry run by default, with a timestamped copy of the
10
+ original file and a check for a singleton lock.
11
+ - Settings the browser protects with an authenticator are exported read only and never written.
12
+ - No dependencies outside the Python standard library.
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Younes Z.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this Software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.5
2
+ Name: braveprefs
3
+ Version: 0.1.0
4
+ Summary: Export, compare and restore browser profile settings as one small readable JSON file.
5
+ Project-URL: Homepage, https://github.com/Rezarys/braveprefs
6
+ Project-URL: Source, https://github.com/Rezarys/braveprefs
7
+ Author: Younes Z.
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: backup,browser,chromium,cli,configuration,dotfiles,migration,preferences,profile,restore,settings
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+
20
+ # braveprefs
21
+
22
+ ```
23
+ pip install braveprefs
24
+ ```
25
+
26
+ Put your browser settings in one short JSON file, compare that file between two machines, and write it back on a fresh install. No sync account, no QR code, no network.
27
+
28
+ This project is not affiliated with Brave Software or with any browser publisher. It reads and writes files on your own disk and nothing else.
29
+
30
+ ## Why
31
+
32
+ Today the way to move your settings to a new machine is to copy the whole profile directory, several gigabytes of caches and databases, to recover a few kilobytes of choices. The rest of that directory does not restore cleanly anyway. `braveprefs` takes the few kilobytes.
33
+
34
+ The output is a file you can read, keep in a git repository, and diff.
35
+
36
+ ## Use
37
+
38
+ Find your profile directory first (see below), then:
39
+
40
+ ```
41
+ braveprefs export "<profile directory>" -o settings.json
42
+ braveprefs diff settings.json other-machine.json
43
+ braveprefs import "<profile directory>" settings.json
44
+ ```
45
+
46
+ `import` is a dry run by default: it prints what it would change and writes nothing. Add `--apply` to write, with the browser closed:
47
+
48
+ ```
49
+ braveprefs import "<profile directory>" settings.json --apply
50
+ ```
51
+
52
+ Before writing, it copies the original `Preferences` file next to itself with a timestamp, so you can always put it back. If a `SingletonLock` file is present, the browser may still be running and the command stops; close the browser, or pass `--force` if you know the lock is stale.
53
+
54
+ `diff` exits with status 1 when the two files differ and 0 when they match, so it fits in a script.
55
+
56
+ ## What never leaves your profile
57
+
58
+ The list of settings this tool touches is a list of named keys, written out one by one in `src/braveprefs/keys.py`. It is not a name pattern. A key that is not named there is never read and never written. Concretely, none of this is ever in the exported file:
59
+
60
+ - passwords, cookies, tokens, session state
61
+ - browsing history, downloads history, visited sites
62
+ - wallet data, payment methods, or any encrypted store
63
+ - your extensions and their data, except the id of your current theme, which is technically an extension in this browser
64
+ - account identifiers and anything the browser uses to sign you in
65
+
66
+ The exported file is plain text on purpose. Open it and read it.
67
+
68
+ Two settings in the list are folder paths, the download folder and the save as folder, and a folder path can contain your account name. When either is present the exported file says so in its `notes` field. Read the file before you share it. A folder path that exists on one machine may not exist on another, especially across operating systems; import writes it anyway, so check it still makes sense before you apply it.
69
+
70
+ ## Settings the browser protects, and what this tool does about them
71
+
72
+ Some settings are not stored like the others. Upstream Chromium splits the profile in two: `Preferences`, and a second file `Secure Preferences` for a fixed set of tracked settings, with a validation authenticator recorded for each of them under `protection.macs`. Two things follow. Writing one of those settings into the plain file has no effect, because the browser reads it from the other file. Writing it into the protected file without a matching authenticator is undone at the next start, on the platforms where upstream turns enforcement on, which are Windows and macOS by default. Either way an outside tool cannot put those values back and make them stay, and it would look like it had worked.
73
+
74
+ So this tool does not write them. It exports them under a separate `read_only` key in the file, and `export` prints them by name, so you can still read them, compare two machines, and set them yourself in the settings screen. The settings in that group are the home button and home page, what opens on startup, pinned tabs, and the default search engine.
75
+
76
+ On top of that fixed list, `export` and `import` also look at your actual profile: any covered setting that sits in `Secure Preferences`, or that has an authenticator recorded for it, is treated as read only for that profile even if the fixed list does not mention it. A file edited by hand cannot get around this either, because the list of names is checked again on the way in.
77
+
78
+ This is the honest shape of the problem rather than a workaround. A tool that says what it cannot do stays useful. A tool whose writes quietly disappear does not.
79
+
80
+ ## Which settings are covered
81
+
82
+ Roughly sixty named keys, grouped as:
83
+
84
+ - appearance and layout: bookmarks bar, side panel, toolbar buttons, dark mode, tab behaviour
85
+ - new tab page: clock, stats row, what the page shows
86
+ - fonts and text: font families, default and minimum sizes, caret browsing
87
+ - languages and spelling: accepted languages, dictionaries, translation preferences
88
+ - downloads and printing: download folder, ask where to save, default printer
89
+ - privacy and autofill: do not track, suggestions, preloading, address and payment autofill
90
+ - shields: your own filter rules, filter list subscriptions, embed and language settings
91
+ - startup and search: read only, see the section above
92
+
93
+ The file `src/braveprefs/keys.py` is the list, with a one line label for each key. Every key in it has its own test.
94
+
95
+ ## Finding your profile directory
96
+
97
+ Open the browser's version page (`brave://version` in that browser, `chrome://version` in others) and copy the value next to **Profile Path**. That is the directory to pass to `braveprefs`. It is the directory that contains a file named `Preferences`, not the directory above it.
98
+
99
+ On Windows it sits under `C:\Users\<you>\AppData\Local\BraveSoftware\Brave-Browser`. On other systems the version page is the reliable answer.
100
+
101
+ ## What has not been tested
102
+
103
+ No real browser profile was read while building this. The key names and the protection mechanism come from the upstream Chromium source, and the test suite runs on example files written for the tests. If you run it on a real profile, whether it works or not, please open an issue and say what happened. That is the one thing I cannot check from here.
104
+
105
+ ## Where this came from
106
+
107
+ The request thread: https://github.com/brave/brave-browser/issues/46192
108
+
109
+ There are other tools in this area that apply a hardened set of settings chosen by their author. This one does the opposite: it moves *your* settings, whatever they are, and decides nothing for you.
110
+
111
+ ## Notes
112
+
113
+ Built with AI assistance, reviewed and tested by me.
114
+
115
+ Requires Python 3.8 or later. No dependencies outside the standard library.
116
+
117
+ Run the tests with `python3 -m unittest discover -s tests -t tests` from a checkout, with `src` on `PYTHONPATH`.
118
+
119
+ MIT licensed. Younes Z.
@@ -0,0 +1,100 @@
1
+ # braveprefs
2
+
3
+ ```
4
+ pip install braveprefs
5
+ ```
6
+
7
+ Put your browser settings in one short JSON file, compare that file between two machines, and write it back on a fresh install. No sync account, no QR code, no network.
8
+
9
+ This project is not affiliated with Brave Software or with any browser publisher. It reads and writes files on your own disk and nothing else.
10
+
11
+ ## Why
12
+
13
+ Today the way to move your settings to a new machine is to copy the whole profile directory, several gigabytes of caches and databases, to recover a few kilobytes of choices. The rest of that directory does not restore cleanly anyway. `braveprefs` takes the few kilobytes.
14
+
15
+ The output is a file you can read, keep in a git repository, and diff.
16
+
17
+ ## Use
18
+
19
+ Find your profile directory first (see below), then:
20
+
21
+ ```
22
+ braveprefs export "<profile directory>" -o settings.json
23
+ braveprefs diff settings.json other-machine.json
24
+ braveprefs import "<profile directory>" settings.json
25
+ ```
26
+
27
+ `import` is a dry run by default: it prints what it would change and writes nothing. Add `--apply` to write, with the browser closed:
28
+
29
+ ```
30
+ braveprefs import "<profile directory>" settings.json --apply
31
+ ```
32
+
33
+ Before writing, it copies the original `Preferences` file next to itself with a timestamp, so you can always put it back. If a `SingletonLock` file is present, the browser may still be running and the command stops; close the browser, or pass `--force` if you know the lock is stale.
34
+
35
+ `diff` exits with status 1 when the two files differ and 0 when they match, so it fits in a script.
36
+
37
+ ## What never leaves your profile
38
+
39
+ The list of settings this tool touches is a list of named keys, written out one by one in `src/braveprefs/keys.py`. It is not a name pattern. A key that is not named there is never read and never written. Concretely, none of this is ever in the exported file:
40
+
41
+ - passwords, cookies, tokens, session state
42
+ - browsing history, downloads history, visited sites
43
+ - wallet data, payment methods, or any encrypted store
44
+ - your extensions and their data, except the id of your current theme, which is technically an extension in this browser
45
+ - account identifiers and anything the browser uses to sign you in
46
+
47
+ The exported file is plain text on purpose. Open it and read it.
48
+
49
+ Two settings in the list are folder paths, the download folder and the save as folder, and a folder path can contain your account name. When either is present the exported file says so in its `notes` field. Read the file before you share it. A folder path that exists on one machine may not exist on another, especially across operating systems; import writes it anyway, so check it still makes sense before you apply it.
50
+
51
+ ## Settings the browser protects, and what this tool does about them
52
+
53
+ Some settings are not stored like the others. Upstream Chromium splits the profile in two: `Preferences`, and a second file `Secure Preferences` for a fixed set of tracked settings, with a validation authenticator recorded for each of them under `protection.macs`. Two things follow. Writing one of those settings into the plain file has no effect, because the browser reads it from the other file. Writing it into the protected file without a matching authenticator is undone at the next start, on the platforms where upstream turns enforcement on, which are Windows and macOS by default. Either way an outside tool cannot put those values back and make them stay, and it would look like it had worked.
54
+
55
+ So this tool does not write them. It exports them under a separate `read_only` key in the file, and `export` prints them by name, so you can still read them, compare two machines, and set them yourself in the settings screen. The settings in that group are the home button and home page, what opens on startup, pinned tabs, and the default search engine.
56
+
57
+ On top of that fixed list, `export` and `import` also look at your actual profile: any covered setting that sits in `Secure Preferences`, or that has an authenticator recorded for it, is treated as read only for that profile even if the fixed list does not mention it. A file edited by hand cannot get around this either, because the list of names is checked again on the way in.
58
+
59
+ This is the honest shape of the problem rather than a workaround. A tool that says what it cannot do stays useful. A tool whose writes quietly disappear does not.
60
+
61
+ ## Which settings are covered
62
+
63
+ Roughly sixty named keys, grouped as:
64
+
65
+ - appearance and layout: bookmarks bar, side panel, toolbar buttons, dark mode, tab behaviour
66
+ - new tab page: clock, stats row, what the page shows
67
+ - fonts and text: font families, default and minimum sizes, caret browsing
68
+ - languages and spelling: accepted languages, dictionaries, translation preferences
69
+ - downloads and printing: download folder, ask where to save, default printer
70
+ - privacy and autofill: do not track, suggestions, preloading, address and payment autofill
71
+ - shields: your own filter rules, filter list subscriptions, embed and language settings
72
+ - startup and search: read only, see the section above
73
+
74
+ The file `src/braveprefs/keys.py` is the list, with a one line label for each key. Every key in it has its own test.
75
+
76
+ ## Finding your profile directory
77
+
78
+ Open the browser's version page (`brave://version` in that browser, `chrome://version` in others) and copy the value next to **Profile Path**. That is the directory to pass to `braveprefs`. It is the directory that contains a file named `Preferences`, not the directory above it.
79
+
80
+ On Windows it sits under `C:\Users\<you>\AppData\Local\BraveSoftware\Brave-Browser`. On other systems the version page is the reliable answer.
81
+
82
+ ## What has not been tested
83
+
84
+ No real browser profile was read while building this. The key names and the protection mechanism come from the upstream Chromium source, and the test suite runs on example files written for the tests. If you run it on a real profile, whether it works or not, please open an issue and say what happened. That is the one thing I cannot check from here.
85
+
86
+ ## Where this came from
87
+
88
+ The request thread: https://github.com/brave/brave-browser/issues/46192
89
+
90
+ There are other tools in this area that apply a hardened set of settings chosen by their author. This one does the opposite: it moves *your* settings, whatever they are, and decides nothing for you.
91
+
92
+ ## Notes
93
+
94
+ Built with AI assistance, reviewed and tested by me.
95
+
96
+ Requires Python 3.8 or later. No dependencies outside the standard library.
97
+
98
+ Run the tests with `python3 -m unittest discover -s tests -t tests` from a checkout, with `src` on `PYTHONPATH`.
99
+
100
+ MIT licensed. Younes Z.
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "braveprefs"
3
+ version = "0.1.0"
4
+ description = "Export, compare and restore browser profile settings as one small readable JSON file."
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Younes Z." }]
9
+ keywords = ["browser", "chromium", "preferences", "settings", "backup", "restore", "dotfiles", "configuration", "profile", "migration", "cli"]
10
+ classifiers = ["Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Topic :: Utilities"]
11
+ dependencies = []
12
+
13
+ [project.scripts]
14
+ braveprefs = "braveprefs.cli:main"
15
+
16
+ [project.urls]
17
+ Homepage = "https://github.com/Rezarys/braveprefs"
18
+ Source = "https://github.com/Rezarys/braveprefs"
19
+
20
+ [build-system]
21
+ requires = ["hatchling"]
22
+ build-backend = "hatchling.build"
23
+
24
+ [tool.hatch.build.targets.wheel]
25
+ packages = ["src/braveprefs"]
26
+
27
+ [tool.hatch.build.targets.sdist]
28
+ include = ["src", "tests", "README.md", "LICENSE", "CHANGELOG.md", "pyproject.toml"]
@@ -0,0 +1,5 @@
1
+ """braveprefs: export, compare and restore a named list of browser profile settings."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,143 @@
1
+ """Command line for braveprefs: export, diff, import."""
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+
7
+ from . import core
8
+ from .core import PrefsError
9
+
10
+ USAGE = "braveprefs {export|diff|import} ..."
11
+
12
+
13
+ def _render(value):
14
+ if isinstance(value, (dict, list)):
15
+ return json.dumps(value, sort_keys=True, ensure_ascii=False)
16
+ return json.dumps(value, ensure_ascii=False)
17
+
18
+
19
+ def _cmd_export(args, out):
20
+ bundle = core.export_settings(args.profile)
21
+ if args.output in (None, "-"):
22
+ json.dump(bundle, out, indent=2, sort_keys=True, ensure_ascii=False)
23
+ out.write("\n")
24
+ else:
25
+ core.write_bundle(bundle, args.output)
26
+ out.write("wrote %s\n" % args.output)
27
+ kept = len(bundle["settings"])
28
+ locked = len(bundle["read_only"])
29
+ out.write("%d setting(s) you can restore, %d read only\n" % (kept, locked))
30
+ if locked:
31
+ out.write(
32
+ "read only means the browser signs those values and undoes a change made from "
33
+ "outside it:\n"
34
+ )
35
+ for path in sorted(bundle["read_only"], key=core.group_order):
36
+ out.write(" %s (%s)\n" % (path, bundle["labels"].get(path, "")))
37
+ return 0
38
+
39
+
40
+ def _cmd_diff(args, out):
41
+ left = core.read_bundle(args.first)
42
+ right = core.read_bundle(args.second)
43
+ rows = core.diff_bundles(left, right)
44
+ if not rows:
45
+ out.write("no difference in the settings both files carry\n")
46
+ return 0
47
+ out.write("%d difference(s)\n" % len(rows))
48
+ for path, label, status, value_a, value_b in sorted(rows, key=lambda r: core.group_order(r[0])):
49
+ out.write("%s (%s)\n %s\n" % (path, label, status))
50
+ if value_a is not None or status == "different":
51
+ out.write(" first: %s\n" % _render(value_a))
52
+ if value_b is not None or status == "different":
53
+ out.write(" second: %s\n" % _render(value_b))
54
+ return 1
55
+
56
+
57
+ def _cmd_import(args, out):
58
+ bundle = core.read_bundle(args.bundle)
59
+ if args.apply:
60
+ rows, backup = core.apply_import(args.profile, bundle, force=args.force)
61
+ else:
62
+ rows, backup = core.plan_import(args.profile, bundle), None
63
+
64
+ written = 0
65
+ for path, action, current, new in sorted(rows, key=lambda r: core.group_order(r[0])):
66
+ if action == core.UNCHANGED:
67
+ continue
68
+ out.write("%s\n %s\n" % (path, action))
69
+ if action == core.SET:
70
+ written += 1
71
+ out.write(" from: %s\n" % _render(current))
72
+ out.write(" to: %s\n" % _render(new))
73
+ unchanged = sum(1 for row in rows if row[1] == core.UNCHANGED)
74
+ out.write("%d already match\n" % unchanged)
75
+ if args.apply:
76
+ out.write("wrote %d setting(s)\n" % written)
77
+ if backup:
78
+ out.write("copied the original file to %s\n" % backup)
79
+ else:
80
+ out.write("nothing to write, so no copy was made\n")
81
+ else:
82
+ out.write(
83
+ "this was a dry run and nothing was written. Close the browser and add --apply "
84
+ "to write %d setting(s).\n" % written
85
+ )
86
+ return 0
87
+
88
+
89
+ def build_parser():
90
+ parser = argparse.ArgumentParser(
91
+ prog="braveprefs",
92
+ description="Export, compare and restore a named list of browser profile settings.",
93
+ )
94
+ subparsers = parser.add_subparsers(dest="command")
95
+
96
+ exporter = subparsers.add_parser("export", help="read the settings of a profile")
97
+ exporter.add_argument("profile", help="path of the browser profile directory")
98
+ exporter.add_argument("-o", "--output", help="file to write, or - for standard output")
99
+ exporter.set_defaults(handler=_cmd_export)
100
+
101
+ differ = subparsers.add_parser("diff", help="compare two exported files")
102
+ differ.add_argument("first")
103
+ differ.add_argument("second")
104
+ differ.set_defaults(handler=_cmd_diff)
105
+
106
+ importer = subparsers.add_parser("import", help="write settings back into a profile")
107
+ importer.add_argument("profile", help="path of the browser profile directory")
108
+ importer.add_argument("bundle", help="file produced by braveprefs export")
109
+ importer.add_argument(
110
+ "--apply", action="store_true",
111
+ help="actually write; without it the command only says what would change",
112
+ )
113
+ importer.add_argument(
114
+ "--dry-run", action="store_true",
115
+ help="say what would change and write nothing. This is the default.",
116
+ )
117
+ importer.add_argument(
118
+ "--force", action="store_true",
119
+ help="write even though a singleton lock file is present",
120
+ )
121
+ importer.set_defaults(handler=_cmd_import)
122
+ return parser
123
+
124
+
125
+ def main(argv=None, out=None, err=None):
126
+ out = out or sys.stdout
127
+ err = err or sys.stderr
128
+ parser = build_parser()
129
+ args = parser.parse_args(list(sys.argv[1:] if argv is None else argv))
130
+ if getattr(args, "handler", None) is None:
131
+ parser.print_help(out)
132
+ return 2
133
+ if getattr(args, "dry_run", False):
134
+ args.apply = False
135
+ try:
136
+ return args.handler(args, out)
137
+ except PrefsError as exc:
138
+ err.write("braveprefs: %s\n" % exc)
139
+ return 1
140
+
141
+
142
+ if __name__ == "__main__":
143
+ sys.exit(main())