Replace key=value args with JSON payload in client
Remaining args after --secrets/--user are space-joined and parsed as JSON, so the full action payload is expressed directly rather than through a custom key=value scheme. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,68 +1,57 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// Conduit client — thin CLI wrapper for Claude to call the conduit server.
|
// Conduit client — thin CLI wrapper for Claude to call the conduit server.
|
||||||
// Usage:
|
// Usage:
|
||||||
// node client/index.mjs --secrets /path/to/secrets.json --user agent <action> [key=value ...]
|
// 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 list-actions
|
// node client/index.mjs --secrets /path/to/secrets.json --user agent '{"action":' '"edit-file",' '"filename": "/workspace/foo.mjs"}'
|
||||||
// node client/index.mjs --secrets /path/to/secrets.json --user agent edit-file filename=/workspace/foo.mjs
|
|
||||||
|
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from 'fs';
|
||||||
import { sign_request } from "./auth.mjs";
|
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) {
|
function get_arg(argv, flag) {
|
||||||
const i = argv.indexOf(flag);
|
const i = argv.indexOf(flag);
|
||||||
return i !== -1 ? argv[i + 1] : null;
|
return i !== -1 ? argv[i + 1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function call_action(action, params, auth_headers) {
|
function get_remaining(argv) {
|
||||||
const body_string = JSON.stringify({ action, ...params });
|
// 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`, {
|
const res = await fetch(`${BASE_URL}/action`, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json", ...auth_headers(body_string) },
|
headers: { 'Content-Type': 'application/json', ...auth_headers(body_string) },
|
||||||
body: body_string,
|
body: body_string,
|
||||||
});
|
});
|
||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
return { status: res.status, body };
|
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() {
|
async function main() {
|
||||||
const secrets_path = get_arg(process.argv, "--secrets");
|
const secrets_path = get_arg(process.argv, '--secrets');
|
||||||
const username = get_arg(process.argv, "--user");
|
const username = get_arg(process.argv, '--user');
|
||||||
|
|
||||||
if (!secrets_path || !username) {
|
if (!secrets_path || !username) {
|
||||||
console.error("Usage: conduit --secrets <path> --user <name> <action> [key=value ...]");
|
console.error('Usage: conduit --secrets <path> --user <name> <json payload>');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let secrets;
|
let secrets;
|
||||||
try {
|
try {
|
||||||
secrets = JSON.parse(readFileSync(secrets_path, "utf8"));
|
secrets = JSON.parse(readFileSync(secrets_path, 'utf8'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Cannot read secrets file: ${err.message}`);
|
console.error(`Cannot read secrets file: ${err.message}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -74,20 +63,28 @@ async function main() {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { action, params } = parse_args(process.argv);
|
const remaining = get_remaining(process.argv);
|
||||||
if (!action) {
|
if (!remaining.length) {
|
||||||
console.error("Usage: conduit --secrets <path> --user <name> <action> [key=value ...]");
|
console.error('Usage: conduit --secrets <path> --user <name> <json payload>');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload;
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(remaining.join(' '));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Invalid JSON payload: ${err.message}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auth_headers = (body_string) => sign_request(user_entry.secret, username, body_string);
|
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));
|
console.log(JSON.stringify(body, null, 2));
|
||||||
process.exit(status >= 400 ? 1 : 0);
|
process.exit(status >= 400 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
console.error("Conduit error:", err.message);
|
console.error('Conduit error:', err.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user