from __future__ import annotations from typing import Any from yuxi.utils.logging_config import logger def run_setup_wizard(config: dict[str, Any], interactive: bool = True) -> dict[str, Any]: result: dict[str, Any] = {} ship_url = config.get("ship_url", "") if not ship_url and interactive: ship_url = input("Enter your Urbit ship URL (e.g. http://localhost:8080): ").strip() from .probe import validate_urbit_url valid, warnings = validate_urbit_url(ship_url) if warnings: for w in warnings: logger.warning(f"[Urbit Setup] {w}") if not valid and interactive: allow_private = input("URL validation failed. Allow private network connections? (y/n): ").strip().lower() if allow_private == "y": config["network"] = config.get("network", {}) config["network"]["dangerously_allow_private_network"] = True result["dangerously_allow_private_network"] = True result["ship_url"] = ship_url ship_name = config.get("ship_name", "") if not ship_name and interactive: ship_name = input("Enter your Urbit ship name (without ~): ").strip() result["ship_name"] = ship_name ship_code = config.get("ship_code", "") if not ship_code and interactive: ship_code = input("Enter your ship +code: ").strip() result["ship_code"] = ship_code group_channels = config.get("group_channels", []) if not group_channels and interactive: channels_input = input( "Group channels to monitor (comma-separated, e.g. chat/some-group, diary/some-diary): " ).strip() if channels_input: group_channels = [c.strip() for c in channels_input.split(",") if c.strip()] result["group_channels"] = group_channels dm_allowlist = config.get("dm_allowlist", []) if not dm_allowlist and interactive: allowlist_input = input( "DM whitelist ships (comma-separated with urbit: prefix, e.g. urbit:zod, urbit:bus): " ).strip() if allowlist_input: dm_allowlist = [s.strip() for s in allowlist_input.split(",") if s.strip()] result["dm_allowlist"] = dm_allowlist auto_discover = config.get("auto_discover_channels", True) result["auto_discover_channels"] = auto_discover logger.info("[Urbit Setup] Configuration complete") return result def quick_setup(ship_url: str, ship_name: str, ship_code: str, **kw) -> dict[str, Any]: config: dict[str, Any] = { "ship_url": ship_url, "ship_name": ship_name, "ship_code": ship_code, "group_channels": kw.pop("group_channels", []), "dm_allowlist": kw.pop("dm_allowlist", []), "auto_discover_channels": kw.pop("auto_discover", True), } config.update(kw) return run_setup_wizard(config, interactive=False) def get_setup_schema() -> dict[str, Any]: return { "name": "urbit", "display_name": "Urbit (Tlon)", "description": "Connect to your Urbit ship for group chat and DM", "required_fields": [ {"key": "ship_url", "label": "Ship URL", "type": "url", "placeholder": "http://localhost:8080"}, {"key": "ship_name", "label": "Ship Name", "type": "text", "placeholder": "zod"}, {"key": "ship_code", "label": "Ship +code", "type": "password", "placeholder": "XXXX-XXXX-XXXX-XXXX"}, ], "optional_fields": [ {"key": "dm_allowlist", "label": "DM Allowlist", "type": "list"}, {"key": "group_channels", "label": "Group Channels", "type": "list"}, { "key": "autoDiscoverChannels", "label": "Auto-discover Channels", "type": "boolean", "default": True, }, { "key": "dm_policy", "label": "DM Policy", "type": "select", "options": ["open", "disabled", "pairing", "allowlist"], "default": "pairing", }, { "key": "group_policy", "label": "Group Policy", "type": "select", "options": ["open", "disabled", "allowlist"], "default": "open", "description": "Controls how the bot handles incoming group messages", }, { "key": "owner_ship", "label": "Owner Ship", "type": "text", "description": "Ship that receives admin notifications", }, { "key": "autoAcceptDmInvites", "label": "Auto Accept DM Invites", "type": "boolean", "default": False, }, { "key": "autoAcceptGroupInvites", "label": "Auto Accept Group Invites", "type": "boolean", "default": False, }, { "key": "groupInviteAllowlist", "label": "Group Invite Allowlist", "type": "list", "description": "Group paths allowed for auto-accept", }, { "key": "streaming", "label": "Streaming Mode", "type": "select", "options": ["off", "partial"], "default": "off", "description": "Controls how streaming responses are delivered", }, { "key": "configWrites", "label": "Config Writes", "type": "object", "description": "Custom config values pushed to Settings Store on connect", }, { "key": "authorization.channel_rules", "label": "Channel Rules", "type": "object", "description": 'Per-channel authorization rules, e.g. {"chat/my-group": {"allow": ["zod"]}}', }, { "key": "authorization.default_authorized_ships", "label": "Default Authorized Ships", "type": "list", "description": "Ships authorized across all channels by default", }, { "key": "network.dangerously_allow_private_network", "label": "Allow Private Network", "type": "boolean", "default": False, "description": "Allow connections to private/local network addresses", }, { "key": "trustedLocalFileRoots", "label": "Trusted Local File Roots", "type": "list", "description": "Trusted directory paths for file operations", }, { "key": "timeoutSeconds", "label": "Request Timeout (s)", "type": "number", "default": 30, "min": 5, "max": 300, }, { "key": "showModelSignature", "label": "Show Model Signature", "type": "boolean", "default": False, }, { "key": "responsePrefix", "label": "Response Prefix", "type": "text", "description": "Optional prefix added before bot responses", }, { "key": "commands.native", "label": "Enable Native Commands", "type": "boolean", "default": False, }, { "key": "commands.nativeSkills", "label": "Native Command Skills", "type": "list", }, ], }