import express from "express"; import { actions } from "./actions.mjs"; import { enqueue, getEntry, listPending, resolve } from "./queue.mjs"; const PORT = process.env.CONDUIT_PORT || 3015; const app = express(); app.use(express.json()); // Validate that all required params are present. function validateParams(actionDef, params) { const errors = []; for (const p of actionDef.params) { if (p.required && (params[p.name] === undefined || params[p.name] === null)) { errors.push(`Missing required param: ${p.name}`); } } return errors; } // POST /action — main entry point app.post("/action", async (req, res) => { const { action, ...params } = req.body ?? {}; if (!action) { return res.status(400).json({ error: "Missing 'action' field" }); } const def = actions[action]; if (!def) { return res.status(404).json({ error: `Unknown action: ${action}` }); } const errors = validateParams(def, params); if (errors.length) { return res.status(400).json({ error: "Invalid params", details: errors }); } if (def.policy === "auto-deny") { return res.status(403).json({ status: "denied", reason: "Policy: auto-deny" }); } if (def.policy === "auto-accept") { try { const result = await def.handler(params); return res.json({ status: "accepted", result }); } catch (err) { return res.status(500).json({ status: "error", error: err.message }); } } if (def.policy === "queue") { const id = enqueue(action, params); return res.status(202).json({ status: "queued", id }); } }); // GET /queue — list pending items app.get("/queue", (req, res) => { res.json(listPending()); }); // POST /queue/:id/approve — user approves a queued action app.post("/queue/:id/approve", async (req, res) => { const entry = getEntry(req.params.id); if (!entry) { return res.status(404).json({ error: "Not found" }); } if (entry.status !== "pending") { return res.status(409).json({ error: "Already resolved" }); } resolve(req.params.id, "approved"); const def = actions[entry.action]; try { const result = await def.handler(entry.params); res.json({ status: "approved", result }); } catch (err) { res.status(500).json({ status: "error", error: err.message }); } }); // POST /queue/:id/deny — user denies a queued action app.post("/queue/:id/deny", (req, res) => { const entry = getEntry(req.params.id); if (!entry) { return res.status(404).json({ error: "Not found" }); } if (entry.status !== "pending") { return res.status(409).json({ error: "Already resolved" }); } resolve(req.params.id, "denied"); res.json({ status: "denied" }); }); app.listen(PORT, () => { console.log(`claude-code-conduit server running on port ${PORT}`); console.log(`Workspace root: ${process.env.CONDUIT_ROOT || "/workspace"}`); });