cursor-storage-reset 1.0.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shivanshu814
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 OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: cursor-storage-reset
3
+ Version: 1.0.0
4
+ Summary: Safely regenerate Cursor globalStorage telemetry fields in storage.json
5
+ Author: Shivanshu814
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/shivanshu814/cursor-storage-reset
8
+ Project-URL: Repository, https://github.com/shivanshu814/cursor-storage-reset
9
+ Project-URL: Issues, https://github.com/shivanshu814/cursor-storage-reset/issues
10
+ Keywords: cursor,storage,telemetry,privacy
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
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.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.4; extra == "dev"
29
+ Requires-Dist: pytest-cov>=4.1; extra == "dev"
30
+ Requires-Dist: mypy>=1.8; extra == "dev"
31
+ Requires-Dist: ruff>=0.3; extra == "dev"
32
+ Requires-Dist: build>=1.0; extra == "dev"
33
+ Requires-Dist: twine>=5.0; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # Cursor storage reset
37
+
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
40
+ [![PyPI](https://img.shields.io/pypi/v/cursor-storage-reset.svg)](https://pypi.org/project/cursor-storage-reset/)
41
+
42
+ **Repository:** [github.com/shivanshu814/cursor-storage-reset](https://github.com/shivanshu814/cursor-storage-reset) · **Author:** [@shivanshu814](https://github.com/shivanshu814)
43
+
44
+ Small, tested, cross-platform utility. It regenerates these fields in Cursor’s `storage.json`:
45
+
46
+ - `telemetry.macMachineId`
47
+ - `telemetry.machineId`
48
+ - `telemetry.devDeviceId`
49
+
50
+ Writes use an **atomic replace** (temp file + `fsync`) so a crash mid-save is unlikely to leave truncated JSON. Keys outside the telemetry trio are preserved.
51
+
52
+ ## Install
53
+
54
+ ### From PyPI (recommended)
55
+
56
+ ```bash
57
+ pip install cursor-storage-reset
58
+ ```
59
+
60
+ PyPI project: [pypi.org/project/cursor-storage-reset](https://pypi.org/project/cursor-storage-reset/).
61
+
62
+ ### From source
63
+
64
+ Clone (same name as on GitHub):
65
+
66
+ ```bash
67
+ git clone https://github.com/shivanshu814/cursor-storage-reset.git
68
+ cd cursor-storage-reset
69
+ ```
70
+
71
+ From the repository root (editable install for development):
72
+
73
+ ```bash
74
+ python3 -m venv .venv
75
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
76
+ pip install -e ".[dev]"
77
+ ```
78
+
79
+ Or install without dev tools:
80
+
81
+ ```bash
82
+ pip install .
83
+ ```
84
+
85
+ After install, the console entry point `cursor-storage-reset` is available.
86
+
87
+ ## Usage
88
+
89
+ ```bash
90
+ # Use the default path for the current OS
91
+ cursor-storage-reset
92
+
93
+ # Custom file
94
+ cursor-storage-reset /path/to/storage.json
95
+
96
+ # Show the resolved default for this machine
97
+ cursor-storage-reset --print-default
98
+
99
+ python -m cursor_storage_reset --version
100
+ ```
101
+
102
+ Restart Cursor after a successful run.
103
+
104
+ ### Default paths
105
+
106
+ | OS | Default `storage.json` |
107
+ |--------|-------------------------|
108
+ | macOS | `~/Library/Application Support/Cursor/User/globalStorage/storage.json` |
109
+ | Linux | `$XDG_CONFIG_HOME/Cursor/User/globalStorage/storage.json`, or `~/.config/...` if unset |
110
+ | Windows | `%APPDATA%\Cursor\User\globalStorage\storage.json` |
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ pip install -e ".[dev]"
116
+ pytest
117
+ ```
118
+
119
+ ### Maintainer: publish to PyPI
120
+
121
+ 1. Create an account on [pypi.org](https://pypi.org) and an **API token** (scope: entire account or this project).
122
+ 2. Build and upload (do **not** commit the token):
123
+
124
+ ```bash
125
+ pip install build twine
126
+ python -m build
127
+ twine check dist/*
128
+ TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-YOUR_TOKEN_HERE twine upload dist/*
129
+ ```
130
+
131
+ Use [TestPyPI](https://test.pypi.org/) first if you want a dry run (`twine upload --repository testpypi dist/*` after configuring `~/.pypirc`).
132
+
133
+ **Without storing a token in GitHub:** enable [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) on PyPI for project `cursor-storage-reset`, repository `shivanshu814/cursor-storage-reset`, workflow `publish-pypi.yml`. Then open **Actions → Publish Python package to PyPI → Run workflow**, or push a tag `v1.0.0` to trigger a release build.
134
+
135
+ ## API
136
+
137
+ ```python
138
+ from pathlib import Path
139
+ from cursor_storage_reset import default_storage_path, refresh_telemetry_ids
140
+
141
+ refresh_telemetry_ids(Path("/explicit/storage.json"))
142
+ print(default_storage_path())
143
+ ```
144
+
145
+ ## Responsibility
146
+
147
+ This tool only edits **local** files you point it at. You must follow Cursor’s terms of service and applicable law. Use for legitimate purposes (e.g. privacy, troubleshooting your own setup).
148
+
149
+ ## Layout
150
+
151
+ | Path | Role |
152
+ |------|------|
153
+ | `src/cursor_storage_reset/paths.py` | Pure path resolution (testable per OS) |
154
+ | `src/cursor_storage_reset/storage.py` | JSON load/save + atomic write + ID generation |
155
+ | `src/cursor_storage_reset/cli.py` | `argparse` CLI |
156
+ | `src/cursor_storage_reset/exceptions.py` | Narrow error types |
157
+ | `tests/` | Pytest coverage for paths + storage |
158
+
159
+ Legacy root-level `mac.py` / `linux.py` / `windows.py` scripts were removed in favor of one cross-platform CLI and `python -m cursor_storage_reset`.
@@ -0,0 +1,124 @@
1
+ # Cursor storage reset
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
5
+ [![PyPI](https://img.shields.io/pypi/v/cursor-storage-reset.svg)](https://pypi.org/project/cursor-storage-reset/)
6
+
7
+ **Repository:** [github.com/shivanshu814/cursor-storage-reset](https://github.com/shivanshu814/cursor-storage-reset) · **Author:** [@shivanshu814](https://github.com/shivanshu814)
8
+
9
+ Small, tested, cross-platform utility. It regenerates these fields in Cursor’s `storage.json`:
10
+
11
+ - `telemetry.macMachineId`
12
+ - `telemetry.machineId`
13
+ - `telemetry.devDeviceId`
14
+
15
+ Writes use an **atomic replace** (temp file + `fsync`) so a crash mid-save is unlikely to leave truncated JSON. Keys outside the telemetry trio are preserved.
16
+
17
+ ## Install
18
+
19
+ ### From PyPI (recommended)
20
+
21
+ ```bash
22
+ pip install cursor-storage-reset
23
+ ```
24
+
25
+ PyPI project: [pypi.org/project/cursor-storage-reset](https://pypi.org/project/cursor-storage-reset/).
26
+
27
+ ### From source
28
+
29
+ Clone (same name as on GitHub):
30
+
31
+ ```bash
32
+ git clone https://github.com/shivanshu814/cursor-storage-reset.git
33
+ cd cursor-storage-reset
34
+ ```
35
+
36
+ From the repository root (editable install for development):
37
+
38
+ ```bash
39
+ python3 -m venv .venv
40
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
41
+ pip install -e ".[dev]"
42
+ ```
43
+
44
+ Or install without dev tools:
45
+
46
+ ```bash
47
+ pip install .
48
+ ```
49
+
50
+ After install, the console entry point `cursor-storage-reset` is available.
51
+
52
+ ## Usage
53
+
54
+ ```bash
55
+ # Use the default path for the current OS
56
+ cursor-storage-reset
57
+
58
+ # Custom file
59
+ cursor-storage-reset /path/to/storage.json
60
+
61
+ # Show the resolved default for this machine
62
+ cursor-storage-reset --print-default
63
+
64
+ python -m cursor_storage_reset --version
65
+ ```
66
+
67
+ Restart Cursor after a successful run.
68
+
69
+ ### Default paths
70
+
71
+ | OS | Default `storage.json` |
72
+ |--------|-------------------------|
73
+ | macOS | `~/Library/Application Support/Cursor/User/globalStorage/storage.json` |
74
+ | Linux | `$XDG_CONFIG_HOME/Cursor/User/globalStorage/storage.json`, or `~/.config/...` if unset |
75
+ | Windows | `%APPDATA%\Cursor\User\globalStorage\storage.json` |
76
+
77
+ ## Development
78
+
79
+ ```bash
80
+ pip install -e ".[dev]"
81
+ pytest
82
+ ```
83
+
84
+ ### Maintainer: publish to PyPI
85
+
86
+ 1. Create an account on [pypi.org](https://pypi.org) and an **API token** (scope: entire account or this project).
87
+ 2. Build and upload (do **not** commit the token):
88
+
89
+ ```bash
90
+ pip install build twine
91
+ python -m build
92
+ twine check dist/*
93
+ TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-YOUR_TOKEN_HERE twine upload dist/*
94
+ ```
95
+
96
+ Use [TestPyPI](https://test.pypi.org/) first if you want a dry run (`twine upload --repository testpypi dist/*` after configuring `~/.pypirc`).
97
+
98
+ **Without storing a token in GitHub:** enable [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) on PyPI for project `cursor-storage-reset`, repository `shivanshu814/cursor-storage-reset`, workflow `publish-pypi.yml`. Then open **Actions → Publish Python package to PyPI → Run workflow**, or push a tag `v1.0.0` to trigger a release build.
99
+
100
+ ## API
101
+
102
+ ```python
103
+ from pathlib import Path
104
+ from cursor_storage_reset import default_storage_path, refresh_telemetry_ids
105
+
106
+ refresh_telemetry_ids(Path("/explicit/storage.json"))
107
+ print(default_storage_path())
108
+ ```
109
+
110
+ ## Responsibility
111
+
112
+ This tool only edits **local** files you point it at. You must follow Cursor’s terms of service and applicable law. Use for legitimate purposes (e.g. privacy, troubleshooting your own setup).
113
+
114
+ ## Layout
115
+
116
+ | Path | Role |
117
+ |------|------|
118
+ | `src/cursor_storage_reset/paths.py` | Pure path resolution (testable per OS) |
119
+ | `src/cursor_storage_reset/storage.py` | JSON load/save + atomic write + ID generation |
120
+ | `src/cursor_storage_reset/cli.py` | `argparse` CLI |
121
+ | `src/cursor_storage_reset/exceptions.py` | Narrow error types |
122
+ | `tests/` | Pytest coverage for paths + storage |
123
+
124
+ Legacy root-level `mac.py` / `linux.py` / `windows.py` scripts were removed in favor of one cross-platform CLI and `python -m cursor_storage_reset`.
@@ -0,0 +1,71 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cursor-storage-reset"
7
+ version = "1.0.0"
8
+ description = "Safely regenerate Cursor globalStorage telemetry fields in storage.json"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.8"
12
+ authors = [{ name = "Shivanshu814" }]
13
+ keywords = ["cursor", "storage", "telemetry", "privacy"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: MacOS :: MacOS X",
19
+ "Operating System :: Microsoft :: Windows",
20
+ "Operating System :: POSIX :: Linux",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Typing :: Typed",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/shivanshu814/cursor-storage-reset"
32
+ Repository = "https://github.com/shivanshu814/cursor-storage-reset"
33
+ Issues = "https://github.com/shivanshu814/cursor-storage-reset/issues"
34
+
35
+ [project.scripts]
36
+ cursor-storage-reset = "cursor_storage_reset.cli:main"
37
+
38
+ [project.optional-dependencies]
39
+ dev = [
40
+ "pytest>=7.4",
41
+ "pytest-cov>=4.1",
42
+ "mypy>=1.8",
43
+ "ruff>=0.3",
44
+ "build>=1.0",
45
+ "twine>=5.0",
46
+ ]
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.setuptools.package-data]
52
+ cursor_storage_reset = ["py.typed"]
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ addopts = "-ra -q"
57
+
58
+ [tool.coverage.run]
59
+ branch = true
60
+ source = ["cursor_storage_reset"]
61
+
62
+ [tool.mypy]
63
+ python_version = "3.8"
64
+ packages = ["cursor_storage_reset"]
65
+ strict = true
66
+ show_error_codes = true
67
+
68
+ [tool.ruff]
69
+ line-length = 100
70
+ target-version = "py38"
71
+ select = ["E", "F", "I", "UP"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,31 @@
1
+ """
2
+ Regenerate Cursor ``storage.json`` telemetry fields with atomic persistence.
3
+
4
+ Public API is intentionally small: path resolution, the refresh operation,
5
+ and version metadata for packaging.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from cursor_storage_reset.paths import default_storage_path, resolve_default_storage_path
11
+ from cursor_storage_reset.storage import TELEMETRY_KEYS, refresh_telemetry_ids
12
+
13
+ __all__ = [
14
+ "TELEMETRY_KEYS",
15
+ "default_storage_path",
16
+ "refresh_telemetry_ids",
17
+ "resolve_default_storage_path",
18
+ "__version__",
19
+ ]
20
+
21
+
22
+ def _package_version() -> str:
23
+ try:
24
+ from importlib.metadata import version
25
+
26
+ return version("cursor-storage-reset")
27
+ except Exception:
28
+ return "1.0.0"
29
+
30
+
31
+ __version__ = _package_version()
@@ -0,0 +1,3 @@
1
+ from cursor_storage_reset.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,71 @@
1
+ """Command-line interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from cursor_storage_reset import __version__
10
+ from cursor_storage_reset.exceptions import InvalidStorageFile
11
+ from cursor_storage_reset.paths import default_storage_path
12
+ from cursor_storage_reset.storage import refresh_telemetry_ids
13
+
14
+
15
+ def _build_parser() -> argparse.ArgumentParser:
16
+ parser = argparse.ArgumentParser(
17
+ description=(
18
+ "Regenerate Cursor globalStorage telemetry identifiers in storage.json "
19
+ "(atomic write; other keys preserved)."
20
+ ),
21
+ )
22
+ parser.add_argument(
23
+ "storage_path",
24
+ nargs="?",
25
+ type=Path,
26
+ help="Path to storage.json (default: platform-specific Cursor location)",
27
+ )
28
+ parser.add_argument(
29
+ "--print-default",
30
+ action="store_true",
31
+ help="Print the default storage path for this platform and exit",
32
+ )
33
+ parser.add_argument(
34
+ "--version",
35
+ action="version",
36
+ version=f"%(prog)s {__version__}",
37
+ )
38
+ return parser
39
+
40
+
41
+ def main(argv: list[str] | None = None) -> int:
42
+ args = _build_parser().parse_args(argv)
43
+
44
+ default_path = default_storage_path()
45
+ if args.print_default:
46
+ print(default_path)
47
+ return 0
48
+
49
+ path = args.storage_path.expanduser() if args.storage_path else default_path
50
+
51
+ try:
52
+ updated = refresh_telemetry_ids(path)
53
+ except FileNotFoundError:
54
+ print(
55
+ "Could not find storage.json. Ensure Cursor is installed "
56
+ "or pass an explicit path.",
57
+ file=sys.stderr,
58
+ )
59
+ return 1
60
+ except InvalidStorageFile as exc:
61
+ print(str(exc), file=sys.stderr)
62
+ return 1
63
+
64
+ print("Success: Cursor telemetry identifiers were reset.")
65
+ print(f"Updated: {updated}")
66
+ print("Restart Cursor for changes to take effect.")
67
+ return 0
68
+
69
+
70
+ if __name__ == "__main__":
71
+ raise SystemExit(main())
@@ -0,0 +1,12 @@
1
+ """Domain-specific errors (kept narrow; stdlib errors used where they fit)."""
2
+
3
+
4
+ from __future__ import annotations
5
+
6
+
7
+ class StorageResetError(RuntimeError):
8
+ """Base class for recoverable storage refresh failures."""
9
+
10
+
11
+ class InvalidStorageFile(StorageResetError):
12
+ """Raised when ``storage.json`` is missing, unreadable, or not a JSON object."""
@@ -0,0 +1,51 @@
1
+ """
2
+ Platform-aware resolution of Cursor's ``storage.json`` location.
3
+
4
+ Uses the same layout Cursor documents for user data (VS Code–style paths).
5
+ ``XDG_CONFIG_HOME`` is honored on Linux/BSD; ``%APPDATA%`` on Windows.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Mapping
14
+
15
+ __all__ = ["default_storage_path", "resolve_default_storage_path"]
16
+
17
+ _CURSOR_STORAGE = Path("Cursor/User/globalStorage/storage.json")
18
+
19
+
20
+ def resolve_default_storage_path(
21
+ *,
22
+ platform: str,
23
+ home: Path,
24
+ environ: Mapping[str, str],
25
+ ) -> Path:
26
+ """
27
+ Pure path resolver for testing and nonstandard environments.
28
+
29
+ ``platform`` follows ``sys.platform`` (``darwin``, ``win32``, ``linux``, …).
30
+ Platforms other than ``darwin`` and ``win32`` use the XDG-style config base.
31
+ """
32
+ if platform == "darwin":
33
+ return home / "Library/Application Support" / _CURSOR_STORAGE
34
+
35
+ if platform == "win32":
36
+ appdata = str(environ.get("APPDATA", "") or "").strip()
37
+ root = Path(appdata) if appdata else home / "AppData" / "Roaming"
38
+ return root / _CURSOR_STORAGE
39
+
40
+ xdg = str(environ.get("XDG_CONFIG_HOME", "") or "").strip()
41
+ base = Path(xdg) if xdg else home / ".config"
42
+ return base / _CURSOR_STORAGE
43
+
44
+
45
+ def default_storage_path() -> Path:
46
+ """Default ``storage.json`` for this process (current platform + environment)."""
47
+ return resolve_default_storage_path(
48
+ platform=sys.platform,
49
+ home=Path.home(),
50
+ environ=os.environ,
51
+ )
@@ -0,0 +1,92 @@
1
+ """Read/modify ``storage.json`` with atomic persistence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import secrets
8
+ import tempfile
9
+ import uuid
10
+ from pathlib import Path
11
+ from typing import Any, Final, Mapping, MutableMapping
12
+
13
+ from cursor_storage_reset.exceptions import InvalidStorageFile
14
+
15
+ __all__ = ["TELEMETRY_KEYS", "refresh_telemetry_ids"]
16
+
17
+ TELEMETRY_KEYS: Final[tuple[str, ...]] = (
18
+ "telemetry.macMachineId",
19
+ "telemetry.machineId",
20
+ "telemetry.devDeviceId",
21
+ )
22
+
23
+ _TELEMETRY_HEX_ID_LENGTH = 64
24
+
25
+
26
+ def _random_hex_id(length_chars: int) -> str:
27
+ if length_chars <= 0 or length_chars % 2:
28
+ raise ValueError("length_chars must be a positive even integer")
29
+ return secrets.token_hex(length_chars // 2)
30
+
31
+
32
+ def _load_storage(path: Path) -> MutableMapping[str, Any]:
33
+ try:
34
+ text = path.read_text(encoding="utf-8")
35
+ except OSError as exc:
36
+ raise InvalidStorageFile(f"cannot read storage file: {path}") from exc
37
+ try:
38
+ parsed = json.loads(text)
39
+ except json.JSONDecodeError as exc:
40
+ raise InvalidStorageFile(f"invalid JSON in storage file: {path}") from exc
41
+ if not isinstance(parsed, dict):
42
+ raise InvalidStorageFile(f"storage root must be a JSON object: {path}")
43
+ return parsed # type: ignore[return-value]
44
+
45
+
46
+ def _atomic_write_json(path: Path, data: Mapping[str, Any]) -> None:
47
+ path = path.expanduser()
48
+ path.parent.mkdir(parents=True, exist_ok=True)
49
+
50
+ fd, tmp_name = tempfile.mkstemp(
51
+ prefix=f".{path.name}.",
52
+ suffix=".tmp",
53
+ dir=str(path.parent),
54
+ )
55
+ tmp_path = Path(tmp_name)
56
+ try:
57
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
58
+ json.dump(data, handle, indent=4)
59
+ handle.write("\n")
60
+ handle.flush()
61
+ os.fsync(handle.fileno())
62
+ os.replace(tmp_path, path)
63
+ except BaseException:
64
+ try:
65
+ tmp_path.unlink(missing_ok=True)
66
+ except AttributeError:
67
+ if tmp_path.exists():
68
+ tmp_path.unlink()
69
+ raise
70
+
71
+
72
+ def refresh_telemetry_ids(storage_path: Path) -> Path:
73
+ """
74
+ Regenerate telemetry identifiers in Cursor ``storage.json``.
75
+
76
+ Returns the resolved path written. Other keys are preserved.
77
+
78
+ Raises:
79
+ FileNotFoundError: if ``storage_path`` does not exist.
80
+ InvalidStorageFile: on read/parse errors.
81
+ """
82
+ target = storage_path.expanduser().resolve()
83
+ if not target.is_file():
84
+ raise FileNotFoundError(str(target))
85
+
86
+ data = _load_storage(target)
87
+ data[TELEMETRY_KEYS[0]] = _random_hex_id(_TELEMETRY_HEX_ID_LENGTH)
88
+ data[TELEMETRY_KEYS[1]] = _random_hex_id(_TELEMETRY_HEX_ID_LENGTH)
89
+ data[TELEMETRY_KEYS[2]] = str(uuid.uuid4())
90
+
91
+ _atomic_write_json(target, data)
92
+ return target
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: cursor-storage-reset
3
+ Version: 1.0.0
4
+ Summary: Safely regenerate Cursor globalStorage telemetry fields in storage.json
5
+ Author: Shivanshu814
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/shivanshu814/cursor-storage-reset
8
+ Project-URL: Repository, https://github.com/shivanshu814/cursor-storage-reset
9
+ Project-URL: Issues, https://github.com/shivanshu814/cursor-storage-reset/issues
10
+ Keywords: cursor,storage,telemetry,privacy
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
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.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.4; extra == "dev"
29
+ Requires-Dist: pytest-cov>=4.1; extra == "dev"
30
+ Requires-Dist: mypy>=1.8; extra == "dev"
31
+ Requires-Dist: ruff>=0.3; extra == "dev"
32
+ Requires-Dist: build>=1.0; extra == "dev"
33
+ Requires-Dist: twine>=5.0; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # Cursor storage reset
37
+
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
40
+ [![PyPI](https://img.shields.io/pypi/v/cursor-storage-reset.svg)](https://pypi.org/project/cursor-storage-reset/)
41
+
42
+ **Repository:** [github.com/shivanshu814/cursor-storage-reset](https://github.com/shivanshu814/cursor-storage-reset) · **Author:** [@shivanshu814](https://github.com/shivanshu814)
43
+
44
+ Small, tested, cross-platform utility. It regenerates these fields in Cursor’s `storage.json`:
45
+
46
+ - `telemetry.macMachineId`
47
+ - `telemetry.machineId`
48
+ - `telemetry.devDeviceId`
49
+
50
+ Writes use an **atomic replace** (temp file + `fsync`) so a crash mid-save is unlikely to leave truncated JSON. Keys outside the telemetry trio are preserved.
51
+
52
+ ## Install
53
+
54
+ ### From PyPI (recommended)
55
+
56
+ ```bash
57
+ pip install cursor-storage-reset
58
+ ```
59
+
60
+ PyPI project: [pypi.org/project/cursor-storage-reset](https://pypi.org/project/cursor-storage-reset/).
61
+
62
+ ### From source
63
+
64
+ Clone (same name as on GitHub):
65
+
66
+ ```bash
67
+ git clone https://github.com/shivanshu814/cursor-storage-reset.git
68
+ cd cursor-storage-reset
69
+ ```
70
+
71
+ From the repository root (editable install for development):
72
+
73
+ ```bash
74
+ python3 -m venv .venv
75
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
76
+ pip install -e ".[dev]"
77
+ ```
78
+
79
+ Or install without dev tools:
80
+
81
+ ```bash
82
+ pip install .
83
+ ```
84
+
85
+ After install, the console entry point `cursor-storage-reset` is available.
86
+
87
+ ## Usage
88
+
89
+ ```bash
90
+ # Use the default path for the current OS
91
+ cursor-storage-reset
92
+
93
+ # Custom file
94
+ cursor-storage-reset /path/to/storage.json
95
+
96
+ # Show the resolved default for this machine
97
+ cursor-storage-reset --print-default
98
+
99
+ python -m cursor_storage_reset --version
100
+ ```
101
+
102
+ Restart Cursor after a successful run.
103
+
104
+ ### Default paths
105
+
106
+ | OS | Default `storage.json` |
107
+ |--------|-------------------------|
108
+ | macOS | `~/Library/Application Support/Cursor/User/globalStorage/storage.json` |
109
+ | Linux | `$XDG_CONFIG_HOME/Cursor/User/globalStorage/storage.json`, or `~/.config/...` if unset |
110
+ | Windows | `%APPDATA%\Cursor\User\globalStorage\storage.json` |
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ pip install -e ".[dev]"
116
+ pytest
117
+ ```
118
+
119
+ ### Maintainer: publish to PyPI
120
+
121
+ 1. Create an account on [pypi.org](https://pypi.org) and an **API token** (scope: entire account or this project).
122
+ 2. Build and upload (do **not** commit the token):
123
+
124
+ ```bash
125
+ pip install build twine
126
+ python -m build
127
+ twine check dist/*
128
+ TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-YOUR_TOKEN_HERE twine upload dist/*
129
+ ```
130
+
131
+ Use [TestPyPI](https://test.pypi.org/) first if you want a dry run (`twine upload --repository testpypi dist/*` after configuring `~/.pypirc`).
132
+
133
+ **Without storing a token in GitHub:** enable [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) on PyPI for project `cursor-storage-reset`, repository `shivanshu814/cursor-storage-reset`, workflow `publish-pypi.yml`. Then open **Actions → Publish Python package to PyPI → Run workflow**, or push a tag `v1.0.0` to trigger a release build.
134
+
135
+ ## API
136
+
137
+ ```python
138
+ from pathlib import Path
139
+ from cursor_storage_reset import default_storage_path, refresh_telemetry_ids
140
+
141
+ refresh_telemetry_ids(Path("/explicit/storage.json"))
142
+ print(default_storage_path())
143
+ ```
144
+
145
+ ## Responsibility
146
+
147
+ This tool only edits **local** files you point it at. You must follow Cursor’s terms of service and applicable law. Use for legitimate purposes (e.g. privacy, troubleshooting your own setup).
148
+
149
+ ## Layout
150
+
151
+ | Path | Role |
152
+ |------|------|
153
+ | `src/cursor_storage_reset/paths.py` | Pure path resolution (testable per OS) |
154
+ | `src/cursor_storage_reset/storage.py` | JSON load/save + atomic write + ID generation |
155
+ | `src/cursor_storage_reset/cli.py` | `argparse` CLI |
156
+ | `src/cursor_storage_reset/exceptions.py` | Narrow error types |
157
+ | `tests/` | Pytest coverage for paths + storage |
158
+
159
+ Legacy root-level `mac.py` / `linux.py` / `windows.py` scripts were removed in favor of one cross-platform CLI and `python -m cursor_storage_reset`.
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/cursor_storage_reset/__init__.py
5
+ src/cursor_storage_reset/__main__.py
6
+ src/cursor_storage_reset/cli.py
7
+ src/cursor_storage_reset/exceptions.py
8
+ src/cursor_storage_reset/paths.py
9
+ src/cursor_storage_reset/py.typed
10
+ src/cursor_storage_reset/storage.py
11
+ src/cursor_storage_reset.egg-info/PKG-INFO
12
+ src/cursor_storage_reset.egg-info/SOURCES.txt
13
+ src/cursor_storage_reset.egg-info/dependency_links.txt
14
+ src/cursor_storage_reset.egg-info/entry_points.txt
15
+ src/cursor_storage_reset.egg-info/requires.txt
16
+ src/cursor_storage_reset.egg-info/top_level.txt
17
+ tests/test_paths.py
18
+ tests/test_storage.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cursor-storage-reset = cursor_storage_reset.cli:main
@@ -0,0 +1,8 @@
1
+
2
+ [dev]
3
+ pytest>=7.4
4
+ pytest-cov>=4.1
5
+ mypy>=1.8
6
+ ruff>=0.3
7
+ build>=1.0
8
+ twine>=5.0
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from cursor_storage_reset.paths import resolve_default_storage_path
6
+
7
+
8
+ def test_darwin_path() -> None:
9
+ home = Path("/Users/tester")
10
+ p = resolve_default_storage_path(platform="darwin", home=home, environ={})
11
+ assert p == home / "Library/Application Support/Cursor/User/globalStorage/storage.json"
12
+
13
+
14
+ def test_linux_respects_xdg() -> None:
15
+ home = Path("/home/tester")
16
+ env = {"XDG_CONFIG_HOME": "/alt/cfg"}
17
+ p = resolve_default_storage_path(platform="linux", home=home, environ=env)
18
+ assert p == Path("/alt/cfg/Cursor/User/globalStorage/storage.json")
19
+
20
+
21
+ def test_linux_fallback_config() -> None:
22
+ home = Path("/home/tester")
23
+ p = resolve_default_storage_path(platform="linux", home=home, environ={})
24
+ assert p == home / ".config/Cursor/User/globalStorage/storage.json"
25
+
26
+
27
+ def test_windows_uses_appdata() -> None:
28
+ home = Path("C:/Users/tester")
29
+ env = {"APPDATA": r"C:\Users\tester\AppData\Roaming"}
30
+ p = resolve_default_storage_path(platform="win32", home=home, environ=env)
31
+ assert p == Path(r"C:\Users\tester\AppData\Roaming/Cursor/User/globalStorage/storage.json")
32
+
33
+
34
+ def test_windows_appdata_missing_falls_back() -> None:
35
+ home = Path("C:/Users/tester")
36
+ p = resolve_default_storage_path(platform="win32", home=home, environ={})
37
+ assert p == home / "AppData/Roaming/Cursor/User/globalStorage/storage.json"
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ from cursor_storage_reset.exceptions import InvalidStorageFile
9
+ from cursor_storage_reset.storage import TELEMETRY_KEYS, refresh_telemetry_ids
10
+
11
+
12
+ def test_refresh_preserves_other_keys(tmp_path: Path) -> None:
13
+ path = tmp_path / "storage.json"
14
+ path.write_text(
15
+ json.dumps(
16
+ {
17
+ "keep": "value",
18
+ TELEMETRY_KEYS[0]: "oldmac",
19
+ TELEMETRY_KEYS[1]: "oldmid",
20
+ TELEMETRY_KEYS[2]: "00000000-0000-0000-0000-000000000000",
21
+ },
22
+ ),
23
+ encoding="utf-8",
24
+ )
25
+
26
+ refresh_telemetry_ids(path)
27
+ data = json.loads(path.read_text(encoding="utf-8"))
28
+
29
+ assert data["keep"] == "value"
30
+ assert data[TELEMETRY_KEYS[0]] != "oldmac"
31
+ assert data[TELEMETRY_KEYS[1]] != "oldmid"
32
+ assert data[TELEMETRY_KEYS[2]] != "00000000-0000-0000-0000-000000000000"
33
+ assert len(data[TELEMETRY_KEYS[0]]) == 64
34
+ assert len(data[TELEMETRY_KEYS[1]]) == 64
35
+
36
+
37
+ def test_missing_file(tmp_path: Path) -> None:
38
+ with pytest.raises(FileNotFoundError):
39
+ refresh_telemetry_ids(tmp_path / "nope.json")
40
+
41
+
42
+ def test_invalid_json(tmp_path: Path) -> None:
43
+ path = tmp_path / "storage.json"
44
+ path.write_text("{not json", encoding="utf-8")
45
+ with pytest.raises(InvalidStorageFile):
46
+ refresh_telemetry_ids(path)