import json import urllib.request import urllib.error import sys import os CONFIG_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json') def load_config(): with open(CONFIG_PATH) as f: return json.load(f) def api_url(endpoint): cfg = load_config() return f"http://{cfg['modHost']}:{cfg['modPort']}{endpoint}" def api_get(endpoint): url = api_url(endpoint) cfg = load_config() try: req = urllib.request.Request(url, method='GET') with urllib.request.urlopen(req, timeout=cfg['timeout']) as resp: return json.loads(resp.read().decode()) except urllib.error.URLError as e: return {"error": str(e)} def api_post(endpoint, data): url = api_url(endpoint) cfg = load_config() try: req = urllib.request.Request( url, data=json.dumps(data).encode(), headers={'Content-Type': 'application/json'}, method='POST' ) with urllib.request.urlopen(req, timeout=cfg['timeout']) as resp: return json.loads(resp.read().decode()) except urllib.error.URLError as e: return {"error": str(e)} def cmd_health(): print(api_get('/health')) def cmd_status(): game = api_get('/api/state/game') resources = api_get('/api/state/resources') dups = api_get('/api/state/duplicants') alerts = api_get('/api/state/alert') if 'error' in game: print(f"Error: {game['error']}") return print("=== Game ===") print(f" Cycle: {game.get('cycle', '?')}") print(f" Duplicants: {game.get('duplicantCount', '?')}") print(f" World: {game.get('worldName', '?')}") print("\n=== Resources (top 15) ===") if isinstance(resources, list): for r in sorted(resources, key=lambda x: x.get('amount', 0), reverse=True)[:15]: print(f" {r.get('name', '?'):20s} {r.get('amount', 0):>10.1f} {r.get('unit', '')}") print("\n=== Duplicants ===") if isinstance(dups, list): for d in dups: print(f" {d.get('name'):12s} stress={d.get('stress', '?'):>5} food={d.get('calories', 0)/1000:>6.0f} kcal" f" stamina={d.get('stamina', '?'):>5} o2={d.get('oxygen', '?'):>5}") print("\n=== Alerts ===") if isinstance(alerts, list): for a in alerts: print(f" [{a.get('severity', '?')}] {a.get('title', '?')}: {a.get('message', '')}" if alerts else " (none)") def cmd_resources(): data = api_get('/api/state/resources') if isinstance(data, list): for r in sorted(data, key=lambda x: x.get('amount', 0), reverse=True): print(f"{r.get('name', '?'):30s} {r.get('amount', 0):>12.1f} {r.get('unit', '')}") else: print(json.dumps(data, indent=2, ensure_ascii=False)) def cmd_duplicants(): print(json.dumps(api_get('/api/state/duplicants'), indent=2, ensure_ascii=False)) def cmd_buildings(): data = api_get('/api/state/buildings') if isinstance(data, list): for b in data: print(f" {b.get('name', '?'):25s} at ({b.get('x', '?')}, {b.get('y', '?')}) " f"{'ON' if b.get('isOperational') else 'OFF'}") else: print(json.dumps(data, indent=2, ensure_ascii=False)) def cmd_research(): data = api_get('/api/state/research') if isinstance(data, list): for t in data: status = "DONE" if t.get('isComplete') else f"{t.get('progress', 0)*100:.0f}%" print(f" {t.get('name', '?'):25s} [{status}] ({t.get('category', '?')})") else: print(json.dumps(data, indent=2, ensure_ascii=False)) def cmd_geysers(): data = api_get('/api/state/geysers') if isinstance(data, list): for g in data: print(f" {g.get('name', '?'):25s} at ({g.get('x', '?')}, {g.get('y', '?')}) " f"state={g.get('state', '?')} rate={g.get('emitRate', '?')}g/s") else: print(json.dumps(data, indent=2, ensure_ascii=False)) def cmd_critters(): data = api_get('/api/state/critters') if isinstance(data, list): for c in data: print(f" {c.get('name', '?'):20s} ({c.get('species', '?')}) at ({c.get('x', '?')}, {c.get('y', '?')}) " f"age={c.get('age', '?'):.1f} happy={c.get('happiness', '?')}") else: print(json.dumps(data, indent=2, ensure_ascii=False)) def cmd_dig(args): if len(args) < 4: print("Usage: dig ") return result = api_post('/api/action/dig', { "x": int(args[0]), "y": int(args[1]), "width": int(args[2]), "height": int(args[3]) }) print(json.dumps(result, indent=2, ensure_ascii=False)) def cmd_build(args): if len(args) < 3: print("Usage: build [rotation]") return data = {"buildingId": args[0], "x": int(args[1]), "y": int(args[2])} if len(args) >= 4: data["rotation"] = args[3] result = api_post('/api/action/build', data) print(json.dumps(result, indent=2, ensure_ascii=False)) def cmd_deconstruct(args): if len(args) < 3: print("Usage: deconstruct ") return result = api_post('/api/action/deconstruct', { "buildingId": args[0], "x": int(args[1]), "y": int(args[2]) }) print(json.dumps(result, indent=2, ensure_ascii=False)) def cmd_prioritize(args): if len(args) < 3: print("Usage: prioritize ") return result = api_post('/api/action/prioritize', { "x": int(args[0]), "y": int(args[1]), "priority": int(args[2]) }) print(json.dumps(result, indent=2, ensure_ascii=False)) def cmd_research_select(args): if len(args) < 1: print("Usage: research_select ") return result = api_post('/api/action/research', {"techId": args[0]}) print(json.dumps(result, indent=2, ensure_ascii=False)) COMMANDS = { 'health': cmd_health, 'status': cmd_status, 'resources': cmd_resources, 'duplicants': cmd_duplicants, 'buildings': cmd_buildings, 'research': cmd_research, 'geysers': cmd_geysers, 'critters': cmd_critters, 'dig': cmd_dig, 'build': cmd_build, 'deconstruct': cmd_deconstruct, 'prioritize': cmd_prioritize, 'research_select': cmd_research_select, } if __name__ == '__main__': cmd = sys.argv[1] if len(sys.argv) > 1 else 'help' if cmd == 'help' or cmd not in COMMANDS: print("ONI Agent API Client") print("") print("Commands:") print(" health Check Mod connection") print(" status Game overview (cycle, resources, dups, alerts)") print(" resources List all resources with amounts") print(" duplicants Show duplicant details") print(" buildings List all buildings") print(" research Show research tree progress") print(" geysers Show geyser states") print(" critters Show critter list") print(" dig Dig area") print(" build Place building") print(" deconstruct Remove building") print(" prioritize

Set priority") print(" research_select Select tech to research") else: COMMANDS[cmd](sys.argv[2:])