BareGit

Add CLI game startup for diagnostics

Allow starting a local test game from the server command line and print its
spectator URL. Forward NetHack raw startup messages to the journal.
Author: MetroWind <chris.corsair@gmail.com>
Date: Thu Sep 24 18:49:04 2026 -0700
Commit: dc67da32c5843ced392b3b386fc0bc3846aa8aea

Changes

diff --git a/src/main.cpp b/src/main.cpp
index 43ab773..51bcbc7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -126,12 +126,14 @@ void printUsage()
         "  --lifecycle-sweep-seconds N         Expiry sweep interval (15)\n"
         "  --max-mcp-body-bytes N              MCP request size (1048576)\n"
         "  --max-worker-output-bytes N         Worker log byte limit\n"
+        "  --start-game                        Start a local test game\n"
         "  --help                              Show this help\n",
         stdout);
 }
 
 ParseResult parseOptions(int argc, char* argv[],
                          nethack_mcp::ServerConfig& config,
+                         bool& start_game,
                          std::string& error)
 {
     config.data_root = std::filesystem::temp_directory_path()
@@ -182,6 +184,11 @@ ParseResult parseOptions(int argc, char* argv[],
             ++index;
             continue;
         }
+        if(argument == "--start-game")
+        {
+            start_game = true;
+            continue;
+        }
         if(index + 1 >= argc)
         {
             return ParseResult::ERROR;
@@ -388,8 +395,10 @@ int main(int argc, char* argv[])
     }
 
     nethack_mcp::ServerConfig config;
+    bool start_game = false;
     std::string parse_error;
-    const ParseResult parsed = parseOptions(argc, argv, config, parse_error);
+    const ParseResult parsed = parseOptions(argc, argv, config, start_game,
+                                            parse_error);
     if(parsed == ParseResult::HELP)
     {
         return 0;
@@ -421,6 +430,21 @@ int main(int argc, char* argv[])
     }
     std::unique_ptr<nethack_mcp::GameManager> manager =
         std::move(*manager_result);
+    std::string viewer_url;
+    if(start_game)
+    {
+        const nethack_mcp::ToolResult started = manager->createGame(
+            {{"model_slug", "local-test"}, {"name", "CLI"}},
+            "command-line");
+        if(!started.success)
+        {
+            std::fprintf(stderr, "could not start test game: %s: %s\n",
+                         started.code.c_str(), started.message.c_str());
+            manager->shutdown();
+            return 1;
+        }
+        viewer_url = started.value.at("viewer_url").get<std::string>();
+    }
     nethack_mcp::McpServer mcp(*manager);
     nethack_mcp::GameHttpServer http_server(*manager, mcp);
     std::string error;
@@ -434,6 +458,10 @@ int main(int argc, char* argv[])
                  manager->publicBaseUrl().c_str());
     std::fprintf(stderr, "Spectators: %s\n",
                  manager->publicBaseUrl().c_str());
+    if(!viewer_url.empty())
+    {
+        std::fprintf(stderr, "Test game: %s\n", viewer_url.c_str());
+    }
     std::signal(SIGINT, requestStop);
     std::signal(SIGTERM, requestStop);
     while(!stop_requested && http_server.running())
diff --git a/src/window_adapter.cpp b/src/window_adapter.cpp
index de60635..c11d356 100644
--- a/src/window_adapter.cpp
+++ b/src/window_adapter.cpp
@@ -219,7 +219,13 @@ void WindowAdapter::handleCallback(const char* name, void* return_ptr,
     if(std::strcmp(name, "shim_raw_print") == 0
        || std::strcmp(name, "shim_raw_print_bold") == 0)
     {
-        addMessage(va_arg(arguments, const char*));
+        const char* text = va_arg(arguments, const char*);
+        if(text != nullptr)
+        {
+            std::fprintf(stderr, "NetHack: %s\n", text);
+            std::fflush(stderr);
+        }
+        addMessage(text);
         return;
     }
 
diff --git a/web/home.html b/web/home.html
index 9271bdf..589f3cc 100644
--- a/web/home.html
+++ b/web/home.html
@@ -26,7 +26,7 @@
           <h1 id="hero-title">
             <span>Do you have tokens to waste?</span>
             <span>Do you suck at NetHack?</span>
-            <strong>This MCP is for you!</strong>
+            <strong>This MCP server is for you!</strong>
           </h1>
           <div class="prompt-panel">
             <div class="prompt-heading">