claude-session-share 1.0.0 → 1.2.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 +43 -220
- package/dist/__tests__/e2e.test.js +4 -2
- package/dist/__tests__/path-encoding.test.js +37 -15
- package/dist/__tests__/prompts.test.js +147 -0
- package/dist/__tests__/sanitizer.test.js +323 -0
- package/dist/__tests__/session-writer.test.js +11 -15
- package/dist/index.js +98 -6
- package/dist/sanitization/sanitizer.js +39 -10
- package/dist/services/session-importer.js +50 -4
- package/dist/session/writer.js +7 -5
- package/dist/utils/path-encoding.js +21 -15
- package/package.json +1 -4
package/README.md
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
# claude-session-share
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
3
|
+
MCP server for sharing Claude Code sessions via GitHub Gist with automatic privacy protection.
|
|
6
4
|
|
|
7
5
|
[](https://www.npmjs.com/package/claude-session-share)
|
|
8
6
|
[](https://opensource.org/licenses/MIT)
|
|
9
7
|
|
|
10
|
-
##
|
|
8
|
+
## Features
|
|
11
9
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
- **🔄 Full Compatibility** - Imported sessions appear in `claude --resume` and preserve conversation context
|
|
10
|
+
- **One-Click Sharing** - Export sessions to GitHub Gist through natural language
|
|
11
|
+
- **Privacy First** - Automatically strips thinking blocks, sanitizes paths, and redacts secrets
|
|
12
|
+
- **Seamless Import** - Import shared sessions that work exactly like native Claude Code sessions
|
|
13
|
+
- **Full Compatibility** - Imported sessions appear in `claude --resume`
|
|
17
14
|
|
|
18
|
-
##
|
|
15
|
+
## Installation
|
|
19
16
|
|
|
20
17
|
### Prerequisites
|
|
21
18
|
|
|
@@ -23,27 +20,16 @@ Share your Claude Code conversations effortlessly while keeping your private dat
|
|
|
23
20
|
- Claude Code CLI
|
|
24
21
|
- GitHub Personal Access Token with `gist` scope
|
|
25
22
|
|
|
26
|
-
### Install via npm
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm install -g claude-session-share
|
|
30
|
-
```
|
|
31
|
-
|
|
32
23
|
### Setup GitHub Token
|
|
33
24
|
|
|
34
25
|
1. Go to [GitHub Settings > Personal Access Tokens](https://github.com/settings/tokens)
|
|
35
26
|
2. Click "Generate new token (classic)"
|
|
36
|
-
3.
|
|
37
|
-
4.
|
|
38
|
-
5. Generate and copy the token
|
|
39
|
-
|
|
40
|
-
## ⚙️ Configuration
|
|
27
|
+
3. Check the **`gist`** scope
|
|
28
|
+
4. Generate and copy the token
|
|
41
29
|
|
|
42
|
-
|
|
30
|
+
### Configure MCP Server
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Create or edit `~/.claude/mcp.json`:
|
|
32
|
+
Add to `~/.claude/mcp.json`:
|
|
47
33
|
|
|
48
34
|
```json
|
|
49
35
|
{
|
|
@@ -59,24 +45,19 @@ Create or edit `~/.claude/mcp.json`:
|
|
|
59
45
|
}
|
|
60
46
|
```
|
|
61
47
|
|
|
62
|
-
### Option 2: Project-Specific Config
|
|
63
|
-
|
|
64
|
-
Create `.mcp.json` in your project directory with the same structure.
|
|
65
|
-
|
|
66
48
|
### Verify Installation
|
|
67
49
|
|
|
68
50
|
```bash
|
|
69
|
-
# Check if the MCP server is recognized
|
|
70
51
|
claude # Start Claude Code
|
|
71
|
-
#
|
|
52
|
+
# Type: /mcp
|
|
72
53
|
# You should see "claude-session-share" in the list
|
|
73
54
|
```
|
|
74
55
|
|
|
75
|
-
##
|
|
56
|
+
## Usage
|
|
76
57
|
|
|
77
|
-
###
|
|
58
|
+
### Share a Session
|
|
78
59
|
|
|
79
|
-
In any Claude Code conversation
|
|
60
|
+
In any Claude Code conversation:
|
|
80
61
|
|
|
81
62
|
```
|
|
82
63
|
"Share my current session to GitHub Gist"
|
|
@@ -85,229 +66,71 @@ In any Claude Code conversation, simply say:
|
|
|
85
66
|
Claude will:
|
|
86
67
|
1. Find your current session
|
|
87
68
|
2. Remove thinking blocks and sanitize paths/secrets
|
|
88
|
-
3. Upload to a secret
|
|
69
|
+
3. Upload to a secret GitHub Gist
|
|
89
70
|
4. Return a shareable link
|
|
90
71
|
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
✓ Session shared successfully!
|
|
94
|
-
Link: https://gist.github.com/username/abc123...
|
|
72
|
+
### Import a Session
|
|
95
73
|
|
|
96
|
-
Share this link with anyone. They can import it with:
|
|
97
|
-
"Import session from https://gist.github.com/username/abc123..."
|
|
98
74
|
```
|
|
99
|
-
|
|
100
|
-
### Importing a Session
|
|
101
|
-
|
|
102
|
-
To import a shared session:
|
|
103
|
-
|
|
104
|
-
```
|
|
105
|
-
"Import this session: https://gist.github.com/username/abc123..."
|
|
75
|
+
"Import this session: https://gist.github.com/username/abc123"
|
|
106
76
|
```
|
|
107
77
|
|
|
108
|
-
|
|
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
|
|
113
|
-
|
|
114
|
-
### Resuming an Imported Session
|
|
78
|
+
### Resume an Imported Session
|
|
115
79
|
|
|
116
80
|
```bash
|
|
117
|
-
cd your-project-directory
|
|
118
81
|
claude --resume
|
|
119
82
|
# Select the imported session from the list
|
|
120
83
|
```
|
|
121
84
|
|
|
122
|
-
|
|
85
|
+
Or directly:
|
|
86
|
+
```bash
|
|
87
|
+
claude --resume <session-id>
|
|
88
|
+
```
|
|
123
89
|
|
|
124
|
-
##
|
|
90
|
+
## Privacy Protection
|
|
125
91
|
|
|
126
92
|
Every shared session is automatically sanitized:
|
|
127
93
|
|
|
128
|
-
###
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
- **API Keys** - `sk_test_abc123`, `ghp_token`, AWS keys → `[REDACTED]`
|
|
133
|
-
- **Tokens** - Bearer tokens, OAuth tokens → `[REDACTED]`
|
|
134
|
-
- **Secrets** - Environment variables, passwords (key=value format) → `[REDACTED]`
|
|
135
|
-
|
|
136
|
-
### ✅ What Gets Preserved
|
|
94
|
+
### Removed/Sanitized
|
|
95
|
+
- Thinking blocks (internal reasoning)
|
|
96
|
+
- Absolute paths → relative paths
|
|
97
|
+
- API keys, tokens, secrets → `[REDACTED]`
|
|
137
98
|
|
|
99
|
+
### Preserved
|
|
138
100
|
- Conversation flow and context
|
|
139
|
-
- Code examples
|
|
140
|
-
-
|
|
101
|
+
- Code examples
|
|
102
|
+
- Relative file paths
|
|
141
103
|
- Tool use history
|
|
142
|
-
- UUIDs and message chains (remapped on import)
|
|
143
|
-
|
|
144
|
-
### Known Limitations
|
|
145
104
|
|
|
146
|
-
|
|
147
|
-
- 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
|
-
|
|
152
|
-
The server provides two MCP tools:
|
|
105
|
+
## MCP Tools
|
|
153
106
|
|
|
154
107
|
### `share_session`
|
|
155
|
-
|
|
156
|
-
Exports the current session to GitHub Gist.
|
|
157
|
-
|
|
158
|
-
**Parameters:**
|
|
159
|
-
- `sessionPath` (optional) - Path to session file (defaults to most recent)
|
|
160
|
-
|
|
161
|
-
**Returns:**
|
|
162
|
-
- `gistUrl` - Shareable GitHub Gist URL
|
|
163
|
-
- `messageCount` - Number of messages exported
|
|
108
|
+
Exports current session to GitHub Gist.
|
|
164
109
|
|
|
165
110
|
### `import_session`
|
|
111
|
+
Imports session from GitHub Gist URL.
|
|
166
112
|
|
|
167
|
-
|
|
113
|
+
## Troubleshooting
|
|
168
114
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
- `projectPath` (optional) - Local project directory (defaults to current directory)
|
|
115
|
+
### "Not authenticated" Error
|
|
116
|
+
Ensure `GITHUB_TOKEN` is set in MCP configuration.
|
|
172
117
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
- `sessionId` - New session ID
|
|
176
|
-
- `messageCount` - Number of messages imported
|
|
118
|
+
### Imported Session Doesn't Appear
|
|
119
|
+
Restart Claude Code to refresh the session list.
|
|
177
120
|
|
|
178
|
-
|
|
121
|
+
### MCP Server Not Listed
|
|
122
|
+
Verify `~/.claude/mcp.json` and restart Claude Code.
|
|
179
123
|
|
|
180
|
-
|
|
124
|
+
## Development
|
|
181
125
|
|
|
182
126
|
```bash
|
|
183
127
|
git clone https://github.com/OmkarKovvali/claude-session-share.git
|
|
184
128
|
cd claude-session-share
|
|
185
129
|
npm install
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Build
|
|
189
|
-
|
|
190
|
-
```bash
|
|
191
130
|
npm run build
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Run Tests
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
131
|
npm test
|
|
198
|
-
# 337 tests with full coverage
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Project Structure
|
|
202
|
-
|
|
203
|
-
```
|
|
204
|
-
claude-session-share/
|
|
205
|
-
├── src/
|
|
206
|
-
│ ├── index.ts # MCP server entry point
|
|
207
|
-
│ ├── gist/ # GitHub Gist integration
|
|
208
|
-
│ ├── sanitization/ # Privacy protection
|
|
209
|
-
│ ├── services/ # Share/import orchestration
|
|
210
|
-
│ ├── session/ # Session read/write
|
|
211
|
-
│ └── utils/ # UUID remapping, etc.
|
|
212
|
-
├── dist/ # Compiled output
|
|
213
|
-
├── .planning/ # Project planning docs
|
|
214
|
-
└── package.json
|
|
215
132
|
```
|
|
216
133
|
|
|
217
|
-
##
|
|
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
|
|
261
|
-
|
|
262
|
-
### "Not authenticated" Error
|
|
263
|
-
|
|
264
|
-
Make sure your `GITHUB_TOKEN` is set in the MCP configuration:
|
|
265
|
-
```json
|
|
266
|
-
"env": {
|
|
267
|
-
"GITHUB_TOKEN": "ghp_your_token_here"
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### "No sessions found" Error
|
|
272
|
-
|
|
273
|
-
Ensure you're in a directory with an active Claude Code session. Sessions are stored in `~/.claude/projects/`.
|
|
274
|
-
|
|
275
|
-
### Imported Session Doesn't Appear
|
|
276
|
-
|
|
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.
|
|
283
|
-
|
|
284
|
-
### MCP Server Not Listed
|
|
285
|
-
|
|
286
|
-
Verify your MCP configuration:
|
|
287
|
-
```bash
|
|
288
|
-
cat ~/.claude/mcp.json
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
Then restart Claude Code.
|
|
292
|
-
|
|
293
|
-
## 📄 License
|
|
134
|
+
## License
|
|
294
135
|
|
|
295
136
|
MIT © Omkar Kovvali
|
|
296
|
-
|
|
297
|
-
See [LICENSE](LICENSE) file for details.
|
|
298
|
-
|
|
299
|
-
## 🙏 Acknowledgments
|
|
300
|
-
|
|
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)
|
|
304
|
-
|
|
305
|
-
## 📞 Support
|
|
306
|
-
|
|
307
|
-
- **Issues**: [GitHub Issues](https://github.com/OmkarKovvali/claude-session-share/issues)
|
|
308
|
-
- **Discussions**: [GitHub Discussions](https://github.com/OmkarKovvali/claude-session-share/discussions)
|
|
309
|
-
- **Email**: okovvali5@gmail.com
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
**Made with ❤️ for the Claude Code community**
|
|
@@ -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:
|
|
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).
|
|
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
|
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Prompt Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests slash command functionality via MCP prompts.
|
|
5
|
+
* Prompts convert slash commands to natural language that triggers tool handlers.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
describe('MCP Prompts', () => {
|
|
9
|
+
// Test prompt definitions (schema)
|
|
10
|
+
const promptDefinitions = {
|
|
11
|
+
prompts: [
|
|
12
|
+
{
|
|
13
|
+
name: 'share',
|
|
14
|
+
description: 'Share current Claude Code session to GitHub Gist',
|
|
15
|
+
arguments: [
|
|
16
|
+
{
|
|
17
|
+
name: 'session_path',
|
|
18
|
+
description: 'Optional: Path to session file (defaults to most recent)',
|
|
19
|
+
required: false,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'import',
|
|
25
|
+
description: 'Import shared session from GitHub Gist URL',
|
|
26
|
+
arguments: [
|
|
27
|
+
{
|
|
28
|
+
name: 'gist_url',
|
|
29
|
+
description: 'GitHub Gist URL (e.g., https://gist.github.com/user/abc123)',
|
|
30
|
+
required: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'project_path',
|
|
34
|
+
description: 'Optional: Local project directory (defaults to current directory)',
|
|
35
|
+
required: false,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
// Test GetPrompt handler logic
|
|
42
|
+
const handleGetPrompt = (name, args) => {
|
|
43
|
+
if (name === 'share') {
|
|
44
|
+
const sessionPath = args?.session_path;
|
|
45
|
+
return {
|
|
46
|
+
description: 'Share session to GitHub Gist',
|
|
47
|
+
messages: [
|
|
48
|
+
{
|
|
49
|
+
role: 'user',
|
|
50
|
+
content: {
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: sessionPath
|
|
53
|
+
? `Share my session from ${sessionPath} to GitHub Gist`
|
|
54
|
+
: 'Share my current session to GitHub Gist',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (name === 'import') {
|
|
61
|
+
const gistUrl = args?.gist_url;
|
|
62
|
+
const projectPath = args?.project_path;
|
|
63
|
+
if (!gistUrl) {
|
|
64
|
+
throw new Error('gist_url argument is required');
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
description: 'Import session from Gist',
|
|
68
|
+
messages: [
|
|
69
|
+
{
|
|
70
|
+
role: 'user',
|
|
71
|
+
content: {
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: projectPath
|
|
74
|
+
? `Import session from ${gistUrl} to ${projectPath}`
|
|
75
|
+
: `Import session from ${gistUrl}`,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
82
|
+
};
|
|
83
|
+
describe('ListPrompts', () => {
|
|
84
|
+
it('should list share and import prompts', () => {
|
|
85
|
+
expect(promptDefinitions.prompts).toHaveLength(2);
|
|
86
|
+
expect(promptDefinitions.prompts[0].name).toBe('share');
|
|
87
|
+
expect(promptDefinitions.prompts[1].name).toBe('import');
|
|
88
|
+
});
|
|
89
|
+
it('should include descriptions for both prompts', () => {
|
|
90
|
+
expect(promptDefinitions.prompts[0].description).toContain('Share current Claude Code session');
|
|
91
|
+
expect(promptDefinitions.prompts[1].description).toContain('Import shared session');
|
|
92
|
+
});
|
|
93
|
+
it('should define correct arguments for share prompt', () => {
|
|
94
|
+
const sharePrompt = promptDefinitions.prompts.find((p) => p.name === 'share');
|
|
95
|
+
expect(sharePrompt?.arguments).toHaveLength(1);
|
|
96
|
+
expect(sharePrompt?.arguments?.[0].name).toBe('session_path');
|
|
97
|
+
expect(sharePrompt?.arguments?.[0].required).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
it('should define correct arguments for import prompt', () => {
|
|
100
|
+
const importPrompt = promptDefinitions.prompts.find((p) => p.name === 'import');
|
|
101
|
+
expect(importPrompt?.arguments).toHaveLength(2);
|
|
102
|
+
expect(importPrompt?.arguments?.[0].name).toBe('gist_url');
|
|
103
|
+
expect(importPrompt?.arguments?.[0].required).toBe(true);
|
|
104
|
+
expect(importPrompt?.arguments?.[1].name).toBe('project_path');
|
|
105
|
+
expect(importPrompt?.arguments?.[1].required).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('GetPrompt - share', () => {
|
|
109
|
+
it('should return share prompt without arguments', () => {
|
|
110
|
+
const response = handleGetPrompt('share', {});
|
|
111
|
+
expect(response.description).toBe('Share session to GitHub Gist');
|
|
112
|
+
expect(response.messages).toHaveLength(1);
|
|
113
|
+
expect(response.messages[0].role).toBe('user');
|
|
114
|
+
expect(response.messages[0].content.type).toBe('text');
|
|
115
|
+
expect(response.messages[0].content.text).toBe('Share my current session to GitHub Gist');
|
|
116
|
+
});
|
|
117
|
+
it('should return share prompt with session_path argument', () => {
|
|
118
|
+
const response = handleGetPrompt('share', { session_path: '/path/to/session.jsonl' });
|
|
119
|
+
expect(response.messages[0].content.text).toBe('Share my session from /path/to/session.jsonl to GitHub Gist');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('GetPrompt - import', () => {
|
|
123
|
+
it('should return import prompt with gist_url only', () => {
|
|
124
|
+
const response = handleGetPrompt('import', { gist_url: 'https://gist.github.com/user/abc123' });
|
|
125
|
+
expect(response.description).toBe('Import session from Gist');
|
|
126
|
+
expect(response.messages).toHaveLength(1);
|
|
127
|
+
expect(response.messages[0].role).toBe('user');
|
|
128
|
+
expect(response.messages[0].content.type).toBe('text');
|
|
129
|
+
expect(response.messages[0].content.text).toBe('Import session from https://gist.github.com/user/abc123');
|
|
130
|
+
});
|
|
131
|
+
it('should return import prompt with both gist_url and project_path', () => {
|
|
132
|
+
const response = handleGetPrompt('import', {
|
|
133
|
+
gist_url: 'https://gist.github.com/user/abc123',
|
|
134
|
+
project_path: '/my/project',
|
|
135
|
+
});
|
|
136
|
+
expect(response.messages[0].content.text).toBe('Import session from https://gist.github.com/user/abc123 to /my/project');
|
|
137
|
+
});
|
|
138
|
+
it('should error when import prompt missing gist_url', () => {
|
|
139
|
+
expect(() => handleGetPrompt('import', {})).toThrow('gist_url argument is required');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('GetPrompt - error cases', () => {
|
|
143
|
+
it('should error on unknown prompt name', () => {
|
|
144
|
+
expect(() => handleGetPrompt('unknown', {})).toThrow('Unknown prompt: unknown');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|