Last active
October 12, 2025 17:05
-
-
Save hangox/69b10be0a640de9d30099d70c021a36c to your computer and use it in GitHub Desktop.
同步所有 Claude Code 项目的脚本 - 自动读取并同步 ~/.claude/projects/ 中的所有项目
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| 同步所有 Claude Code 项目的脚本 | |
| 读取 ~/.claude/projects/ 目录中的所有项目, | |
| 并在每个项目目录中运行 specstory sync 命令 | |
| 支持平台: macOS, Linux, Windows | |
| """ | |
| import json | |
| import os | |
| import platform | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| from typing import Optional, Set | |
| def get_system_info() -> dict: | |
| """获取系统信息""" | |
| system = platform.system().lower() | |
| return { | |
| 'system': system, | |
| 'is_windows': system == 'windows', | |
| 'is_mac': system == 'darwin', | |
| 'is_linux': system == 'linux' | |
| } | |
| def get_claude_projects_dir() -> Path: | |
| """获取 Claude Code 的 projects 目录(跨平台)""" | |
| home = Path.home() | |
| # Windows 和 Unix-like 系统都使用 .claude 目录 | |
| claude_dir = home / ".claude" / "projects" | |
| if not claude_dir.exists(): | |
| raise FileNotFoundError( | |
| f"Claude projects 目录不存在: {claude_dir}\n" | |
| f"请确认已安装 Claude Code 并创建过项目" | |
| ) | |
| return claude_dir | |
| def get_project_path_from_sessions(project_dir: Path) -> Optional[str]: | |
| """ | |
| 从项目的 session 文件中提取真实的项目路径 | |
| 参考 Rust 代码 get_project_path_from_sessions 函数 | |
| Args: | |
| project_dir: Claude 项目目录(~/.claude/projects/xxx) | |
| Returns: | |
| 项目的真实路径,如果无法提取则返回 None | |
| """ | |
| # 查找所有 .jsonl 文件 | |
| jsonl_files = list(project_dir.glob("*.jsonl")) | |
| if not jsonl_files: | |
| return None | |
| # 遍历每个 JSONL 文件 | |
| for jsonl_file in jsonl_files: | |
| try: | |
| with open(jsonl_file, 'r', encoding='utf-8') as f: | |
| # 只检查前 10 行(参考 Rust 实现) | |
| for i, line in enumerate(f): | |
| if i >= 10: | |
| break | |
| line = line.strip() | |
| if not line: | |
| continue | |
| try: | |
| data = json.loads(line) | |
| # 提取 cwd 字段 | |
| cwd = data.get('cwd') | |
| if cwd and isinstance(cwd, str) and cwd.strip(): | |
| return cwd | |
| except json.JSONDecodeError: | |
| continue | |
| except Exception as e: | |
| print(f"读取文件 {jsonl_file} 时出错: {e}") | |
| continue | |
| return None | |
| def get_all_claude_projects() -> Set[str]: | |
| """ | |
| 获取所有 Claude Code 项目的真实路径 | |
| Returns: | |
| 项目路径的集合(去重) | |
| """ | |
| projects_dir = get_claude_projects_dir() | |
| project_paths = set() | |
| print(f"正在扫描 Claude Code 项目目录: {projects_dir}\n") | |
| # 遍历所有子目录 | |
| for entry in projects_dir.iterdir(): | |
| if not entry.is_dir(): | |
| continue | |
| # 从 session 文件中提取真实路径 | |
| project_path = get_project_path_from_sessions(entry) | |
| if project_path: | |
| # 验证路径是否存在 | |
| if os.path.exists(project_path): | |
| project_paths.add(project_path) | |
| print(f"✓ 找到项目: {project_path}") | |
| else: | |
| print(f"✗ 路径不存在: {project_path}") | |
| else: | |
| print(f"✗ 无法从 {entry.name} 提取项目路径") | |
| return project_paths | |
| def run_specstory_sync(project_path: str) -> bool: | |
| """ | |
| 在指定项目目录中运行 specstory sync 命令(跨平台) | |
| Args: | |
| project_path: 项目目录路径 | |
| Returns: | |
| 命令是否执行成功 | |
| """ | |
| print(f"\n{'='*80}") | |
| print(f"正在同步项目: {project_path}") | |
| print(f"{'='*80}") | |
| try: | |
| sys_info = get_system_info() | |
| # 根据不同平台构建命令 | |
| if sys_info['is_windows']: | |
| # Windows: 使用 cmd.exe 或 PowerShell | |
| result = subprocess.run( | |
| ['specstory', 'sync'], | |
| cwd=project_path, | |
| capture_output=True, | |
| text=True, | |
| timeout=300, | |
| shell=True # Windows 需要 shell=True 来查找命令 | |
| ) | |
| else: | |
| # macOS / Linux: 直接运行命令 | |
| result = subprocess.run( | |
| ['specstory', 'sync'], | |
| cwd=project_path, | |
| capture_output=True, | |
| text=True, | |
| timeout=300, | |
| shell=False | |
| ) | |
| # 打印输出 | |
| if result.stdout: | |
| print(result.stdout) | |
| if result.stderr: | |
| print(result.stderr, flush=True) | |
| if result.returncode == 0: | |
| print(f"✓ 同步成功: {project_path}") | |
| return True | |
| else: | |
| print(f"✗ 同步失败 (退出码 {result.returncode}): {project_path}") | |
| return False | |
| except FileNotFoundError: | |
| print(f"✗ 未找到 specstory 命令,请确认已安装并添加到 PATH") | |
| return False | |
| except subprocess.TimeoutExpired: | |
| print(f"✗ 命令超时: {project_path}") | |
| return False | |
| except Exception as e: | |
| print(f"✗ 执行出错: {e}") | |
| return False | |
| def main(): | |
| """主函数""" | |
| sys_info = get_system_info() | |
| print("Claude Code 项目同步工具") | |
| print(f"运行平台: {sys_info['system']}") | |
| print(f"Python 版本: {sys.version.split()[0]}") | |
| print("="*80) | |
| try: | |
| # 获取所有项目路径 | |
| project_paths = get_all_claude_projects() | |
| if not project_paths: | |
| print("\n未找到任何 Claude Code 项目") | |
| return | |
| print(f"\n共找到 {len(project_paths)} 个项目\n") | |
| # 统计结果 | |
| success_count = 0 | |
| fail_count = 0 | |
| # 对每个项目运行 specstory sync | |
| for i, project_path in enumerate(sorted(project_paths), 1): | |
| print(f"\n[{i}/{len(project_paths)}] 处理项目...") | |
| if run_specstory_sync(project_path): | |
| success_count += 1 | |
| else: | |
| fail_count += 1 | |
| # 打印总结 | |
| print(f"\n{'='*80}") | |
| print("同步完成!") | |
| print(f"成功: {success_count} 个项目") | |
| print(f"失败: {fail_count} 个项目") | |
| print(f"总计: {len(project_paths)} 个项目") | |
| print(f"{'='*80}") | |
| except FileNotFoundError as e: | |
| print(f"错误: {e}") | |
| except KeyboardInterrupt: | |
| print("\n\n用户中断操作") | |
| except Exception as e: | |
| print(f"\n发生错误: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment