From 63b62b5f0c81ff12be0a1fa2c534efdd37fe23a7 Mon Sep 17 00:00:00 2001 From: operator Date: Sat, 3 Oct 2026 18:59:52 +0000 Subject: [PATCH] bridge.py: front-door to muse.ai metadata bridge CLI --- bin/bridge.py | 89 ++++++++++++++++++++++++++++++++++++++++++++ bridge/mappings.json | 1 + 2 files changed, 90 insertions(+) create mode 100755 bin/bridge.py create mode 100644 bridge/mappings.json diff --git a/bin/bridge.py b/bin/bridge.py new file mode 100755 index 0000000..7aa408d --- /dev/null +++ b/bin/bridge.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +Bridge CLI: Front-Door ↔ muse.ai metadata bridge + +Manages mappings between front-door channels and muse.ai side chats. +Storage: ~/Projects/NetVM/bridge/mappings.json (operator-managed via SSH) + +Usage: + bridge.py list --channel #ops + bridge.py add --agent 646 --chat --channel #ops --purpose "incident-123" + bridge.py remove +""" +import json +import argparse +from pathlib import Path +from datetime import datetime, timezone + +BRIDGE_DIR = Path.home() / "Projects" / "NetVM" / "bridge" +MAPPINGS_FILE = BRIDGE_DIR / "mappings.json" + +def load_mappings(): + if not MAPPINGS_FILE.exists(): + return [] + with open(MAPPINGS_FILE) as f: + return json.load(f) + +def save_mappings(mappings): + BRIDGE_DIR.mkdir(parents=True, exist_ok=True) + with open(MAPPINGS_FILE, 'w') as f: + json.dump(mappings, f, indent=2) + +def cmd_list(args): + mappings = load_mappings() + if args.channel: + mappings = [m for m in mappings if m['frontdoor_channel'] == args.channel] + if not mappings: + print("No mappings found.") + return + for m in mappings: + print(f"{m['bridge_id']}: {m['agent']}/{m['muse_side_chat_id'][:8]} -> {m['frontdoor_channel']} ({m['purpose']}) [{m['status']}]") + +def cmd_add(args): + mappings = load_mappings() + bridge_id = f"br-{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}" + mapping = { + "bridge_id": bridge_id, + "frontdoor_channel": args.channel, + "muse_side_chat_id": args.chat, + "agent": args.agent, + "linked_at": datetime.now(timezone.utc).isoformat(), + "linked_by": args.by or "operator", + "purpose": args.purpose, + "status": "active" + } + mappings.append(mapping) + save_mappings(mappings) + print(f"Added {bridge_id}") + +def cmd_remove(args): + mappings = load_mappings() + mappings = [m for m in mappings if m['bridge_id'] != args.bridge_id] + save_mappings(mappings) + print(f"Removed {args.bridge_id}") + +def main(): + p = argparse.ArgumentParser(description="Bridge: front-door to muse.ai") + sub = p.add_subparsers(dest='cmd', required=True) + + p_list = sub.add_parser('list', help='List mappings') + p_list.add_argument('--channel', help='Filter by front-door channel') + p_list.set_defaults(func=cmd_list) + + p_add = sub.add_parser('add', help='Add a mapping') + p_add.add_argument('--agent', required=True, help='Agent (muse/pip/646)') + p_add.add_argument('--chat', required=True, help='muse.ai side chat ID') + p_add.add_argument('--channel', required=True, help='Front-door channel (#ops, #lobby, etc.)') + p_add.add_argument('--purpose', required=True, help='Purpose of the side chat') + p_add.add_argument('--by', help='Who linked (operator/agent)') + p_add.set_defaults(func=cmd_add) + + p_rm = sub.add_parser('remove', help='Remove a mapping') + p_rm.add_argument('bridge_id', help='Bridge ID to remove') + p_rm.set_defaults(func=cmd_remove) + + args = p.parse_args() + args.func(args) + +if __name__ == '__main__': + main() diff --git a/bridge/mappings.json b/bridge/mappings.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/bridge/mappings.json @@ -0,0 +1 @@ +[]