Files
Hermit/src/parliament_cli.c
T
WHCandCursor 812e3ee265 feat: parliament seat collaboration loop (0.9.19)
Seats publish via parliament_note, sync pushes on write, member join without
stealing the chair, and worker tools are hard-gated when parliament is active.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-29 11:29:38 +08:00

367 lines
12 KiB
C

#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
#endif
#include "cst_parliament_cli.h"
#include "cst_agent.h"
#include "cst_discover.h"
#include "cst_net.h"
#include "cst_parliament.h"
#include "cst_peer.h"
#include "cst_realm.h"
#include "os_compat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
static void sleep_ms(int ms) {
#ifdef _WIN32
Sleep((DWORD)ms);
#else
struct timespec ts = {ms / 1000, (ms % 1000) * 1000000L};
nanosleep(&ts, NULL);
#endif
}
/* Prefer CST_REALM_PASSWORD; otherwise prompt on a TTY. */
static int resolve_password(char *buf, size_t n, const char **out) {
const char *env = getenv("CST_REALM_PASSWORD");
if (env && env[0]) {
*out = env;
return 0;
}
if (cst_prompt_secret("Realm password (input hidden): ", buf, n) != 0) {
fprintf(stderr, "password required (set CST_REALM_PASSWORD or type it)\n");
return -1;
}
*out = buf;
return 0;
}
int cst_parliament_cli_realm_new(void) {
char dir[OS_PATH_MAX], fp[33];
if (cst_realm_dir(dir, sizeof(dir)) != 0) {
fprintf(stderr, "cannot resolve realm directory\n");
return 1;
}
cst_realm_keys_t k;
if (cst_realm_generate(&k) != 0 || cst_realm_save(dir, &k) != 0) {
fprintf(stderr, "cannot create realm keys in %s\n", dir);
return 1;
}
cst_realm_fingerprint(k.pk, fp);
printf("realm_created dir=%s fingerprint=%s\n", dir, fp);
return 0;
}
int cst_parliament_cli_realm_password_init(const char *password) {
char pwbuf[256];
const char *pw = password;
if ((!pw || !pw[0]) && resolve_password(pwbuf, sizeof(pwbuf), &pw) != 0)
return 1;
char dir[OS_PATH_MAX], fp[33];
if (cst_realm_dir(dir, sizeof(dir)) != 0) {
fprintf(stderr, "cannot resolve realm directory\n");
return 1;
}
if (cst_realm_init_password(dir, pw) != 0) {
fprintf(stderr, "password realm init failed\n");
return 1;
}
cst_realm_keys_t k;
if (cst_realm_unlock_password(dir, pw, &k) != 0)
return 1;
cst_realm_fingerprint(k.pk, fp);
printf("realm_password_mode dir=%s fingerprint=%s\n", dir, fp);
if (pw == pwbuf)
memset(pwbuf, 0, sizeof(pwbuf));
return 0;
}
int cst_parliament_cli_login(const char *password, const char *instance_id,
const char *device_label, int peer_port) {
return cst_parliament_cli_login_and_serve(password, instance_id, device_label,
peer_port > 0 ? peer_port : 9100);
}
int cst_parliament_cli_login_and_serve(const char *password,
const char *instance_id,
const char *device_label, int peer_port) {
char err[256], pwbuf[256];
const char *pw = password;
if ((!pw || !pw[0]) && resolve_password(pwbuf, sizeof(pwbuf), &pw) != 0)
return 1;
char *hand = cst_parliament_login(pw, instance_id, device_label, err,
sizeof(err));
if (!hand) {
fprintf(stderr, "%s\n", err[0] ? err : "login failed");
if (pw == pwbuf)
memset(pwbuf, 0, sizeof(pwbuf));
return 1;
}
fputs(hand, stdout);
free(hand);
if (cst_parliament_daemon_start(pw, instance_id,
peer_port > 0 ? peer_port : 9100, err,
sizeof(err)) != 0) {
fprintf(stderr, "warning: service: %s (chair login still valid)\n",
err[0] ? err : "start failed");
printf("service=failed\n");
if (pw == pwbuf)
memset(pwbuf, 0, sizeof(pwbuf));
return 0;
}
if (cst_parliament_daemon_running())
printf("service=running\n");
if (pw == pwbuf)
memset(pwbuf, 0, sizeof(pwbuf));
return 0;
}
int cst_parliament_cli_service(const char *password, const char *instance_id,
int peer_port, int foreground) {
char err[256], pwbuf[256];
const char *pw = password;
if (!instance_id || !instance_id[0]) {
fprintf(stderr, "instance id required\n");
return 1;
}
if ((!pw || !pw[0]) && resolve_password(pwbuf, sizeof(pwbuf), &pw) != 0)
return 1;
int rc = 0;
if (foreground) {
if (cst_parliament_daemon_run(pw, instance_id,
peer_port > 0 ? peer_port : 9100, err,
sizeof(err)) != 0) {
fprintf(stderr, "%s\n", err[0] ? err : "service failed");
rc = 1;
}
} else if (cst_parliament_daemon_start(pw, instance_id,
peer_port > 0 ? peer_port : 9100, err,
sizeof(err)) != 0) {
fprintf(stderr, "%s\n", err[0] ? err : "service start failed");
rc = 1;
} else {
printf("service=running\n");
}
if (pw == pwbuf)
memset(pwbuf, 0, sizeof(pwbuf));
return rc;
}
int cst_parliament_cli_service_stop(void) {
if (cst_parliament_daemon_stop() != 0) {
fprintf(stderr, "service stop failed\n");
return 1;
}
printf("service=stopped\n");
return 0;
}
int cst_parliament_cli_new_project(const char *project_id) {
if (cst_parliament_new_project(project_id) != 0) {
fprintf(stderr, "new project failed\n");
return 1;
}
printf("parliament_new_project id=%s\n",
project_id && project_id[0] ? project_id : "default");
return 0;
}
int cst_parliament_cli_realm_enroll(const char *instance_id, const char *label) {
if (!instance_id || !instance_id[0]) {
fprintf(stderr, "instance id required\n");
return 1;
}
char dir[OS_PATH_MAX];
cst_realm_keys_t k;
if (cst_realm_dir(dir, sizeof(dir)) != 0 || cst_realm_load(dir, &k) != 0) {
fprintf(stderr, "load realm keys failed (run --realm-new first)\n");
return 1;
}
cst_realm_cert_t cert;
uint8_t seed[32], sk[64];
if (cst_realm_enroll(&k, instance_id, label, 0, &cert, seed, sk) != 0 ||
cst_realm_instance_save(instance_id, &cert, seed) != 0) {
fprintf(stderr, "enroll/save failed\n");
return 1;
}
char fp[33];
cst_realm_fingerprint(k.pk, fp);
printf("enrolled id=%s label=%s realm=%s\n", instance_id,
label ? label : "", fp);
return 0;
}
static int load_realm_for_cli(cst_realm_keys_t *realm) {
char dir[OS_PATH_MAX];
if (cst_realm_dir(dir, sizeof(dir)) != 0)
return -1;
if (cst_realm_is_password_mode(dir)) {
const char *pw = getenv("CST_REALM_PASSWORD");
if (!pw || !pw[0])
return -1;
return cst_realm_unlock_password(dir, pw, realm);
}
return cst_realm_load(dir, realm);
}
int cst_parliament_cli_peer_listen(int port, const char *instance_id) {
if (!instance_id || !instance_id[0]) {
fprintf(stderr, "instance id required\n");
return 1;
}
char dir[OS_PATH_MAX], err[256];
cst_realm_keys_t realm;
cst_realm_cert_t cert;
uint8_t sk[64];
if (cst_realm_dir(dir, sizeof(dir)) != 0 || load_realm_for_cli(&realm) != 0 ||
cst_realm_instance_load(instance_id, &cert, sk) != 0) {
fprintf(stderr, "cannot load realm/instance %s\n", instance_id);
return 1;
}
if (cst_realm_revoked(dir, instance_id)) {
fprintf(stderr, "instance revoked\n");
return 1;
}
os_net_init();
os_socket_t ls = os_net_listen("0.0.0.0", port > 0 ? port : 0, err, sizeof(err));
if (ls == OS_SOCKET_BAD) {
fprintf(stderr, "listen failed: %s\n", err);
return 1;
}
int bound = os_net_port(ls);
printf("peer_listen port=%d id=%s\n", bound, instance_id);
fflush(stdout);
cst_peer_session_t sess;
if (cst_peer_accept((intptr_t)ls, &cert, sk, realm.pk, 60000, &sess, err,
sizeof(err)) != 0) {
fprintf(stderr, "accept/handshake failed: %s\n", err);
os_net_close(ls);
return 1;
}
printf("peer_ok remote=%s host=%s sealed=%d\n", sess.remote.instance_id,
sess.host, sess.sealed);
fflush(stdout);
{
cst_peer_sync_bundle_t local = {0};
cst_parliament_meta_t m;
memset(&m, 0, sizeof(m));
(void)cst_parliament_load(&m);
unsigned long before = m.revision;
local.revision = m.revision;
snprintf(local.parliament_id, sizeof(local.parliament_id), "%s",
m.parliament_id);
snprintf(local.project_id, sizeof(local.project_id), "%s", m.project_id);
snprintf(local.chair_instance, sizeof(local.chair_instance), "%s",
m.chair_instance);
local.context = cst_parliament_context_get();
if (cst_peer_sync(&sess, &local, 15000, err, sizeof(err)) != 0)
fprintf(stderr, "peer_sync failed: %s\n", err);
else {
if (local.revision > before)
(void)cst_parliament_apply_sync(local.revision, local.parliament_id,
local.project_id, local.chair_instance,
local.context);
printf("peer_sync rev=%lu sealed=1\n", local.revision);
}
free(local.context);
}
cst_peer_close(&sess);
os_net_close(ls);
return 0;
}
int cst_parliament_cli_peer_dial(const char *host, int port,
const char *instance_id) {
if (!host || port <= 0 || !instance_id) {
fprintf(stderr, "host, port and instance id required\n");
return 1;
}
char dir[OS_PATH_MAX], err[256];
cst_realm_keys_t realm;
cst_realm_cert_t cert;
uint8_t sk[64];
if (cst_realm_dir(dir, sizeof(dir)) != 0 || load_realm_for_cli(&realm) != 0 ||
cst_realm_instance_load(instance_id, &cert, sk) != 0) {
fprintf(stderr, "cannot load realm/instance %s\n", instance_id);
return 1;
}
cst_peer_session_t sess;
if (cst_peer_dial(host, port, &cert, sk, realm.pk, 30000, &sess, err,
sizeof(err)) != 0) {
fprintf(stderr, "dial/handshake failed: %s\n", err);
return 1;
}
printf("peer_ok remote=%s sealed=%d\n", sess.remote.instance_id, sess.sealed);
fflush(stdout);
{
cst_peer_sync_bundle_t local = {0};
cst_parliament_meta_t m;
memset(&m, 0, sizeof(m));
(void)cst_parliament_load(&m);
unsigned long before = m.revision;
local.revision = m.revision;
snprintf(local.parliament_id, sizeof(local.parliament_id), "%s",
m.parliament_id);
snprintf(local.project_id, sizeof(local.project_id), "%s", m.project_id);
snprintf(local.chair_instance, sizeof(local.chair_instance), "%s",
m.chair_instance);
local.context = cst_parliament_context_get();
if (cst_peer_sync(&sess, &local, 15000, err, sizeof(err)) != 0)
fprintf(stderr, "peer_sync failed: %s\n", err);
else {
if (local.revision > before)
(void)cst_parliament_apply_sync(local.revision, local.parliament_id,
local.project_id, local.chair_instance,
local.context);
printf("peer_sync rev=%lu sealed=1\n", local.revision);
}
free(local.context);
}
cst_peer_close(&sess);
return 0;
}
int cst_parliament_cli_discover(const char *instance_id, int peer_port,
int wait_ms) {
if (!instance_id || peer_port <= 0) {
fprintf(stderr, "instance id and peer port required\n");
return 1;
}
char dir[OS_PATH_MAX], fp[33];
cst_realm_keys_t realm;
if (cst_realm_dir(dir, sizeof(dir)) != 0 || load_realm_for_cli(&realm) != 0 ||
cst_realm_fingerprint(realm.pk, fp) != 0) {
fprintf(stderr, "cannot load realm\n");
return 1;
}
cst_discover_t *d = cst_discover_start(fp, instance_id, instance_id, peer_port);
if (!d) {
fprintf(stderr, "discover start failed\n");
return 1;
}
int left = wait_ms > 0 ? wait_ms : 3000;
while (left > 0) {
cst_peer_info_t peers[16];
int n = cst_discover_peers(d, peers, 16);
for (int i = 0; i < n; i++) {
printf("peer iid=%s host=%s port=%d name=%s\n", peers[i].instance_id,
peers[i].host, peers[i].port, peers[i].name);
}
if (n > 0)
break;
sleep_ms(200);
left -= 200;
}
cst_discover_stop(d);
return 0;
}