Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions bridge-server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 12 additions & 11 deletions bridge-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,26 @@
"author": "Appsome",
"license": "GPL-3.0",
"dependencies": {
"express": "^4.18.2",
"ws": "^8.14.2",
"better-sqlite3": "^9.2.2",
"dotenv": "^16.3.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"prom-client": "^15.1.3",
"uuid": "^9.0.1",
"winston": "^3.11.0",
"uuid": "^9.0.1"
"ws": "^8.14.2"
},
"devDependencies": {
"@types/express": "^4.17.20",
"@types/ws": "^8.5.8",
"@types/better-sqlite3": "^7.6.8",
"@types/cors": "^2.8.15",
"@types/uuid": "^9.0.7",
"typescript": "^5.3.3",
"ts-node": "^10.9.1",
"nodemon": "^3.0.2",
"@types/express": "^4.17.20",
"@types/jest": "^29.5.8",
"@types/node": "^20.10.4",
"@types/uuid": "^9.0.7",
"@types/ws": "^8.5.8",
"jest": "^29.7.0",
"@types/jest": "^29.5.8"
"nodemon": "^3.0.2",
"ts-node": "^10.9.1",
"typescript": "^5.3.3"
}
}
42 changes: 40 additions & 2 deletions bridge-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import { logger } from './logger.js';
import { db } from './database.js';
import { spawner } from './spawner.js';
import WebSocketHandler from './websocket/handler.js';
import {
register,
httpRequestCounter,
httpRequestDuration,
activeSessionsGauge
} from './metrics.js';

// Import API routers
import sessionsRouter from './api/sessions.js';
Expand All @@ -33,9 +39,25 @@ app.use(cors({
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Request logging
// Request logging and metrics
app.use((req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
logger.debug(`${req.method} ${req.path}`);

// Track response to record metrics
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestCounter.inc({
method: req.method,
path: req.route?.path || req.path,
status: res.statusCode.toString(),
});
httpRequestDuration.observe({
method: req.method,
path: req.route?.path || req.path,
}, duration);
});

next();
});

Expand All @@ -44,15 +66,31 @@ const startTime = Date.now();

// Health check endpoint
app.get('/health', (req: Request, res: Response) => {
const activeSessions = spawner.getAllProcesses().length;

// Update active sessions gauge
activeSessionsGauge.set(activeSessions);

res.json({
status: 'ok',
version: process.env.npm_package_version || '1.0.0',
uptime: Math.floor((Date.now() - startTime) / 1000),
sessions: spawner.getAllProcesses().length,
sessions: activeSessions,
timestamp: new Date().toISOString(),
});
});

// Prometheus metrics endpoint
app.get('/metrics', async (req: Request, res: Response) => {
try {
res.set('Content-Type', register.contentType);
res.send(await register.metrics());
} catch (err) {
logger.error('Failed to generate metrics:', err);
res.status(500).send('Failed to generate metrics');
}
});

// API routes
app.use('/api/sessions', sessionsRouter);
app.use('/api/sessions', messagesRouter);
Expand Down
94 changes: 94 additions & 0 deletions bridge-server/src/metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Prometheus Metrics Module
*
* Exposes application metrics for monitoring and alerting
*/

import { Counter, Histogram, Gauge, register } from 'prom-client';

// Enable default metrics (CPU, memory, event loop, etc.)
register.setDefaultLabels({
app: 'claude-code-bridge',
});

// Session metrics
export const sessionCounter = new Counter({
name: 'claude_code_sessions_total',
help: 'Total number of Claude Code sessions created',
labelNames: ['status'] as const,
});

export const activeSessionsGauge = new Gauge({
name: 'claude_code_sessions_active',
help: 'Number of currently active Claude Code sessions',
});

// Message metrics
export const messageCounter = new Counter({
name: 'claude_code_messages_total',
help: 'Total number of messages processed',
labelNames: ['direction'] as const, // 'inbound' or 'outbound'
});

export const messageHistogram = new Histogram({
name: 'claude_code_message_duration_seconds',
help: 'Message processing time in seconds',
buckets: [0.1, 0.5, 1, 2, 5, 10, 30, 60],
});

// WebSocket metrics
export const wsConnectionsGauge = new Gauge({
name: 'claude_code_websocket_connections',
help: 'Number of active WebSocket connections',
});

export const wsMessageCounter = new Counter({
name: 'claude_code_websocket_messages_total',
help: 'Total number of WebSocket messages',
labelNames: ['type'] as const, // 'received' or 'sent'
});

// API endpoint metrics
export const httpRequestCounter = new Counter({
name: 'claude_code_http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'path', 'status'] as const,
});

export const httpRequestDuration = new Histogram({
name: 'claude_code_http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'path'] as const,
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
});

// Error metrics
export const errorCounter = new Counter({
name: 'claude_code_errors_total',
help: 'Total number of errors',
labelNames: ['type'] as const,
});

// Database metrics
export const dbQueryCounter = new Counter({
name: 'claude_code_db_queries_total',
help: 'Total number of database queries',
labelNames: ['operation'] as const,
});

export const dbQueryDuration = new Histogram({
name: 'claude_code_db_query_duration_seconds',
help: 'Database query duration in seconds',
labelNames: ['operation'] as const,
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1],
});

// File operation metrics
export const fileOperationCounter = new Counter({
name: 'claude_code_file_operations_total',
help: 'Total number of file operations',
labelNames: ['operation'] as const, // 'read', 'write', 'list', etc.
});

// Export the registry for /metrics endpoint
export { register };
8 changes: 8 additions & 0 deletions bridge-server/src/spawner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { spawn, ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { config } from './config.js';
import { logger } from './logger.js';
import { sessionCounter, activeSessionsGauge } from './metrics.js';
import type { CLIProcess } from './types/index.js';

export interface SpawnerEvents {
Expand Down Expand Up @@ -70,6 +71,8 @@ export class CLISpawner extends EventEmitter {
child.on('exit', (code: number | null, signal: NodeJS.Signals | null) => {
logger.info(`[${sessionId}] Process exited with code ${code}, signal ${signal}`);
this.processes.delete(sessionId);
activeSessionsGauge.set(this.processes.size);
sessionCounter.inc({ status: code === 0 ? 'completed' : 'failed' });
this.emit('exit', sessionId, code);
});

Expand All @@ -81,6 +84,11 @@ export class CLISpawner extends EventEmitter {
});

this.processes.set(sessionId, cliProcess);

// Update metrics
sessionCounter.inc({ status: 'created' });
activeSessionsGauge.set(this.processes.size);

return cliProcess;
}

Expand Down
17 changes: 12 additions & 5 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,11 +373,18 @@ User Plugin Bridge Server

### Metrics (Bridge Server)

- Session count (active/total)
- Message throughput (msg/sec)
- CLI spawn time (ms)
- Response latency (ms)
- Error rate
The bridge server exposes Prometheus metrics at `GET /metrics`:

- `claude_code_sessions_total` - Total sessions created (counter)
- `claude_code_sessions_active` - Currently active sessions (gauge)
- `claude_code_messages_total` - Total messages processed (counter)
- `claude_code_message_duration_seconds` - Message processing time (histogram)
- `claude_code_websocket_connections` - Active WebSocket connections (gauge)
- `claude_code_http_requests_total` - HTTP request count by method/path/status (counter)
- `claude_code_http_request_duration_seconds` - HTTP request duration (histogram)
- `claude_code_errors_total` - Error count by type (counter)
- `claude_code_db_queries_total` - Database query count (counter)
- `claude_code_file_operations_total` - File operation count (counter)

### Logs

Expand Down
60 changes: 60 additions & 0 deletions marketplace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Claude Code
description: Integrate Claude Code AI assistant directly in Mattermost with native slash commands, interactive components, and thread context injection
homepage_url: https://github.com/appsome/claude-code-mattermost-plugin
icon_path: assets/icon.png
release_notes_url: https://github.com/appsome/claude-code-mattermost-plugin/releases
support_url: https://github.com/appsome/claude-code-mattermost-plugin/issues

maintainer_name: Appsome
maintainer_url: https://github.com/appsome

labels:
- ai
- development
- code
- automation
- productivity

# Requirements
min_server_version: "9.0.0"

# Features
features:
- Hands-free AI coding with slash commands
- Thread context integration - add conversation history to Claude sessions
- Interactive approve/reject buttons for code changes
- File browser and operations via dialogs
- Real-time streaming output via WebSocket
- Mobile-friendly button-based interface
- Dual mode - remote bridge server OR embedded CLI process

# Screenshots (to be added)
# screenshots:
# - path: assets/screenshot-commands.png
# caption: "Slash commands interface"
# - path: assets/screenshot-thread-context.png
# caption: "Thread context integration"
# - path: assets/screenshot-file-browser.png
# caption: "Interactive file browser"

# Installation requirements
installation_notes: |
**Prerequisites:**
- Claude Code CLI installed on the server or via bridge server
- For embedded mode: Claude Code CLI must be installed on Mattermost server
- For bridge mode: Separate Node.js bridge server (included in release)

**Configuration:**
1. Go to System Console > Plugins > Claude Code
2. Choose mode (embedded or bridge)
3. Configure Claude Code CLI path or bridge server URL
4. Enable the plugin

**Bridge Server (Optional for multi-user deployments):**
```bash
docker run -d -p 3002:3002 \
-v $(pwd)/data:/data \
ghcr.io/appsome/claude-code-bridge:latest
```

See full documentation: https://github.com/appsome/claude-code-mattermost-plugin/blob/main/docs/INSTALLATION.md
Loading