ccmanager 1.3.1 → 1.4.0

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/README.md CHANGED
@@ -15,6 +15,7 @@ https://github.com/user-attachments/assets/15914a88-e288-4ac9-94d5-8127f2e19dbf
15
15
  - Switch between sessions seamlessly
16
16
  - Visual status indicators for session states (busy, waiting, idle)
17
17
  - Create, merge, and delete worktrees from within the app
18
+ - **Copy Claude Code session data** between worktrees to maintain conversation context
18
19
  - Configurable keyboard shortcuts
19
20
  - Command presets with automatic fallback support
20
21
  - Configurable state detection strategies for different CLI tools
@@ -154,6 +155,41 @@ CCManager supports configuring the command and arguments used to run Claude Code
154
155
  For detailed configuration options and examples, see [docs/command-config.md](docs/command-config.md).
155
156
 
156
157
 
158
+ ## Session Data Copying
159
+
160
+ CCManager can copy Claude Code session data (conversation history, context, and project state) when creating new worktrees, allowing you to maintain context across different branches.
161
+
162
+ ### Features
163
+
164
+ - **Seamless Context Transfer**: Continue conversations in new worktrees without losing context
165
+ - **Configurable Default**: Set whether to copy session data by default
166
+ - **Per-Creation Choice**: Decide on each worktree creation whether to copy data
167
+ - **Safe Operation**: Copying is non-fatal - worktree creation succeeds even if copying fails
168
+
169
+ ### How It Works
170
+
171
+ When creating a new worktree, CCManager:
172
+ 1. Asks whether to copy session data from the current worktree
173
+ 2. Copies all session files from `~/.claude/projects/[source-path]` to `~/.claude/projects/[target-path]`
174
+ 3. Preserves conversation history, project context, and Claude Code state
175
+ 4. Allows immediate continuation of conversations in the new worktree
176
+
177
+ ### Configuration
178
+
179
+ 1. Navigate to **Configuration** → **Configure Worktree**
180
+ 2. Toggle **Copy Session Data** to set the default behavior
181
+ 3. Save changes
182
+
183
+ The default choice (copy or start fresh) will be pre-selected when creating new worktrees.
184
+
185
+ ### Use Cases
186
+
187
+ - **Feature Development**: Copy session data when creating feature branches to maintain project context
188
+ - **Experimentation**: Start fresh when testing unrelated changes
189
+ - **Collaboration**: Share session state across team worktrees
190
+ - **Context Preservation**: Maintain long conversations across multiple development branches
191
+
192
+
157
193
  ## Status Change Hooks
158
194
 
159
195
  CCManager can execute custom commands when Claude Code session status changes. This enables powerful automation workflows like desktop notifications, logging, or integration with other tools.
@@ -150,11 +150,11 @@ const App = ({ devcontainerConfig }) => {
150
150
  }
151
151
  }, 50); // Small delay to ensure proper cleanup
152
152
  };
153
- const handleCreateWorktree = async (path, branch, baseBranch, copyClaudeDirectory) => {
153
+ const handleCreateWorktree = async (path, branch, baseBranch, copySessionData, copyClaudeDirectory) => {
154
154
  setView('creating-worktree');
155
155
  setError(null);
156
156
  // Create the worktree
157
- const result = worktreeService.createWorktree(path, branch, baseBranch, copyClaudeDirectory);
157
+ const result = worktreeService.createWorktree(path, branch, baseBranch, copySessionData, copyClaudeDirectory);
158
158
  if (result.success) {
159
159
  // Success - return to menu
160
160
  handleReturnToMenu();
@@ -8,6 +8,7 @@ const ConfigureWorktree = ({ onComplete }) => {
8
8
  const worktreeConfig = configurationManager.getWorktreeConfig();
9
9
  const [autoDirectory, setAutoDirectory] = useState(worktreeConfig.autoDirectory);
10
10
  const [pattern, setPattern] = useState(worktreeConfig.autoDirectoryPattern || '../{branch}');
11
+ const [copySessionData, setCopySessionData] = useState(worktreeConfig.copySessionData ?? true);
11
12
  const [editMode, setEditMode] = useState('menu');
12
13
  const [tempPattern, setTempPattern] = useState(pattern);
13
14
  useInput((input, key) => {
@@ -25,6 +26,10 @@ const ConfigureWorktree = ({ onComplete }) => {
25
26
  label: `Pattern: ${pattern}`,
26
27
  value: 'pattern',
27
28
  },
29
+ {
30
+ label: `Copy Session Data: ${copySessionData ? '✅ Enabled' : '❌ Disabled'}`,
31
+ value: 'toggleCopy',
32
+ },
28
33
  {
29
34
  label: '💾 Save Changes',
30
35
  value: 'save',
@@ -43,11 +48,15 @@ const ConfigureWorktree = ({ onComplete }) => {
43
48
  setTempPattern(pattern);
44
49
  setEditMode('pattern');
45
50
  break;
51
+ case 'toggleCopy':
52
+ setCopySessionData(!copySessionData);
53
+ break;
46
54
  case 'save':
47
55
  // Save the configuration
48
56
  configurationManager.setWorktreeConfig({
49
57
  autoDirectory,
50
58
  autoDirectoryPattern: pattern,
59
+ copySessionData,
51
60
  });
52
61
  onComplete();
53
62
  break;
@@ -83,7 +92,7 @@ const ConfigureWorktree = ({ onComplete }) => {
83
92
  React.createElement(Box, { marginBottom: 1 },
84
93
  React.createElement(Text, { bold: true, color: "green" }, "Configure Worktree Settings")),
85
94
  React.createElement(Box, { marginBottom: 1 },
86
- React.createElement(Text, { dimColor: true }, "Configure automatic worktree directory generation")),
95
+ React.createElement(Text, { dimColor: true }, "Configure worktree creation settings")),
87
96
  autoDirectory && (React.createElement(Box, { marginBottom: 1 },
88
97
  React.createElement(Text, null,
89
98
  "Example: branch \"feature/my-feature\" \u2192 directory \"",
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  interface NewWorktreeProps {
3
- onComplete: (path: string, branch: string, baseBranch: string, copyClaudeDirectory: boolean) => void;
3
+ onComplete: (path: string, branch: string, baseBranch: string, copySessionData: boolean, copyClaudeDirectory: boolean) => void;
4
4
  onCancel: () => void;
5
5
  }
6
6
  declare const NewWorktree: React.FC<NewWorktreeProps>;
@@ -14,6 +14,8 @@ const NewWorktree = ({ onComplete, onCancel }) => {
14
14
  const [path, setPath] = useState('');
15
15
  const [branch, setBranch] = useState('');
16
16
  const [baseBranch, setBaseBranch] = useState('');
17
+ const [copyClaudeDirectory, setCopyClaudeDirectory] = useState(true);
18
+ const [copySessionData, setCopySessionData] = useState(worktreeConfig.copySessionData ?? true);
17
19
  // Initialize worktree service and load branches (memoized to avoid re-initialization)
18
20
  const { branches, defaultBranch } = useMemo(() => {
19
21
  const service = new WorktreeService();
@@ -50,30 +52,22 @@ const NewWorktree = ({ onComplete, onCancel }) => {
50
52
  };
51
53
  const handleBaseBranchSelect = (item) => {
52
54
  setBaseBranch(item.value);
53
- // Check if .claude directory exists in the base branch
54
- const service = new WorktreeService();
55
- if (service.hasClaudeDirectoryInBranch(item.value)) {
56
- setStep('copy-settings');
57
- }
58
- else {
59
- // Skip copy-settings step and complete with copySettings = false
60
- if (isAutoDirectory) {
61
- const autoPath = generateWorktreeDirectory(branch, worktreeConfig.autoDirectoryPattern);
62
- onComplete(autoPath, branch, item.value, false);
63
- }
64
- else {
65
- onComplete(path, branch, item.value, false);
66
- }
67
- }
55
+ setStep('copy-settings');
68
56
  };
69
57
  const handleCopySettingsSelect = (item) => {
58
+ setCopyClaudeDirectory(item.value);
59
+ setStep('copy-session');
60
+ };
61
+ const handleCopySessionSelect = (item) => {
62
+ const shouldCopy = item.value === 'yes';
63
+ setCopySessionData(shouldCopy);
70
64
  if (isAutoDirectory) {
71
65
  // Generate path from branch name
72
66
  const autoPath = generateWorktreeDirectory(branch, worktreeConfig.autoDirectoryPattern);
73
- onComplete(autoPath, branch, baseBranch, item.value);
67
+ onComplete(autoPath, branch, baseBranch, shouldCopy, copyClaudeDirectory);
74
68
  }
75
69
  else {
76
- onComplete(path, branch, baseBranch, item.value);
70
+ onComplete(path, branch, baseBranch, shouldCopy, copyClaudeDirectory);
77
71
  }
78
72
  };
79
73
  // Calculate generated path for preview (memoized to avoid expensive recalculations)
@@ -129,6 +123,15 @@ const NewWorktree = ({ onComplete, onCancel }) => {
129
123
  },
130
124
  { label: 'No - Start without .claude directory', value: false },
131
125
  ], onSelect: handleCopySettingsSelect, initialIndex: 0 }))),
126
+ step === 'copy-session' && (React.createElement(Box, { flexDirection: "column" },
127
+ React.createElement(Box, { marginBottom: 1 },
128
+ React.createElement(Text, null, "Copy Claude Code session data to the new worktree?")),
129
+ React.createElement(Box, { marginBottom: 1 },
130
+ React.createElement(Text, { dimColor: true }, "This will copy conversation history and context from the current worktree")),
131
+ React.createElement(SelectInput, { items: [
132
+ { label: '✅ Yes, copy session data', value: 'yes' },
133
+ { label: '❌ No, start fresh', value: 'no' },
134
+ ], onSelect: handleCopySessionSelect, initialIndex: copySessionData ? 0 : 1 }))),
132
135
  React.createElement(Box, { marginTop: 1 },
133
136
  React.createElement(Text, { dimColor: true },
134
137
  "Press ",
@@ -66,8 +66,12 @@ export class ConfigurationManager {
66
66
  if (!this.config.worktree) {
67
67
  this.config.worktree = {
68
68
  autoDirectory: false,
69
+ copySessionData: true,
69
70
  };
70
71
  }
72
+ if (!Object.prototype.hasOwnProperty.call(this.config.worktree, 'copySessionData')) {
73
+ this.config.worktree.copySessionData = true;
74
+ }
71
75
  if (!this.config.command) {
72
76
  this.config.command = {
73
77
  command: 'claude',
@@ -9,7 +9,7 @@ export declare class WorktreeService {
9
9
  isGitRepository(): boolean;
10
10
  getDefaultBranch(): string;
11
11
  getAllBranches(): string[];
12
- createWorktree(worktreePath: string, branch: string, baseBranch: string, copyClaudeDirectory?: boolean): {
12
+ createWorktree(worktreePath: string, branch: string, baseBranch: string, copySessionData?: boolean, copyClaudeDirectory?: boolean): {
13
13
  success: boolean;
14
14
  error?: string;
15
15
  };
@@ -27,6 +27,7 @@ export declare class WorktreeService {
27
27
  success: boolean;
28
28
  error?: string;
29
29
  };
30
+ private copyClaudeSessionData;
30
31
  hasClaudeDirectoryInBranch(branchName: string): boolean;
31
32
  private copyClaudeDirectoryFromBaseBranch;
32
33
  }
@@ -2,6 +2,7 @@ import { execSync } from 'child_process';
2
2
  import { existsSync, statSync, cpSync } from 'fs';
3
3
  import path from 'path';
4
4
  import { setWorktreeParentBranch } from '../utils/worktreeConfig.js';
5
+ import { getClaudeProjectsDir, pathToClaudeProjectName, } from '../utils/claudeDir.js';
5
6
  const CLAUDE_DIR = '.claude';
6
7
  export class WorktreeService {
7
8
  constructor(rootPath) {
@@ -172,7 +173,7 @@ export class WorktreeService {
172
173
  return [];
173
174
  }
174
175
  }
175
- createWorktree(worktreePath, branch, baseBranch, copyClaudeDirectory = false) {
176
+ createWorktree(worktreePath, branch, baseBranch, copySessionData = false, copyClaudeDirectory = false) {
176
177
  try {
177
178
  // Resolve the worktree path relative to the git repository root
178
179
  const resolvedPath = path.isAbsolute(worktreePath)
@@ -203,6 +204,10 @@ export class WorktreeService {
203
204
  cwd: this.gitRootPath, // Execute from git root to ensure proper resolution
204
205
  encoding: 'utf8',
205
206
  });
207
+ // Copy session data if requested
208
+ if (copySessionData) {
209
+ this.copyClaudeSessionData(this.rootPath, resolvedPath);
210
+ }
206
211
  // Store the parent branch in worktree config
207
212
  try {
208
213
  setWorktreeParentBranch(resolvedPath, baseBranch);
@@ -348,6 +353,32 @@ export class WorktreeService {
348
353
  };
349
354
  }
350
355
  }
356
+ copyClaudeSessionData(sourceWorktreePath, targetWorktreePath) {
357
+ try {
358
+ const projectsDir = getClaudeProjectsDir();
359
+ if (!existsSync(projectsDir)) {
360
+ throw new Error(`Claude projects directory does not exist: ${projectsDir}`);
361
+ }
362
+ // Convert paths to Claude's naming convention
363
+ const sourceProjectName = pathToClaudeProjectName(sourceWorktreePath);
364
+ const targetProjectName = pathToClaudeProjectName(targetWorktreePath);
365
+ const sourceProjectDir = path.join(projectsDir, sourceProjectName);
366
+ const targetProjectDir = path.join(projectsDir, targetProjectName);
367
+ // Only copy if source project exists
368
+ if (existsSync(sourceProjectDir)) {
369
+ cpSync(sourceProjectDir, targetProjectDir, {
370
+ recursive: true,
371
+ force: true,
372
+ errorOnExist: false,
373
+ preserveTimestamps: true,
374
+ });
375
+ }
376
+ }
377
+ catch (error) {
378
+ console.error(`Failed to copy Claude session data: ${error}`);
379
+ throw new Error(`Failed to copy Claude session data: ${error}`);
380
+ }
381
+ }
351
382
  hasClaudeDirectoryInBranch(branchName) {
352
383
  // Find the worktree directory for the branch
353
384
  const worktrees = this.getWorktrees();
@@ -57,6 +57,7 @@ export interface StatusHookConfig {
57
57
  export interface WorktreeConfig {
58
58
  autoDirectory: boolean;
59
59
  autoDirectoryPattern?: string;
60
+ copySessionData?: boolean;
60
61
  }
61
62
  export interface CommandConfig {
62
63
  command: string;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Utilities for Claude Code directory and project path handling.
3
+ * Provides functions to get Claude configuration directories respecting the
4
+ * CLAUDE_CONFIG_DIR environment variable and convert worktree paths to Claude's
5
+ * project naming convention.
6
+ */
7
+ /**
8
+ * Get the Claude directory path, respecting CLAUDE_CONFIG_DIR environment variable
9
+ * @returns The Claude directory path
10
+ */
11
+ export declare function getClaudeDir(): string;
12
+ /**
13
+ * Get the Claude projects directory path
14
+ * @returns The Claude projects directory path
15
+ */
16
+ export declare function getClaudeProjectsDir(): string;
17
+ /**
18
+ * Convert a worktree path to Claude's project naming convention
19
+ * @param worktreePath The path to the worktree
20
+ * @returns The project name used by Claude
21
+ */
22
+ export declare function pathToClaudeProjectName(worktreePath: string): string;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @fileoverview Utilities for Claude Code directory and project path handling.
3
+ * Provides functions to get Claude configuration directories respecting the
4
+ * CLAUDE_CONFIG_DIR environment variable and convert worktree paths to Claude's
5
+ * project naming convention.
6
+ */
7
+ import path from 'path';
8
+ import os from 'os';
9
+ /**
10
+ * Get the Claude directory path, respecting CLAUDE_CONFIG_DIR environment variable
11
+ * @returns The Claude directory path
12
+ */
13
+ export function getClaudeDir() {
14
+ const envConfigDir = process.env['CLAUDE_CONFIG_DIR'];
15
+ if (envConfigDir) {
16
+ return envConfigDir.trim();
17
+ }
18
+ // Default to ~/.claude for backward compatibility and when not set
19
+ return path.join(os.homedir(), '.claude');
20
+ }
21
+ /**
22
+ * Get the Claude projects directory path
23
+ * @returns The Claude projects directory path
24
+ */
25
+ export function getClaudeProjectsDir() {
26
+ return path.join(getClaudeDir(), 'projects');
27
+ }
28
+ /**
29
+ * Convert a worktree path to Claude's project naming convention
30
+ * @param worktreePath The path to the worktree
31
+ * @returns The project name used by Claude
32
+ */
33
+ export function pathToClaudeProjectName(worktreePath) {
34
+ // Convert absolute path to Claude's project naming convention
35
+ // Claude replaces all path separators and dots with dashes
36
+ const resolved = path.resolve(worktreePath);
37
+ // Handle both forward slashes (Linux/macOS) and backslashes (Windows)
38
+ return resolved.replace(/[/\\.]/g, '-');
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",