claude-chats-sync 0.0.1
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.
- package/LICENSE +21 -0
- package/README.md +307 -0
- package/README.zh-CN.md +305 -0
- package/bin/claude-chats-sync +7 -0
- package/bin/claude-chats-sync.cmd +5 -0
- package/bin/claude-chats-sync.js +620 -0
- package/bin/claude-chats-sync.ps1 +17 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 tubo70
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# claude-chats-sync
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/claude-chats-sync)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
> Cross-platform CLI tool to sync Claude Code chat sessions to your project directory
|
|
8
|
+
|
|
9
|
+
[中文文档](README.zh-CN.md) | English
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- 🔄 **Auto-sync** - Creates symlinks from Claude Code local storage to your project folder
|
|
14
|
+
- 📁 **Project-local history** - Chat sessions stored in your project, not user home directory
|
|
15
|
+
- 🔒 **Sensitive data protection** - Cleans API keys from session files
|
|
16
|
+
- 🎯 **Simple to use** - Initialize sync with a single command
|
|
17
|
+
- 📊 **Status tracking** - Check sync status and session count
|
|
18
|
+
- 🌳 **Git-friendly** - Configure Git filters for safe version control
|
|
19
|
+
- 🔧 **Cross-platform** - Works on Windows, macOS, and Linux
|
|
20
|
+
|
|
21
|
+
## 📦 Installation
|
|
22
|
+
|
|
23
|
+
### Install globally (recommended)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g claude-chats-sync
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This allows you to use `claude-chats-sync` command from anywhere.
|
|
30
|
+
|
|
31
|
+
### Install locally
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -D claude-chats-sync
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then use it via `npx`:
|
|
38
|
+
```bash
|
|
39
|
+
npx claude-chats-sync init
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 🚀 Quick Start
|
|
43
|
+
|
|
44
|
+
### Initialize sync
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# In your project directory
|
|
48
|
+
claude-chats-sync init
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This will:
|
|
52
|
+
- Create a `.claudeCodeSessions/` folder in your project
|
|
53
|
+
- Create a symlink in `~/.claude/projects/`
|
|
54
|
+
- Configure Git filter for automatic sensitive data cleaning
|
|
55
|
+
- Add `.claudeCodeSessions/` to `.gitignore` (commented by default)
|
|
56
|
+
|
|
57
|
+
### Check status
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
claude-chats-sync status
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Open history folder
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
claude-chats-sync open
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 📖 Commands
|
|
70
|
+
|
|
71
|
+
| Command | Description |
|
|
72
|
+
|---------|-------------|
|
|
73
|
+
| `init` | Initialize sync for current project |
|
|
74
|
+
| `status` | Check sync status and session count |
|
|
75
|
+
| `open` | Open history folder in file manager |
|
|
76
|
+
| `clean` | Clean sensitive data from session files |
|
|
77
|
+
| `setup-git-filter` | Setup Git filter for automatic cleaning |
|
|
78
|
+
| `help` | Show help message |
|
|
79
|
+
|
|
80
|
+
## 🔧 Usage Examples
|
|
81
|
+
|
|
82
|
+
### Basic usage
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Initialize sync
|
|
86
|
+
claude-chats-sync init
|
|
87
|
+
|
|
88
|
+
# Check status
|
|
89
|
+
claude-chats-sync status
|
|
90
|
+
|
|
91
|
+
# Open sessions folder
|
|
92
|
+
claude-chats-sync open
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Advanced options
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Custom folder name
|
|
99
|
+
claude-chats-sync init --folder-name .sessions
|
|
100
|
+
|
|
101
|
+
# Force migrate existing sessions
|
|
102
|
+
claude-chats-sync init --force
|
|
103
|
+
|
|
104
|
+
# Specify project path
|
|
105
|
+
claude-chats-sync init --project-path /path/to/project
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Cleaning sensitive data
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Manual cleanup
|
|
112
|
+
claude-chats-sync clean
|
|
113
|
+
|
|
114
|
+
# Setup Git auto-filter
|
|
115
|
+
claude-chats-sync setup-git-filter
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## ⚙️ Environment Variables (Recommended)
|
|
119
|
+
|
|
120
|
+
Configure API keys via environment variables to prevent them from appearing in session files:
|
|
121
|
+
|
|
122
|
+
**Linux/macOS** - Add to `~/.bashrc` or `~/.zshrc`:
|
|
123
|
+
```bash
|
|
124
|
+
export ANTHROPIC_AUTH_TOKEN="sk-ant-..."
|
|
125
|
+
export ANTHROPIC_BASE_URL="https://api.example.com" # Optional: for third-party API
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Windows PowerShell**:
|
|
129
|
+
```powershell
|
|
130
|
+
$env:ANTHROPIC_AUTH_TOKEN="sk-ant-..."
|
|
131
|
+
$env:ANTHROPIC_BASE_URL="https://api.example.com"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Windows CMD (permanent)**:
|
|
135
|
+
```cmd
|
|
136
|
+
setx ANTHROPIC_AUTH_TOKEN "sk-ant-..."
|
|
137
|
+
setx ANTHROPIC_BASE_URL "https://api.example.com"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 🔒 Security & Version Control
|
|
141
|
+
|
|
142
|
+
### ⚠️ Security Warning
|
|
143
|
+
|
|
144
|
+
Session files may contain sensitive information:
|
|
145
|
+
- API keys and authentication tokens
|
|
146
|
+
- Proprietary code and business logic
|
|
147
|
+
- Private conversations and internal discussions
|
|
148
|
+
- System paths and environment details
|
|
149
|
+
|
|
150
|
+
While this tool provides API key cleaning, **no automated cleaning is 100% complete**. Only commit these files if you fully understand and accept the security risks.
|
|
151
|
+
|
|
152
|
+
### Options for securing API keys
|
|
153
|
+
|
|
154
|
+
**Option 1: Use environment variables (Recommended)**
|
|
155
|
+
|
|
156
|
+
Configure Claude Code to use API keys from environment variables. This prevents them from appearing in session files entirely.
|
|
157
|
+
|
|
158
|
+
**Option 2: Use Git filter**
|
|
159
|
+
|
|
160
|
+
If you store API keys in config files, the Git filter automatically cleans them on commit.
|
|
161
|
+
|
|
162
|
+
### Git filter usage
|
|
163
|
+
|
|
164
|
+
After initialization, commit normally:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
git add .claudeCodeSessions/
|
|
168
|
+
git commit -m "Add conversation history"
|
|
169
|
+
|
|
170
|
+
# API keys are automatically replaced with [REDACTED]
|
|
171
|
+
# Your local files remain unchanged
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Complete Git ignore (safest)
|
|
175
|
+
|
|
176
|
+
**RECOMMENDED**: Ignore session files entirely. Uncomment in `.gitignore`:
|
|
177
|
+
|
|
178
|
+
```gitignore
|
|
179
|
+
.claudeCodeSessions/
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
This prevents accidentally committing sensitive data to your repository.
|
|
183
|
+
|
|
184
|
+
## 🌐 How It Works
|
|
185
|
+
|
|
186
|
+
Claude Code stores chat sessions in `~/.claude/projects/{normalized-project-path}/`.
|
|
187
|
+
|
|
188
|
+
This CLI creates a symbolic link to a folder in your project (default: `.claudeCodeSessions/`), making the chat history part of your project.
|
|
189
|
+
|
|
190
|
+
### Example structure
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
Your Project/
|
|
194
|
+
├── src/
|
|
195
|
+
├── .claudeCodeSessions/ # Chat sessions (synced with ~/.claude)
|
|
196
|
+
│ ├── session-abc123.jsonl
|
|
197
|
+
│ └── session-def456.jsonl
|
|
198
|
+
├── .gitignore # Auto-updated
|
|
199
|
+
├── .gitattributes # Git filter configuration
|
|
200
|
+
└── package.json
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## 🔄 Syncing Across Machines
|
|
204
|
+
|
|
205
|
+
If you choose to sync session files:
|
|
206
|
+
|
|
207
|
+
1. Commit the `.claudeCodeSessions/` folder
|
|
208
|
+
2. Push to GitHub
|
|
209
|
+
3. Pull on another machine
|
|
210
|
+
4. Run `claude-chats-sync init` to create the symlink
|
|
211
|
+
|
|
212
|
+
## 🔧 Platform-specific Notes
|
|
213
|
+
|
|
214
|
+
### Windows
|
|
215
|
+
- Uses junction points (no admin privileges required)
|
|
216
|
+
- Supports PowerShell and CMD
|
|
217
|
+
- Run PowerShell scripts may require: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
|
|
218
|
+
|
|
219
|
+
### macOS
|
|
220
|
+
- Requires execute permissions: `chmod +x $(which claude-chats-sync)`
|
|
221
|
+
- Uses standard symbolic links
|
|
222
|
+
|
|
223
|
+
### Linux
|
|
224
|
+
- Requires execute permissions: `chmod +x $(which claude-chats-sync)`
|
|
225
|
+
- Uses standard symbolic links
|
|
226
|
+
|
|
227
|
+
## 🐛 Troubleshooting
|
|
228
|
+
|
|
229
|
+
### Symlink creation fails (Windows)
|
|
230
|
+
|
|
231
|
+
The tool uses junction points which work without admin privileges. If issues persist:
|
|
232
|
+
- Ensure Node.js is in your PATH
|
|
233
|
+
- Try running as administrator
|
|
234
|
+
- Check that your project path doesn't contain special characters
|
|
235
|
+
|
|
236
|
+
### History not syncing
|
|
237
|
+
|
|
238
|
+
1. Check if symlink exists:
|
|
239
|
+
- Windows: `dir %USERPROFILE%\.claude\projects`
|
|
240
|
+
- macOS/Linux: `ls -la ~/.claude/projects`
|
|
241
|
+
|
|
242
|
+
2. Verify symlink points to your project's `.claudeCodeSessions/` folder
|
|
243
|
+
|
|
244
|
+
### Reinitialize
|
|
245
|
+
|
|
246
|
+
Delete existing symlink:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# Windows
|
|
250
|
+
rmdir "%USERPROFILE%\.claude\projects\{project-name}"
|
|
251
|
+
|
|
252
|
+
# macOS/Linux
|
|
253
|
+
rm ~/.claude/projects/{project-name}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Then run `claude-chats-sync init` again.
|
|
257
|
+
|
|
258
|
+
## 📚 Related Projects
|
|
259
|
+
|
|
260
|
+
- [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=tubo.claude-code-chats-sync) - Full-featured VSCode extension with the same functionality
|
|
261
|
+
|
|
262
|
+
## 💰 Token Usage & Cost Considerations
|
|
263
|
+
|
|
264
|
+
> ⚠️ **IMPORTANT**: When sharing session files, each team member uses their own API key and incurs their own API costs.
|
|
265
|
+
|
|
266
|
+
### Key points
|
|
267
|
+
|
|
268
|
+
1. **Each member pays for their own usage**
|
|
269
|
+
- Every team member must configure their own API key
|
|
270
|
+
- When you continue a shared conversation, **you pay for all new tokens** generated
|
|
271
|
+
- The original creator's API key is **never** used
|
|
272
|
+
|
|
273
|
+
2. **Context window considerations**
|
|
274
|
+
- Long shared conversations consume more tokens as context
|
|
275
|
+
- A shared conversation with 50,000 tokens will consume ~50,000 input tokens each time a new member continues it
|
|
276
|
+
|
|
277
|
+
3. **Cost-saving best practices**
|
|
278
|
+
- Generate conversation summaries before sharing
|
|
279
|
+
- Start new conversations when possible
|
|
280
|
+
- Archive old sessions
|
|
281
|
+
- Monitor your API usage
|
|
282
|
+
|
|
283
|
+
For detailed cost considerations, see [Token Usage Guide](TOKEN_USAGE.md).
|
|
284
|
+
|
|
285
|
+
## 📝 License
|
|
286
|
+
|
|
287
|
+
MIT - see [LICENSE](LICENSE) file for details
|
|
288
|
+
|
|
289
|
+
## 🤝 Contributing
|
|
290
|
+
|
|
291
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
292
|
+
|
|
293
|
+
## 📞 Support
|
|
294
|
+
|
|
295
|
+
- 📧 [Issues](https://github.com/tubo70/claude-chats-sync/issues)
|
|
296
|
+
- 📖 [Documentation](https://github.com/tubo70/claude-chats-sync/wiki)
|
|
297
|
+
- 💬 [Discussions](https://github.com/tubo70/claude-chats-sync/discussions)
|
|
298
|
+
|
|
299
|
+
## 🔗 Links
|
|
300
|
+
|
|
301
|
+
- [npm](https://www.npmjs.com/package/claude-chats-sync)
|
|
302
|
+
- [GitHub](https://github.com/tubo70/claude-chats-sync)
|
|
303
|
+
- [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=tubo.claude-code-chats-sync)
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
Made with ❤️ by [tubo70](https://github.com/tubo70)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# claude-chats-sync
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/claude-chats-sync)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
> 跨平台命令行工具,将 Claude Code 聊天会话同步到项目目录
|
|
8
|
+
|
|
9
|
+
中文文档 | [English](README.md)
|
|
10
|
+
|
|
11
|
+
## ✨ 功能特性
|
|
12
|
+
|
|
13
|
+
- 🔄 **自动同步** - 创建符号链接将 Claude Code 会话同步到项目文件夹
|
|
14
|
+
- 📁 **项目本地历史** - 聊天会话存储在项目中,而不是用户主目录
|
|
15
|
+
- 🔒 **敏感数据保护** - 清理会话文件中的 API keys
|
|
16
|
+
- 🎯 **简单易用** - 单个命令即可初始化同步
|
|
17
|
+
- 📊 **状态跟踪** - 检查同步状态和会话数量
|
|
18
|
+
- 🌳 **Git 友好** - 配置 Git 过滤器以安全地进行版本控制
|
|
19
|
+
- 🔧 **跨平台** - 支持 Windows、macOS 和 Linux
|
|
20
|
+
|
|
21
|
+
## 📦 安装
|
|
22
|
+
|
|
23
|
+
### 全局安装(推荐)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g claude-chats-sync
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
安装后可以在任何地方使用 `claude-chats-sync` 命令。
|
|
30
|
+
|
|
31
|
+
### 本地安装
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -D claude-chats-sync
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
然后通过 `npx` 使用:
|
|
38
|
+
```bash
|
|
39
|
+
npx claude-chats-sync init
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 🚀 快速开始
|
|
43
|
+
|
|
44
|
+
### 初始化同步
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 在项目目录中
|
|
48
|
+
claude-chats-sync init
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
这将:
|
|
52
|
+
- 在项目中创建 `.claudeCodeSessions/` 文件夹
|
|
53
|
+
- 在 `~/.claude/projects/` 中创建符号链接
|
|
54
|
+
- 配置 Git 过滤器以自动清理敏感数据
|
|
55
|
+
- 将 `.claudeCodeSessions/` 添加到 `.gitignore`(默认被注释)
|
|
56
|
+
|
|
57
|
+
### 查看状态
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
claude-chats-sync status
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 打开历史文件夹
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
claude-chats-sync open
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 📖 命令
|
|
70
|
+
|
|
71
|
+
| 命令 | 说明 |
|
|
72
|
+
|------|------|
|
|
73
|
+
| `init` | 初始化当前项目的同步 |
|
|
74
|
+
| `status` | 检查同步状态和会话数量 |
|
|
75
|
+
| `open` | 在文件管理器中打开历史文件夹 |
|
|
76
|
+
| `clean` | 清理会话文件中的敏感数据 |
|
|
77
|
+
| `setup-git-filter` | 设置 Git 自动清理过滤器 |
|
|
78
|
+
| `help` | 显示帮助信息 |
|
|
79
|
+
|
|
80
|
+
## 🔧 使用示例
|
|
81
|
+
|
|
82
|
+
### 基本用法
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# 初始化同步
|
|
86
|
+
claude-chats-sync init
|
|
87
|
+
|
|
88
|
+
# 查看状态
|
|
89
|
+
claude-chats-sync status
|
|
90
|
+
|
|
91
|
+
# 打开会话文件夹
|
|
92
|
+
claude-chats-sync open
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 高级选项
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# 自定义文件夹名称
|
|
99
|
+
claude-chats-sync init --folder-name .sessions
|
|
100
|
+
|
|
101
|
+
# 强制迁移现有会话
|
|
102
|
+
claude-chats-sync init --force
|
|
103
|
+
|
|
104
|
+
# 指定项目路径
|
|
105
|
+
claude-chats-sync init --project-path /path/to/project
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 清理敏感数据
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# 手动清理
|
|
112
|
+
claude-chats-sync clean
|
|
113
|
+
|
|
114
|
+
# 设置 Git 自动过滤器
|
|
115
|
+
claude-chats-sync setup-git-filter
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## ⚙️ 环境变量配置(推荐)
|
|
119
|
+
|
|
120
|
+
通过环境变量配置 API keys,防止它们出现在会话文件中:
|
|
121
|
+
|
|
122
|
+
**Linux/macOS** - 添加到 `~/.bashrc` 或 `~/.zshrc`:
|
|
123
|
+
```bash
|
|
124
|
+
export ANTHROPIC_AUTH_TOKEN="sk-ant-..."
|
|
125
|
+
export ANTHROPIC_BASE_URL="https://api.example.com" # 可选:第三方 API
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Windows PowerShell**:
|
|
129
|
+
```powershell
|
|
130
|
+
$env:ANTHROPIC_AUTH_TOKEN="sk-ant-..."
|
|
131
|
+
$env:ANTHROPIC_BASE_URL="https://api.example.com"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Windows CMD(永久设置)**:
|
|
135
|
+
```cmd
|
|
136
|
+
setx ANTHROPIC_AUTH_TOKEN "sk-ant-..."
|
|
137
|
+
setx ANTHROPIC_BASE_URL="https://api.example.com"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 🔒 安全与版本控制
|
|
141
|
+
|
|
142
|
+
### ⚠️ 安全警告
|
|
143
|
+
|
|
144
|
+
会话文件可能包含敏感信息:
|
|
145
|
+
- API keys 和认证令牌
|
|
146
|
+
- 专有代码和业务逻辑
|
|
147
|
+
- 私人对话和内部讨论
|
|
148
|
+
- 系统路径和环境详情
|
|
149
|
+
|
|
150
|
+
虽然本工具提供了 API key 清理功能,**但没有任何自动清理是 100% 完整的**。只有在你完全理解并接受安全风险时才提交这些文件。
|
|
151
|
+
|
|
152
|
+
### API Key 配置选项
|
|
153
|
+
|
|
154
|
+
**选项 1:使用环境变量(推荐)**
|
|
155
|
+
|
|
156
|
+
配置 Claude Code 从环境变量使用 API keys,防止它们出现在会话文件中。这是最安全的方法。
|
|
157
|
+
|
|
158
|
+
**选项 2:使用 Git 过滤器**
|
|
159
|
+
|
|
160
|
+
如果你在配置文件中存储 API keys,Git 过滤器会在提交时自动清理它们。
|
|
161
|
+
|
|
162
|
+
### Git 过滤器使用
|
|
163
|
+
|
|
164
|
+
初始化后,正常提交即可:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
git add .claudeCodeSessions/
|
|
168
|
+
git commit -m "添加对话历史"
|
|
169
|
+
|
|
170
|
+
# API keys 自动替换为 [REDACTED]
|
|
171
|
+
# 你的本地文件保持不变
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 完全 Git 忽略(最安全)
|
|
175
|
+
|
|
176
|
+
**推荐**:完全忽略会话文件。在 `.gitignore` 中取消注释:
|
|
177
|
+
|
|
178
|
+
```gitignore
|
|
179
|
+
.claudeCodeSessions/
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
这可以防止意外将敏感数据提交到仓库。
|
|
183
|
+
|
|
184
|
+
## 🌐 工作原理
|
|
185
|
+
|
|
186
|
+
Claude Code 将聊天会话存储在 `~/.claude/projects/{normalized-project-path}/` 中。
|
|
187
|
+
|
|
188
|
+
本 CLI 创建一个指向项目文件夹的符号链接(默认:`.claudeCodeSessions/`),使聊天历史成为项目的一部分。
|
|
189
|
+
|
|
190
|
+
### 示例结构
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
Your Project/
|
|
194
|
+
├── src/
|
|
195
|
+
├── .claudeCodeSessions/ # 聊天会话(与 ~/.claude 同步)
|
|
196
|
+
│ ├── session-abc123.jsonl
|
|
197
|
+
│ └── session-def456.jsonl
|
|
198
|
+
├── .gitignore # 自动更新
|
|
199
|
+
├── .gitattributes # Git 过滤器配置
|
|
200
|
+
└── package.json
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## 🔄 跨机器同步
|
|
204
|
+
|
|
205
|
+
如果你选择同步会话文件:
|
|
206
|
+
|
|
207
|
+
1. 提交 `.claudeCodeSessions/` 文件夹
|
|
208
|
+
2. 推送到 GitHub
|
|
209
|
+
3. 在另一台机器上拉取
|
|
210
|
+
4. 运行 `claude-chats-sync init` 创建符号链接
|
|
211
|
+
|
|
212
|
+
## 🔧 平台特定说明
|
|
213
|
+
|
|
214
|
+
### Windows
|
|
215
|
+
- 使用 junction 点(不需要管理员权限)
|
|
216
|
+
- 支持 PowerShell 和 CMD
|
|
217
|
+
- 运行 PowerShell 脚本可能需要:`Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
|
|
218
|
+
|
|
219
|
+
### macOS
|
|
220
|
+
- 需要执行权限:`chmod +x $(which claude-chats-sync)`
|
|
221
|
+
- 使用标准符号链接
|
|
222
|
+
|
|
223
|
+
### Linux
|
|
224
|
+
- 需要执行权限:`chmod +x $(which claude-chats-sync)`
|
|
225
|
+
- 使用标准符号链接
|
|
226
|
+
|
|
227
|
+
## 🐛 故障排除
|
|
228
|
+
|
|
229
|
+
### 符号链接创建失败(Windows)
|
|
230
|
+
|
|
231
|
+
本工具使用 junction 点,无需管理员权限。如果仍有问题:
|
|
232
|
+
- 确保 Node.js 在 PATH 中
|
|
233
|
+
- 尝试以管理员身份运行
|
|
234
|
+
- 检查项目路径不包含特殊字符
|
|
235
|
+
|
|
236
|
+
### 历史记录未同步
|
|
237
|
+
|
|
238
|
+
1. 检查符号链接是否存在:
|
|
239
|
+
- Windows: `dir %USERPROFILE%\.claude\projects`
|
|
240
|
+
- macOS/Linux: `ls -la ~/.claude/projects`
|
|
241
|
+
|
|
242
|
+
2. 验证符号链接是否指向项目的 `.claudeCodeSessions/` 文件夹
|
|
243
|
+
|
|
244
|
+
### 重新初始化
|
|
245
|
+
|
|
246
|
+
删除现有符号链接:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# Windows
|
|
250
|
+
rmdir "%USERPROFILE%\.claude\projects\{project-name}"
|
|
251
|
+
|
|
252
|
+
# macOS/Linux
|
|
253
|
+
rm ~/.claude/projects/{project-name}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
然后再次运行 `claude-chats-sync init`。
|
|
257
|
+
|
|
258
|
+
## 📚 相关项目
|
|
259
|
+
|
|
260
|
+
- [VSCode 扩展](https://marketplace.visualstudio.com/items?itemName=tubo.claude-code-chats-sync) - 功能完整的 VSCode 扩展
|
|
261
|
+
|
|
262
|
+
## 💰 Token 使用与成本考虑
|
|
263
|
+
|
|
264
|
+
> ⚠️ **重要提示**:在团队成员间共享会话文件时,每个成员使用自己的 API key 并承担自己的 API 成本。
|
|
265
|
+
|
|
266
|
+
### 关键要点
|
|
267
|
+
|
|
268
|
+
1. **每个成员支付自己的使用费用**
|
|
269
|
+
- 每个团队成员必须配置自己的 API key
|
|
270
|
+
- 当你继续共享的对话时,**你需要为所有新生成的 tokens 付费**
|
|
271
|
+
- 原始创建者的 API key **不会被使用**
|
|
272
|
+
|
|
273
|
+
2. **上下文窗口考虑**
|
|
274
|
+
- 较长的共享对话会消耗更多 tokens 作为上下文
|
|
275
|
+
- 一个包含 50,000 tokens 的共享对话,每次新成员继续时都会消耗约 50,000 个输入 tokens
|
|
276
|
+
|
|
277
|
+
3. **节省成本的最佳实践**
|
|
278
|
+
- 共享前生成对话摘要
|
|
279
|
+
- 尽可能开始新对话
|
|
280
|
+
- 归档旧会话
|
|
281
|
+
- 监控 API 使用情况
|
|
282
|
+
|
|
283
|
+
## 📝 许可证
|
|
284
|
+
|
|
285
|
+
MIT - 详见 [LICENSE](LICENSE) 文件
|
|
286
|
+
|
|
287
|
+
## 🤝 贡献
|
|
288
|
+
|
|
289
|
+
欢迎贡献!请提交 issue 或拉取请求。
|
|
290
|
+
|
|
291
|
+
## 📞 支持
|
|
292
|
+
|
|
293
|
+
- 📧 [问题反馈](https://github.com/tubo70/claude-chats-sync/issues)
|
|
294
|
+
- 📖 [文档](https://github.com/tubo70/claude-chats-sync/wiki)
|
|
295
|
+
- 💬 [讨论](https://github.com/tubo70/claude-chats-sync/discussions)
|
|
296
|
+
|
|
297
|
+
## 🔗 链接
|
|
298
|
+
|
|
299
|
+
- [npm](https://www.npmjs.com/package/claude-chats-sync)
|
|
300
|
+
- [GitHub](https://github.com/tubo70/claude-chats-sync)
|
|
301
|
+
- [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=tubo.claude-code-chats-sync)
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
由 [tubo70](https://github.com/tubo70) 用 ❤️ 制作
|
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code Sync CLI
|
|
5
|
+
*
|
|
6
|
+
* 跨平台命令行工具,用于同步 Claude Code 聊天会话到项目目录
|
|
7
|
+
* Cross-platform CLI tool to sync Claude Code chat sessions to project directory
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node claude-sync-cli.js init # Initialize sync
|
|
11
|
+
* node claude-sync-cli.js status # Check sync status
|
|
12
|
+
* node claude-sync-cli.js open # Open history folder
|
|
13
|
+
* node claude-sync-cli.js clean # Clean sensitive data from session files
|
|
14
|
+
* node claude-sync-cli.js setup-git-filter # Setup Git filter for auto-cleaning
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
|
|
22
|
+
// ANSI 颜色代码 / ANSI color codes
|
|
23
|
+
const colors = {
|
|
24
|
+
reset: '\x1b[0m',
|
|
25
|
+
red: '\x1b[31m',
|
|
26
|
+
green: '\x1b[32m',
|
|
27
|
+
yellow: '\x1b[33m',
|
|
28
|
+
blue: '\x1b[34m',
|
|
29
|
+
cyan: '\x1b[36m'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// 工具函数 / Utility functions
|
|
33
|
+
function log(message, color = 'reset') {
|
|
34
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function error(message) {
|
|
38
|
+
log(`❌ Error: ${message}`, 'red');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function success(message) {
|
|
42
|
+
log(`✅ ${message}`, 'green');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function info(message) {
|
|
46
|
+
log(`ℹ️ ${message}`, 'cyan');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function warn(message) {
|
|
50
|
+
log(`⚠️ ${message}`, 'yellow');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 规范化项目路径为 Claude Code 格式
|
|
55
|
+
* Normalize project path to Claude Code format
|
|
56
|
+
*
|
|
57
|
+
* Windows: D:\Projects\MyProject -> d--Projects-MyProject
|
|
58
|
+
* Linux/Mac: /home/user/projects/my-project -> home-user-projects-my-project
|
|
59
|
+
*/
|
|
60
|
+
function normalizeProjectPath(projectPath) {
|
|
61
|
+
if (process.platform === 'win32') {
|
|
62
|
+
// Windows: Replace backslashes and colons with dashes, preserve case
|
|
63
|
+
return projectPath
|
|
64
|
+
.replace(/\\/g, '-')
|
|
65
|
+
.replace(/:/g, '-');
|
|
66
|
+
} else {
|
|
67
|
+
// Linux/Mac: Replace forward slashes with dashes, preserve case
|
|
68
|
+
return projectPath
|
|
69
|
+
.replace(/^\//, '') // Remove leading slash
|
|
70
|
+
.replace(/\//g, '-'); // Replace remaining slashes with dashes
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 获取 Claude Code 项目目录
|
|
76
|
+
* Get Claude Code projects directory
|
|
77
|
+
*/
|
|
78
|
+
function getClaudeProjectsDir() {
|
|
79
|
+
return path.join(os.homedir(), '.claude', 'projects');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 获取项目中的历史文件夹路径
|
|
84
|
+
* Get history folder path in the project
|
|
85
|
+
*/
|
|
86
|
+
function getHistoryFolderPath(projectPath, folderName = '.claudeCodeSessions') {
|
|
87
|
+
return path.join(projectPath, folderName);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 创建符号链接 (跨平台)
|
|
92
|
+
* Create symbolic link (cross-platform)
|
|
93
|
+
*/
|
|
94
|
+
function createSymlink(target, linkPath) {
|
|
95
|
+
if (process.platform === 'win32') {
|
|
96
|
+
// Windows: 使用 junction (不需要管理员权限)
|
|
97
|
+
// Windows: Use junction (no admin privileges required)
|
|
98
|
+
fs.symlinkSync(target, linkPath, 'junction');
|
|
99
|
+
} else {
|
|
100
|
+
// Unix: 使用符号链接
|
|
101
|
+
// Unix: Use symbolic link
|
|
102
|
+
fs.symlinkSync(target, linkPath);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 检查是否为符号链接
|
|
108
|
+
* Check if path is a symbolic link
|
|
109
|
+
*/
|
|
110
|
+
function isSymlink(symlinkPath) {
|
|
111
|
+
try {
|
|
112
|
+
const stats = fs.lstatSync(symlinkPath);
|
|
113
|
+
return stats.isSymbolicLink() || (process.platform === 'win32' && stats.isDirectory());
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 移动目录 (递归)
|
|
121
|
+
* Move directory (recursive)
|
|
122
|
+
*/
|
|
123
|
+
function moveDirectory(src, dest) {
|
|
124
|
+
// 创建目标目录
|
|
125
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
126
|
+
|
|
127
|
+
// 递归复制所有文件和子目录
|
|
128
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
129
|
+
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
const srcPath = path.join(src, entry.name);
|
|
132
|
+
const destPath = path.join(dest, entry.name);
|
|
133
|
+
|
|
134
|
+
if (entry.isDirectory()) {
|
|
135
|
+
moveDirectory(srcPath, destPath);
|
|
136
|
+
} else {
|
|
137
|
+
fs.copyFileSync(srcPath, destPath);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 删除源目录
|
|
142
|
+
fs.rmSync(src, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 清理会话文件内容中的敏感信息
|
|
147
|
+
* Clean sensitive information from session file content
|
|
148
|
+
*/
|
|
149
|
+
function cleanSensitiveData(content) {
|
|
150
|
+
// Pattern for Anthropic API keys (normal format)
|
|
151
|
+
const apiKeyPattern = /"primaryApiKey"\s*:\s*"sk-ant-[^"]*"/g;
|
|
152
|
+
|
|
153
|
+
// Pattern for API keys within escaped JSON strings
|
|
154
|
+
const apiKeyPatternEscaped = /\\"primaryApiKey\\":\s*\\"sk-ant-[^"]*\\"/g;
|
|
155
|
+
|
|
156
|
+
// Pattern for ANTHROPIC_AUTH_TOKEN (escaped format)
|
|
157
|
+
const authTokenPatternEscaped = /\\"ANTHROPIC_AUTH_TOKEN\\"\\s*:\\s*\\"[^"]*\\"/g;
|
|
158
|
+
|
|
159
|
+
// Pattern for other API keys
|
|
160
|
+
const genericApiKeyPattern = /"(apiKey|api_key|authorization|token|bearer)"\s*:\s*"[^"]*"/gi;
|
|
161
|
+
|
|
162
|
+
// Clean API keys
|
|
163
|
+
let cleaned = content.replace(apiKeyPattern, '"primaryApiKey": "[REDACTED]"');
|
|
164
|
+
cleaned = cleaned.replace(apiKeyPatternEscaped, '\\"primaryApiKey\\": \\"[REDACTED]\\"');
|
|
165
|
+
cleaned = cleaned.replace(authTokenPatternEscaped, '\\"ANTHROPIC_AUTH_TOKEN\\": \\"[REDACTED]\\"');
|
|
166
|
+
cleaned = cleaned.replace(genericApiKeyPattern, '"$1": "[REDACTED]"');
|
|
167
|
+
|
|
168
|
+
return cleaned;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 初始化同步
|
|
173
|
+
* Initialize sync
|
|
174
|
+
*/
|
|
175
|
+
function init(projectPath, options = {}) {
|
|
176
|
+
const { folderName = '.claudeCodeSessions', force = false } = options;
|
|
177
|
+
|
|
178
|
+
const historyFolder = getHistoryFolderPath(projectPath, folderName);
|
|
179
|
+
const claudeProjectsDir = getClaudeProjectsDir();
|
|
180
|
+
const normalizedPath = normalizeProjectPath(projectPath);
|
|
181
|
+
const symlinkPath = path.join(claudeProjectsDir, normalizedPath);
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// 检查符号链接是否已存在
|
|
185
|
+
// Check if symlink already exists
|
|
186
|
+
if (fs.existsSync(symlinkPath)) {
|
|
187
|
+
if (isSymlink(symlinkPath)) {
|
|
188
|
+
success('Claude Code Chats Sync already initialized');
|
|
189
|
+
info(`History folder: ${historyFolder}`);
|
|
190
|
+
info(`Linked to: ${symlinkPath}`);
|
|
191
|
+
return;
|
|
192
|
+
} else if (fs.lstatSync(symlinkPath).isDirectory()) {
|
|
193
|
+
// 现有真实目录 - 用户之前使用过 Claude Code
|
|
194
|
+
// Existing real directory - user has used Claude Code before
|
|
195
|
+
const files = fs.readdirSync(symlinkPath);
|
|
196
|
+
const sessionFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
197
|
+
|
|
198
|
+
if (sessionFiles.length > 0 && !force) {
|
|
199
|
+
warn(`Found ${sessionFiles.length} existing Claude Code session(s) in Claude's storage.`);
|
|
200
|
+
info('Use --force to move them to your project folder');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (sessionFiles.length > 0 && force) {
|
|
205
|
+
// 移动现有目录到项目文件夹
|
|
206
|
+
// Move existing directory to project folder
|
|
207
|
+
moveDirectory(symlinkPath, historyFolder);
|
|
208
|
+
success(`Moved ${sessionFiles.length} session(s) to project folder!`);
|
|
209
|
+
} else {
|
|
210
|
+
// 空目录,直接删除
|
|
211
|
+
// Empty directory, just remove it
|
|
212
|
+
fs.rmSync(symlinkPath, { recursive: true, force: true });
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
error(`A file exists at Claude Code location: ${symlinkPath}`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 创建历史文件夹 (如果不存在)
|
|
221
|
+
// Create history folder if it doesn't exist
|
|
222
|
+
if (!fs.existsSync(historyFolder)) {
|
|
223
|
+
fs.mkdirSync(historyFolder, { recursive: true });
|
|
224
|
+
success(`Created folder: ${historyFolder}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 确保 .claude/projects 目录存在
|
|
228
|
+
// Ensure .claude/projects directory exists
|
|
229
|
+
if (!fs.existsSync(claudeProjectsDir)) {
|
|
230
|
+
fs.mkdirSync(claudeProjectsDir, { recursive: true });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 创建符号链接
|
|
234
|
+
// Create symbolic link
|
|
235
|
+
createSymlink(historyFolder, symlinkPath);
|
|
236
|
+
|
|
237
|
+
success('Claude Code Chats Sync initialized!');
|
|
238
|
+
info(`History folder: ${historyFolder}`);
|
|
239
|
+
info(`Linked to: ${symlinkPath}`);
|
|
240
|
+
|
|
241
|
+
// 添加到 .gitignore
|
|
242
|
+
// Add to .gitignore
|
|
243
|
+
addToGitIgnore(projectPath, folderName);
|
|
244
|
+
|
|
245
|
+
// 设置 Git 过滤器
|
|
246
|
+
// Setup Git filter
|
|
247
|
+
setupGitFilter(projectPath, folderName, false);
|
|
248
|
+
|
|
249
|
+
} catch (err) {
|
|
250
|
+
error(`Failed to initialize: ${err.message}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 检查同步状态
|
|
256
|
+
* Check sync status
|
|
257
|
+
*/
|
|
258
|
+
function status(projectPath, options = {}) {
|
|
259
|
+
const { folderName = '.claudeCodeSessions' } = options;
|
|
260
|
+
|
|
261
|
+
const historyFolder = getHistoryFolderPath(projectPath, folderName);
|
|
262
|
+
const claudeProjectsDir = getClaudeProjectsDir();
|
|
263
|
+
const normalizedPath = normalizeProjectPath(projectPath);
|
|
264
|
+
const symlinkPath = path.join(claudeProjectsDir, normalizedPath);
|
|
265
|
+
|
|
266
|
+
log('\n📊 Claude Code Chats Sync Status\n', 'blue');
|
|
267
|
+
|
|
268
|
+
// 检查历史文件夹
|
|
269
|
+
// Check history folder
|
|
270
|
+
if (fs.existsSync(historyFolder)) {
|
|
271
|
+
const files = fs.readdirSync(historyFolder).filter(f => f.endsWith('.jsonl'));
|
|
272
|
+
success('History folder exists');
|
|
273
|
+
info(` Path: ${historyFolder}`);
|
|
274
|
+
info(` Sessions: ${files.length}`);
|
|
275
|
+
} else {
|
|
276
|
+
error('History folder not found');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 检查符号链接
|
|
280
|
+
// Check symlink
|
|
281
|
+
if (fs.existsSync(symlinkPath)) {
|
|
282
|
+
success('Symlink created');
|
|
283
|
+
info(` Path: ${symlinkPath}`);
|
|
284
|
+
} else {
|
|
285
|
+
error('Symlink not created');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log('');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 添加到 .gitignore
|
|
293
|
+
* Add to .gitignore
|
|
294
|
+
*/
|
|
295
|
+
function addToGitIgnore(projectPath, folderName = '.claudeCodeSessions') {
|
|
296
|
+
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
let content = '';
|
|
300
|
+
if (fs.existsSync(gitignorePath)) {
|
|
301
|
+
content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const ignoreEntry = `# Claude Code conversation history
|
|
305
|
+
# Uncomment the line below to ignore session files, OR configure Git filter for safe sharing
|
|
306
|
+
# ${folderName}/`;
|
|
307
|
+
|
|
308
|
+
// 仅在不存在时添加
|
|
309
|
+
// Only add if not already present
|
|
310
|
+
if (!content.includes(`# ${folderName}/`) && !content.includes(`${folderName}/`)) {
|
|
311
|
+
if (content && !content.endsWith('\n')) {
|
|
312
|
+
content += '\n';
|
|
313
|
+
}
|
|
314
|
+
content += `\n${ignoreEntry}\n`;
|
|
315
|
+
fs.writeFileSync(gitignorePath, content, 'utf-8');
|
|
316
|
+
success('Added .gitignore entry (commented by default)');
|
|
317
|
+
}
|
|
318
|
+
} catch (err) {
|
|
319
|
+
warn('Could not update .gitignore (not a Git repository?)');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 设置 Git 过滤器
|
|
325
|
+
* Setup Git filter
|
|
326
|
+
*/
|
|
327
|
+
function setupGitFilter(projectPath, folderName = '.claudeCodeSessions', showMessage = true) {
|
|
328
|
+
try {
|
|
329
|
+
// 检查是否为 Git 仓库
|
|
330
|
+
// Check if we're in a Git repository
|
|
331
|
+
const gitDir = path.join(projectPath, '.git');
|
|
332
|
+
if (!fs.existsSync(gitDir)) {
|
|
333
|
+
warn('Not a Git repository. Git filter will not be configured.');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 创建清理过滤器脚本
|
|
338
|
+
// Create clean filter script
|
|
339
|
+
const filterScriptPath = path.join(projectPath, '.gitfilters', 'clean-sessions.js');
|
|
340
|
+
const filterDir = path.dirname(filterScriptPath);
|
|
341
|
+
|
|
342
|
+
if (!fs.existsSync(filterDir)) {
|
|
343
|
+
fs.mkdirSync(filterDir, { recursive: true });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const filterScript = `#!/usr/bin/env node
|
|
347
|
+
const fs = require('fs');
|
|
348
|
+
|
|
349
|
+
// Pattern for Anthropic API keys (normal format)
|
|
350
|
+
const apiKeyPattern = /"primaryApiKey"\\\\s*:\\\\s*"sk-ant-[^"]*"/g;
|
|
351
|
+
|
|
352
|
+
// Pattern for API keys within escaped JSON strings
|
|
353
|
+
const apiKeyPatternEscaped = /\\\\\\\\"primaryApiKey\\\\\\\\"\\\\s*:\\\\\\\\s*\\\\\\"sk-ant-[^"]*\\\\\\"/g;
|
|
354
|
+
|
|
355
|
+
// Pattern for ANTHROPIC_AUTH_TOKEN (escaped format)
|
|
356
|
+
const authTokenPatternEscaped = /\\\\"ANTHROPIC_AUTH_TOKEN\\\\"\\\\s*:\\\\\\s*\\\\"[^"]*\\\\"/g;
|
|
357
|
+
|
|
358
|
+
// Pattern for other API keys
|
|
359
|
+
const genericApiKeyPattern = /"(apiKey|api_key|authorization|token|bearer)"\\\\s*:\\\\s*"[^"]*"/gi;
|
|
360
|
+
|
|
361
|
+
let data = '';
|
|
362
|
+
process.stdin.setEncoding('utf8');
|
|
363
|
+
|
|
364
|
+
process.stdin.on('data', (chunk) => {
|
|
365
|
+
data += chunk;
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
process.stdin.on('end', () => {
|
|
369
|
+
let cleaned = data.replace(apiKeyPattern, '"primaryApiKey": "[REDACTED]"');
|
|
370
|
+
cleaned = cleaned.replace(apiKeyPatternEscaped, '\\\\\\\\"primaryApiKey\\\\\\\\": \\\\"[REDACTED]\\\\"');
|
|
371
|
+
cleaned = cleaned.replace(authTokenPatternEscaped, '\\\\\\\\"ANTHROPIC_AUTH_TOKEN\\\\\\\\": \\\\"[REDACTED]\\\\"');
|
|
372
|
+
cleaned = cleaned.replace(genericApiKeyPattern, '"$1": "[REDACTED]"');
|
|
373
|
+
process.stdout.write(cleaned);
|
|
374
|
+
});
|
|
375
|
+
`;
|
|
376
|
+
|
|
377
|
+
fs.writeFileSync(filterScriptPath, filterScript, 'utf-8');
|
|
378
|
+
|
|
379
|
+
// 在 Unix-like 系统上设置为可执行
|
|
380
|
+
// Make it executable on Unix-like systems
|
|
381
|
+
if (process.platform !== 'win32') {
|
|
382
|
+
try {
|
|
383
|
+
fs.chmodSync(filterScriptPath, 0o755);
|
|
384
|
+
} catch (e) {
|
|
385
|
+
// Ignore permission errors
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 在 .gitconfig 中配置 Git 过滤器
|
|
390
|
+
// Configure Git filter in .gitconfig
|
|
391
|
+
const gitConfigPath = path.join(projectPath, '.gitconfig');
|
|
392
|
+
|
|
393
|
+
let gitConfig = '';
|
|
394
|
+
if (fs.existsSync(gitConfigPath)) {
|
|
395
|
+
gitConfig = fs.readFileSync(gitConfigPath, 'utf-8');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!gitConfig.includes('[filter "claude-clean"]')) {
|
|
399
|
+
if (gitConfig && !gitConfig.endsWith('\n')) {
|
|
400
|
+
gitConfig += '\n';
|
|
401
|
+
}
|
|
402
|
+
gitConfig += `[filter "claude-clean"]
|
|
403
|
+
clean = node .gitfilters/clean-sessions.js
|
|
404
|
+
`;
|
|
405
|
+
fs.writeFileSync(gitConfigPath, gitConfig, 'utf-8');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// 在本地 Git 配置中配置过滤器
|
|
409
|
+
// Configure the filter in local Git config
|
|
410
|
+
try {
|
|
411
|
+
execSync(
|
|
412
|
+
`git config filter.claude-clean.clean "node .gitfilters/clean-sessions.js"`,
|
|
413
|
+
{ cwd: projectPath, stdio: 'pipe' }
|
|
414
|
+
);
|
|
415
|
+
} catch (err) {
|
|
416
|
+
warn(`Failed to configure local Git filter: ${err.message}`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// 在 .gitattributes 中配置过滤器
|
|
420
|
+
// Configure the filter in .gitattributes
|
|
421
|
+
const gitAttributesPath = path.join(projectPath, '.gitattributes');
|
|
422
|
+
|
|
423
|
+
let gitAttributes = '';
|
|
424
|
+
if (fs.existsSync(gitAttributesPath)) {
|
|
425
|
+
gitAttributes = fs.readFileSync(gitAttributesPath, 'utf-8');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const filterLine = `${folderName}/*.jsonl filter=claude-clean`;
|
|
429
|
+
|
|
430
|
+
if (!gitAttributes.includes(filterLine)) {
|
|
431
|
+
if (gitAttributes && !gitAttributes.endsWith('\n')) {
|
|
432
|
+
gitAttributes += '\n';
|
|
433
|
+
}
|
|
434
|
+
gitAttributes += `\n# Claude Code sessions - clean sensitive data on commit\n${filterLine}\n`;
|
|
435
|
+
fs.writeFileSync(gitAttributesPath, gitAttributes, 'utf-8');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (showMessage) {
|
|
439
|
+
success('Git filter configured');
|
|
440
|
+
info('Session files will be automatically cleaned on commit');
|
|
441
|
+
info('Original files remain unchanged. Only committed versions are cleaned.');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
} catch (err) {
|
|
445
|
+
error(`Failed to setup Git filter: ${err.message}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* 清理会话文件中的敏感数据
|
|
451
|
+
* Clean sensitive data from session files
|
|
452
|
+
*/
|
|
453
|
+
function cleanSessions(projectPath, options = {}) {
|
|
454
|
+
const { folderName = '.claudeCodeSessions' } = options;
|
|
455
|
+
|
|
456
|
+
const historyFolder = getHistoryFolderPath(projectPath, folderName);
|
|
457
|
+
|
|
458
|
+
if (!fs.existsSync(historyFolder)) {
|
|
459
|
+
error('History folder does not exist');
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const files = fs.readdirSync(historyFolder).filter(f => f.endsWith('.jsonl'));
|
|
464
|
+
|
|
465
|
+
if (files.length === 0) {
|
|
466
|
+
warn('No session files to clean');
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
info(`Cleaning ${files.length} session file(s)...`);
|
|
471
|
+
|
|
472
|
+
let cleanedCount = 0;
|
|
473
|
+
for (const file of files) {
|
|
474
|
+
const filePath = path.join(historyFolder, file);
|
|
475
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
476
|
+
const cleaned = cleanSensitiveData(content);
|
|
477
|
+
|
|
478
|
+
// 将清理后的内容写回文件
|
|
479
|
+
// Write cleaned content back to file
|
|
480
|
+
fs.writeFileSync(filePath, cleaned, 'utf-8');
|
|
481
|
+
cleanedCount++;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
success(`Cleaned ${cleanedCount} session file(s)`);
|
|
485
|
+
info('Sensitive data has been redacted');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* 打开历史文件夹
|
|
490
|
+
* Open history folder
|
|
491
|
+
*/
|
|
492
|
+
function openFolder(projectPath, options = {}) {
|
|
493
|
+
const { folderName = '.claudeCodeSessions' } = options;
|
|
494
|
+
|
|
495
|
+
const historyFolder = getHistoryFolderPath(projectPath, folderName);
|
|
496
|
+
|
|
497
|
+
if (!fs.existsSync(historyFolder)) {
|
|
498
|
+
error('History folder does not exist. Please initialize first.');
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
const { exec } = require('child_process');
|
|
504
|
+
|
|
505
|
+
let command;
|
|
506
|
+
switch (process.platform) {
|
|
507
|
+
case 'darwin':
|
|
508
|
+
command = 'open';
|
|
509
|
+
break;
|
|
510
|
+
case 'win32':
|
|
511
|
+
command = 'explorer';
|
|
512
|
+
break;
|
|
513
|
+
default:
|
|
514
|
+
command = 'xdg-open';
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
exec(`${command} "${historyFolder}"`);
|
|
518
|
+
success(`Opened history folder: ${historyFolder}`);
|
|
519
|
+
} catch (err) {
|
|
520
|
+
error(`Failed to open folder: ${err.message}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* 显示帮助信息
|
|
526
|
+
* Show help message
|
|
527
|
+
*/
|
|
528
|
+
function showHelp() {
|
|
529
|
+
const help = `
|
|
530
|
+
Claude Code Sync CLI - 跨平台 Claude Code 会话同步工具
|
|
531
|
+
Claude Code Sync CLI - Cross-platform Claude Code session sync tool
|
|
532
|
+
|
|
533
|
+
Usage: node claude-sync-cli.js <command> [options]
|
|
534
|
+
|
|
535
|
+
Commands:
|
|
536
|
+
init Initialize sync for current project
|
|
537
|
+
status Check sync status and session count
|
|
538
|
+
open Open history folder in file manager
|
|
539
|
+
clean Clean sensitive data from session files
|
|
540
|
+
setup-git-filter Setup Git filter for automatic cleaning
|
|
541
|
+
help Show this help message
|
|
542
|
+
|
|
543
|
+
Options:
|
|
544
|
+
--folder-name <name> History folder name (default: .claudeCodeSessions)
|
|
545
|
+
--force Force migration of existing sessions
|
|
546
|
+
--project-path <path> Project path (default: current directory)
|
|
547
|
+
|
|
548
|
+
Examples:
|
|
549
|
+
node claude-sync-cli.js init
|
|
550
|
+
node claude-sync-cli.js init --folder-name .sessions
|
|
551
|
+
node claude-sync-cli.js init --force
|
|
552
|
+
node claude-sync-cli.js status
|
|
553
|
+
node claude-sync-cli.js clean
|
|
554
|
+
node claude-sync-cli.js setup-git-filter
|
|
555
|
+
|
|
556
|
+
Environment Variables:
|
|
557
|
+
ANTHROPIC_AUTH_TOKEN Recommended: Configure API key via env var
|
|
558
|
+
ANTHROPIC_BASE_URL Optional: Third-party API endpoint
|
|
559
|
+
|
|
560
|
+
For more information, visit: https://github.com/tubo70/claude-code-sync-extension
|
|
561
|
+
`;
|
|
562
|
+
|
|
563
|
+
console.log(help);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* 主函数
|
|
568
|
+
* Main function
|
|
569
|
+
*/
|
|
570
|
+
function main() {
|
|
571
|
+
const args = process.argv.slice(2);
|
|
572
|
+
const command = args[0];
|
|
573
|
+
|
|
574
|
+
// 解析选项
|
|
575
|
+
// Parse options
|
|
576
|
+
const options = {};
|
|
577
|
+
let projectPath = process.cwd();
|
|
578
|
+
|
|
579
|
+
for (let i = 1; i < args.length; i++) {
|
|
580
|
+
const arg = args[i];
|
|
581
|
+
if (arg === '--folder-name' && args[i + 1]) {
|
|
582
|
+
options.folderName = args[++i];
|
|
583
|
+
} else if (arg === '--project-path' && args[i + 1]) {
|
|
584
|
+
projectPath = args[++i];
|
|
585
|
+
} else if (arg === '--force') {
|
|
586
|
+
options.force = true;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
591
|
+
showHelp();
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
switch (command) {
|
|
596
|
+
case 'init':
|
|
597
|
+
init(projectPath, options);
|
|
598
|
+
break;
|
|
599
|
+
case 'status':
|
|
600
|
+
status(projectPath, options);
|
|
601
|
+
break;
|
|
602
|
+
case 'open':
|
|
603
|
+
openFolder(projectPath, options);
|
|
604
|
+
break;
|
|
605
|
+
case 'clean':
|
|
606
|
+
cleanSessions(projectPath, options);
|
|
607
|
+
break;
|
|
608
|
+
case 'setup-git-filter':
|
|
609
|
+
setupGitFilter(projectPath, options.folderName, true);
|
|
610
|
+
break;
|
|
611
|
+
default:
|
|
612
|
+
error(`Unknown command: ${command}`);
|
|
613
|
+
info('Run "node claude-sync-cli.js help" for usage information');
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// 运行主函数
|
|
619
|
+
// Run main function
|
|
620
|
+
main();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Claude Code Sync CLI - PowerShell wrapper
|
|
2
|
+
|
|
3
|
+
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
4
|
+
$CliScript = Join-Path $ScriptDir "claude-sync.js"
|
|
5
|
+
|
|
6
|
+
# Check if Node.js is installed
|
|
7
|
+
try {
|
|
8
|
+
$null = & node --version 2>&1
|
|
9
|
+
} catch {
|
|
10
|
+
Write-Host "❌ Error: Node.js is not installed or not in PATH" -ForegroundColor Red
|
|
11
|
+
Write-Host "Please install Node.js from https://nodejs.org/" -ForegroundColor Yellow
|
|
12
|
+
exit 1
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Run the CLI script
|
|
16
|
+
& node $CliScript $args
|
|
17
|
+
exit $LASTEXITCODE
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-chats-sync",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Cross-platform CLI tool to sync Claude Code chat sessions to project directory",
|
|
5
|
+
"main": "bin/claude-chats-sync.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-chats-sync": "./bin/claude-chats-sync.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"init": "node bin/claude-chats-sync.js init",
|
|
11
|
+
"status": "node bin/claude-chats-sync.js status",
|
|
12
|
+
"open": "node bin/claude-chats-sync.js open",
|
|
13
|
+
"clean": "node bin/claude-chats-sync.js clean",
|
|
14
|
+
"setup-git-filter": "node bin/claude-chats-sync.js setup-git-filter",
|
|
15
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"claude-ai",
|
|
21
|
+
"sync",
|
|
22
|
+
"chat",
|
|
23
|
+
"sessions",
|
|
24
|
+
"cli",
|
|
25
|
+
"vscode",
|
|
26
|
+
"git",
|
|
27
|
+
"version-control",
|
|
28
|
+
"developer-tools"
|
|
29
|
+
],
|
|
30
|
+
"author": {
|
|
31
|
+
"name": "tubo70",
|
|
32
|
+
"email": "445527@qq.com"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/tubo70/claude-chats-sync.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/tubo70/claude-chats-sync/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/tubo70/claude-chats-sync#readme",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=14.0.0"
|
|
45
|
+
},
|
|
46
|
+
"os": [
|
|
47
|
+
"win32",
|
|
48
|
+
"darwin",
|
|
49
|
+
"linux"
|
|
50
|
+
],
|
|
51
|
+
"preferGlobal": true,
|
|
52
|
+
"files": [
|
|
53
|
+
"bin/",
|
|
54
|
+
"README.md",
|
|
55
|
+
"LICENSE"
|
|
56
|
+
]
|
|
57
|
+
}
|