claudepath 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,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ matrix:
14
+ os: [ubuntu-latest, macos-latest]
15
+ python-version: ["3.8", "3.11", "3.13"]
16
+
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
+
25
+ - name: Install package and test dependencies
26
+ run: pip install -e ".[dev]"
27
+
28
+ - name: Run tests
29
+ run: pytest tests/ -v
@@ -0,0 +1,30 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ environment: pypi
12
+ permissions:
13
+ id-token: write # Required for trusted publishing (no API token needed)
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.11"
22
+
23
+ - name: Install build tools
24
+ run: pip install build
25
+
26
+ - name: Build package
27
+ run: python -m build
28
+
29
+ - name: Publish to PyPI
30
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,34 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ .Python
7
+
8
+ # Virtual environments
9
+ .venv/
10
+ venv/
11
+ env/
12
+
13
+ # Build / distribution
14
+ dist/
15
+ build/
16
+ *.egg-info/
17
+ *.egg
18
+
19
+ # PyPI credentials
20
+ .pypirc
21
+
22
+ # Testing
23
+ .pytest_cache/
24
+ .coverage
25
+ htmlcov/
26
+ coverage.xml
27
+
28
+ # IDEs
29
+ .idea/
30
+ .vscode/
31
+ *.iml
32
+
33
+ # macOS
34
+ .DS_Store
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-02-25
4
+
5
+ ### Added
6
+ - `claudepath mv` — move a project directory and update all Claude Code references
7
+ - `claudepath remap` — update references only (directory already moved manually)
8
+ - `claudepath list` — list all projects tracked by Claude Code
9
+ - `claudepath help` — show usage and examples
10
+ - `--dry-run` flag to preview changes without modifying files
11
+ - `--no-backup` flag to skip automatic backup
12
+ - `--yes` flag to skip confirmation prompt
13
+ - `--claude-dir` flag to override the Claude data directory
14
+ - Automatic backup to `~/.claude/backups/claudepath/{timestamp}/` before any changes
15
+ - Automatic rollback on failure
16
+ - Proper JSON parsing for `sessions-index.json` (fixes gap in existing community tools)
17
+ - Recursive update of subagent `.jsonl` files
18
+ - Line-by-line processing for large session files (>9MB)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fernando Chullo
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,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: claudepath
3
+ Version: 0.1.0
4
+ Summary: Move or rename Claude Code projects without losing session history and context
5
+ Project-URL: Homepage, https://github.com/Mahiler1909/claudepath
6
+ Project-URL: Repository, https://github.com/Mahiler1909/claudepath
7
+ Project-URL: Issues, https://github.com/Mahiler1909/claudepath/issues
8
+ Author: Fernando Chullo
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: anthropic,claude,claude-code,cli,move,project
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
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: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.8
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # claudepath
31
+
32
+ Move or rename [Claude Code](https://claude.ai/claude-code) projects without losing session history, memory, and context.
33
+
34
+ ## The Problem
35
+
36
+ When you move or rename a project directory, Claude Code loses all your session history because sessions are keyed to the absolute path of your project. You end up with orphaned data in `~/.claude/projects/` and a fresh start.
37
+
38
+ **claudepath** fixes this by updating all path references in Claude Code's data files after a move.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ # With pipx (recommended — isolated install)
44
+ pipx install claudepath
45
+
46
+ # With pip
47
+ pip install claudepath
48
+ ```
49
+
50
+ ### Homebrew (macOS/Linux)
51
+
52
+ ```bash
53
+ brew tap Mahiler1909/tools
54
+ brew install claudepath
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### Move a project
60
+
61
+ Moves the directory on disk **and** updates all Claude Code references:
62
+
63
+ ```bash
64
+ claudepath mv ~/old/location/my-project ~/new/location/my-project
65
+ ```
66
+
67
+ ### Remap after a manual move
68
+
69
+ If you already moved the directory yourself, just update Claude's references:
70
+
71
+ ```bash
72
+ claudepath remap ~/old/location/my-project ~/new/location/my-project
73
+ ```
74
+
75
+ ### Preview changes (dry run)
76
+
77
+ See exactly what would change without modifying anything:
78
+
79
+ ```bash
80
+ claudepath mv ~/old/path ~/new/path --dry-run
81
+ ```
82
+
83
+ ### List tracked projects
84
+
85
+ ```bash
86
+ claudepath list
87
+ ```
88
+
89
+ ### Full help
90
+
91
+ ```bash
92
+ claudepath help
93
+ claudepath mv --help
94
+ ```
95
+
96
+ ## What it updates
97
+
98
+ | File / Directory | What changes |
99
+ |---|---|
100
+ | `~/.claude/projects/{encoded-dir}/` | Renamed to match new path |
101
+ | `sessions-index.json` | `originalPath`, `projectPath`, `fullPath` updated (proper JSON parsing) |
102
+ | `{session}.jsonl` files | `cwd` and file path references updated (line-by-line, handles large files) |
103
+ | Subagent `.jsonl` files | Same as above, recursive |
104
+ | `~/.claude/history.jsonl` | `project` field updated for all matching entries |
105
+
106
+ > **Note:** `file-history/`, `todos/`, `tasks/`, and `shell-snapshots/` are keyed by session UUID, not by project path — they don't need updating.
107
+
108
+ ## Options
109
+
110
+ | Flag | Description |
111
+ |---|---|
112
+ | `--dry-run` | Preview all changes without writing anything |
113
+ | `--no-backup` | Skip creating a backup before modifying files |
114
+ | `--yes` / `-y` | Skip the confirmation prompt |
115
+ | `--claude-dir <path>` | Override the Claude data directory (default: `~/.claude`) |
116
+
117
+ ## Backup & Rollback
118
+
119
+ By default, claudepath creates a backup before making any changes:
120
+
121
+ ```
122
+ ~/.claude/backups/claudepath/{timestamp}/
123
+ ```
124
+
125
+ The backup includes:
126
+ - The full project data directory (`~/.claude/projects/{encoded}/`)
127
+ - `~/.claude/history.jsonl`
128
+
129
+ If any step fails, claudepath automatically restores from the backup. Use `--no-backup` only if you already have your own backup or want to skip the extra time.
130
+
131
+ ## How Claude Code encodes paths
132
+
133
+ Claude Code stores project data in `~/.claude/projects/` using an encoded directory name: every `/` in the absolute path is replaced with `-`.
134
+
135
+ ```
136
+ /Users/foo/my-project → -Users-foo-my-project
137
+ ```
138
+
139
+ This means moving `/Users/foo/old-name` to `/Users/foo/new-name` requires:
140
+ 1. Renaming `-Users-foo-old-name` to `-Users-foo-new-name`
141
+ 2. Updating all path strings inside the data files
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,116 @@
1
+ # claudepath
2
+
3
+ Move or rename [Claude Code](https://claude.ai/claude-code) projects without losing session history, memory, and context.
4
+
5
+ ## The Problem
6
+
7
+ When you move or rename a project directory, Claude Code loses all your session history because sessions are keyed to the absolute path of your project. You end up with orphaned data in `~/.claude/projects/` and a fresh start.
8
+
9
+ **claudepath** fixes this by updating all path references in Claude Code's data files after a move.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ # With pipx (recommended — isolated install)
15
+ pipx install claudepath
16
+
17
+ # With pip
18
+ pip install claudepath
19
+ ```
20
+
21
+ ### Homebrew (macOS/Linux)
22
+
23
+ ```bash
24
+ brew tap Mahiler1909/tools
25
+ brew install claudepath
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Move a project
31
+
32
+ Moves the directory on disk **and** updates all Claude Code references:
33
+
34
+ ```bash
35
+ claudepath mv ~/old/location/my-project ~/new/location/my-project
36
+ ```
37
+
38
+ ### Remap after a manual move
39
+
40
+ If you already moved the directory yourself, just update Claude's references:
41
+
42
+ ```bash
43
+ claudepath remap ~/old/location/my-project ~/new/location/my-project
44
+ ```
45
+
46
+ ### Preview changes (dry run)
47
+
48
+ See exactly what would change without modifying anything:
49
+
50
+ ```bash
51
+ claudepath mv ~/old/path ~/new/path --dry-run
52
+ ```
53
+
54
+ ### List tracked projects
55
+
56
+ ```bash
57
+ claudepath list
58
+ ```
59
+
60
+ ### Full help
61
+
62
+ ```bash
63
+ claudepath help
64
+ claudepath mv --help
65
+ ```
66
+
67
+ ## What it updates
68
+
69
+ | File / Directory | What changes |
70
+ |---|---|
71
+ | `~/.claude/projects/{encoded-dir}/` | Renamed to match new path |
72
+ | `sessions-index.json` | `originalPath`, `projectPath`, `fullPath` updated (proper JSON parsing) |
73
+ | `{session}.jsonl` files | `cwd` and file path references updated (line-by-line, handles large files) |
74
+ | Subagent `.jsonl` files | Same as above, recursive |
75
+ | `~/.claude/history.jsonl` | `project` field updated for all matching entries |
76
+
77
+ > **Note:** `file-history/`, `todos/`, `tasks/`, and `shell-snapshots/` are keyed by session UUID, not by project path — they don't need updating.
78
+
79
+ ## Options
80
+
81
+ | Flag | Description |
82
+ |---|---|
83
+ | `--dry-run` | Preview all changes without writing anything |
84
+ | `--no-backup` | Skip creating a backup before modifying files |
85
+ | `--yes` / `-y` | Skip the confirmation prompt |
86
+ | `--claude-dir <path>` | Override the Claude data directory (default: `~/.claude`) |
87
+
88
+ ## Backup & Rollback
89
+
90
+ By default, claudepath creates a backup before making any changes:
91
+
92
+ ```
93
+ ~/.claude/backups/claudepath/{timestamp}/
94
+ ```
95
+
96
+ The backup includes:
97
+ - The full project data directory (`~/.claude/projects/{encoded}/`)
98
+ - `~/.claude/history.jsonl`
99
+
100
+ If any step fails, claudepath automatically restores from the backup. Use `--no-backup` only if you already have your own backup or want to skip the extra time.
101
+
102
+ ## How Claude Code encodes paths
103
+
104
+ Claude Code stores project data in `~/.claude/projects/` using an encoded directory name: every `/` in the absolute path is replaced with `-`.
105
+
106
+ ```
107
+ /Users/foo/my-project → -Users-foo-my-project
108
+ ```
109
+
110
+ This means moving `/Users/foo/old-name` to `/Users/foo/new-name` requires:
111
+ 1. Renaming `-Users-foo-old-name` to `-Users-foo-new-name`
112
+ 2. Updating all path strings inside the data files
113
+
114
+ ## License
115
+
116
+ MIT
@@ -0,0 +1,20 @@
1
+ class Claudepath < Formula
2
+ include Language::Python::Virtualenv
3
+
4
+ desc "Move Claude Code projects without losing session history and context"
5
+ homepage "https://github.com/Mahiler1909/claudepath"
6
+ url "https://files.pythonhosted.org/packages/source/c/claudepath/claudepath-0.1.0.tar.gz"
7
+ # sha256 will be filled in after PyPI publish
8
+ sha256 "FILL_IN_AFTER_PYPI_PUBLISH"
9
+ license "MIT"
10
+
11
+ depends_on "python@3.11"
12
+
13
+ def install
14
+ virtualenv_install_with_resources
15
+ end
16
+
17
+ test do
18
+ system "#{bin}/claudepath", "help"
19
+ end
20
+ end
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "claudepath"
7
+ version = "0.1.0"
8
+ description = "Move or rename Claude Code projects without losing session history and context"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.8"
12
+ authors = [
13
+ { name = "Fernando Chullo" }
14
+ ]
15
+ keywords = ["claude", "claude-code", "anthropic", "cli", "project", "move"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.8",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Software Development :: Libraries :: Python Modules",
29
+ "Topic :: Utilities",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ dev = ["pytest"]
34
+
35
+ [project.scripts]
36
+ claudepath = "claudepath.cli:main"
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/Mahiler1909/claudepath"
40
+ Repository = "https://github.com/Mahiler1909/claudepath"
41
+ Issues = "https://github.com/Mahiler1909/claudepath/issues"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/claudepath"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from claudepath.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,110 @@
1
+ """
2
+ Backup and rollback utilities for claudepath.
3
+
4
+ Before modifying any Claude Code data files, a backup is created.
5
+ If any step fails, the backup can be restored automatically.
6
+
7
+ Backups are stored in: ~/.claude/backups/claudepath/{timestamp}/
8
+ """
9
+
10
+ import shutil
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+
16
+ def create_backup(
17
+ project_dir: Path,
18
+ history_path: Path,
19
+ backup_base: Path,
20
+ ) -> Path:
21
+ """Create a backup of the project directory and history.jsonl.
22
+
23
+ Args:
24
+ project_dir: The ~/.claude/projects/{encoded}/ directory to back up.
25
+ history_path: The ~/.claude/history.jsonl file to back up.
26
+ backup_base: Base directory for backups (~/.claude/backups/claudepath/).
27
+
28
+ Returns:
29
+ Path to the created backup directory.
30
+ """
31
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
32
+ backup_dir = backup_base / timestamp
33
+ backup_dir.mkdir(parents=True, exist_ok=True)
34
+
35
+ # Back up the project directory
36
+ if project_dir.exists():
37
+ dest = backup_dir / "project_dir"
38
+ shutil.copytree(str(project_dir), str(dest))
39
+
40
+ # Back up history.jsonl
41
+ if history_path.exists():
42
+ shutil.copy2(str(history_path), str(backup_dir / "history.jsonl"))
43
+
44
+ # Write a manifest so restore knows what to put back where
45
+ manifest = backup_dir / "manifest.txt"
46
+ manifest.write_text(
47
+ f"project_dir={project_dir}\nhistory_path={history_path}\n",
48
+ encoding="utf-8",
49
+ )
50
+
51
+ return backup_dir
52
+
53
+
54
+ def restore_backup(backup_dir: Path) -> bool:
55
+ """Restore files from a backup directory created by create_backup().
56
+
57
+ Reads the manifest to know where to restore each item.
58
+ Returns True on success, False if anything went wrong.
59
+ """
60
+ manifest = backup_dir / "manifest.txt"
61
+ if not manifest.exists():
62
+ return False
63
+
64
+ config = {}
65
+ for line in manifest.read_text(encoding="utf-8").splitlines():
66
+ if "=" in line:
67
+ k, v = line.split("=", 1)
68
+ config[k.strip()] = v.strip()
69
+
70
+ project_dir = Path(config.get("project_dir", ""))
71
+ history_path = Path(config.get("history_path", ""))
72
+
73
+ success = True
74
+
75
+ # Restore project directory
76
+ backup_project = backup_dir / "project_dir"
77
+ if backup_project.exists() and project_dir:
78
+ if project_dir.exists():
79
+ shutil.rmtree(project_dir)
80
+ try:
81
+ shutil.copytree(str(backup_project), str(project_dir))
82
+ except OSError:
83
+ success = False
84
+
85
+ # Restore history.jsonl
86
+ backup_history = backup_dir / "history.jsonl"
87
+ if backup_history.exists() and history_path:
88
+ try:
89
+ shutil.copy2(str(backup_history), str(history_path))
90
+ except OSError:
91
+ success = False
92
+
93
+ return success
94
+
95
+
96
+ def get_backup_base(claude_dir: Path) -> Path:
97
+ """Return the base directory for claudepath backups."""
98
+ return claude_dir / "backups" / "claudepath"
99
+
100
+
101
+ def find_latest_backup(backup_base: Path) -> Optional[Path]:
102
+ """Return the most recently created backup directory, or None."""
103
+ if not backup_base.exists():
104
+ return None
105
+ backups = sorted(
106
+ [d for d in backup_base.iterdir() if d.is_dir()],
107
+ key=lambda d: d.name,
108
+ reverse=True,
109
+ )
110
+ return backups[0] if backups else None