claude-session-share 1.0.0 → 1.1.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
@@ -1,21 +1,21 @@
1
1
  # claude-session-share
2
2
 
3
- > **MCP server for sharing Claude Code sessions via GitHub Gist with automatic privacy protection**
3
+ MCP server for sharing Claude Code sessions via GitHub Gist with automatic privacy protection.
4
4
 
5
- Share your Claude Code conversations effortlessly while keeping your private data safe. This MCP server enables you to export sessions to shareable GitHub Gist links and import them back—all through natural language.
5
+ Share your Claude Code conversations while keeping private data safe. Export sessions to shareable GitHub Gist links and import them back—all through natural language or CLI.
6
6
 
7
7
  [![npm version](https://badge.fury.io/js/claude-session-share.svg)](https://www.npmjs.com/package/claude-session-share)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
9
 
10
- ## Features
10
+ ## Features
11
11
 
12
- - **🔗 One-Click Sharing** - Export sessions to GitHub Gist with a simple command
13
- - **🔒 Privacy First** - Automatically strips thinking blocks, sanitizes paths, and redacts secrets
14
- - **📥 Seamless Import** - Import shared sessions that work exactly like native Claude Code sessions
15
- - **💬 Natural Language** - Just ask Claude to "share my session" or "import from [link]"
16
- - **🔄 Full Compatibility** - Imported sessions appear in `claude --resume` and preserve conversation context
12
+ - **One-Click Sharing** - Export sessions to GitHub Gist with a simple command
13
+ - **Privacy First** - Automatically strips thinking blocks, sanitizes paths, and redacts secrets
14
+ - **Seamless Import** - Import shared sessions that work exactly like native Claude Code sessions
15
+ - **Natural Language** - Just ask Claude to "share my session" or "import from [link]"
16
+ - **Full Compatibility** - Imported sessions appear in `claude --resume` and preserve conversation context
17
17
 
18
- ## 📦 Installation
18
+ ## Installation
19
19
 
20
20
  ### Prerequisites
21
21
 
@@ -37,11 +37,11 @@ npm install -g claude-session-share
37
37
  4. Check the **`gist`** scope
38
38
  5. Generate and copy the token
39
39
 
40
- ## ⚙️ Configuration
40
+ ## Configuration
41
41
 
42
- Add the MCP server to your Claude Code configuration:
42
+ Add the MCP server to your Claude Code configuration.
43
43
 
44
- ### Option 1: User Config (Recommended)
44
+ ### User Config (Recommended)
45
45
 
46
46
  Create or edit `~/.claude/mcp.json`:
47
47
 
@@ -59,24 +59,23 @@ Create or edit `~/.claude/mcp.json`:
59
59
  }
60
60
  ```
61
61
 
62
- ### Option 2: Project-Specific Config
62
+ ### Project-Specific Config
63
63
 
64
64
  Create `.mcp.json` in your project directory with the same structure.
65
65
 
66
66
  ### Verify Installation
67
67
 
68
68
  ```bash
69
- # Check if the MCP server is recognized
70
69
  claude # Start Claude Code
71
70
  # Then type: /mcp
72
71
  # You should see "claude-session-share" in the list
73
72
  ```
74
73
 
75
- ## 🚀 Usage
74
+ ## Usage
76
75
 
77
- ### Sharing a Session
76
+ ### Natural Language (via MCP)
78
77
 
79
- In any Claude Code conversation, simply say:
78
+ In any Claude Code conversation:
80
79
 
81
80
  ```
82
81
  "Share my current session to GitHub Gist"
@@ -88,28 +87,33 @@ Claude will:
88
87
  3. Upload to a secret (unlisted) GitHub Gist
89
88
  4. Return a shareable link
90
89
 
91
- **Example output:**
92
- ```
93
- ✓ Session shared successfully!
94
- Link: https://gist.github.com/username/abc123...
90
+ To import a shared session:
95
91
 
96
- Share this link with anyone. They can import it with:
97
- "Import session from https://gist.github.com/username/abc123..."
92
+ ```
93
+ "Import this session: https://gist.github.com/username/abc123..."
98
94
  ```
99
95
 
100
- ### Importing a Session
96
+ ### Command Line (Standalone CLI)
101
97
 
102
- To import a shared session:
98
+ #### Share a session
103
99
 
104
- ```
105
- "Import this session: https://gist.github.com/username/abc123..."
100
+ ```bash
101
+ # Share most recent session
102
+ npx claude-session-share share
103
+
104
+ # Share specific session file
105
+ npx claude-session-share share --session-path ~/.claude/projects/abc/session.jsonl
106
106
  ```
107
107
 
108
- Claude will:
109
- 1. Fetch the session from the Gist
110
- 2. Remap UUIDs to avoid conflicts
111
- 3. Write to your local `.claude/projects/` directory
112
- 4. Make it available for resuming
108
+ #### Import a session
109
+
110
+ ```bash
111
+ # Import to current directory
112
+ npx claude-session-share import https://gist.github.com/user/abc123
113
+
114
+ # Import to specific directory
115
+ npx claude-session-share import abc123 --project-path /Users/name/project
116
+ ```
113
117
 
114
118
  ### Resuming an Imported Session
115
119
 
@@ -119,13 +123,17 @@ claude --resume
119
123
  # Select the imported session from the list
120
124
  ```
121
125
 
122
- The imported session works exactly like a native Claude Code session—full conversation history, no thinking blocks, perfect privacy.
126
+ Or resume directly with the session ID:
127
+
128
+ ```bash
129
+ claude --resume <session-id>
130
+ ```
123
131
 
124
- ## 🔐 Privacy Protection
132
+ ## Privacy Protection
125
133
 
126
134
  Every shared session is automatically sanitized:
127
135
 
128
- ### What Gets Removed/Sanitized
136
+ ### What Gets Removed/Sanitized
129
137
 
130
138
  - **Thinking Blocks** - Internal reasoning stripped completely
131
139
  - **Absolute Paths** - `/Users/you/project/file.ts` → `file.ts`
@@ -133,7 +141,7 @@ Every shared session is automatically sanitized:
133
141
  - **Tokens** - Bearer tokens, OAuth tokens → `[REDACTED]`
134
142
  - **Secrets** - Environment variables, passwords (key=value format) → `[REDACTED]`
135
143
 
136
- ### What Gets Preserved
144
+ ### What Gets Preserved
137
145
 
138
146
  - Conversation flow and context
139
147
  - Code examples and explanations
@@ -145,11 +153,8 @@ Every shared session is automatically sanitized:
145
153
 
146
154
  - Passwords in connection strings (e.g., `postgresql://user:pass@host/db`) are not detected
147
155
  - Secrets in natural language (not key=value format) may not be redacted
148
- - These tradeoffs prevent false positives on legitimate content
149
-
150
- ## 📚 MCP Tools Reference
151
156
 
152
- The server provides two MCP tools:
157
+ ## MCP Tools Reference
153
158
 
154
159
  ### `share_session`
155
160
 
@@ -167,17 +172,17 @@ Exports the current session to GitHub Gist.
167
172
  Imports a session from a GitHub Gist.
168
173
 
169
174
  **Parameters:**
170
- - `gistUrl` - GitHub Gist URL (e.g., `https://gist.github.com/user/abc123`)
171
- - `projectPath` (optional) - Local project directory (defaults to current directory)
175
+ - `gistUrl` - GitHub Gist URL or bare gist ID
176
+ - `projectPath` - Local project directory for import
172
177
 
173
178
  **Returns:**
174
179
  - `sessionPath` - Path to imported session file
175
180
  - `sessionId` - New session ID
176
181
  - `messageCount` - Number of messages imported
177
182
 
178
- ## 🛠️ Development
183
+ ## Development
179
184
 
180
- ### Clone and Setup
185
+ ### Setup
181
186
 
182
187
  ```bash
183
188
  git clone https://github.com/OmkarKovvali/claude-session-share.git
@@ -195,7 +200,7 @@ npm run build
195
200
 
196
201
  ```bash
197
202
  npm test
198
- # 337 tests with full coverage
203
+ # 420 tests
199
204
  ```
200
205
 
201
206
  ### Project Structure
@@ -204,64 +209,21 @@ npm test
204
209
  claude-session-share/
205
210
  ├── src/
206
211
  │ ├── index.ts # MCP server entry point
212
+ │ ├── cli.ts # CLI entry point
207
213
  │ ├── gist/ # GitHub Gist integration
208
214
  │ ├── sanitization/ # Privacy protection
209
215
  │ ├── services/ # Share/import orchestration
210
216
  │ ├── session/ # Session read/write
211
- │ └── utils/ # UUID remapping, etc.
217
+ │ └── utils/ # UUID remapping, path encoding
212
218
  ├── dist/ # Compiled output
213
- ├── .planning/ # Project planning docs
214
219
  └── package.json
215
220
  ```
216
221
 
217
- ## 🧪 Testing
218
-
219
- The project includes comprehensive test coverage:
220
-
221
- - **Unit Tests** - All modules tested individually
222
- - **Integration Tests** - Service orchestration verified
223
- - **E2E Tests** - Full share→import→resume workflow validated
224
- - **Real API Tests** - GitHub Gist integration tested with actual API
225
-
226
- Run tests:
227
- ```bash
228
- npm test # All tests
229
- npm test -- session-reader # Specific test file
230
- ```
231
-
232
- ## 🤝 Contributing
233
-
234
- Contributions welcome! Please:
235
-
236
- 1. Fork the repository
237
- 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
238
- 3. Commit your changes (`git commit -m 'Add amazing feature'`)
239
- 4. Push to the branch (`git push origin feature/amazing-feature`)
240
- 5. Open a Pull Request
241
-
242
- ### Code Style
243
-
244
- - TypeScript with strict mode
245
- - ESM modules
246
- - Functional programming style (immutable transformations)
247
- - Comprehensive tests for new features
248
-
249
- ## 📋 Roadmap
250
-
251
- - [x] Core share/import functionality
252
- - [x] Privacy sanitization
253
- - [x] MCP server integration
254
- - [x] End-to-end testing
255
- - [ ] Web interface for browsing shared sessions
256
- - [ ] Session versioning and updates
257
- - [ ] Organization/team sharing features
258
- - [ ] Custom sanitization rules
259
-
260
- ## 🐛 Troubleshooting
222
+ ## Troubleshooting
261
223
 
262
224
  ### "Not authenticated" Error
263
225
 
264
- Make sure your `GITHUB_TOKEN` is set in the MCP configuration:
226
+ Ensure `GITHUB_TOKEN` is set in MCP configuration:
265
227
  ```json
266
228
  "env": {
267
229
  "GITHUB_TOKEN": "ghp_your_token_here"
@@ -274,12 +236,7 @@ Ensure you're in a directory with an active Claude Code session. Sessions are st
274
236
 
275
237
  ### Imported Session Doesn't Appear
276
238
 
277
- Check that the session was written to the correct location:
278
- ```bash
279
- ls -la ~/.claude/projects/*/
280
- ```
281
-
282
- Each project directory should have a `.jsonl` file—that's your session.
239
+ After importing, restart Claude Code to refresh the session list. The session file should be in `~/.claude/projects/-{encoded-path}/`.
283
240
 
284
241
  ### MCP Server Not Listed
285
242
 
@@ -287,27 +244,21 @@ Verify your MCP configuration:
287
244
  ```bash
288
245
  cat ~/.claude/mcp.json
289
246
  ```
290
-
291
247
  Then restart Claude Code.
292
248
 
293
- ## 📄 License
249
+ ## Contributing
294
250
 
295
- MIT © Omkar Kovvali
296
-
297
- See [LICENSE](LICENSE) file for details.
251
+ 1. Fork the repository
252
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
253
+ 3. Commit your changes
254
+ 4. Push to the branch
255
+ 5. Open a Pull Request
298
256
 
299
- ## 🙏 Acknowledgments
257
+ ## License
300
258
 
301
- - Built with [Model Context Protocol](https://modelcontextprotocol.io/)
302
- - Uses [GitHub Gist API](https://docs.github.com/en/rest/gists)
303
- - Powered by [Claude Code](https://www.anthropic.com/claude)
259
+ MIT © Omkar Kovvali
304
260
 
305
- ## 📞 Support
261
+ ## Support
306
262
 
307
263
  - **Issues**: [GitHub Issues](https://github.com/OmkarKovvali/claude-session-share/issues)
308
- - **Discussions**: [GitHub Discussions](https://github.com/OmkarKovvali/claude-session-share/discussions)
309
264
  - **Email**: okovvali5@gmail.com
310
-
311
- ---
312
-
313
- **Made with ❤️ for the Claude Code community**
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Tests for CLI entry point
3
+ *
4
+ * Validates command parsing and integration with services
5
+ */
6
+ import { describe, it, expect, vi } from 'vitest';
7
+ import { uploadSession } from '../services/session-uploader.js';
8
+ import { importSession } from '../services/session-importer.js';
9
+ // Mock the services at module level
10
+ vi.mock('../services/session-uploader.js', () => ({
11
+ uploadSession: vi.fn(),
12
+ }));
13
+ vi.mock('../services/session-importer.js', () => ({
14
+ importSession: vi.fn(),
15
+ }));
16
+ // Mock fs/promises for findMostRecentSession
17
+ vi.mock('fs/promises', async () => {
18
+ const actual = await vi.importActual('fs/promises');
19
+ return {
20
+ ...actual,
21
+ readdir: vi.fn(),
22
+ stat: vi.fn(),
23
+ };
24
+ });
25
+ describe('CLI argument parsing', () => {
26
+ it('should parse share command with session path', () => {
27
+ // Test that share command with --session-path flag works
28
+ const args = ['share', '--session-path', '/path/to/session.jsonl'];
29
+ // Find the session-path value
30
+ const pathIndex = args.indexOf('--session-path');
31
+ const sessionPath = pathIndex !== -1 && pathIndex + 1 < args.length
32
+ ? args[pathIndex + 1]
33
+ : null;
34
+ expect(sessionPath).toBe('/path/to/session.jsonl');
35
+ });
36
+ it('should parse share command without session path', () => {
37
+ const args = ['share'];
38
+ const pathIndex = args.indexOf('--session-path');
39
+ const sessionPath = pathIndex !== -1 && pathIndex + 1 < args.length
40
+ ? args[pathIndex + 1]
41
+ : null;
42
+ expect(sessionPath).toBeNull();
43
+ });
44
+ it('should parse import command with gist URL and default project path', () => {
45
+ // Simulate: node cli.js import https://gist...
46
+ // After "import" is consumed, remaining args are:
47
+ const argsAfterCommand = ['https://gist.github.com/user/abc123'];
48
+ const gistUrl = argsAfterCommand[0];
49
+ const pathIndex = argsAfterCommand.indexOf('--project-path');
50
+ const projectPath = pathIndex !== -1 && pathIndex + 1 < argsAfterCommand.length
51
+ ? argsAfterCommand[pathIndex + 1]
52
+ : process.cwd();
53
+ expect(gistUrl).toBe('https://gist.github.com/user/abc123');
54
+ expect(projectPath).toBe(process.cwd());
55
+ });
56
+ it('should parse import command with project-path flag', () => {
57
+ // Simulate: node cli.js import abc123 --project-path /tmp/test
58
+ const argsAfterCommand = ['abc123', '--project-path', '/tmp/test'];
59
+ const gistUrl = argsAfterCommand[0];
60
+ const pathIndex = argsAfterCommand.indexOf('--project-path');
61
+ const projectPath = pathIndex !== -1 && pathIndex + 1 < argsAfterCommand.length
62
+ ? argsAfterCommand[pathIndex + 1]
63
+ : process.cwd();
64
+ expect(gistUrl).toBe('abc123');
65
+ expect(projectPath).toBe('/tmp/test');
66
+ });
67
+ it('should handle missing gist URL in import command', () => {
68
+ // Simulate: node cli.js import (no URL provided)
69
+ const argsAfterCommand = [];
70
+ const gistUrl = argsAfterCommand[0];
71
+ expect(gistUrl).toBeUndefined();
72
+ });
73
+ });
74
+ describe('CLI service integration', () => {
75
+ it('uploadSession service should be importable', () => {
76
+ expect(uploadSession).toBeDefined();
77
+ expect(typeof uploadSession).toBe('function');
78
+ });
79
+ it('importSession service should be importable', () => {
80
+ expect(importSession).toBeDefined();
81
+ expect(typeof importSession).toBe('function');
82
+ });
83
+ it('uploadSession mock can be configured', () => {
84
+ vi.mocked(uploadSession).mockResolvedValue('https://gist.github.com/test/123');
85
+ expect(vi.mocked(uploadSession)).toBeDefined();
86
+ });
87
+ it('importSession mock can be configured', () => {
88
+ vi.mocked(importSession).mockResolvedValue({
89
+ sessionPath: '/path/to/session.jsonl',
90
+ sessionId: 'test-id',
91
+ messageCount: 10,
92
+ projectPath: '/test',
93
+ });
94
+ expect(vi.mocked(importSession)).toBeDefined();
95
+ });
96
+ });
97
+ describe('CLI usage validation', () => {
98
+ it('should have share and import as valid commands', () => {
99
+ const validCommands = ['share', 'import'];
100
+ expect(validCommands).toContain('share');
101
+ expect(validCommands).toContain('import');
102
+ expect(validCommands).not.toContain('unknown');
103
+ });
104
+ it('should validate share command structure', () => {
105
+ // share command can have optional --session-path
106
+ const shareArgs1 = ['share'];
107
+ const shareArgs2 = ['share', '--session-path', '/path'];
108
+ expect(shareArgs1[0]).toBe('share');
109
+ expect(shareArgs2[0]).toBe('share');
110
+ expect(shareArgs2.includes('--session-path')).toBe(true);
111
+ });
112
+ it('should validate import command structure', () => {
113
+ // import command requires gist URL, optional --project-path
114
+ const importArgsAfterCommand1 = ['https://gist.github.com/user/id'];
115
+ const importArgsAfterCommand2 = ['abc123', '--project-path', '/path'];
116
+ expect(importArgsAfterCommand1[0]).toBe('https://gist.github.com/user/id');
117
+ expect(importArgsAfterCommand2[0]).toBe('abc123');
118
+ expect(importArgsAfterCommand2.includes('--project-path')).toBe(true);
119
+ });
120
+ });
@@ -162,10 +162,12 @@ describe('End-to-End Session Sharing Workflow', () => {
162
162
  for (const msg of assistantMsgs) {
163
163
  expect(msg.snapshot.thinking).toBeNull();
164
164
  }
165
- // Verify: Paths sanitized (absolute relative)
165
+ // Verify: Original paths sanitized, then restored to import project path
166
+ // - Original path (/Users/test/myproject) is NOT preserved
167
+ // - cwd is restored to the import directory (absolute path)
166
168
  const userMsg = importedMessages[0];
167
169
  expect(userMsg.cwd).not.toContain('/Users/test/');
168
- expect(userMsg.cwd).toMatch(/^(myproject|\.)/); // Should be relative
170
+ expect(userMsg.cwd).toBe(importDir); // Restored to import project path
169
171
  const firstAssistant = importedMessages[1];
170
172
  const toolResultContent = firstAssistant.snapshot.messages[1].content;
171
173
  expect(toolResultContent).not.toContain('/Users/test/myproject/');
@@ -2,6 +2,9 @@
2
2
  * Tests for path encoding utilities
3
3
  *
4
4
  * Validates Claude Code's path encoding scheme for session directories.
5
+ * Claude Code encoding:
6
+ * 1. Replaces `/` with `-` (keeping leading dash from root /)
7
+ * 2. Replaces `_` with `-` (normalizes underscores to hyphens)
5
8
  */
6
9
  import { describe, it, expect } from 'vitest';
7
10
  import { encodeProjectPath, decodeProjectPath, getSessionDirectory } from '../utils/path-encoding.js';
@@ -9,68 +12,87 @@ import { homedir } from 'os';
9
12
  import { join } from 'path';
10
13
  describe('path-encoding', () => {
11
14
  describe('encodeProjectPath', () => {
12
- it('encodes absolute Unix path correctly', () => {
15
+ it('encodes absolute Unix path correctly (keeps leading dash)', () => {
13
16
  const result = encodeProjectPath('/Users/name/project');
14
- expect(result).toBe('Users-name-project');
17
+ expect(result).toBe('-Users-name-project');
15
18
  });
16
19
  it('encodes path with multiple segments', () => {
17
20
  const result = encodeProjectPath('/Users/name/my-project/subdir');
18
- expect(result).toBe('Users-name-my-project-subdir');
21
+ expect(result).toBe('-Users-name-my-project-subdir');
19
22
  });
20
23
  it('handles path with existing dashes', () => {
21
24
  const result = encodeProjectPath('/Users/name/my-awesome-project');
22
- expect(result).toBe('Users-name-my-awesome-project');
25
+ expect(result).toBe('-Users-name-my-awesome-project');
23
26
  });
24
27
  it('handles single segment path', () => {
25
28
  const result = encodeProjectPath('/project');
26
- expect(result).toBe('project');
29
+ expect(result).toBe('-project');
27
30
  });
28
31
  it('handles path with multiple consecutive slashes', () => {
29
32
  const result = encodeProjectPath('/Users//name///project');
30
- expect(result).toBe('Users--name---project');
33
+ expect(result).toBe('-Users--name---project');
31
34
  });
32
35
  it('handles path with trailing slash', () => {
33
36
  const result = encodeProjectPath('/Users/name/project/');
34
- expect(result).toBe('Users-name-project-');
37
+ expect(result).toBe('-Users-name-project-');
38
+ });
39
+ it('converts underscores to hyphens', () => {
40
+ const result = encodeProjectPath('/Users/name/my_project');
41
+ expect(result).toBe('-Users-name-my-project');
42
+ });
43
+ it('handles path with both underscores and dashes', () => {
44
+ const result = encodeProjectPath('/Users/name/my_cool-project');
45
+ expect(result).toBe('-Users-name-my-cool-project');
35
46
  });
36
47
  });
37
48
  describe('decodeProjectPath', () => {
38
- it('decodes encoded path correctly', () => {
39
- const result = decodeProjectPath('Users-name-project');
49
+ it('decodes encoded path correctly (leading dash becomes /)', () => {
50
+ const result = decodeProjectPath('-Users-name-project');
40
51
  expect(result).toBe('/Users/name/project');
41
52
  });
42
53
  it('decodes path with multiple segments', () => {
43
- const result = decodeProjectPath('Users-name-myproject-subdir');
54
+ const result = decodeProjectPath('-Users-name-myproject-subdir');
44
55
  expect(result).toBe('/Users/name/myproject/subdir');
45
56
  });
46
- it('is inverse of encodeProjectPath', () => {
57
+ it('is inverse of encodeProjectPath for paths without underscores', () => {
47
58
  const original = '/Users/name/project';
48
59
  const encoded = encodeProjectPath(original);
49
60
  const decoded = decodeProjectPath(encoded);
50
61
  expect(decoded).toBe(original);
51
62
  });
52
63
  it('handles single segment', () => {
53
- const result = decodeProjectPath('project');
64
+ const result = decodeProjectPath('-project');
54
65
  expect(result).toBe('/project');
55
66
  });
67
+ it('handles paths without leading dash (legacy format)', () => {
68
+ // Legacy format without leading dash
69
+ const result = decodeProjectPath('Users-name-project');
70
+ expect(result).toBe('Users/name/project');
71
+ });
56
72
  });
57
73
  describe('getSessionDirectory', () => {
58
74
  it('constructs correct session directory path', () => {
59
75
  const projectPath = '/Users/name/project';
60
76
  const result = getSessionDirectory(projectPath);
61
- const expected = join(homedir(), '.claude', 'projects', 'Users-name-project');
77
+ const expected = join(homedir(), '.claude', 'projects', '-Users-name-project');
62
78
  expect(result).toBe(expected);
63
79
  });
64
80
  it('handles complex project paths', () => {
65
81
  const projectPath = '/Users/name/my-awesome-project/subdir';
66
82
  const result = getSessionDirectory(projectPath);
67
- const expected = join(homedir(), '.claude', 'projects', 'Users-name-my-awesome-project-subdir');
83
+ const expected = join(homedir(), '.claude', 'projects', '-Users-name-my-awesome-project-subdir');
68
84
  expect(result).toBe(expected);
69
85
  });
70
86
  it('handles project path with existing dashes', () => {
71
87
  const projectPath = '/opt/code/my-app';
72
88
  const result = getSessionDirectory(projectPath);
73
- const expected = join(homedir(), '.claude', 'projects', 'opt-code-my-app');
89
+ const expected = join(homedir(), '.claude', 'projects', '-opt-code-my-app');
90
+ expect(result).toBe(expected);
91
+ });
92
+ it('handles project path with underscores', () => {
93
+ const projectPath = '/Users/name/cc_links';
94
+ const result = getSessionDirectory(projectPath);
95
+ const expected = join(homedir(), '.claude', 'projects', '-Users-name-cc-links');
74
96
  expect(result).toBe(expected);
75
97
  });
76
98
  });