feat: complete ONI Agent project with full Mod API, Python toolchain, and development guide

- Implement all Mod API endpoints (buildings, research, geysers, alerts, critters, deconstruct, prioritize, research, schedule, wardrobe)
- Enhance Python tools with comprehensive CLI, analysis (O2/food/power/temp/water), and 7 blueprints (SPOM, toilet, ranch, farm, cooling, bedroom)
- Add utility scripts: auto_repair, auto_analyze, watch mode, setup
- Write Agent-Mod integration constraints and development guide
- Create skills directory with ONI agent skill definition
This commit is contained in:
root
2026-05-22 08:44:56 +08:00
commit 0e17e8a6ac
14 changed files with 1579 additions and 0 deletions

204
tools/oni_api.py Normal file
View File

@ -0,0 +1,204 @@
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 <x> <y> <width> <height>")
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 <buildingId> <x> <y> [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 <buildingId> <x> <y>")
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 <x> <y> <priority>")
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 <techId>")
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 <x> <y> <w> <h> Dig area")
print(" build <id> <x> <y> Place building")
print(" deconstruct <id> <x> <y> Remove building")
print(" prioritize <x> <y> <p> Set priority")
print(" research_select <id> Select tech to research")
else:
COMMANDS[cmd](sys.argv[2:])