feat: password Realm, single-chair login, shared parliament context (0.9.12)
Derive Realm keys from a user password (Argon2id, no private key on disk). Login claims the sole chair with handover; peers share parliament context; --new-project clears it. CLI peer/discover helpers and Docker alpine recipe included; headless empty-answer retries raised for unattended runs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+135
@@ -1,6 +1,7 @@
|
||||
#include "cst_agent.h"
|
||||
#include "cst_ext.h"
|
||||
#include "cst_models.h"
|
||||
#include "cst_parliament_cli.h"
|
||||
#include "cst_tools.h"
|
||||
#include "cst_version.h"
|
||||
#include "cst_webui.h"
|
||||
@@ -47,6 +48,15 @@ static void usage(const char *argv0) {
|
||||
" -n, --max-steps N model calls per turn (30)\n"
|
||||
" --orchestrate delegate edits and shell work to role workers\n"
|
||||
" --no-orchestrate override workspace orchestration setting\n"
|
||||
" --realm-new create Realm file keys (legacy/dev)\n"
|
||||
" --realm-password-init create password-derived Realm (no sk on disk)\n"
|
||||
" --login unlock with password; this device becomes chair\n"
|
||||
" --new-project ID replace all parliament context for a new project\n"
|
||||
" --realm-enroll ID enroll this instance under the Realm\n"
|
||||
" --realm-label S display label for enroll/login\n"
|
||||
" --peer-listen [N] accept one Realm peer handshake (port N)\n"
|
||||
" --peer-dial H:N dial host:port and complete handshake\n"
|
||||
" --discover-wait MS advertise and list same-Realm peers\n"
|
||||
" -H, --headless -p TEXT run one prompt without the TUI\n"
|
||||
" --web serve the UI over HTTP on 127.0.0.1:8787\n"
|
||||
" --web-port N port (default 8787, 0 = pick a free one)\n"
|
||||
@@ -363,6 +373,12 @@ int main(int argc, char **argv) {
|
||||
const char *prompt = NULL;
|
||||
int new_session = 0, do_list = 0, do_migrate = 0;
|
||||
const char *resume_id = NULL;
|
||||
int do_realm_new = 0, do_realm_enroll = 0, do_peer_listen = 0, do_peer_dial = 0,
|
||||
do_discover = 0, do_realm_pw_init = 0, do_login = 0, do_new_project = 0;
|
||||
const char *realm_enroll_id = NULL, *realm_label = NULL;
|
||||
const char *peer_dial_spec = NULL, *parliament_instance = NULL;
|
||||
const char *new_project_id = NULL;
|
||||
int peer_listen_port = 0, discover_wait_ms = 3000, discover_peer_port = 9100;
|
||||
/* WebUI (--web): a second front end on the same engine. */
|
||||
int web_mode = 0;
|
||||
cst_webui_opts_t web;
|
||||
@@ -412,6 +428,17 @@ int main(int argc, char **argv) {
|
||||
{"web-open", no_argument, 0, 0x106},
|
||||
{"orchestrate", no_argument, 0, 0x108},
|
||||
{"no-orchestrate", no_argument, 0, 0x109},
|
||||
{"realm-new", no_argument, 0, 0x110},
|
||||
{"realm-enroll", required_argument, 0, 0x111},
|
||||
{"realm-label", required_argument, 0, 0x112},
|
||||
{"peer-listen", optional_argument, 0, 0x113},
|
||||
{"peer-dial", required_argument, 0, 0x114},
|
||||
{"discover-wait", required_argument, 0, 0x115},
|
||||
{"instance", required_argument, 0, 0x116},
|
||||
{"peer-port", required_argument, 0, 0x117},
|
||||
{"realm-password-init", no_argument, 0, 0x118},
|
||||
{"login", no_argument, 0, 0x119},
|
||||
{"new-project", required_argument, 0, 0x11a},
|
||||
{0, 0, 0, 0}};
|
||||
int c;
|
||||
while ((c = getopt_long(argc, argv, "w:s:b:k:m:P:r:n:T:Hp:dahVNcR:LyA:C:e:M:",
|
||||
@@ -423,6 +450,48 @@ int main(int argc, char **argv) {
|
||||
case 0x109:
|
||||
cfg.orchestrate_override = -1;
|
||||
break;
|
||||
case 0x110:
|
||||
do_realm_new = 1;
|
||||
break;
|
||||
case 0x111:
|
||||
do_realm_enroll = 1;
|
||||
realm_enroll_id = optarg;
|
||||
break;
|
||||
case 0x112:
|
||||
realm_label = optarg;
|
||||
break;
|
||||
case 0x113:
|
||||
do_peer_listen = 1;
|
||||
peer_listen_port = optarg && optarg[0] ? atoi(optarg) : 0;
|
||||
break;
|
||||
case 0x114:
|
||||
do_peer_dial = 1;
|
||||
peer_dial_spec = optarg;
|
||||
break;
|
||||
case 0x115:
|
||||
do_discover = 1;
|
||||
discover_wait_ms = atoi(optarg);
|
||||
if (discover_wait_ms <= 0)
|
||||
discover_wait_ms = 3000;
|
||||
break;
|
||||
case 0x116:
|
||||
parliament_instance = optarg;
|
||||
break;
|
||||
case 0x117:
|
||||
discover_peer_port = atoi(optarg);
|
||||
if (discover_peer_port <= 0)
|
||||
discover_peer_port = 9100;
|
||||
break;
|
||||
case 0x118:
|
||||
do_realm_pw_init = 1;
|
||||
break;
|
||||
case 0x119:
|
||||
do_login = 1;
|
||||
break;
|
||||
case 0x11a:
|
||||
do_new_project = 1;
|
||||
new_project_id = optarg;
|
||||
break;
|
||||
case 'w':
|
||||
snprintf(cfg.workspace_root, sizeof(cfg.workspace_root), "%s", optarg);
|
||||
break;
|
||||
@@ -580,6 +649,72 @@ int main(int argc, char **argv) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (do_realm_new) {
|
||||
int rc = cst_parliament_cli_realm_new();
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_realm_pw_init) {
|
||||
const char *pw = getenv("CST_REALM_PASSWORD");
|
||||
int rc = cst_parliament_cli_realm_password_init(pw);
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_login) {
|
||||
const char *pw = getenv("CST_REALM_PASSWORD");
|
||||
const char *iid = parliament_instance ? parliament_instance
|
||||
: getenv("CST_INSTANCE_ID");
|
||||
if (!iid || !iid[0])
|
||||
iid = "chair-local";
|
||||
int rc = cst_parliament_cli_login(pw, iid, realm_label);
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_new_project) {
|
||||
int rc = cst_parliament_cli_new_project(new_project_id);
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_realm_enroll) {
|
||||
int rc = cst_parliament_cli_realm_enroll(realm_enroll_id, realm_label);
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_peer_listen) {
|
||||
const char *iid = parliament_instance ? parliament_instance : realm_enroll_id;
|
||||
if (!iid)
|
||||
iid = getenv("CST_INSTANCE_ID");
|
||||
int rc = cst_parliament_cli_peer_listen(peer_listen_port, iid);
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_peer_dial) {
|
||||
const char *iid = parliament_instance ? parliament_instance : getenv("CST_INSTANCE_ID");
|
||||
char host[256];
|
||||
int port = 0;
|
||||
host[0] = 0;
|
||||
if (peer_dial_spec) {
|
||||
const char *colon = strrchr(peer_dial_spec, ':');
|
||||
if (colon && colon > peer_dial_spec) {
|
||||
size_t hl = (size_t)(colon - peer_dial_spec);
|
||||
if (hl >= sizeof(host))
|
||||
hl = sizeof(host) - 1;
|
||||
memcpy(host, peer_dial_spec, hl);
|
||||
host[hl] = 0;
|
||||
port = atoi(colon + 1);
|
||||
}
|
||||
}
|
||||
int rc = cst_parliament_cli_peer_dial(host, port, iid);
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
if (do_discover) {
|
||||
const char *iid = parliament_instance ? parliament_instance : getenv("CST_INSTANCE_ID");
|
||||
int rc = cst_parliament_cli_discover(iid, discover_peer_port, discover_wait_ms);
|
||||
curl_global_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (do_list) {
|
||||
int rc = list_sessions(cfg.workspace_root);
|
||||
curl_global_cleanup();
|
||||
|
||||
Reference in New Issue
Block a user