Files

76 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Run the Bridge against the private local development server without displaying secrets."""
import argparse
import json
import os
from pathlib import Path
import re
import subprocess
ROOT = Path(__file__).resolve().parents[1]
def scalar(text, key):
"""Read only the top-level scalar fields emitted by the Paper configuration writer."""
match = re.search(r'^' + re.escape(key) + r':[ \t]*(.*?)[ \t]*$', text, re.M)
if not match:
return ''
raw = match.group(1).strip()
if raw.startswith("'"):
quoted = re.fullmatch(r"'((?:[^']|'')*)'[ \t]*(?:#.*)?", raw)
if quoted:
return quoted.group(1).replace("''", "'")
elif raw.startswith('"'):
quoted = re.fullmatch(r'("(?:[^"\\]|\\.)*")[ \t]*(?:#.*)?', raw)
if quoted:
try:
return json.loads(quoted.group(1))
except json.JSONDecodeError:
pass
else:
return re.split(r'[ \t]+#', raw, maxsplit=1)[0].strip()
raise SystemExit(f'Invalid scalar for {key}; use the Paper-generated configuration format.')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('mode', choices=['doctor', 'chat', 'mcp', 'login', 'status'])
parser.add_argument('action', nargs='?', choices=['status'], help='Only for login: check authentication without starting a login flow')
parser.add_argument('--status', action='store_true', help='With login: check authentication without starting login')
parser.add_argument('--config', type=Path, default=ROOT / '.runtime/server/plugins/MinecraftBuilderMCP/config.yml')
parser.add_argument('--console', action='store_true', help='Use console scope only with allow-local-automation in the disposable fixture')
args = parser.parse_args()
if (args.action or args.status) and args.mode != 'login':
parser.error('The status action is accepted only after login; alternatively use mode status.')
# Login/authentication status needs no Paper config or Paper token. Always use the same
# default state directory as the chat daemon, including when called from another cwd.
env = dict(os.environ)
env.setdefault('MCB_STATE_DIR', str(ROOT / '.runtime/bridge-state'))
needs_backend = args.mode in ('chat', 'mcp')
if needs_backend and not args.config.is_file():
raise SystemExit('Start the Paper plugin once to generate its private config.')
if args.mode in ('doctor', 'chat', 'mcp') and args.config.is_file():
text = args.config.read_text()
actor = 'console' if args.console else scalar(text, 'owner-uuid')
if args.mode == 'mcp' and not actor:
raise SystemExit('Bind the owner in game with /ai setup first.')
port = scalar(text, 'http-port')
if not port.isdigit() or not 1 <= int(port) <= 65535:
raise SystemExit('Paper http-port must be an integer in 1..65535.')
env.update(MCB_BACKEND_URL='http://127.0.0.1:' + port,
MCB_TOKEN=scalar(text, 'admin-token'), MCB_AGENT_TOKEN=scalar(text, 'agent-token'),
MCB_PLAYER_ID=actor, MCB_PROJECT_ID=scalar(text, 'project-id'))
target = 'dist/' + ('login' if args.mode in ('login', 'status') else args.mode) + '.js'
if not (ROOT / 'bridge' / target).is_file():
raise SystemExit('Build the Bridge first: cd bridge && npm ci --ignore-scripts && npm run build')
command = ['node', target]
if args.mode == 'status' or args.action == 'status' or args.status:
command.append('status')
return subprocess.call(command, cwd=ROOT / 'bridge', env=env)
if __name__ == '__main__':
raise SystemExit(main())