diff --git a/client/index.mjs b/client/index.mjs index 45430f9..384dc26 100644 --- a/client/index.mjs +++ b/client/index.mjs @@ -1,68 +1,57 @@ #!/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 +// node client/index.mjs --secrets /path/to/secrets.json --user agent '{"action": "list-actions"}' +// node client/index.mjs --secrets /path/to/secrets.json --user agent '{"action":' '"edit-file",' '"filename": "/workspace/foo.mjs"}' -import { readFileSync } from "fs"; -import { sign_request } from "./auth.mjs"; +import { readFileSync } from 'fs'; +import { sign_request } from './auth.mjs'; -const BASE_URL = process.env.CONDUIT_URL || "http://localhost:3015"; +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 }); +function get_remaining(argv) { + // Collect all args that aren't --secrets or --user and their values + const result = []; + let i = 2; + while (i < argv.length) { + if (argv[i] === '--secrets' || argv[i] === '--user') { + i += 2; + } else { + result.push(argv[i]); + i++; + } + } + return result; +} + +async function call_action(payload, auth_headers) { + const body_string = JSON.stringify(payload); const res = await fetch(`${BASE_URL}/action`, { - method: "POST", - headers: { "Content-Type": "application/json", ...auth_headers(body_string) }, + 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"); + 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 ...]"); + console.error('Usage: conduit --secrets --user '); process.exit(1); } let secrets; try { - secrets = JSON.parse(readFileSync(secrets_path, "utf8")); + secrets = JSON.parse(readFileSync(secrets_path, 'utf8')); } catch (err) { console.error(`Cannot read secrets file: ${err.message}`); process.exit(1); @@ -74,20 +63,28 @@ async function main() { process.exit(1); } - const { action, params } = parse_args(process.argv); - if (!action) { - console.error("Usage: conduit --secrets --user [key=value ...]"); + const remaining = get_remaining(process.argv); + if (!remaining.length) { + console.error('Usage: conduit --secrets --user '); + process.exit(1); + } + + let payload; + try { + payload = JSON.parse(remaining.join(' ')); + } catch (err) { + console.error(`Invalid JSON payload: ${err.message}`); 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); + const { status, body } = await call_action(payload, auth_headers); console.log(JSON.stringify(body, null, 2)); process.exit(status >= 400 ? 1 : 0); } main().catch((err) => { - console.error("Conduit error:", err.message); + console.error('Conduit error:', err.message); process.exit(1); });