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 CHANGED
@@ -1,21 +1,18 @@
1
1
  # claude-session-share
2
2
 
3
- > **MCP server for sharing Claude Code sessions via GitHub Gist with automatic privacy protection**
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
  [![npm version](https://badge.fury.io/js/claude-session-share.svg)](https://www.npmjs.com/package/claude-session-share)
8
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
7
 
10
- ## Features
8
+ ## Features
11
9
 
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
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
- ## 📦 Installation
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. Give it a name like "Claude Session Share"
37
- 4. Check the **`gist`** scope
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
- Add the MCP server to your Claude Code configuration:
30
+ ### Configure MCP Server
43
31
 
44
- ### Option 1: User Config (Recommended)
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
- # Then type: /mcp
52
+ # Type: /mcp
72
53
  # You should see "claude-session-share" in the list
73
54
  ```
74
55
 
75
- ## 🚀 Usage
56
+ ## Usage
76
57
 
77
- ### Sharing a Session
58
+ ### Share a Session
78
59
 
79
- In any Claude Code conversation, simply say:
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 (unlisted) GitHub Gist
69
+ 3. Upload to a secret GitHub Gist
89
70
  4. Return a shareable link
90
71
 
91
- **Example output:**
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
- 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
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
- The imported session works exactly like a native Claude Code session—full conversation history, no thinking blocks, perfect privacy.
85
+ Or directly:
86
+ ```bash
87
+ claude --resume <session-id>
88
+ ```
123
89
 
124
- ## 🔐 Privacy Protection
90
+ ## Privacy Protection
125
91
 
126
92
  Every shared session is automatically sanitized:
127
93
 
128
- ### ✅ What Gets Removed/Sanitized
129
-
130
- - **Thinking Blocks** - Internal reasoning stripped completely
131
- - **Absolute Paths** - `/Users/you/project/file.ts` → `file.ts`
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 and explanations
140
- - File names and relative paths
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
- - Passwords in connection strings (e.g., `postgresql://user:pass@host/db`) are not detected
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
- Imports a session from a GitHub Gist.
113
+ ## Troubleshooting
168
114
 
169
- **Parameters:**
170
- - `gistUrl` - GitHub Gist URL (e.g., `https://gist.github.com/user/abc123`)
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
- **Returns:**
174
- - `sessionPath` - Path to imported session file
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
- ## 🛠️ Development
121
+ ### MCP Server Not Listed
122
+ Verify `~/.claude/mcp.json` and restart Claude Code.
179
123
 
180
- ### Clone and Setup
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
- ## 🧪 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
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: 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
  });
@@ -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
+ });