claude-session-share 1.0.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.
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Session import service
3
+ *
4
+ * Orchestrates the complete workflow of importing a Claude Code session from GitHub Gist:
5
+ * 1. Fetch gist content
6
+ * 2. Extract session JSONL
7
+ * 3. Parse messages with error recovery
8
+ * 4. Remap UUIDs to avoid conflicts
9
+ * 5. Write to local storage
10
+ */
11
+ import { GistClient } from '../gist/client.js';
12
+ import { UUIDMapper } from '../utils/uuid-mapper.js';
13
+ import { writeSessionToLocal } from '../session/writer.js';
14
+ /**
15
+ * Import a session from GitHub Gist
16
+ *
17
+ * Fetches a shared session gist, remaps UUIDs, and writes to local storage.
18
+ * Includes error recovery for malformed messages (logs and continues).
19
+ *
20
+ * @param gistIdOrUrl - GitHub Gist URL or bare gist ID
21
+ * @param projectPath - Local project directory path (e.g., "/Users/name/project")
22
+ * @returns Promise resolving to import result with session path and metadata
23
+ * @throws Error if gist not found, no JSONL file, or write fails
24
+ *
25
+ * @example
26
+ * const result = await importSession('https://gist.github.com/user/abc123', '/Users/name/project');
27
+ * console.log(`Imported ${result.messageCount} messages to ${result.sessionPath}`);
28
+ */
29
+ export async function importSession(gistIdOrUrl, projectPath) {
30
+ try {
31
+ // Step 1: Initialize GistClient (validates GITHUB_TOKEN)
32
+ const gistClient = new GistClient();
33
+ // Step 2: Fetch gist content
34
+ const gist = await gistClient.fetchGist(gistIdOrUrl);
35
+ // Step 3: Extract session JSONL file
36
+ // Look for file with .jsonl extension
37
+ const jsonlFileName = Object.keys(gist.files).find((name) => name.endsWith('.jsonl'));
38
+ if (!jsonlFileName) {
39
+ throw new Error('No JSONL file found in gist. Expected a .jsonl file containing session messages.');
40
+ }
41
+ const jsonlFile = gist.files[jsonlFileName];
42
+ if (!jsonlFile || !jsonlFile.content) {
43
+ throw new Error(`JSONL file "${jsonlFileName}" has no content. The gist may be malformed.`);
44
+ }
45
+ const jsonlContent = jsonlFile.content;
46
+ // Step 4: Parse messages with per-line error recovery
47
+ const messages = [];
48
+ const lines = jsonlContent.split('\n').filter((line) => line.trim());
49
+ let parseErrors = 0;
50
+ for (const [index, line] of lines.entries()) {
51
+ try {
52
+ const message = JSON.parse(line);
53
+ messages.push(message);
54
+ }
55
+ catch (error) {
56
+ // Log error but continue parsing remaining lines
57
+ parseErrors++;
58
+ console.warn(`Failed to parse message at line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
59
+ }
60
+ }
61
+ if (messages.length === 0) {
62
+ throw new Error(`No valid messages found in JSONL file. Parse errors: ${parseErrors}`);
63
+ }
64
+ // Log parse errors if any occurred
65
+ if (parseErrors > 0) {
66
+ console.warn(`Imported ${messages.length} messages with ${parseErrors} parse errors`);
67
+ }
68
+ // Step 5: Remap UUIDs to avoid conflicts
69
+ const mapper = new UUIDMapper();
70
+ const remappedMessages = messages.map((msg) => mapper.remapMessage(msg));
71
+ // Step 6: Write to local storage
72
+ const result = await writeSessionToLocal(remappedMessages, projectPath);
73
+ // Step 7: Return import result
74
+ return {
75
+ sessionPath: result.filePath,
76
+ sessionId: result.sessionId,
77
+ messageCount: remappedMessages.length,
78
+ projectPath,
79
+ };
80
+ }
81
+ catch (error) {
82
+ // Add context to errors for better debugging
83
+ if (error instanceof Error) {
84
+ throw new Error(`Failed to import session: ${error.message}`);
85
+ }
86
+ throw new Error(`Failed to import session: ${String(error)}`);
87
+ }
88
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Session-to-Gist upload service
3
+ *
4
+ * Orchestrates the complete workflow of uploading a Claude Code session to GitHub Gist:
5
+ * 1. Read session messages
6
+ * 2. Sanitize for privacy
7
+ * 3. Convert to JSONL format
8
+ * 4. Extract metadata
9
+ * 5. Upload to Gist
10
+ */
11
+ import { parseSessionFile } from '../session/reader.js';
12
+ import { extractMetadata } from '../session/metadata.js';
13
+ import { sanitizeSession, inferBasePath } from '../sanitization/pipeline.js';
14
+ import { GistClient } from '../gist/client.js';
15
+ /**
16
+ * Upload a session file to GitHub Gist
17
+ *
18
+ * Performs full sanitization pipeline and uploads to secret (unlisted) Gist.
19
+ *
20
+ * @param sessionPath - Absolute path to session JSONL file
21
+ * @returns Promise resolving to Gist URL
22
+ * @throws Error if any step fails (reading, sanitizing, uploading)
23
+ *
24
+ * @example
25
+ * const url = await uploadSession('/Users/name/.claude/projects/abc/session.jsonl');
26
+ * console.log(`Shared at: ${url}`);
27
+ */
28
+ export async function uploadSession(sessionPath) {
29
+ try {
30
+ // Step 1: Read session messages
31
+ const messages = await parseSessionFile(sessionPath);
32
+ if (messages.length === 0) {
33
+ throw new Error('Session file is empty or contains no valid messages');
34
+ }
35
+ // Step 2: Sanitize session for privacy
36
+ const basePath = inferBasePath(messages);
37
+ const sanitizedMessages = sanitizeSession(messages, basePath);
38
+ // Step 3: Convert sanitized messages to JSONL string
39
+ const sessionJsonl = sanitizedMessages.map(msg => JSON.stringify(msg)).join('\n');
40
+ // Step 4: Extract metadata for gist description and metadata file
41
+ const metadata = extractMetadata(sanitizedMessages);
42
+ if (!metadata) {
43
+ throw new Error('Failed to extract session metadata');
44
+ }
45
+ // Step 5: Upload to Gist
46
+ const gistClient = new GistClient();
47
+ // Use metadata title if available, fallback to timestamp-based title
48
+ const description = metadata.projectPath && metadata.projectPath !== 'unknown'
49
+ ? `Claude Code Session - ${metadata.projectPath.split('/').pop()}`
50
+ : `Claude Code Session - ${new Date(metadata.firstTimestamp).toISOString()}`;
51
+ const response = await gistClient.createGist(description, {
52
+ 'session.jsonl': sessionJsonl,
53
+ 'metadata.json': JSON.stringify(metadata, null, 2),
54
+ });
55
+ return response.html_url;
56
+ }
57
+ catch (error) {
58
+ // Add context to errors for better debugging
59
+ if (error instanceof Error) {
60
+ throw new Error(`Failed to upload session: ${error.message}`);
61
+ }
62
+ throw new Error(`Failed to upload session: ${String(error)}`);
63
+ }
64
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Session file discovery for Claude Code projects
3
+ *
4
+ * Finds all session JSONL files (main and agent) for a given project path.
5
+ * Uses Claude Code's path encoding scheme to locate session directories.
6
+ */
7
+ import { readdir } from 'fs/promises';
8
+ import { join } from 'path';
9
+ import { getSessionDirectory } from '../utils/path-encoding.js';
10
+ /**
11
+ * Finds all session files for a project
12
+ *
13
+ * Discovers both main session files (uuid.jsonl) and agent session files
14
+ * (agent-uuid.jsonl) in the project's session directory.
15
+ *
16
+ * @param projectPath - Absolute path to the project
17
+ * @returns Array of session files with metadata
18
+ * @throws Error if home directory cannot be determined
19
+ *
20
+ * @example
21
+ * const sessions = await findSessionFiles('/Users/name/project');
22
+ * // Returns: [
23
+ * // { path: '/Users/name/.claude/projects/Users-name-project/abc-123.jsonl',
24
+ * // sessionId: 'abc-123', isAgent: false },
25
+ * // { path: '/Users/name/.claude/projects/Users-name-project/agent-def-456.jsonl',
26
+ * // sessionId: 'def-456', isAgent: true }
27
+ * // ]
28
+ */
29
+ export async function findSessionFiles(projectPath) {
30
+ // Get home directory
31
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
32
+ if (!homeDir) {
33
+ throw new Error('Cannot determine home directory: HOME and USERPROFILE environment variables not set');
34
+ }
35
+ // Get the encoded session directory path
36
+ const sessionDir = getSessionDirectory(projectPath);
37
+ try {
38
+ // List all files in the session directory
39
+ const files = await readdir(sessionDir);
40
+ // Filter and map to SessionFile objects
41
+ return files
42
+ .filter(filename => filename.endsWith('.jsonl'))
43
+ .map(filename => {
44
+ // Detect agent sessions by filename prefix
45
+ const isAgent = filename.startsWith('agent-');
46
+ // Extract session ID: remove 'agent-' prefix if present, then remove '.jsonl' extension
47
+ const sessionId = isAgent
48
+ ? filename.replace('agent-', '').replace('.jsonl', '')
49
+ : filename.replace('.jsonl', '');
50
+ return {
51
+ path: join(sessionDir, filename),
52
+ sessionId,
53
+ isAgent,
54
+ };
55
+ });
56
+ }
57
+ catch (error) {
58
+ // Handle missing directory gracefully - not an error if project has no sessions yet
59
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
60
+ return [];
61
+ }
62
+ // Re-throw other errors (permission denied, etc.)
63
+ throw error;
64
+ }
65
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Session metadata extraction from parsed messages
3
+ *
4
+ * Extracts useful metadata from session messages including message counts,
5
+ * timestamps, project path, and agent conversation detection.
6
+ */
7
+ /**
8
+ * Extracts metadata from session messages
9
+ *
10
+ * Analyzes message array to extract session metadata including:
11
+ * - Session ID and project path (from user messages)
12
+ * - Message count and timestamp range
13
+ * - Agent conversation detection
14
+ * - Claude Code version
15
+ *
16
+ * @param messages - Array of parsed session messages
17
+ * @returns Session metadata, or null if messages array is empty
18
+ *
19
+ * @example
20
+ * const messages = await parseSessionFile('/path/to/session.jsonl');
21
+ * const metadata = extractMetadata(messages);
22
+ * // Returns: {
23
+ * // sessionId: 'abc-123',
24
+ * // projectPath: '/Users/name/project',
25
+ * // messageCount: 42,
26
+ * // firstTimestamp: '2026-01-11T10:00:00.000Z',
27
+ * // lastTimestamp: '2026-01-11T11:30:00.000Z',
28
+ * // hasAgentConversations: true,
29
+ * // version: '1.2.3'
30
+ * // }
31
+ */
32
+ export function extractMetadata(messages) {
33
+ // Return null for empty arrays
34
+ if (messages.length === 0) {
35
+ return null;
36
+ }
37
+ // Get first and last messages for timestamps
38
+ const firstMessage = messages[0];
39
+ const lastMessage = messages[messages.length - 1];
40
+ // Find first user message to extract cwd and version
41
+ // Use type guard to safely access UserMessage fields
42
+ const firstUserMessage = messages.find(m => m.type === 'user');
43
+ // Detect agent conversations by checking isSidechain flag
44
+ const hasAgentConversations = messages.some(m => m.isSidechain === true);
45
+ // Build metadata object with all fields
46
+ return {
47
+ sessionId: firstMessage.sessionId,
48
+ projectPath: firstUserMessage?.cwd || 'unknown',
49
+ messageCount: messages.length,
50
+ firstTimestamp: firstMessage.timestamp,
51
+ lastTimestamp: lastMessage.timestamp,
52
+ hasAgentConversations,
53
+ version: firstUserMessage?.version || 'unknown',
54
+ };
55
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Streaming JSONL reader for Claude Code session files
3
+ *
4
+ * Provides memory-efficient line-by-line reading with error recovery.
5
+ * Handles large session files without loading entire content into memory.
6
+ */
7
+ import { createReadStream } from 'fs';
8
+ import { createInterface } from 'readline';
9
+ /**
10
+ * Reads session file line-by-line using async generator
11
+ *
12
+ * Streams the file efficiently, yielding non-empty lines.
13
+ * Handles CRLF line endings automatically.
14
+ *
15
+ * @param filePath - Absolute path to session JSONL file
16
+ * @yields Non-empty lines from the file
17
+ *
18
+ * @example
19
+ * for await (const line of readSessionLines('/path/to/session.jsonl')) {
20
+ * console.log(line);
21
+ * }
22
+ */
23
+ export async function* readSessionLines(filePath) {
24
+ // Create read stream with UTF-8 encoding
25
+ const fileStream = createReadStream(filePath, { encoding: 'utf-8' });
26
+ // Create readline interface for line-by-line reading
27
+ // crlfDelay: Infinity handles both \n and \r\n line endings
28
+ const rl = createInterface({
29
+ input: fileStream,
30
+ crlfDelay: Infinity,
31
+ });
32
+ // Yield each non-empty line
33
+ for await (const line of rl) {
34
+ const trimmedLine = line.trim();
35
+ if (trimmedLine) {
36
+ yield trimmedLine;
37
+ }
38
+ }
39
+ }
40
+ /**
41
+ * Checks if a parsed object has required session message fields
42
+ *
43
+ * @param obj - Parsed JSON object
44
+ * @returns true if object has required fields (type, uuid, sessionId)
45
+ */
46
+ function hasRequiredFields(obj) {
47
+ return (typeof obj === 'object' &&
48
+ obj !== null &&
49
+ 'type' in obj &&
50
+ 'uuid' in obj &&
51
+ 'sessionId' in obj &&
52
+ typeof obj.type === 'string' &&
53
+ typeof obj.uuid === 'string' &&
54
+ typeof obj.sessionId === 'string');
55
+ }
56
+ /**
57
+ * Parses a session JSONL file with error recovery
58
+ *
59
+ * Reads the file line-by-line, parsing each line as JSON.
60
+ * Continues processing even if individual lines fail to parse.
61
+ * Skips lines missing required fields (type, uuid, sessionId).
62
+ *
63
+ * @param filePath - Absolute path to session JSONL file
64
+ * @returns Array of successfully parsed session messages
65
+ *
66
+ * @example
67
+ * const messages = await parseSessionFile('/path/to/session.jsonl');
68
+ * console.log(`Loaded ${messages.length} messages`);
69
+ */
70
+ export async function parseSessionFile(filePath) {
71
+ const messages = [];
72
+ let lineNumber = 0;
73
+ try {
74
+ // Process each line from the file
75
+ for await (const line of readSessionLines(filePath)) {
76
+ lineNumber++;
77
+ try {
78
+ // Parse JSON - this is wrapped per line for error recovery
79
+ const parsed = JSON.parse(line);
80
+ // Validate required fields exist
81
+ if (!hasRequiredFields(parsed)) {
82
+ console.warn(`[reader] Line ${lineNumber}: Skipping message missing required fields (type, uuid, sessionId)`);
83
+ continue;
84
+ }
85
+ // Type assertion is safe after validation
86
+ messages.push(parsed);
87
+ }
88
+ catch (parseError) {
89
+ // Log warning but continue processing remaining lines
90
+ console.warn(`[reader] Line ${lineNumber}: Failed to parse JSON - ${parseError instanceof Error ? parseError.message : String(parseError)}`);
91
+ continue;
92
+ }
93
+ }
94
+ }
95
+ catch (fileError) {
96
+ // This catches file-level errors (file not found, permission denied, etc.)
97
+ console.error(`[reader] Error reading file ${filePath}: ${fileError instanceof Error ? fileError.message : String(fileError)}`);
98
+ throw fileError;
99
+ }
100
+ return messages;
101
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * TypeScript types for Claude Code session messages
3
+ *
4
+ * Sessions are stored as JSONL files with three message types:
5
+ * - user: User input messages
6
+ * - assistant: Assistant responses with thinking snapshots
7
+ * - file-history-snapshot: File state tracking
8
+ *
9
+ * Uses discriminated unions for type-safe message handling.
10
+ */
11
+ export {};
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Session writer for local JSONL storage
3
+ *
4
+ * Writes session messages to Claude Code's local storage format:
5
+ * ~/.claude/projects/{encodedPath}/{sessionId}.jsonl
6
+ *
7
+ * Uses atomic file writes and handles filesystem errors gracefully.
8
+ */
9
+ import { mkdir, writeFile } from 'fs/promises';
10
+ import { randomUUID } from 'crypto';
11
+ import { join } from 'path';
12
+ import { homedir } from 'os';
13
+ import { encodeProjectPath } from '../utils/path-encoding.js';
14
+ /**
15
+ * Error thrown when session writing fails
16
+ */
17
+ export class SessionWriteError extends Error {
18
+ code;
19
+ constructor(message, code) {
20
+ super(message);
21
+ this.code = code;
22
+ this.name = 'SessionWriteError';
23
+ }
24
+ }
25
+ /**
26
+ * Write session messages to local Claude Code storage
27
+ *
28
+ * Creates a new session file in ~/.claude/projects/{encodedPath}/{sessionId}.jsonl
29
+ * with JSONL format (one JSON object per line).
30
+ *
31
+ * @param messages - Array of session messages to write
32
+ * @param projectPath - Absolute path to the project (e.g., "/Users/name/project")
33
+ * @returns Promise resolving to written file path and session ID
34
+ * @throws {SessionWriteError} If writing fails (permissions, disk space, etc.)
35
+ *
36
+ * @example
37
+ * const result = await writeSessionToLocal(messages, '/Users/name/my-project');
38
+ * console.log(`Written to: ${result.filePath}`);
39
+ */
40
+ export async function writeSessionToLocal(messages, projectPath) {
41
+ try {
42
+ // 1. Encode project path for directory name
43
+ const encodedPath = encodeProjectPath(projectPath);
44
+ // 2. Generate new session ID for filename
45
+ // Note: Messages already have remapped sessionIds in their fields,
46
+ // but the filename needs its own unique ID
47
+ const sessionId = randomUUID();
48
+ // 3. Build target directory and file paths
49
+ const sessionDirectory = join(homedir(), '.claude', 'projects', encodedPath);
50
+ const targetPath = join(sessionDirectory, `${sessionId}.jsonl`);
51
+ // 4. Create directory structure (handles missing ~/.claude/projects/ gracefully)
52
+ await mkdir(sessionDirectory, { recursive: true });
53
+ // 5. Format as JSONL: one JSON object per line with trailing newline
54
+ const jsonlContent = messages.map((msg) => JSON.stringify(msg)).join('\n') + '\n';
55
+ // 6. Write file atomically
56
+ await writeFile(targetPath, jsonlContent, { encoding: 'utf-8' });
57
+ // 7. Return written file path and session ID for verification
58
+ return {
59
+ filePath: targetPath,
60
+ sessionId,
61
+ };
62
+ }
63
+ catch (error) {
64
+ // Handle filesystem errors with descriptive messages
65
+ if (error.code === 'EACCES') {
66
+ throw new SessionWriteError(`Permission denied: Cannot write to session directory. Check permissions for ~/.claude/projects/`, error.code);
67
+ }
68
+ if (error.code === 'ENOSPC') {
69
+ throw new SessionWriteError(`Disk full: Not enough space to write session file`, error.code);
70
+ }
71
+ // Generic error
72
+ throw new SessionWriteError(`Failed to write session: ${error.message || 'Unknown error'}`, error.code);
73
+ }
74
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Path encoding utilities for Claude Code session directories
3
+ *
4
+ * Claude Code stores sessions in ~/.claude/projects/{encodedPath}/
5
+ * where paths are encoded by replacing `/` with `-` and removing leading `-`
6
+ *
7
+ * Example: /Users/name/project -> Users-name-project
8
+ */
9
+ import { homedir } from 'os';
10
+ import { join } from 'path';
11
+ /**
12
+ * Encodes an absolute project path into Claude Code's directory naming format
13
+ *
14
+ * @param absolutePath - Absolute file system path (e.g., "/Users/name/project")
15
+ * @returns Encoded directory name (e.g., "Users-name-project")
16
+ *
17
+ * @example
18
+ * encodeProjectPath('/Users/name/my-project')
19
+ * // Returns: 'Users-name-my-project'
20
+ */
21
+ export function encodeProjectPath(absolutePath) {
22
+ // Replace all forward slashes with dashes
23
+ const encoded = absolutePath.replace(/\//g, '-');
24
+ // Remove leading dash (from root /)
25
+ return encoded.startsWith('-') ? encoded.slice(1) : encoded;
26
+ }
27
+ /**
28
+ * Decodes a Claude Code directory name back to an absolute path
29
+ *
30
+ * @param encodedName - Encoded directory name (e.g., "Users-name-project")
31
+ * @returns Decoded absolute path (e.g., "/Users/name/project")
32
+ *
33
+ * @example
34
+ * decodeProjectPath('Users-name-my-project')
35
+ * // Returns: '/Users/name/my-project'
36
+ */
37
+ export function decodeProjectPath(encodedName) {
38
+ // Replace all dashes with forward slashes and add leading slash
39
+ return '/' + encodedName.replace(/-/g, '/');
40
+ }
41
+ /**
42
+ * Gets the full session directory path for a project
43
+ *
44
+ * @param projectPath - Absolute project path
45
+ * @returns Full path to ~/.claude/projects/{encodedPath}
46
+ *
47
+ * @example
48
+ * getSessionDirectory('/Users/name/my-project')
49
+ * // Returns: '/Users/name/.claude/projects/Users-name-my-project'
50
+ */
51
+ export function getSessionDirectory(projectPath) {
52
+ const encoded = encodeProjectPath(projectPath);
53
+ return join(homedir(), '.claude', 'projects', encoded);
54
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * UUID remapper for session import with collision avoidance
3
+ *
4
+ * Provides consistent UUID remapping to avoid conflicts when importing
5
+ * sessions into local Claude Code storage. Maintains parent-child
6
+ * relationships through consistent mapping.
7
+ */
8
+ import { randomUUID } from 'crypto';
9
+ /**
10
+ * Maps original UUIDs to new UUIDs consistently
11
+ *
12
+ * Ensures that the same original UUID always maps to the same new UUID
13
+ * within a session import operation. This preserves message threading
14
+ * and parent-child relationships.
15
+ */
16
+ export class UUIDMapper {
17
+ map;
18
+ constructor() {
19
+ this.map = new Map();
20
+ }
21
+ /**
22
+ * Remap a UUID to a new collision-free UUID
23
+ *
24
+ * @param originalUuid - The original UUID to remap (or null)
25
+ * @returns A new UUID that consistently maps from the original, or null if input is null
26
+ *
27
+ * @example
28
+ * const mapper = new UUIDMapper();
29
+ * const newUuid1 = mapper.remap('abc-123'); // Generates new UUID
30
+ * const newUuid2 = mapper.remap('abc-123'); // Returns same UUID as newUuid1
31
+ * mapper.remap(null); // Returns null
32
+ */
33
+ remap(originalUuid) {
34
+ // Handle null gracefully (used for root messages with no parent)
35
+ if (originalUuid === null) {
36
+ return null;
37
+ }
38
+ // Check if we've already mapped this UUID
39
+ const existing = this.map.get(originalUuid);
40
+ if (existing) {
41
+ return existing;
42
+ }
43
+ // Generate a new UUID and cache the mapping
44
+ const newUuid = randomUUID();
45
+ this.map.set(originalUuid, newUuid);
46
+ return newUuid;
47
+ }
48
+ /**
49
+ * Remap all UUIDs in a session message
50
+ *
51
+ * Creates an immutable copy of the message with remapped uuid, sessionId,
52
+ * and parentUuid fields. Preserves all other fields unchanged.
53
+ *
54
+ * @param message - The original session message
55
+ * @returns A new message with remapped UUIDs (original unchanged)
56
+ *
57
+ * @example
58
+ * const mapper = new UUIDMapper();
59
+ * const original = { type: 'user', uuid: 'abc', sessionId: '123', parentUuid: null, ... };
60
+ * const remapped = mapper.remapMessage(original);
61
+ * // original is unchanged, remapped has new UUIDs
62
+ */
63
+ remapMessage(message) {
64
+ // Create immutable copy with remapped UUID fields
65
+ // Spread operator preserves all other fields (type, message, cwd, etc.)
66
+ return {
67
+ ...message,
68
+ uuid: this.remap(message.uuid),
69
+ sessionId: this.remap(message.sessionId),
70
+ parentUuid: this.remap(message.parentUuid),
71
+ };
72
+ }
73
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "claude-session-share",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for sharing Claude Code sessions via GitHub Gist with privacy protection",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "claude-session-share": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist/**/*",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "test": "vitest run",
18
+ "dev": "tsc --watch",
19
+ "prepublishOnly": "npm run build && npm test"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "mcp-server",
24
+ "claude",
25
+ "claude-code",
26
+ "session-sharing",
27
+ "github-gist",
28
+ "privacy",
29
+ "conversation-export",
30
+ "ai-tools"
31
+ ],
32
+ "author": "Omkar Kovvali <okovvali5@gmail.com>",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/OmkarKovvali/claude-session-share.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/OmkarKovvali/claude-session-share/issues"
40
+ },
41
+ "homepage": "https://github.com/OmkarKovvali/claude-session-share#readme",
42
+ "engines": {
43
+ "node": ">=18.0.0"
44
+ },
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.0.4",
47
+ "octokit": "^5.0.5"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.10.5",
51
+ "typescript": "^5.7.3",
52
+ "vitest": "^4.0.16"
53
+ }
54
+ }