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.
- claudepath-0.1.0/.github/workflows/ci.yml +29 -0
- claudepath-0.1.0/.github/workflows/publish.yml +30 -0
- claudepath-0.1.0/.gitignore +34 -0
- claudepath-0.1.0/CHANGELOG.md +18 -0
- claudepath-0.1.0/LICENSE +21 -0
- claudepath-0.1.0/PKG-INFO +145 -0
- claudepath-0.1.0/README.md +116 -0
- claudepath-0.1.0/homebrew/claudepath.rb +20 -0
- claudepath-0.1.0/pyproject.toml +47 -0
- claudepath-0.1.0/src/claudepath/__init__.py +1 -0
- claudepath-0.1.0/src/claudepath/__main__.py +4 -0
- claudepath-0.1.0/src/claudepath/backup.py +110 -0
- claudepath-0.1.0/src/claudepath/cli.py +241 -0
- claudepath-0.1.0/src/claudepath/encoder.py +18 -0
- claudepath-0.1.0/src/claudepath/mover.py +246 -0
- claudepath-0.1.0/src/claudepath/py.typed +0 -0
- claudepath-0.1.0/src/claudepath/scanner.py +124 -0
- claudepath-0.1.0/src/claudepath/updaters.py +145 -0
- claudepath-0.1.0/tests/__init__.py +0 -0
- claudepath-0.1.0/tests/test_encoder.py +32 -0
- claudepath-0.1.0/tests/test_mover.py +239 -0
- claudepath-0.1.0/tests/test_scanner.py +109 -0
- claudepath-0.1.0/tests/test_updaters.py +158 -0
|
@@ -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)
|
claudepath-0.1.0/LICENSE
ADDED
|
@@ -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,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
|