maintainx-debug-bundle
Comprehensive debugging toolkit for MaintainX integrations. Use when experiencing complex issues, need detailed logging, or troubleshooting integration problems with MaintainX. Trigger with phrases like "debug maintainx", "maintainx troubleshoot", "maintainx detailed logs", "diagnose maintainx", "maintainx issue". allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(curl:*), Bash(node:*), Grep version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>
Allowed Tools
No tools specified
Provided by Plugin
maintainx-pack
Claude Code skill pack for MaintainX CMMS (24 skills)
Installation
This skill is included in the maintainx-pack plugin:
/plugin install maintainx-pack@claude-code-plugins-plus
Click to copy
Instructions
# MaintainX Debug Bundle
## Overview
Complete debugging toolkit for diagnosing and resolving MaintainX integration issues with detailed logging, diagnostic scripts, and troubleshooting procedures.
## Prerequisites
- MaintainX API access configured
- Node.js environment
- curl for direct API testing
## Instructions
### Step 1: Environment Diagnostic
```typescript
// scripts/diagnose-env.ts
import axios from 'axios';
interface DiagnosticResult {
category: string;
check: string;
status: 'PASS' | 'FAIL' | 'WARN';
message: string;
}
async function runDiagnostics(): Promise {
const results: DiagnosticResult[] = [];
// Check 1: API Key presence
const apiKey = process.env.MAINTAINX_API_KEY;
results.push({
category: 'Environment',
check: 'API Key Configured',
status: apiKey ? 'PASS' : 'FAIL',
message: apiKey ? 'API key is set' : 'MAINTAINX_API_KEY not found',
});
// Check 2: API Key format
if (apiKey) {
const isValidFormat = apiKey.length > 20 && !apiKey.includes(' ');
results.push({
category: 'Environment',
check: 'API Key Format',
status: isValidFormat ? 'PASS' : 'WARN',
message: isValidFormat
? 'API key format appears valid'
: 'API key format may be incorrect',
});
}
// Check 3: Network connectivity
try {
await axios.get('https://api.getmaintainx.com', { timeout: 5000 });
results.push({
category: 'Network',
check: 'API Reachable',
status: 'PASS',
message: 'Can reach MaintainX API endpoint',
});
} catch (error: any) {
results.push({
category: 'Network',
check: 'API Reachable',
status: 'FAIL',
message: `Cannot reach API: ${error.message}`,
});
}
// Check 4: Authentication
if (apiKey) {
try {
const response = await axios.get(
'https://api.getmaintainx.com/v1/users?limit=1',
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
timeout: 10000,
}
);
results.push({
category: 'Authentication',
check: 'API Key Valid',
status: 'PASS',
message: 'Successfully authenticated with API',
});
} catch (error: any) {
const status = error.response?.status;
results.push({
category: 'Authentication',
check: 'API Key Valid',
status: 'FAIL',
message: `Authentication failed: HTTP ${status || error.message}`,
});
}
}
// Check 5: Node.js version
const nodeVersion = process.version;
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
results.push({
category: 'Environment',
check: 'Node.js Version',
status: majorVersion >= 18 ? 'PASS' : 'WARN',
message: `Node.js ${nodeVersion} (18+ recommended)`,
});
return results;
}
// Print results
async function main() {
console.log('=== MaintainX Integration Diagnostics ===\n');
const results = await runDiagnostics();
// Group by category
const categories = [...new Set(results.map(r => r.category))];
categories.forEach(category => {
console.log(`\n${category}:`);
results
.filter(r => r.category === category)
.forEach(r => {
const icon = r.status === 'PASS' ? '[OK]' : r.status === 'FAIL' ? '[!!]' : '[?]';
console.log(` ${icon} ${r.check}: ${r.message}`);
});
});
// Summary
const passed = results.filter(r => r.status === 'PASS').length;
const failed = results.filter(r => r.status === 'FAIL').length;
const warned = results.filter(r => r.status === 'WARN').length;
console.log(`\n=== Summary ===`);
console.log(`Passed: ${passed}, Failed: ${failed}, Warnings: ${warned}`);
if (failed > 0) {
console.log('\nAction Required: Fix failed checks before proceeding.');
process.exit(1);
}
}
main().catch(console.error);
```
### Step 2: Request/Response Logger
```typescript
// src/debug/logger.ts
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import fs from 'fs';
import path from 'path';
interface LogEntry {
timestamp: string;
requestId: string;
method: string;
url: string;
requestHeaders: Record;
requestBody?: any;
responseStatus?: number;
responseHeaders?: Record;
responseBody?: any;
duration?: number;
error?: string;
}
class DebugLogger {
private logs: LogEntry[] = [];
private logFile: string;
constructor(logDir = './logs') {
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
this.logFile = path.join(logDir, `maintainx-debug-${Date.now()}.json`);
}
createInterceptors(client: AxiosInstance) {
// Request interceptor
client.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
(config as any).metadata = {
requestId,
startTime: Date.now(),
};
const entry: LogEntry = {
timestamp: new Date().toISOString(),
requestId,
method: config.method?.toUpperCase() || 'UNKNOWN',
url: `${config.baseURL}${config.url}`,
requestHeaders: this.sanitizeHeaders(config.headers as Record),
requestBody: config.data,
};
this.logs.push(entry);
this.writeLog();
console.log(`[${requestId}] ${entry.method} ${entry.url}`);
return config;
},
error => {
console.error('Request interceptor error:', error);
return Promise.reject(error);
}
);
// Response interceptor
client.interceptors.response.use(
(response: AxiosResponse) => {
const metadata = (response.config as any).metadata;
const entry = this.logs.find(l => l.requestId === metadata?.requestId);
if (entry) {
entry.responseStatus = response.status;
entry.responseHeaders = response.headers as Record;
entry.responseBody = response.data;
entry.duration = Date.now() - metadata.startTime;
this.writeLog();
console.log(`[${entry.requestId}] ${response.status} (${entry.duration}ms)`);
}
return response;
},
error => {
const metadata = (error.config as any)?.metadata;
const entry = this.logs.find(l => l.requestId === metadata?.requestId);
if (entry) {
entry.responseStatus = error.response?.status;
entry.responseBody = error.response?.data;
entry.error = error.message;
entry.duration = metadata ? Date.now() - metadata.startTime : undefined;
this.writeLog();
console.error(`[${entry.requestId}] ERROR: ${error.response?.status || error.message}`);
}
return Promise.reject(error);
}
);
}
private sanitizeHeaders(headers: Record): Record {
const sanitized = { ...headers };
if (sanitized.Authorization) {
sanitized.Authorization = 'Bearer [REDACTED]';
}
return sanitized;
}
private writeLog() {
fs.writeFileSync(this.logFile, JSON.stringify(this.logs, null, 2));
}
getLogs(): LogEntry[] {
return this.logs;
}
getLogFile(): string {
return this.logFile;
}
printSummary() {
console.log('\n=== Debug Log Summary ===');
console.log(`Total requests: ${this.logs.length}`);
console.log(`Log file: ${this.logFile}`);
const errors = this.logs.filter(l => l.error || (l.responseStatus && l.responseStatus >= 400));
if (errors.length > 0) {
console.log(`\nErrors (${errors.length}):`);
errors.forEach(e => {
console.log(` - [${e.requestId}] ${e.method} ${e.url}: ${e.responseStatus || e.error}`);
});
}
}
}
export { DebugLogger };
```
### Step 3: API Health Check Script
```bash
#!/bin/bash
# scripts/health-check.sh
set -e
echo "=== MaintainX API Health Check ==="
echo ""
# Check environment
if [ -z "$MAINTAINX_API_KEY" ]; then
echo "[FAIL] MAINTAINX_API_KEY not set"
exit 1
fi
echo "[OK] API key configured"
# Test endpoints
ENDPOINTS=(
"/v1/users?limit=1"
"/v1/workorders?limit=1"
"/v1/assets?limit=1"
"/v1/locations?limit=1"
)
BASE_URL="https://api.getmaintainx.com"
for endpoint in "${ENDPOINTS[@]}"; do
echo -n "Testing $endpoint... "
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $MAINTAINX_API_KEY" \
-H "Content-Type: application/json" \
"$BASE_URL$endpoint")
if [ "$HTTP_CODE" == "200" ]; then
echo "[OK] HTTP $HTTP_CODE"
elif [ "$HTTP_CODE" == "429" ]; then
echo "[WARN] Rate limited (HTTP 429)"
else
echo "[FAIL] HTTP $HTTP_CODE"
fi
done
echo ""
echo "=== Health Check Complete ==="
```
### Step 4: Data Validation Tool
```typescript
// scripts/validate-data.ts
import { MaintainXClient } from '../src/api/maintainx-client';
interface ValidationIssue {
resource: string;
resourceId: string;
issue: string;
severity: 'error' | 'warning' | 'info';
}
async function validateData(client: MaintainXClient): Promise {
const issues: ValidationIssue[] = [];
console.log('Validating MaintainX data...\n');
// Validate Work Orders
console.log('Checking work orders...');
const workOrders = await client.getWorkOrders({ limit: 100 });
workOrders.workOrders.forEach(wo => {
// Check for missing title
if (!wo.title || wo.title.trim() === '') {
issues.push({
resource: 'WorkOrder',
resourceId: wo.id,
issue: 'Missing title',
severity: 'error',
});
}
// Check for overdue open work orders
if (wo.status === 'OPEN' && wo.dueDate) {
const dueDate = new Date(wo.dueDate);
if (dueDate < new Date()) {
issues.push({
resource: 'WorkOrder',
resourceId: wo.id,
issue: `Overdue since ${dueDate.toLocaleDateString()}`,
severity: 'warning',
});
}
}
// Check for high priority without assignee
if (wo.priority === 'HIGH' && (!wo.assignees || wo.assignees.length === 0)) {
issues.push({
resource: 'WorkOrder',
resourceId: wo.id,
issue: 'High priority work order without assignee',
severity: 'warning',
});
}
});
// Validate Assets
console.log('Checking assets...');
const assets = await client.getAssets({ limit: 100 });
assets.assets.forEach(asset => {
// Check for assets without location
if (!asset.locationId) {
issues.push({
resource: 'Asset',
resourceId: asset.id,
issue: 'Asset not assigned to a location',
severity: 'info',
});
}
// Check for non-operational assets
if (asset.status === 'NON_OPERATIONAL') {
issues.push({
resource: 'Asset',
resourceId: asset.id,
issue: 'Asset marked as non-operational',
severity: 'warning',
});
}
});
return issues;
}
// Run validation
async function main() {
const client = new MaintainXClient();
const issues = await validateData(client);
console.log('\n=== Validation Results ===\n');
if (issues.length === 0) {
console.log('No issues found!');
return;
}
// Group by severity
const errors = issues.filter(i => i.severity === 'error');
const warnings = issues.filter(i => i.severity === 'warning');
const infos = issues.filter(i => i.severity === 'info');
if (errors.length > 0) {
console.log(`ERRORS (${errors.length}):`);
errors.forEach(i => console.log(` [${i.resource}:${i.resourceId}] ${i.issue}`));
}
if (warnings.length > 0) {
console.log(`\nWARNINGS (${warnings.length}):`);
warnings.forEach(i => console.log(` [${i.resource}:${i.resourceId}] ${i.issue}`));
}
if (infos.length > 0) {
console.log(`\nINFO (${infos.length}):`);
infos.forEach(i => console.log(` [${i.resource}:${i.resourceId}] ${i.issue}`));
}
console.log(`\nTotal: ${errors.length} errors, ${warnings.length} warnings, ${infos.length} info`);
}
main().catch(console.error);
```
### Step 5: Network Debug with Verbose Logging
```bash
#!/bin/bash
# scripts/verbose-request.sh
# Make a verbose request to MaintainX API
curl -v \
-H "Authorization: Bearer $MAINTAINX_API_KEY" \
-H "Content-Type: application/json" \
"https://api.getmaintainx.com/v1/workorders?limit=1" 2>&1 | tee maintainx-debug.log
echo ""
echo "Full output saved to maintainx-debug.log"
```
### Step 6: Support Bundle Generator
```typescript
// scripts/generate-support-bundle.ts
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
interface SupportBundle {
generated: string;
environment: Record;
nodeVersion: string;
npmVersion: string;
installedPackages: string[];
diagnostics: any;
recentLogs: any[];
}
async function generateSupportBundle(): Promise {
console.log('Generating MaintainX Support Bundle...\n');
const bundle: SupportBundle = {
generated: new Date().toISOString(),
environment: {
NODE_ENV: process.env.NODE_ENV || 'development',
MAINTAINX_API_KEY: process.env.MAINTAINX_API_KEY ? '[SET]' : '[NOT SET]',
},
nodeVersion: process.version,
npmVersion: execSync('npm --version').toString().trim(),
installedPackages: [],
diagnostics: {},
recentLogs: [],
};
// Get relevant packages
try {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
bundle.installedPackages = Object.keys(packageJson.dependencies || {});
} catch (e) {
bundle.installedPackages = ['Unable to read package.json'];
}
// Include recent logs if available
const logsDir = './logs';
if (fs.existsSync(logsDir)) {
const logFiles = fs.readdirSync(logsDir)
.filter(f => f.endsWith('.json'))
.sort()
.slice(-5);
logFiles.forEach(file => {
try {
const content = JSON.parse(fs.readFileSync(path.join(logsDir, file), 'utf8'));
bundle.recentLogs.push({ file, content: content.slice(-20) });
} catch (e) {
// Skip unreadable files
}
});
}
// Write bundle
const bundlePath = `maintainx-support-bundle-${Date.now()}.json`;
fs.writeFileSync(bundlePath, JSON.stringify(bundle, null, 2));
console.log(`Support bundle generated: ${bundlePath}`);
console.log('\nIncluded:');
console.log(' - Environment information');
console.log(' - Node.js and npm versions');
console.log(' - Installed packages');
console.log(' - Recent API logs');
console.log('\nShare this file (without API keys) when requesting support.');
}
generateSupportBundle().catch(console.error);
```
## Output
- Environment diagnostic report
- Request/response logs with timing
- API health check results
- Data validation issues
- Support bundle for troubleshooting
## Debug Commands Quick Reference
```bash
# Run environment diagnostics
npx ts-node scripts/diagnose-env.ts
# API health check
./scripts/health-check.sh
# Validate data
npx ts-node scripts/validate-data.ts
# Generate support bundle
npx ts-node scripts/generate-support-bundle.ts
# Verbose API request
./scripts/verbose-request.sh
```
## Resources
- [MaintainX Help Center](https://help.getmaintainx.com/)
- [MaintainX API Status](https://status.getmaintainx.com)
- [MaintainX Community](https://community.getmaintainx.com/)
## Next Steps
For rate limit handling, see `maintainx-rate-limits`.