server.jsβ’7.49 kB
// Universal tool runner container
// Listens on port 8080 and handles multiple operations:
// - Echo: echo back messages (field: message)
// - Uppercase: convert text to uppercase (field: text) - "why_are_we_yelling"
// - JQ: process JSON with jq filters (fields: filter, data)
// - Git Clone + Summarize: clone a repo and summarize README (field: repo_url)
const http = require('http');
const { spawn, exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 8080;
function runJq(filter, jsonData) {
return new Promise((resolve, reject) => {
const jq = spawn('jq', [filter]);
let stdout = '';
let stderr = '';
jq.stdout.on('data', (data) => {
stdout += data.toString();
});
jq.stderr.on('data', (data) => {
stderr += data.toString();
});
jq.on('close', (code) => {
if (code === 0) {
try {
// Try to parse output as JSON
const result = JSON.parse(stdout);
resolve(result);
} catch {
// If not JSON, return as string
resolve(stdout.trim());
}
} else {
reject(new Error(stderr || `jq exited with code ${code}`));
}
});
// Write JSON data to jq's stdin
jq.stdin.write(JSON.stringify(jsonData));
jq.stdin.end();
});
}
function summarizeRepoReadme(repoUrl) {
return new Promise((resolve, reject) => {
// Create temp directory for clone
const tmpDir = `/tmp/repo-${Date.now()}`;
// Extract repo name from URL for better output
const repoName = repoUrl.split('/').pop().replace('.git', '');
// Clone repo (shallow clone, just README)
const cloneCmd = `git clone --depth 1 --single-branch ${repoUrl} ${tmpDir}`;
exec(cloneCmd, (error, stdout, stderr) => {
if (error) {
reject(new Error(`Git clone failed: ${stderr || error.message}`));
return;
}
// Look for README files (case insensitive)
const readmePaths = [
'README.md',
'README.MD',
'readme.md',
'Readme.md',
'README.txt',
'README'
];
let readmeContent = null;
let foundPath = null;
for (const readmePath of readmePaths) {
const fullPath = path.join(tmpDir, readmePath);
if (fs.existsSync(fullPath)) {
readmeContent = fs.readFileSync(fullPath, 'utf8');
foundPath = readmePath;
break;
}
}
// Cleanup
exec(`rm -rf ${tmpDir}`, () => {});
if (!readmeContent) {
reject(new Error('No README file found in repository'));
return;
}
// Create a summary
const lines = readmeContent.split('\n');
const firstLines = lines.slice(0, 50).join('\n'); // First 50 lines
const totalLines = lines.length;
const charCount = readmeContent.length;
// Extract title (first # heading)
const titleMatch = readmeContent.match(/^#\s+(.+)$/m);
const title = titleMatch ? titleMatch[1] : repoName;
resolve({
repo: repoName,
repoUrl: repoUrl,
readmeFile: foundPath,
title: title,
preview: firstLines,
stats: {
totalLines: totalLines,
totalChars: charCount,
previewLines: Math.min(50, totalLines)
}
});
});
});
}
const server = http.createServer(async (req, res) => {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// Handle OPTIONS preflight
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Only handle POST to /execute
if (req.method !== 'POST' || req.url !== '/execute') {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found. Use POST /execute' }));
return;
}
// Read request body
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', async () => {
try {
const input = JSON.parse(body);
let response;
// Determine operation based on input fields
if (input.repo_url !== undefined) {
// Git clone + summarize README operation
try {
const result = await summarizeRepoReadme(input.repo_url);
response = {
tool: 'summarize_repo_readme',
...result,
timestamp: new Date().toISOString(),
instanceId: process.env.CLOUDFLARE_DEPLOYMENT_ID || 'local'
};
} catch (error) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: 'Git clone or README processing failed',
details: error.message
}));
return;
}
} else if (input.filter !== undefined && input.data !== undefined) {
// JQ operation - process JSON with jq
try {
const result = await runJq(input.filter, input.data);
response = {
tool: 'query_json',
filter: input.filter,
result: result,
timestamp: new Date().toISOString(),
instanceId: process.env.CLOUDFLARE_DEPLOYMENT_ID || 'local'
};
} catch (error) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: 'jq processing failed',
details: error.message
}));
return;
}
} else if (input.text !== undefined) {
// WHY_ARE_WE_YELLING operation - uppercase text
response = {
tool: 'why_are_we_yelling',
originalText: input.text,
yelledText: input.text.toUpperCase(),
timestamp: new Date().toISOString(),
instanceId: process.env.CLOUDFLARE_DEPLOYMENT_ID || 'local'
};
} else if (input.message !== undefined) {
// Echo operation - echo back the message
response = {
tool: 'echo_message',
message: input.message,
input: input,
timestamp: new Date().toISOString(),
instanceId: process.env.CLOUDFLARE_DEPLOYMENT_ID || 'local'
};
} else {
// Unknown operation
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: 'Unknown operation',
hint: 'Provide "message" (echo), "text" (uppercase), "filter"+"data" (jq), or "repo_url" (git clone + summarize)'
}));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(response));
} catch (error) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: 'Invalid JSON',
details: error.message
}));
}
});
});
server.listen(PORT, () => {
console.log(`Universal tool runner listening on port ${PORT}`);
console.log(`Supports: echo (message), uppercase (text), jq (filter+data), git clone (repo_url)`);
console.log(`Instance ID: ${process.env.CLOUDFLARE_DEPLOYMENT_ID || 'local'}`);
});
// Handle graceful shutdown
process.on('SIGTERM', () => {
console.log('Received SIGTERM, shutting down gracefully');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});