Jargonify/server.js
Richard Nixon 0e9f6952b4 feat: add LinkedIn jargon translator web app
Single-page interactive translator that converts plain language into
LinkedIn-style corporate posts and decodes them back to human speak.

- Two translation modes: to LinkedIn and to Human
- Cringe intensity slider (1-5)
- 3 variation cards per translation with copy buttons
- Dual engine support: direct API key or local proxy server
- i18n toggle for Portuguese and English (UI + AI prompts)
- Dark theme with LinkedIn blue accent, fully responsive
- Local proxy server (server.js) bridges to the LLM CLI
2026-03-19 22:56:50 +00:00

96 lines
3.2 KiB
JavaScript

const http = require("http");
const { spawn } = require("child_process");
const fs = require("fs");
const path = require("path");
const PORT = 3000;
const server = http.createServer((req, res) => {
// CORS
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.method === "OPTIONS") {
res.writeHead(204);
res.end();
return;
}
// Serve index.html
if ((req.method === "GET" || req.method === "HEAD") && (req.url === "/" || req.url === "/index.html")) {
const filePath = path.join(__dirname, "index.html");
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(500);
res.end("Erro ao carregar index.html");
return;
}
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.end(data);
});
return;
}
// Translation endpoint
if (req.method === "POST" && req.url === "/translate") {
let body = "";
req.on("data", (chunk) => { body += chunk; });
req.on("end", () => {
let parsed;
try {
parsed = JSON.parse(body);
} catch (e) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "JSON inválido" }));
return;
}
const { prompt } = parsed;
if (!prompt || typeof prompt !== "string") {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Campo 'prompt' é obrigatório" }));
return;
}
console.log(`[Jargonify] Traduzindo (${prompt.length} chars)...`);
const claudePath = "/opt/homebrew/bin/claude";
const childEnv = { ...process.env, PATH: process.env.PATH + ":/opt/homebrew/bin", LANG: "en_US.UTF-8", LC_ALL: "en_US.UTF-8" };
delete childEnv.CLAUDECODE;
delete childEnv.CLAUDE_CODE_ENTRYPOINT;
const child = spawn(claudePath, ["-p", "-"], { encoding: "utf-8", env: childEnv, timeout: 60000 });
let stdout = "";
let stderr = "";
child.stdout.on("data", (data) => { stdout += data; });
child.stderr.on("data", (data) => { stderr += data; });
child.on("close", (code) => {
if (code !== 0) {
console.error("[Jargonify] Erro (code " + code + "):", stderr);
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: `Erro ao executar Claude CLI: ${stderr || "exit code " + code}` }));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ result: stdout.trim() }));
});
child.on("error", (err) => {
console.error("[Jargonify] Spawn erro:", err.message);
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: `Erro ao iniciar Claude CLI: ${err.message}` }));
});
child.stdin.write(prompt);
child.stdin.end();
});
return;
}
res.writeHead(404);
res.end("Not found");
});
server.listen(PORT, () => {
console.log(`\n ✨ Jargonify Server rodando em http://localhost:${PORT}\n`);
console.log(` Abra no browser para usar com traduções via Claude.\n`);
});