#!/usr/bin/env node // Conduit client — thin CLI wrapper for Claude to call the conduit server. // Usage: // node client/index.mjs --secrets /path/to/secrets.json --user agent [key=value ...] // node client/index.mjs --secrets /path/to/secrets.json --user agent list-actions // node client/index.mjs --secrets /path/to/secrets.json --user agent edit-file filename=/workspace/foo.mjs import { readFileSync } from "fs"; import { sign_request } from "./auth.mjs"; const BASE_URL = process.env.CONDUIT_URL || "http://localhost:3015"; function get_arg(argv, flag) { const i = argv.indexOf(flag); return i !== -1 ? argv[i + 1] : null; } async function call_action(action, params, auth_headers) { const body_string = JSON.stringify({ action, ...params }); const res = await fetch(`${BASE_URL}/action`, { method: "POST", headers: { "Content-Type": "application/json", ...auth_headers(body_string) }, body: body_string, }); const body = await res.json(); return { status: res.status, body }; } function parse_args(argv) { // Skip --secrets and --user flags and their values const filtered = []; let i = 2; while (i < argv.length) { if (argv[i] === "--secrets" || argv[i] === "--user") { i += 2; } else { filtered.push(argv[i]); i++; } } const [action, ...rest] = filtered; const params = {}; for (const arg of rest) { const eq = arg.indexOf("="); if (eq === -1) { console.error(`Bad argument (expected key=value): ${arg}`); process.exit(1); } params[arg.slice(0, eq)] = arg.slice(eq + 1); } return { action, params }; } async function main() { const secrets_path = get_arg(process.argv, "--secrets"); const username = get_arg(process.argv, "--user"); if (!secrets_path || !username) { console.error("Usage: conduit --secrets --user [key=value ...]"); process.exit(1); } let secrets; try { secrets = JSON.parse(readFileSync(secrets_path, "utf8")); } catch (err) { console.error(`Cannot read secrets file: ${err.message}`); process.exit(1); } const user_entry = secrets.users?.[username]; if (!user_entry) { console.error(`User '${username}' not found in secrets file`); process.exit(1); } const { action, params } = parse_args(process.argv); if (!action) { console.error("Usage: conduit --secrets --user [key=value ...]"); process.exit(1); } const auth_headers = (body_string) => sign_request(user_entry.secret, username, body_string); const { status, body } = await call_action(action, params, auth_headers); console.log(JSON.stringify(body, null, 2)); process.exit(status >= 400 ? 1 : 0); } main().catch((err) => { console.error("Conduit error:", err.message); process.exit(1); });