summaryrefslogtreecommitdiff
path: root/clients/umbrella-cli
diff options
context:
space:
mode:
authorauric <auric@japegames.com>2026-02-21 15:11:51 -0600
committerauric <auric@japegames.com>2026-02-21 15:11:51 -0600
commit52f92ea70f74008d82d21fef5085fb7380314ea1 (patch)
tree357ec1f0ec75779fc945d3b7460e976fe677ae31 /clients/umbrella-cli
parentaf012ffe7594350021741c62bd1205b65dfec07f (diff)
parentfc10d8a0818bb87001a64a72552ed28fe60931ee (diff)
Merge pull request #2 from ihateamongus/claude/trusting-dirac
State probing overhaul, A2S queries, tail/broadcast, bot audit log
Diffstat (limited to 'clients/umbrella-cli')
-rw-r--r--clients/umbrella-cli/main.c145
1 files changed, 122 insertions, 23 deletions
diff --git a/clients/umbrella-cli/main.c b/clients/umbrella-cli/main.c
index 69357e4..8562e92 100644
--- a/clients/umbrella-cli/main.c
+++ b/clients/umbrella-cli/main.c
@@ -4,9 +4,11 @@
* Usage:
* umbrella-cli list
* umbrella-cli status <unit>
- * umbrella-cli attach <unit> # interactive console session
- * umbrella-cli input <unit> <cmd> # send a single command
+ * umbrella-cli tail <unit> # print recent output, then exit
+ * umbrella-cli attach <unit> # interactive console session
+ * umbrella-cli input <unit> <cmd> # send a single command
* umbrella-cli action <unit> <action>
+ * umbrella-cli broadcast <message>
*/
#include <stdio.h>
@@ -124,30 +126,44 @@ static int cmd_list(int fd) {
char buf[PROTO_MAX];
if (recv_msg(fd, buf, sizeof(buf)) <= 0) return 1;
- /* Simple display: find all "name"/"display"/"state" triplets */
- printf("%-24s %-32s %s\n", "NAME", "DISPLAY", "STATE");
- printf("%-24s %-32s %s\n",
+ printf("%-24s %-32s %-14s %-8s %s\n",
+ "NAME", "DISPLAY", "STATE", "PLAYERS", "MAP");
+ printf("%-24s %-32s %-14s %-8s %s\n",
"────────────────────────",
"────────────────────────────────",
- "───────");
+ "──────────────",
+ "────────",
+ "───────────────────");
- /* Walk the units array manually */
const char *p = buf;
while ((p = strstr(p, "\"name\":")) != NULL) {
char name[64] = {0}, display[128] = {0}, state[32] = {0};
+ char players_s[16] = {0}, max_players_s[16] = {0}, map[64] = {0};
- /* Extract from this position forward */
char snippet[512];
strncpy(snippet, p, sizeof(snippet) - 1);
- jget(snippet, "name", name, sizeof(name));
- jget(snippet, "display", display, sizeof(display));
- jget(snippet, "state", state, sizeof(state));
+ jget(snippet, "name", name, sizeof(name));
+ jget(snippet, "display", display, sizeof(display));
+ jget(snippet, "state", state, sizeof(state));
+ jget(snippet, "players", players_s, sizeof(players_s));
+ jget(snippet, "max_players", max_players_s, sizeof(max_players_s));
+ jget(snippet, "map", map, sizeof(map));
- if (name[0])
- printf("%-24s %-32s %s\n", name, display, state);
+ if (!name[0]) { p++; continue; }
- p++; /* advance past current match */
+ /* Format player count as "N/M" or "-" */
+ char players_fmt[16] = "-";
+ int pl = players_s[0] ? atoi(players_s) : -1;
+ int mx = max_players_s[0] ? atoi(max_players_s) : -1;
+ if (pl >= 0 && mx >= 0)
+ snprintf(players_fmt, sizeof(players_fmt), "%d/%d", pl, mx);
+
+ printf("%-24s %-32s %-14s %-8s %s\n",
+ name, display, state, players_fmt,
+ map[0] ? map : "-");
+
+ p++;
}
return 0;
}
@@ -171,15 +187,88 @@ static int cmd_status(int fd, const char *unit) {
}
char display[128] = {0}, state[32] = {0}, pid[16] = {0};
- jget(buf, "display", display, sizeof(display));
- jget(buf, "state", state, sizeof(state));
- jget(buf, "pid", pid, sizeof(pid));
+ char players_s[16] = {0}, max_players_s[16] = {0}, map[64] = {0};
+ jget(buf, "display", display, sizeof(display));
+ jget(buf, "state", state, sizeof(state));
+ jget(buf, "pid", pid, sizeof(pid));
+ jget(buf, "players", players_s, sizeof(players_s));
+ jget(buf, "max_players", max_players_s, sizeof(max_players_s));
+ jget(buf, "map", map, sizeof(map));
printf("Unit : %s\n", unit);
printf("Display : %s\n", display);
printf("State : %s\n", state);
if (pid[0] && strcmp(pid, "0") != 0)
printf("PID : %s\n", pid);
+
+ int pl = players_s[0] ? atoi(players_s) : -1;
+ int mx = max_players_s[0] ? atoi(max_players_s) : -1;
+ if (pl >= 0 && mx >= 0)
+ printf("Players : %d/%d\n", pl, mx);
+ if (map[0])
+ printf("Map : %s\n", map);
+
+ return 0;
+}
+
+static int cmd_tail(int fd, const char *unit) {
+ char msg[256];
+ snprintf(msg, sizeof(msg),
+ "{\"cmd\":\"tail\",\"unit\":\"%s\"}", unit);
+ if (send_msg(fd, msg) != 0) return 1;
+
+ char buf[PROTO_MAX];
+ if (recv_msg(fd, buf, sizeof(buf)) <= 0) return 1;
+
+ char type[32] = {0};
+ jget(buf, "type", type, sizeof(type));
+ if (strcmp(type, "error") == 0) {
+ char errmsg[256] = {0};
+ jget(buf, "message", errmsg, sizeof(errmsg));
+ fprintf(stderr, "Error: %s\n", errmsg);
+ return 1;
+ }
+
+ char data[PROTO_MAX] = {0};
+ jget(buf, "data", data, sizeof(data));
+ if (data[0])
+ printf("%s", data);
+ return 0;
+}
+
+static int cmd_broadcast(int fd, const char *message) {
+ char msg[1024];
+ /* Simple JSON escaping for the message */
+ char escaped[900] = {0};
+ int j = 0;
+ for (int i = 0; message[i] && j < (int)sizeof(escaped) - 2; i++) {
+ if (message[i] == '"') { escaped[j++] = '\\'; escaped[j++] = '"'; }
+ else if (message[i] == '\\') { escaped[j++] = '\\'; escaped[j++] = '\\'; }
+ else escaped[j++] = message[i];
+ }
+ escaped[j] = '\0';
+
+ snprintf(msg, sizeof(msg), "{\"cmd\":\"broadcast\",\"message\":\"%s\"}", escaped);
+ if (send_msg(fd, msg) != 0) return 1;
+
+ char buf[PROTO_MAX];
+ if (recv_msg(fd, buf, sizeof(buf)) <= 0) return 1;
+
+ char type[32] = {0};
+ jget(buf, "type", type, sizeof(type));
+ if (strcmp(type, "error") == 0) {
+ char errmsg[256] = {0};
+ jget(buf, "message", errmsg, sizeof(errmsg));
+ fprintf(stderr, "Error: %s\n", errmsg);
+ return 1;
+ }
+
+ char sent_s[16] = {0}, failed_s[16] = {0};
+ jget(buf, "sent", sent_s, sizeof(sent_s));
+ jget(buf, "failed", failed_s, sizeof(failed_s));
+ printf("Broadcast sent to %s unit(s), %s failed\n",
+ sent_s[0] ? sent_s : "0",
+ failed_s[0] ? failed_s : "0");
return 0;
}
@@ -363,17 +452,21 @@ static void usage(void) {
"Usage: umbrella-cli <command> [args]\n"
"\n"
"Commands:\n"
- " list List all units\n"
- " status <unit> Show unit status\n"
- " attach <unit> Interactive console session\n"
- " input <unit> <cmd> Send a single command\n"
- " action <unit> <action> Run a named action\n"
+ " list List all units\n"
+ " status <unit> Show unit status\n"
+ " tail <unit> Print recent output and exit\n"
+ " attach <unit> Interactive console session\n"
+ " input <unit> <cmd> Send a single command\n"
+ " action <unit> <action> Run a named action\n"
+ " broadcast <message> Send message to all running units\n"
"\n"
"Examples:\n"
" umbrella-cli list\n"
+ " umbrella-cli tail tf2-novemen\n"
" umbrella-cli attach tf2-novemen\n"
" umbrella-cli input tf2-novemen \"say Server restarting soon\"\n"
- " umbrella-cli action tf2-novemen update\n");
+ " umbrella-cli action tf2-novemen update\n"
+ " umbrella-cli broadcast \"Server restart in 5 minutes\"\n");
}
int main(int argc, char *argv[]) {
@@ -390,6 +483,9 @@ int main(int argc, char *argv[]) {
} else if (strcmp(cmd, "status") == 0) {
if (argc < 3) { fprintf(stderr, "status: need unit name\n"); ret = 1; }
else ret = cmd_status(fd, argv[2]);
+ } else if (strcmp(cmd, "tail") == 0) {
+ if (argc < 3) { fprintf(stderr, "tail: need unit name\n"); ret = 1; }
+ else ret = cmd_tail(fd, argv[2]);
} else if (strcmp(cmd, "attach") == 0) {
if (argc < 3) { fprintf(stderr, "attach: need unit name\n"); ret = 1; }
else ret = cmd_attach(fd, argv[2]);
@@ -399,6 +495,9 @@ int main(int argc, char *argv[]) {
} else if (strcmp(cmd, "action") == 0) {
if (argc < 4) { fprintf(stderr, "action: need unit and action name\n"); ret = 1; }
else ret = cmd_action(fd, argv[2], argv[3]);
+ } else if (strcmp(cmd, "broadcast") == 0) {
+ if (argc < 3) { fprintf(stderr, "broadcast: need message\n"); ret = 1; }
+ else ret = cmd_broadcast(fd, argv[2]);
} else {
fprintf(stderr, "Unknown command: %s\n\n", cmd);
usage();