ccshare
Version:
Share Claude Code prompts and results easily
279 lines • 10.7 kB
JavaScript
import axios from 'axios';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
const VERSION = packageJson.version;
export function transformToShareData(htmlData, sessionData) {
const { prompts, fileDiffs, assistantActions, toolExecutions, sessionInfo, techStack } = htmlData;
// Count additions and deletions from diff
function countDiffChanges(diff) {
const lines = diff.split('\n');
let additions = 0;
let deletions = 0;
lines.forEach(line => {
if (line.startsWith('+') && !line.startsWith('+++')) {
additions++;
}
else if (line.startsWith('-') && !line.startsWith('---')) {
deletions++;
}
});
return { additions, deletions };
}
// Transform file diffs with additions/deletions count
const transformedFileDiffs = fileDiffs.map(file => {
const { additions, deletions } = countDiffChanges(file.diff);
return {
path: file.path,
diff: file.diff,
additions,
deletions
};
});
// Create mapping from prompt UUID to index
const promptUuidToIndex = {};
prompts.forEach((prompt, index) => {
if (prompt.uuid) {
promptUuidToIndex[prompt.uuid] = index + 1;
}
});
const sharePrompts = prompts.map((item, index) => {
// Find the next assistant response to get usage and response time
const promptIndex = sessionData.prompts.findIndex(p => p.content === item.prompt &&
p.timestamp === item.timestamp);
let usage = undefined;
let responseTimeMs = undefined;
let model = undefined;
let toolCalls = undefined;
// Find the corresponding prompt in sessionData
const originalPrompt = sessionData.prompts[promptIndex];
const isAutoGenerated = originalPrompt?.isAutoGenerated;
if (promptIndex !== -1 && promptIndex < sessionData.prompts.length - 1) {
const nextPrompt = sessionData.prompts[promptIndex + 1];
if (nextPrompt.role === 'assistant') {
usage = nextPrompt.usage;
responseTimeMs = nextPrompt.responseTimeMs;
model = nextPrompt.model;
toolCalls = nextPrompt.toolCalls;
}
}
return {
id: index + 1,
content: item.prompt,
timestamp: item.timestamp || new Date().toISOString(),
sourceFile: item.sourceFile,
usage,
responseTimeMs,
isAutoGenerated,
model,
toolCalls
};
});
// Calculate tool statistics
let toolStats = undefined;
if (sessionData.toolCalls && sessionData.toolCalls.length > 0) {
const byTool = {};
let mcpCalls = 0;
sessionData.toolCalls.forEach(call => {
byTool[call.name] = (byTool[call.name] || 0) + 1;
if (call.isMCP)
mcpCalls++;
});
toolStats = {
totalCalls: sessionData.toolCalls.length,
byTool,
mcpCalls,
regularCalls: sessionData.toolCalls.length - mcpCalls
};
}
// Transform MCP servers data
const mcpServers = sessionData.metadata?.mcpServers?.map(server => ({
name: server.name,
tools: server.tools,
callCount: server.tools.reduce((sum, toolName) => sum + (toolStats?.byTool[toolName] || 0), 0)
}));
// Create workflow items from assistantActions and toolExecutions
const workflow = [];
// Add tool executions to workflow
if (toolExecutions) {
toolExecutions.forEach(exec => {
const promptIndex = exec.promptId ? promptUuidToIndex[exec.promptId] : undefined;
workflow.push({
type: 'tool_execution',
timestamp: exec.timestamp,
tool: exec.tool,
parameters: exec.parameters,
promptId: promptIndex ? promptIndex.toString() : undefined
});
// Add tool result if exists
if (exec.result) {
workflow.push({
type: 'tool_result',
timestamp: exec.timestamp, // Using same timestamp, though ideally should be slightly later
tool: exec.tool,
result: exec.result,
status: exec.status,
promptId: promptIndex ? promptIndex.toString() : undefined,
fileChange: exec.fileChange
});
}
});
}
// Add assistant actions to workflow
if (assistantActions) {
assistantActions.forEach(action => {
const promptIndex = action.promptId ? promptUuidToIndex[action.promptId] : undefined;
workflow.push({
type: 'assistant_action',
timestamp: action.timestamp,
description: action.description,
actionType: action.type,
promptId: promptIndex ? promptIndex.toString() : undefined
});
});
}
// Sort workflow by timestamp
workflow.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
// Group workflow items by prompt
const workflowByPrompt = {};
workflow.forEach(item => {
if (item.promptId) {
if (!workflowByPrompt[item.promptId]) {
workflowByPrompt[item.promptId] = [];
}
workflowByPrompt[item.promptId].push(item);
}
});
// Add workflow to each prompt
sharePrompts.forEach(prompt => {
const promptIdStr = prompt.id.toString();
if (workflowByPrompt[promptIdStr]) {
prompt.workflow = workflowByPrompt[promptIdStr];
}
});
const shareData = {
title: 'Claude Code Prompts',
createdAt: new Date().toISOString(),
sessionInfo: {
totalPrompts: sessionInfo?.totalPrompts || sharePrompts.length,
timeRange: sessionInfo?.timeRange,
sources: sessionInfo?.sources,
projectPath: sessionInfo?.projectPath || sessionData.metadata?.workingDirectory,
claudeProjectPath: sessionInfo?.claudeProjectPath || sessionData.metadata?.claudeProjectPath
},
techStack: {
languages: techStack?.languages || [],
frameworks: techStack?.frameworks || [],
tools: techStack?.tools || [],
databases: techStack?.databases || []
},
prompts: sharePrompts,
fileDiffs: transformedFileDiffs, // File diffs at share level
metadata: {
generatedBy: 'ccshare',
version: VERSION,
platform: sessionData.metadata?.platform || process.platform,
nodeVersion: sessionData.metadata?.nodeVersion,
claudeSettings: sessionData.metadata?.claudeSettings,
sessionStats: sessionData.metadata?.sessionStats,
workingDirectory: sessionData.metadata?.workingDirectory,
claudeProjectPath: sessionData.metadata?.claudeProjectPath
},
models: sessionData.metadata?.models,
mcpServers: mcpServers && mcpServers.length > 0 ? mcpServers : undefined,
toolStats
};
return shareData;
}
export async function shareToAPIRaw(data, apiUrl) {
try {
const response = await axios.post(apiUrl, data, {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
maxBodyLength: Infinity,
timeout: 10000 // 10 second timeout
});
// Check if response is HTML (likely a login page or error page)
if (typeof response.data === 'string' && response.data.includes('<!DOCTYPE html>')) {
throw new Error('API returned HTML instead of JSON (possibly requires authentication)');
}
// Check different possible response formats
const url = response.data?.url ||
response.data?.share_url ||
response.data?.shareUrl ||
response.data?.slug ? `${apiUrl}/${response.data.slug}` : null;
if (!url) {
console.error('Unexpected API response:', response.data);
throw new Error('No URL in API response');
}
return { url };
}
catch (error) {
if (error.response?.status === 401) {
throw new Error('Authentication required');
}
if (error.response?.status === 404) {
throw new Error('API endpoint not found');
}
throw error;
}
}
export async function shareToAPI(shareData, apiUrl = 'https://ccshare.cc/shares') {
try {
const response = await axios.post(apiUrl, shareData, {
headers: {
'Content-Type': 'application/json'
},
timeout: 10000 // 10 second timeout
});
return response.data;
}
catch (error) {
if (error.response) {
// Server responded with error
return {
error: error.response.data?.message || error.response.data?.error || `Server error: ${error.response.status}`
};
}
else if (error.request) {
// No response received
return {
error: 'No response from server. Make sure the API is running on localhost:3000'
};
}
else {
// Request setup error
return {
error: error.message || 'Failed to send request'
};
}
}
}
export async function fetchFromSlug(slug, apiUrl = 'https://ccshare.cc/shares') {
try {
const response = await axios.get(`${apiUrl}/${slug}`, {
headers: {
'Accept': 'application/json'
},
timeout: 10000 // 10 second timeout
});
return response.data;
}
catch (error) {
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;
}
}
//# sourceMappingURL=share-service.js.map