From 0083e80acac848eac7f31213aaa77f81b49e8411 Mon Sep 17 00:00:00 2001 From: Seth Freiberg Date: Wed, 18 Mar 2026 22:29:19 -0400 Subject: [PATCH] Persistent Haiku cost tracking, Sethian whitelist web app - Haiku cost persists to /var/log/mc_anthropic_cost.json (survives restarts) - Status printer reads persistent cost file instead of journalctl - Seeded at $3.08 estimated cumulative spend - Whitelist app: Sethian Dark theme, mission description, server info Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/training_status_printer.py | 40 ++++- web/whitelist_app.py | 266 +++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+), 8 deletions(-) create mode 100644 web/whitelist_app.py diff --git a/scripts/training_status_printer.py b/scripts/training_status_printer.py index 39e6200..7fb50e8 100644 --- a/scripts/training_status_printer.py +++ b/scripts/training_status_printer.py @@ -106,8 +106,7 @@ def get_bot_stats(): def get_gemini_usage(): - """Track Gemini API calls. Reads/writes a local JSON counter.""" - # Count Gemini calls from bot log + """Track Gemini API calls from bot log.""" gemini_calls = remote_cmd(f"grep -c 'Gemini.*Generated' {BOT_LOG} 2>/dev/null") gemini_errors = remote_cmd(f"grep -c 'Gemini.*Error' {BOT_LOG} 2>/dev/null") @@ -120,7 +119,6 @@ def get_gemini_usage(): except: errors = 0 - # Estimate cost total_input_tokens = calls * EST_INPUT_TOKENS_PER_CALL total_output_tokens = calls * EST_OUTPUT_TOKENS_PER_CALL input_cost = (total_input_tokens / 1_000_000) * GEMINI_INPUT_COST_PER_M @@ -136,6 +134,21 @@ def get_gemini_usage(): } +def get_haiku_usage(): + """Track Claude Haiku API spend from persistent cost file.""" + raw = remote_cmd("cat /var/log/mc_anthropic_cost.json 2>/dev/null") + cost = 0.0 + budget = 5.0 + if raw and raw.startswith("{"): + try: + import json as _json + data = _json.loads(raw) + cost = data.get("total_cost", 0.0) + except: + pass + return {"cost": cost, "budget": budget} + + def get_dataset_size(): """Get current seed dataset size.""" try: @@ -195,15 +208,25 @@ def build_receipt(): p.text(f" Last: {last_msg}\n") p.text("-" * COLS + "\n") - # Gemini API + # Haiku API (main cost) + haiku = get_haiku_usage() + p.set(font='b', align='left', bold=True) + p.text("CLAUDE HAIKU API (dev God)\n") + p.set(font='b', align='left', bold=False) + p.set(font='b', align='left', bold=True) + p.text(f" Spent: ${haiku['cost']:.4f}\n") + p.set(font='b', align='left', bold=False) + p.text(f" Budget: ${haiku['budget']:.2f}\n") + p.text(f" Remaining: ${haiku['budget'] - haiku['cost']:.4f}\n") + p.text("-" * COLS + "\n") + + # Gemini API (bot prompts) gemini = get_gemini_usage() p.set(font='b', align='left', bold=True) - p.text("GEMINI API (flash-lite)\n") + p.text("GEMINI API (bot prompts)\n") p.set(font='b', align='left', bold=False) p.text(f" Calls: {gemini['calls']}\n") p.text(f" Errors: {gemini['errors']}\n") - p.text(f" Est input tokens: {gemini['est_input_tokens']:,}\n") - p.text(f" Est output tokens: {gemini['est_output_tokens']:,}\n") p.set(font='b', align='left', bold=True) p.text(f" Est cost: ${gemini['est_cost_usd']:.4f}\n") p.set(font='b', align='left', bold=False) @@ -278,6 +301,7 @@ def main(): dataset_size = get_dataset_size() dev_audit, prod_audit, bug_count = get_audit_stats() num_bots, num_sends, last_msg = get_bot_stats() + haiku = get_haiku_usage() statuses = get_service_status() print(f"Dataset: {dataset_size} seed examples") @@ -285,9 +309,9 @@ def main(): print(f"Prod audit: {prod_audit} entries") print(f"Bug reports: {bug_count}") print(f"Bots: {num_bots} active, {num_sends} messages sent") + print(f"Haiku: ${haiku['cost']:.4f} / ${haiku['budget']:.2f} ({haiku['budget'] - haiku['cost']:.4f} remaining)") print(f"Gemini: {gemini['calls']} calls, {gemini['errors']} errors, ${gemini['est_cost_usd']:.4f}") print(f"Services: {statuses}") - print(f"Threshold: ${COST_PRINT_THRESHOLD} (would print: {should_print(current_cost) or force})") return if not force and not should_print(current_cost): diff --git a/web/whitelist_app.py b/web/whitelist_app.py new file mode 100644 index 0000000..0bd9883 --- /dev/null +++ b/web/whitelist_app.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +""" +Minecraft whitelist web app — lightweight self-service whitelisting. +Sethian Dark theme. minecraft.sethpc.xyz +""" + +import html +import json +import os +import re +import socket +import struct +import time +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import parse_qs + +PORT = 8099 +INVITE_KEY = os.environ.get("INVITE_KEY", "REDACTED_INVITE_KEY") + +SERVERS = [ + {"name": "Paper AI", "host": "127.0.0.1", "rcon_port": 25577, "rcon_pass": "REDACTED_RCON", "show": True, "address": "sethpc.xyz:25567", "desc": "Full AI stack — pray to God, sudo commands, divine interventions"}, + {"name": "Shrink World", "host": "127.0.0.1", "rcon_port": 25576, "rcon_pass": "REDACTED_RCON", "show": True, "address": "sethpc.xyz:25566", "desc": "Survival challenge — border shrinks on death, 5x creepers, pray for help"}, + {"name": "Vanilla", "host": "127.0.0.1", "rcon_port": 25575, "rcon_pass": "REDACTED_RCON", "show": False, "address": None, "desc": None}, +] + +WHITELIST_LOG = "/var/log/mc_whitelist.log" + +LOGO_URL = "https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" + + +def rcon_command(cmd, host, port, password): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(5) + s.connect((host, port)) + def send_packet(req_id, ptype, payload): + data = struct.pack(" + + + + +Minecraft AI Server — sethpc.xyz + + + + +
+ logo + sethpc.xyz +
+ +
+
+

Minecraft AI Server

+

An AI runs on this server that listens to in-game chat and does things in the world based on what you say.

+
+ +
+

What Is This?

+

There's an AI character playing God on the server. It runs on local hardware — no cloud, no OpenAI — using a small open-source model we're actively training.

+

Every interaction you have gets logged as training data to improve the model. The more you play, the smarter it gets.

+

What Can You Do?

+
    +
  • pray <message> — Talk to God. Pray for items, smite your enemies, or say something offensive and get punished.
  • +
  • sudo <request> — Natural language commands. "sudo give me a diamond sword" just works.
  • +
  • bug_log <description> — Report when something goes wrong. Helps us fix the AI.
  • +
+

What We Need

+

Try to break it. Ask for weird things. Confuse it. Phrase things in ways nobody would expect. Every interaction — good or bad — makes the model better.

+
+ + {content} + + +
+ + +""" + +FORM = """ +
+

Join the Server

+
+ + + + + +
+ {error} +
+""" + +def success_content(username, results): + servers = "" + for srv in SERVERS: + if not srv["show"]: + continue + servers += f""" +
+
{srv['name']}
+
{srv['address']}
+
{srv['desc']}
+
""" + + return f""" +
+

Welcome, {html.escape(username)}!

+

You're whitelisted. Add these servers in Minecraft:

+ {servers} +
+ +
+

Quick Start

+

pray lord give me tools — ask God for help

+

sudo give me a diamond sword — direct command translation

+

sudo set time to day — world commands work too

+

bug_log it gave me the wrong item — report issues

+
""" + + +class Handler(BaseHTTPRequestHandler): + def log_message(self, fmt, *args): + pass + + def do_GET(self): + content = FORM.format(error="") + self.send_response(200) + self.send_header("Content-Type", "text/html") + self.end_headers() + self.wfile.write(PAGE.format(content=content, logo=LOGO_URL).encode()) + + def do_POST(self): + length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(length).decode() + params = parse_qs(body) + username = params.get("username", [""])[0].strip() + key = params.get("key", [""])[0].strip() + + if key != INVITE_KEY: + content = FORM.format(error='

Invalid invite key.

') + self.send_response(200) + self.send_header("Content-Type", "text/html") + self.end_headers() + self.wfile.write(PAGE.format(content=content, logo=LOGO_URL).encode()) + return + + if not is_valid_username(username): + content = FORM.format(error='

Invalid username. 3-16 characters, letters/numbers/underscore only.

') + self.send_response(200) + self.send_header("Content-Type", "text/html") + self.end_headers() + self.wfile.write(PAGE.format(content=content, logo=LOGO_URL).encode()) + return + + results = whitelist_player(username) + try: + with open(WHITELIST_LOG, "a") as f: + f.write(json.dumps({"time": time.strftime("%Y-%m-%dT%H:%M:%SZ"), "username": username, "results": results}) + "\n") + except: + pass + + content = success_content(username, results) + self.send_response(200) + self.send_header("Content-Type", "text/html") + self.end_headers() + self.wfile.write(PAGE.format(content=content, logo=LOGO_URL).encode()) + + +if __name__ == "__main__": + print(f"Whitelist app running on port {PORT}") + print(f"Invite key: {INVITE_KEY}") + HTTPServer(("0.0.0.0", PORT), Handler).serve_forever()