/**
* Claude Viewer - Express Server
* Admin dashboard for visualizing Claude Code conversation history
*/
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
import os from 'os';
// Import data access functions from shared library
import {
getClaudeUsers,
parseHistoryFile,
getUserSessions,
parseSessionTranscript,
extractSessionMetadata,
getProjectConversations,
mapConversationsToSessions,
buildConversationThread,
getAllConversations,
calculateStats,
getConversationDetails
} from './lib/data-access.js';
// ES module __dirname equivalent
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = process.env.CLAUDE_VIEWER_PORT || 2204;
// Serve static files
app.use(express.static(path.join(__dirname, 'public')));
/**
* API Endpoint: Get list of users
*/
app.get('/api/users', (req, res) => {
const users = getClaudeUsers();
res.json({
success: true,
count: users.length,
users: users.map(u => ({
username: u.username,
fileSize: u.fileSize,
lastModified: u.lastModified
}))
});
});
/**
* API Endpoint: Get all conversations with enriched metadata
*/
app.get('/api/conversations', (req, res) => {
const conversations = getAllConversations();
res.json({
success: true,
count: conversations.length,
conversations: conversations
});
});
/**
* API Endpoint: Get statistics with enriched metadata
*/
app.get('/api/stats', (req, res) => {
const stats = calculateStats();
res.json({
success: true,
stats
});
});
/**
* API Endpoint: Get full transcript for a specific session
*/
app.get('/api/conversation/:sessionId/:username', (req, res) => {
const { sessionId, username } = req.params;
try {
const details = getConversationDetails(sessionId, username);
if (!details) {
return res.status(404).json({
success: false,
error: 'Session not found'
});
}
res.json({
success: true,
...details
});
} catch (err) {
console.error(`Error fetching conversation ${sessionId}:`, err.message);
res.status(500).json({
success: false,
error: err.message
});
}
});
/**
* API Endpoint: Get Claude Code configuration
*/
app.get('/api/config', (req, res) => {
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
try {
if (fs.existsSync(settingsPath)) {
const content = fs.readFileSync(settingsPath, 'utf-8');
const settings = JSON.parse(content);
res.json({
success: true,
cleanupPeriodDays: settings.cleanupPeriodDays || 30,
settingsPath: settingsPath,
configured: !!settings.cleanupPeriodDays
});
} else {
res.json({
success: true,
cleanupPeriodDays: 30,
settingsPath: settingsPath,
configured: false
});
}
} catch (err) {
console.error(`Error reading settings:`, err.message);
res.json({
success: true,
cleanupPeriodDays: 30,
settingsPath: settingsPath,
configured: false,
error: err.message
});
}
});
// Start server
app.listen(PORT, () => {
console.log(`\n==============================================`);
console.log(`Claude Viewer Server Running`);
console.log(`==============================================`);
console.log(`URL: http://localhost:${PORT}`);
console.log(`Time: ${new Date().toLocaleString()}`);
console.log(`==============================================\n`);
});