ccshare
Version:
Share Claude Code prompts and results easily
500 lines • 110 kB
JSON
{
"prompts": [
{
"userPrompt": {
"parentUuid": "03d8adfc-0f52-4301-b980-e01af85f7e8a",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"type": "user",
"message": {
"role": "user",
"content": "ccshare 어떤 플래그 뒤에 slug(예: AdsrMP) 를 입력하면 해당 프롬프트 리스트를 claude -p 로 입력해주는 처리를 해줘"
},
"uuid": "c90aade3-01db-4efe-bde5-8f97b5968391",
"timestamp": "2025-07-22T15:26:47.548Z"
},
"sessionEntries": [
{
"parentUuid": "c90aade3-01db-4efe-bde5-8f97b5968391",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"message": {
"id": "msg_01BZvZCjtD3t1DubgDWPQwQ4",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-20250514",
"content": [
{
"type": "text",
"text": "`ccshare` 명령에 slug를 받아서 해당 프롬프트들을 `claude -p` 명령으로 실행하는 기능을 추가하겠습니다."
}
],
"stop_reason": null,
"stop_sequence": null,
"usage": {
"input_tokens": 0,
"cache_creation_input_tokens": 127746,
"cache_read_input_tokens": 0,
"output_tokens": 1,
"service_tier": "standard"
}
},
"requestId": "req_011CRNHszSv1L6UCAsSdnwpp",
"type": "assistant",
"uuid": "2a080374-c0c8-42e4-a685-d60c49d2af50",
"timestamp": "2025-07-22T15:26:56.916Z"
},
{
"parentUuid": "2a080374-c0c8-42e4-a685-d60c49d2af50",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"message": {
"id": "msg_01BZvZCjtD3t1DubgDWPQwQ4",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-20250514",
"content": [
{
"type": "tool_use",
"id": "toolu_01DUCcSnTZN1PkS7mAhKp5av",
"name": "TodoWrite",
"input": {
"todos": [
{
"id": "1",
"content": "Add load command to fetch prompts from slug",
"status": "in_progress",
"priority": "high"
},
{
"id": "2",
"content": "Execute claude -p with fetched prompts",
"status": "pending",
"priority": "high"
},
{
"id": "3",
"content": "Handle API response and error cases",
"status": "pending",
"priority": "medium"
}
]
}
}
],
"stop_reason": null,
"stop_sequence": null,
"usage": {
"input_tokens": 0,
"cache_creation_input_tokens": 127746,
"cache_read_input_tokens": 0,
"output_tokens": 208,
"service_tier": "standard"
}
},
"requestId": "req_011CRNHszSv1L6UCAsSdnwpp",
"type": "assistant",
"uuid": "2e760446-6d61-4841-8f9f-cd2fd9c1d13f",
"timestamp": "2025-07-22T15:26:58.168Z"
},
{
"parentUuid": "2e760446-6d61-4841-8f9f-cd2fd9c1d13f",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"type": "user",
"message": {
"role": "user",
"content": [
{
"tool_use_id": "toolu_01DUCcSnTZN1PkS7mAhKp5av",
"type": "tool_result",
"content": "Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable"
}
]
},
"uuid": "92c8020c-ef28-4309-8329-4cd7d67b28d5",
"timestamp": "2025-07-22T15:26:58.243Z",
"toolUseResult": {
"oldTodos": [
{
"content": "Add watch mode to ccshare CLI",
"status": "completed",
"priority": "high",
"id": "1"
},
{
"content": "Create file watcher for JSONL files",
"status": "completed",
"priority": "high",
"id": "2"
},
{
"content": "Implement real-time prompt display",
"status": "completed",
"priority": "high",
"id": "3"
},
{
"content": "Add hotkey to trigger share from watch mode",
"status": "completed",
"priority": "medium",
"id": "4"
}
],
"newTodos": [
{
"content": "Add load command to fetch prompts from slug",
"status": "in_progress",
"priority": "high",
"id": "1"
},
{
"content": "Execute claude -p with fetched prompts",
"status": "pending",
"priority": "high",
"id": "2"
},
{
"content": "Handle API response and error cases",
"status": "pending",
"priority": "medium",
"id": "3"
}
]
}
},
{
"parentUuid": "92c8020c-ef28-4309-8329-4cd7d67b28d5",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"message": {
"id": "msg_01J7A8HcQzEwXULnEdnxmvT6",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-20250514",
"content": [
{
"type": "text",
"text": "먼저 share-service.ts에 slug로 데이터를 가져오는 함수를 추가하겠습니다:"
}
],
"stop_reason": null,
"stop_sequence": null,
"usage": {
"input_tokens": 3,
"cache_creation_input_tokens": 247,
"cache_read_input_tokens": 127746,
"output_tokens": 10,
"service_tier": "standard"
}
},
"requestId": "req_011CRNHtmttbNGYJjcg9zF1w",
"type": "assistant",
"uuid": "d1fef184-23cf-493a-ab3e-1db2e6a0f1dc",
"timestamp": "2025-07-22T15:27:03.992Z"
},
{
"parentUuid": "d1fef184-23cf-493a-ab3e-1db2e6a0f1dc",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"message": {
"id": "msg_01J7A8HcQzEwXULnEdnxmvT6",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-20250514",
"content": [
{
"type": "tool_use",
"id": "toolu_01MSkzKiyQLtEmzzCuZpyA1W",
"name": "Edit",
"input": {
"file_path": "/Users/james/Development/scripts/ccshare/src/share-service.ts",
"old_string": "export async function shareToAPI(shareData: ShareData, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareResponse> {\n try {\n const response = await axios.post<ShareResponse>(apiUrl, shareData, {\n headers: {\n 'Content-Type': 'application/json'\n },\n timeout: 10000 // 10 second timeout\n });\n \n return response.data;\n } catch (error: any) {\n if (error.response) {\n // Server responded with error\n return {\n error: error.response.data?.message || error.response.data?.error || `Server error: ${error.response.status}`\n };\n } else if (error.request) {\n // No response received\n return {\n error: 'No response from server. Make sure the API is running on localhost:3000'\n };\n } else {\n // Request setup error\n return {\n error: error.message || 'Failed to send request'\n };\n }\n }\n}",
"new_string": "export async function shareToAPI(shareData: ShareData, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareResponse> {\n try {\n const response = await axios.post<ShareResponse>(apiUrl, shareData, {\n headers: {\n 'Content-Type': 'application/json'\n },\n timeout: 10000 // 10 second timeout\n });\n \n return response.data;\n } catch (error: any) {\n if (error.response) {\n // Server responded with error\n return {\n error: error.response.data?.message || error.response.data?.error || `Server error: ${error.response.status}`\n };\n } else if (error.request) {\n // No response received\n return {\n error: 'No response from server. Make sure the API is running on localhost:3000'\n };\n } else {\n // Request setup error\n return {\n error: error.message || 'Failed to send request'\n };\n }\n }\n}\n\nexport async function fetchFromSlug(slug: string, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareData | null> {\n try {\n const response = await axios.get<ShareData>(`${apiUrl}/${slug}`, {\n headers: {\n 'Accept': 'application/json'\n },\n timeout: 10000 // 10 second timeout\n });\n \n return response.data;\n } catch (error: any) {\n if (error.response?.status === 404) {\n console.error(`Share not found: ${slug}`);\n } else if (error.response) {\n console.error(`Server error: ${error.response.status}`);\n } else {\n console.error(`Failed to fetch share: ${error.message}`);\n }\n return null;\n }\n}"
}
}
],
"stop_reason": null,
"stop_sequence": null,
"usage": {
"input_tokens": 3,
"cache_creation_input_tokens": 247,
"cache_read_input_tokens": 127746,
"output_tokens": 873,
"service_tier": "standard"
}
},
"requestId": "req_011CRNHtmttbNGYJjcg9zF1w",
"type": "assistant",
"uuid": "e2553911-0ea0-475b-a48d-5cb79a07798d",
"timestamp": "2025-07-22T15:27:15.964Z"
},
{
"parentUuid": "e2553911-0ea0-475b-a48d-5cb79a07798d",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"type": "user",
"message": {
"role": "user",
"content": [
{
"tool_use_id": "toolu_01MSkzKiyQLtEmzzCuZpyA1W",
"type": "tool_result",
"content": "The file /Users/james/Development/scripts/ccshare/src/share-service.ts has been updated. Here's the result of running `cat -n` on a snippet of the edited file:\n 208→ \n 209→ return shareData;\n 210→}\n 211→\n 212→export async function shareToAPI(shareData: ShareData, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareResponse> {\n 213→ try {\n 214→ const response = await axios.post<ShareResponse>(apiUrl, shareData, {\n 215→ headers: {\n 216→ 'Content-Type': 'application/json'\n 217→ },\n 218→ timeout: 10000 // 10 second timeout\n 219→ });\n 220→ \n 221→ return response.data;\n 222→ } catch (error: any) {\n 223→ if (error.response) {\n 224→ // Server responded with error\n 225→ return {\n 226→ error: error.response.data?.message || error.response.data?.error || `Server error: ${error.response.status}`\n 227→ };\n 228→ } else if (error.request) {\n 229→ // No response received\n 230→ return {\n 231→ error: 'No response from server. Make sure the API is running on localhost:3000'\n 232→ };\n 233→ } else {\n 234→ // Request setup error\n 235→ return {\n 236→ error: error.message || 'Failed to send request'\n 237→ };\n 238→ }\n 239→ }\n 240→}\n 241→\n 242→export async function fetchFromSlug(slug: string, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareData | null> {\n 243→ try {\n 244→ const response = await axios.get<ShareData>(`${apiUrl}/${slug}`, {\n 245→ headers: {\n 246→ 'Accept': 'application/json'\n 247→ },\n 248→ timeout: 10000 // 10 second timeout\n 249→ });\n 250→ \n 251→ return response.data;\n 252→ } catch (error: any) {\n 253→ if (error.response?.status === 404) {\n 254→ console.error(`Share not found: ${slug}`);\n 255→ } else if (error.response) {\n 256→ console.error(`Server error: ${error.response.status}`);\n 257→ } else {\n 258→ console.error(`Failed to fetch share: ${error.message}`);\n 259→ }\n 260→ return null;\n 261→ }\n 262→}"
}
]
},
"uuid": "605b8b9b-6976-44a3-acf5-3b45f684598a",
"timestamp": "2025-07-22T15:27:16.043Z",
"toolUseResult": {
"filePath": "/Users/james/Development/scripts/ccshare/src/share-service.ts",
"oldString": "export async function shareToAPI(shareData: ShareData, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareResponse> {\n try {\n const response = await axios.post<ShareResponse>(apiUrl, shareData, {\n headers: {\n 'Content-Type': 'application/json'\n },\n timeout: 10000 // 10 second timeout\n });\n \n return response.data;\n } catch (error: any) {\n if (error.response) {\n // Server responded with error\n return {\n error: error.response.data?.message || error.response.data?.error || `Server error: ${error.response.status}`\n };\n } else if (error.request) {\n // No response received\n return {\n error: 'No response from server. Make sure the API is running on localhost:3000'\n };\n } else {\n // Request setup error\n return {\n error: error.message || 'Failed to send request'\n };\n }\n }\n}",
"newString": "export async function shareToAPI(shareData: ShareData, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareResponse> {\n try {\n const response = await axios.post<ShareResponse>(apiUrl, shareData, {\n headers: {\n 'Content-Type': 'application/json'\n },\n timeout: 10000 // 10 second timeout\n });\n \n return response.data;\n } catch (error: any) {\n if (error.response) {\n // Server responded with error\n return {\n error: error.response.data?.message || error.response.data?.error || `Server error: ${error.response.status}`\n };\n } else if (error.request) {\n // No response received\n return {\n error: 'No response from server. Make sure the API is running on localhost:3000'\n };\n } else {\n // Request setup error\n return {\n error: error.message || 'Failed to send request'\n };\n }\n }\n}\n\nexport async function fetchFromSlug(slug: string, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareData | null> {\n try {\n const response = await axios.get<ShareData>(`${apiUrl}/${slug}`, {\n headers: {\n 'Accept': 'application/json'\n },\n timeout: 10000 // 10 second timeout\n });\n \n return response.data;\n } catch (error: any) {\n if (error.response?.status === 404) {\n console.error(`Share not found: ${slug}`);\n } else if (error.response) {\n console.error(`Server error: ${error.response.status}`);\n } else {\n console.error(`Failed to fetch share: ${error.message}`);\n }\n return null;\n }\n}",
"originalFile": "import axios from 'axios';\nimport { HtmlData } from './html-generator.js';\nimport { SessionData } from './types.js';\n\nexport interface ShareData {\n title: string;\n createdAt: string;\n sessionInfo: ShareSessionInfo;\n techStack: ShareTechStack;\n prompts: SharePrompt[];\n metadata: ShareMetadata;\n claudeMd?: string;\n models?: string[]; // List of models used\n mcpServers?: ShareMCPServer[]; // MCP servers if any\n toolStats?: ShareToolStats; // Tool usage statistics\n}\n\nexport interface ShareSessionInfo {\n totalPrompts: number;\n timeRange?: string;\n sources?: string[];\n projectPath?: string;\n claudeProjectPath?: string;\n}\n\nexport interface ShareTechStack {\n languages: string[];\n frameworks: string[];\n tools: string[];\n databases: string[];\n}\n\nexport interface SharePrompt {\n id: number;\n content: string;\n timestamp: string;\n sourceFile?: string;\n fileDiffs: ShareFileDiff[];\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n total_tokens?: number;\n };\n responseTimeMs?: number;\n isAutoGenerated?: boolean;\n model?: string; // Model used for this response\n toolCalls?: string[]; // Tools called in this prompt\n}\n\nexport interface ShareMCPServer {\n name: string;\n tools: string[];\n callCount: number;\n}\n\nexport interface ShareToolStats {\n totalCalls: number;\n byTool: { [toolName: string]: number };\n mcpCalls: number;\n regularCalls: number;\n}\n\nexport interface ShareFileDiff {\n path: string;\n diff: string;\n additions?: number;\n deletions?: number;\n}\n\nexport interface ShareMetadata {\n generatedBy: string;\n version: string;\n platform: string;\n}\n\nexport interface ShareResponse {\n id?: string;\n url?: string;\n message?: string;\n error?: string;\n}\n\nexport function transformToShareData(htmlData: HtmlData, sessionData: SessionData): ShareData {\n const { promptsWithChanges, sessionInfo, techStack } = htmlData;\n \n // Count additions and deletions from diff\n function countDiffChanges(diff: string): { additions: number; deletions: number } {\n const lines = diff.split('\\n');\n let additions = 0;\n let deletions = 0;\n \n lines.forEach(line => {\n if (line.startsWith('+') && !line.startsWith('+++')) {\n additions++;\n } else if (line.startsWith('-') && !line.startsWith('---')) {\n deletions++;\n }\n });\n \n return { additions, deletions };\n }\n \n const prompts: SharePrompt[] = promptsWithChanges.map((item, index) => {\n const fileDiffs: ShareFileDiff[] = item.fileDiffs.map(file => {\n const { additions, deletions } = countDiffChanges(file.diff);\n return {\n path: file.path,\n diff: file.diff,\n additions,\n deletions\n };\n });\n \n // Find the next assistant response to get usage and response time\n const promptIndex = sessionData.prompts.findIndex(p => \n p.content === item.prompt && \n p.timestamp === item.timestamp\n );\n \n let usage = undefined;\n let responseTimeMs = undefined;\n let model = undefined;\n let toolCalls = undefined;\n \n // Find the corresponding prompt in sessionData\n const originalPrompt = sessionData.prompts[promptIndex];\n const isAutoGenerated = originalPrompt?.isAutoGenerated;\n \n if (promptIndex !== -1 && promptIndex < sessionData.prompts.length - 1) {\n const nextPrompt = sessionData.prompts[promptIndex + 1];\n if (nextPrompt.role === 'assistant') {\n usage = nextPrompt.usage;\n responseTimeMs = nextPrompt.responseTimeMs;\n model = nextPrompt.model;\n toolCalls = nextPrompt.toolCalls;\n }\n }\n \n return {\n id: index + 1,\n content: item.prompt,\n timestamp: item.timestamp || new Date().toISOString(),\n sourceFile: item.sourceFile,\n fileDiffs,\n usage,\n responseTimeMs,\n isAutoGenerated,\n model,\n toolCalls\n };\n });\n \n // Calculate tool statistics\n let toolStats: ShareToolStats | undefined = undefined;\n if (sessionData.toolCalls && sessionData.toolCalls.length > 0) {\n const byTool: { [toolName: string]: number } = {};\n let mcpCalls = 0;\n \n sessionData.toolCalls.forEach(call => {\n byTool[call.name] = (byTool[call.name] || 0) + 1;\n if (call.isMCP) mcpCalls++;\n });\n \n toolStats = {\n totalCalls: sessionData.toolCalls.length,\n byTool,\n mcpCalls,\n regularCalls: sessionData.toolCalls.length - mcpCalls\n };\n }\n \n // Transform MCP servers data\n const mcpServers: ShareMCPServer[] | undefined = sessionData.metadata?.mcpServers?.map(server => ({\n name: server.name,\n tools: server.tools,\n callCount: server.tools.reduce((sum, toolName) => \n sum + (toolStats?.byTool[toolName] || 0), 0)\n }));\n \n const shareData: ShareData = {\n title: 'Claude Code Prompts',\n createdAt: new Date().toISOString(),\n sessionInfo: {\n totalPrompts: sessionInfo?.totalPrompts || prompts.length,\n timeRange: sessionInfo?.timeRange,\n sources: sessionInfo?.sources,\n projectPath: sessionInfo?.projectPath || sessionData.metadata?.workingDirectory,\n claudeProjectPath: sessionInfo?.claudeProjectPath || sessionData.metadata?.claudeProjectPath\n },\n techStack: {\n languages: techStack?.languages || [],\n frameworks: techStack?.frameworks || [],\n tools: techStack?.tools || [],\n databases: techStack?.databases || []\n },\n prompts,\n metadata: {\n generatedBy: 'ccshare',\n version: '0.2.0',\n platform: sessionData.metadata?.platform || process.platform\n },\n models: sessionData.metadata?.models,\n mcpServers: mcpServers && mcpServers.length > 0 ? mcpServers : undefined,\n toolStats\n };\n \n return shareData;\n}\n\nexport async function shareToAPI(shareData: ShareData, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareResponse> {\n try {\n const response = await axios.post<ShareResponse>(apiUrl, shareData, {\n headers: {\n 'Content-Type': 'application/json'\n },\n timeout: 10000 // 10 second timeout\n });\n \n return response.data;\n } catch (error: any) {\n if (error.response) {\n // Server responded with error\n return {\n error: error.response.data?.message || error.response.data?.error || `Server error: ${error.response.status}`\n };\n } else if (error.request) {\n // No response received\n return {\n error: 'No response from server. Make sure the API is running on localhost:3000'\n };\n } else {\n // Request setup error\n return {\n error: error.message || 'Failed to send request'\n };\n }\n }\n}",
"structuredPatch": [
{
"oldStart": 237,
"oldLines": 4,
"newStart": 237,
"newLines": 26,
"lines": [
" };",
" }",
" }",
"+}",
"+",
"+export async function fetchFromSlug(slug: string, apiUrl: string = 'http://localhost:3000/shares'): Promise<ShareData | null> {",
"+ try {",
"+ const response = await axios.get<ShareData>(`${apiUrl}/${slug}`, {",
"+ headers: {",
"+ 'Accept': 'application/json'",
"+ },",
"+ timeout: 10000 // 10 second timeout",
"+ });",
"+ ",
"+ return response.data;",
"+ } catch (error: any) {",
"+ if (error.response?.status === 404) {",
"+ console.error(`Share not found: ${slug}`);",
"+ } else if (error.response) {",
"+ console.error(`Server error: ${error.response.status}`);",
"+ } else {",
"+ console.error(`Failed to fetch share: ${error.message}`);",
"+ }",
"+ return null;",
"+ }",
" }",
"\\ No newline at end of file"
]
}
],
"userModified": false,
"replaceAll": false
}
},
{
"parentUuid": "605b8b9b-6976-44a3-acf5-3b45f684598a",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"message": {
"id": "msg_01Go4Fc9YHibHUqo3NxXLRwq",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-20250514",
"content": [
{
"type": "text",
"text": "이제 CLI에 load 명령을 추가하겠습니다:"
}
],
"stop_reason": null,
"stop_sequence": null,
"usage": {
"input_tokens": 2,
"cache_creation_input_tokens": 1578,
"cache_read_input_tokens": 127993,
"output_tokens": 1,
"service_tier": "standard"
}
},
"requestId": "req_011CRNHv5mwzYHeyx6xNZ6S8",
"type": "assistant",
"uuid": "f6fee62b-e30f-4626-bce3-041582edf149",
"timestamp": "2025-07-22T15:27:21.501Z"
},
{
"parentUuid": "f6fee62b-e30f-4626-bce3-041582edf149",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"message": {
"id": "msg_01Go4Fc9YHibHUqo3NxXLRwq",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-20250514",
"content": [
{
"type": "tool_use",
"id": "toolu_01YGGL2a5JRDmffNeDqmexQu",
"name": "Edit",
"input": {
"file_path": "/Users/james/Development/scripts/ccshare/src/cli.ts",
"old_string": "import { transformToShareData, shareToAPI } from './share-service.js';",
"new_string": "import { transformToShareData, shareToAPI, fetchFromSlug } from './share-service.js';"
}
}
],
"stop_reason": null,
"stop_sequence": null,
"usage": {
"input_tokens": 2,
"cache_creation_input_tokens": 1578,
"cache_read_input_tokens": 127993,
"output_tokens": 171,
"service_tier": "standard"
}
},
"requestId": "req_011CRNHv5mwzYHeyx6xNZ6S8",
"type": "assistant",
"uuid": "4347a276-b3e2-4320-a498-7d9a835d6aa5",
"timestamp": "2025-07-22T15:27:23.184Z"
},
{
"parentUuid": "4347a276-b3e2-4320-a498-7d9a835d6aa5",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"type": "user",
"message": {
"role": "user",
"content": [
{
"tool_use_id": "toolu_01YGGL2a5JRDmffNeDqmexQu",
"type": "tool_result",
"content": "The file /Users/james/Development/scripts/ccshare/src/cli.ts has been updated. Here's the result of running `cat -n` on a snippet of the edited file:\n 7→import { uploadSession } from './upload.js';\n 8→import { SessionData } from './types.js';\n 9→import { generateHtml } from './html-generator.js';\n 10→import { detectTechStack } from './tech-detector.js';\n 11→import { transformToShareData, shareToAPI, fetchFromSlug } from './share-service.js';\n 12→import { createAutoPostForm } from './browser-post.js';\n 13→import { SessionWatcher } from './watch.js';\n 14→import fs from 'fs/promises';\n 15→import path from 'path';"
}
]
},
"uuid": "b7a54683-784d-4884-a271-cf0b26c0dff5",
"timestamp": "2025-07-22T15:27:23.253Z",
"toolUseResult": {
"filePath": "/Users/james/Development/scripts/ccshare/src/cli.ts",
"oldString": "import { transformToShareData, shareToAPI } from './share-service.js';",
"newString": "import { transformToShareData, shareToAPI, fetchFromSlug } from './share-service.js';",
"originalFile": "#!/usr/bin/env node\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport inquirer from 'inquirer';\nimport ora from 'ora';\nimport { captureSession } from './capture.js';\nimport { uploadSession } from './upload.js';\nimport { SessionData } from './types.js';\nimport { generateHtml } from './html-generator.js';\nimport { detectTechStack } from './tech-detector.js';\nimport { transformToShareData, shareToAPI } from './share-service.js';\nimport { createAutoPostForm } from './browser-post.js';\nimport { SessionWatcher } from './watch.js';\nimport fs from 'fs/promises';\nimport path from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\n\nconst execAsync = promisify(exec);\n\nconst program = new Command();\n\nprogram\n .name('ccshare')\n .description('Share Claude Code prompts and results')\n .version('0.1.0')\n .option('-s, --session <path>', 'Path to session file or directory')\n .option('-a, --all', 'Include all session history')\n .option('-n, --no-select', 'Skip prompt selection')\n .option('-r, --recent <number>', 'Include only the N most recent prompts', parseInt)\n .option('--share', 'Share to localhost:3000/shares API')\n .option('--api-url <url>', 'Custom API URL for sharing', 'http://localhost:3000/shares')\n .option('--json', 'Output JSON format instead of HTML')\n .option('--include-claude-md', 'Include CLAUDE.md file without asking')\n .option('--exclude-auto', 'Exclude auto-generated prompts')\n .action(async (options) => {\n // Default action - generate HTML report and open it\n try {\n const spinner = ora('Analyzing Claude Code session...').start();\n \n // Find and capture session\n const sessionData = await captureSession(options.session, options.all);\n \n spinner.succeed('Analysis complete');\n \n // Build data for HTML with prompts grouped with their changes\n let userPrompts = sessionData.prompts.filter(p => p.role === 'user');\n \n // Filter out auto-generated prompts if requested\n if (options.excludeAuto) {\n userPrompts = userPrompts.filter(p => !p.isAutoGenerated);\n if (!options.json) {\n console.log(chalk.cyan('\\n🤖 Excluding auto-generated prompts'));\n }\n }\n \n // If --recent flag is used, limit to N most recent prompts\n if (options.recent && options.recent > 0) {\n userPrompts = userPrompts.slice(-options.recent);\n if (!options.json) {\n console.log(chalk.cyan(`\\n📝 Limiting to ${options.recent} most recent prompts`));\n }\n }\n \n // Allow user to select prompts if not disabled\n if (options.select !== false && userPrompts.length > 0 && !options.recent) {\n const promptChoices = userPrompts.map((prompt, index) => {\n const cleanContent = prompt.content.replace(/\\n/g, ' ').replace(/\\s+/g, ' ').trim();\n const displayContent = cleanContent.substring(0, 80);\n const autoLabel = prompt.isAutoGenerated ? ' [AUTO]' : '';\n return {\n name: `${index + 1}. ${displayContent}${cleanContent.length > 80 ? '...' : ''}${autoLabel}`,\n value: index,\n checked: !prompt.isAutoGenerated // Auto-generated prompts unchecked by default\n };\n });\n \n const { selectedPrompts } = await inquirer.prompt([\n {\n type: 'checkbox',\n name: 'selectedPrompts',\n message: 'Select prompts to include (use spacebar to select/deselect):',\n choices: promptChoices,\n pageSize: 15\n }\n ]);\n \n // Filter prompts based on selection\n userPrompts = userPrompts.filter((_, index) => selectedPrompts.includes(index));\n \n if (userPrompts.length === 0) {\n console.log(chalk.yellow('\\nNo prompts selected.'));\n process.exit(0);\n }\n }\n \n // Match file diffs with prompts\n const promptsWithChanges = userPrompts.map((prompt, index) => {\n let associatedDiffs: Array<{ path: string; diff: string }> = [];\n \n // First, check if we have file changes from toolUseResult\n if (prompt.associatedFiles && prompt.associatedFiles.length > 0) {\n // Find changes from sessionData.changes that match this prompt's files\n const relatedChanges = sessionData.changes.filter(change => \n prompt.associatedFiles!.includes(change.path) && change.diff\n );\n \n associatedDiffs = relatedChanges.map(change => ({\n path: change.path,\n diff: change.diff!\n }));\n \n // Debug log\n if (process.env.DEBUG_FILE_CHANGES) {\n console.log(`\\nPrompt ${index + 1}: \"${prompt.content.substring(0, 50)}...\"`);\n console.log(`Associated files: ${prompt.associatedFiles.join(', ')}`);\n console.log(`Found ${associatedDiffs.length} diffs`);\n }\n }\n \n // Only use toolUseResult, no git diffs\n \n return {\n prompt: prompt.content,\n timestamp: prompt.timestamp,\n sourceFile: (prompt as any).sourceFile,\n fileDiffs: associatedDiffs\n };\n });\n \n // Detect tech stack\n const techStack = await detectTechStack(process.cwd());\n \n // Calculate session info\n let sessionInfo = undefined;\n if (options.session || options.all) {\n const timestamps = userPrompts\n .map(p => new Date(p.timestamp))\n .filter(d => !isNaN(d.getTime()));\n \n const sources = [...new Set(userPrompts\n .map((p: any) => p.sourceFile)\n .filter(Boolean))];\n \n sessionInfo = {\n totalPrompts: userPrompts.length,\n timeRange: timestamps.length >= 2 ? \n `${timestamps[0].toLocaleDateString('en-US')} ~ ${timestamps[timestamps.length - 1].toLocaleDateString('en-US')}` : \n undefined,\n sources: sources.length > 0 ? sources : undefined,\n projectPath: sessionData.metadata?.workingDirectory,\n claudeProjectPath: sessionData.metadata?.claudeProjectPath\n };\n }\n \n const htmlData = {\n promptsWithChanges,\n sessionInfo,\n techStack\n };\n \n // If JSON output requested, output JSON and exit\n if (options.json) {\n const shareData = transformToShareData(htmlData, sessionData);\n console.log(JSON.stringify(shareData, null, 2));\n process.exit(0);\n }\n \n // If share option is used, open browser with POST request\n if (options.share) {\n const shareSpinner = ora('Preparing to share...').start();\n try {\n const shareData = transformToShareData(htmlData, sessionData);\n \n // Check if CLAUDE.md exists and ask if user wants to include it\n const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');\n try {\n const claudeMdContent = await fs.readFile(claudeMdPath, 'utf-8');\n \n // If --include-claude-md flag is set, include without asking\n if (options.includeClaudeMd) {\n shareData.claudeMd = claudeMdContent;\n } else {\n shareSpinner.stop();\n \n const { includeClaudeMd } = await inquirer.prompt([\n {\n type: 'confirm',\n name: 'includeClaudeMd',\n message: 'Found CLAUDE.md file. Include it in the share?',\n default: true\n }\n ]);\n \n if (includeClaudeMd) {\n shareData.claudeMd = claudeMdContent;\n }\n \n shareSpinner.start('Preparing to share...');\n }\n } catch {\n // CLAUDE.md doesn't exist, continue without it\n }\n \n // Show JSON if requested (for debugging)\n if (process.env.DEBUG_SHARE) {\n console.log('\\nJSON being sent to API:');\n console.log(JSON.stringify(shareData, null, 2));\n }\n \n // Create temporary HTML file with auto-submitting form\n const tempHtmlPath = await createAutoPostForm(shareData, options.apiUrl);\n \n shareSpinner.succeed('Opening browser with share data...');\n \n // Open the temporary HTML file in browser\n const openCommand = process.platform === 'darwin' ? 'open' : \n process.platform === 'win32' ? 'start' : 'xdg-open';\n \n try {\n await execAsync(`${openCommand} \"${tempHtmlPath}\"`);\n console.log(chalk.cyan(`📄 Sharing to ${options.apiUrl}...`));\n } catch (err) {\n console.log(chalk.yellow(`⚠️ Could not open browser. Please open: ${tempHtmlPath}`));\n }\n } catch (err: any) {\n shareSpinner.fail(`Share failed: ${err.message}`);\n }\n \n // Exit after sharing\n process.exit(0);\n }\n \n // Generate HTML (only if not sharing)\n const html = generateHtml(htmlData);\n \n // Create reports directory if it doesn't exist\n const reportsDir = path.join(process.cwd(), 'ccshare-reports');\n await fs.mkdir(reportsDir, { recursive: true });\n \n // Save HTML file\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);\n const filename = `claude-session-${timestamp}.html`;\n const filepath = path.join(reportsDir, filename);\n \n await fs.writeFile(filepath, html, 'utf-8');\n \n console.log(chalk.green(`\\n✅ HTML report generated: ccshare-reports/${filename}`));\n \n // Open the file\n const openCommand = process.platform === 'darwin' ? 'open' : \n process.platform === 'win32' ? 'start' : 'xdg-open';\n \n try {\n await execAsync(`${openCommand} \"${filepath}\"`);\n console.log(chalk.cyan('📄 Opening in your default browser...'));\n } catch (err) {\n console.log(chalk.yellow('⚠️ Could not auto-open file. Please open manually.'));\n }\n \n } catch (error: any) {\n console.error(chalk.red('Error:'), error.message);\n process.exit(1);\n }\n });\n\nprogram\n .command('share')\n .description('Capture and share current Claude Code session')\n .option('-f, --file <path>', 'Path to Claude Code session file')\n .option('-m, --message <message>', 'Optional message to include with share')\n .action(async (options) => {\n try {\n console.log(chalk.blue('🚀 CCShare - Claude Code Session Sharing Tool'));\n \n const spinner = ora('Capturing Claude Code session...').start();\n \n let sessionData: SessionData;\n \n if (options.file) {\n sessionData = await captureSession(options.file);\n } else {\n const answers = await inquirer.prompt([\n {\n type: 'input',\n name: 'sessionPath',\n message: 'Enter path to Claude Code session file (or press enter to use default):',\n default: process.env.HOME + '/.claude/sessions/latest.json'\n }\n ]);\n sessionData = await captureSession(answers.sessionPath);\n }\n \n spinner.succeed('Session captured successfully');\n \n if (!options.message) {\n const messageAnswer = await inquirer.prompt([\n {\n type: 'input',\n name: 'message',\n message: 'Add a description for this share (optional):'\n }\n ]);\n sessionData.message = messageAnswer.message;\n } else {\n sessionData.message = options.message;\n }\n \n spinner.start('Uploading session to ccshare...');\n const shareUrl = await uploadSession(sessionData);\n spinner.succeed('Session uploaded successfully');\n \n console.log(chalk.green('\\n✅ Your session has been shared!'));\n console.log(chalk.white('Share URL: ') + chalk.cyan(shareUrl));\n console.log(chalk.gray('\\nAnyone with this link can view your Claude Code session.'));\n \n } catch (error: any) {\n console.error(chalk.red('Error:'), error.message);\n process.exit(1);\n }\n });\n\nprogram\n .command('watch')\n .description('Watch for new prompts and share on demand')\n .option('--api-url <url>', 'Custom API URL for sharing', 'http://localhost:3000/shares')\n .option('--include-claude-md', 'Include CLAUDE.md file without asking')\n .option('--exclude-auto', 'Exclude auto-generated prompts')\n .action(async (options) => {\n try {\n const watcher = new SessionWatcher({\n apiUrl: options.apiUrl,\n includeClaudeMd: options.includeClaudeMd,\n excludeAuto: options.excludeAuto\n });\n \n await watcher.start();\n } catch (error: any) {\n console.error(chalk.red('Error:'), error.message);\n process.exit(1);\n }\n });\n\nprogram\n .command('list')\n .description('List your recent shares')\n .action(async () => {\n console.log(chalk.yellow('This feature is coming soon!'));\n });\n\nprogram.parse();",
"structuredPatch": [
{
"oldStart": 8,
"oldLines": 7,
"newStart": 8,
"newLines": 7,
"lines": [
" import { SessionData } from './types.js';",
" import { generateHtml } from './html-generator.js';",
" import { detectTechStack } from './tech-detector.js';",
"-import { transformToShareData, shareToAPI } from './share-service.js';",
"+import { transformToShareData, shareToAPI, fetchFromSlug } from './share-service.js';",
" import { createAutoPostForm } from './browser-post.js';",
" import { SessionWatcher } from './watch.js';",
" import fs from 'fs/promises';"
]
}
],
"userModified": false,
"replaceAll": false
}
},
{
"parentUuid": "b7a54683-784d-4884-a271-cf0b26c0dff5",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"message": {
"id": "msg_01T1nxBL3axXRBRafk2zah3q",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-20250514",
"content": [
{
"type": "text",
"text": "이제 load 명령을 추가하겠습니다:"
}
],
"stop_reason": null,
"stop_sequence": null,
"usage": {
"input_tokens": 2,
"cache_creation_input_tokens": 380,
"cache_read_input_tokens": 129571,
"output_tokens": 1,
"service_tier": "standard"
}
},
"requestId": "req_011CRNHvca7K7SW7BUGT11K4",
"type": "assistant",
"uuid": "4f0e3a04-d917-43c4-a133-81cf9d06fa75",
"timestamp": "2025-07-22T15:27:27.236Z"
},
{
"parentUuid": "4f0e3a04-d917-43c4-a133-81cf9d06fa75",
"isSidechain": false,
"userType": "external",
"cwd": "/Users/james/Development/scripts/ccshare",
"sessionId": "fbdf51cf-9191-4b70-8f1d-9074b39e5fb9",
"version": "1.0.57",
"gitBranch": "main",
"message": {
"id": "msg_01T1nxBL3axXRBRafk2zah3q",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-20250514",
"content": [
{
"type": "tool_use",
"id": "toolu_01P95Podz6AbpLRA6YHWaX1a",
"name": "Edit",
"input": {
"file_path": "/Users/james/Development/scripts/ccshare/src/cli.ts",
"old_string": "program\n .command('list')\n .description('List your recent shares')\n .action(async () => {\n console.log(chalk.yellow('This feature is coming soon!'));\n });\n\nprogram.parse();",
"new_string": "program\n .command('load <slug>')\n .description('Load prompts from a shared slug and execute with claude -p')\n .option('--api-url <url>', 'Custom API URL', 'http://localhost:3000/shares')\n .option('--dry-run', 'Show prompts without executing')\n .option('--select', 'Select which prompts to execute')\n .action(async (slug, options) => {\n try {\n const spinner = ora(`Fetching share: ${slug}...`).start();\n \n const shareData = await fetchFromSlug(slug, options.apiUrl);\n \n if (!shareData) {\n spinner.fail('Failed to fetch share');\n process.exit(1);\n }\n \n spinner.succeed(`Fetched ${shareData.prompts.length} prompts`);\n \n // Extract user prompts\n const userPrompts = shareData.prompts.filter(p => !p.isAutoGenerated);\n \n if (userPrompts.length === 0) {\n console.log(chalk.yellow('No user prompts found in this share.'));\n process.exit(0);\n }\n \n // Select prompts if requested\n let selectedPrompts = userPrompts;\n if (options.select) {\n const promptChoices = userPrompts.map((prompt, index) => {\n const preview = prompt.content.substring(0, 80).replace(/\\n/g, ' ');\n return {\n name: `${index + 1}. ${preview}${prompt.content.length > 80 ? '...' : ''}`,\n value: index,\n checked: true\n };\n });\n \n const { selected } = await inquirer.prompt([\n {\n type: 'checkbox',\n name: 'selected',\n message: 'Select prompts to execute:',\n choices: promptChoices,\n pageSize: 15\n }\n ]);\n \n selectedPrompts = userPrompts.filter((_, index) => selected.includes(index));\n }\n \n if (selectedPrompts.length === 0) {\n console.log(chalk.yellow('No prompts selected.'));\n process.exit(0);\n }\n \n // Show what will be executed\n console.log(chalk.cyan(`\\nWill execute ${selectedPrompts.length} prompts:`));\n selectedPrompts.forEach((prompt, index) => {\n const preview = prompt.content.substring(0, 100).replace(/\\n/g, ' ');\n console.log(chalk.white(`${index + 1}. ${preview}${prompt.content.length > 100 ? '...' : ''}`));\n });\n \n if (options.dryRun) {\n console.log(chalk.gray('\\n[Dry run mode - not executing]'));\n process.exit(0);\n }\n \n // Execute each prompt with claude -p\n console.log(chalk.cyan('\\nExecuting prompts...\\n'));\n \n for (let i = 0; i < selectedPrompts.length; i++) {\n const prompt = selectedPrompts[i];\n console.log(chalk.blue(`[${i + 1}/${selectedPrompts.length}] Executing prompt...`));\n \n try {\n // Escape quotes in the prompt content\n const escapedPrompt = pr