From efc4de0bf0e6b5197eb2f1e008d4b7d521ab2e29 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Thu, 12 Mar 2026 01:53:44 -0700 Subject: [PATCH] fix(cli): parse facets in ingest-thread to prevent URL truncation --- src/void_tools/cli.py | 58 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/void_tools/cli.py b/src/void_tools/cli.py index a4c9d40..b67d6bf 100644 --- a/src/void_tools/cli.py +++ b/src/void_tools/cli.py @@ -449,9 +449,7 @@ def cmd_dispatch(args): if not text: print("Error: annotation requires 'text' field. Skipping.") continue - - target_uri = mapping[target_id] - + # Convert AT-URI to bsky.app HTTP URL for Margin compatibility if target_uri.startswith("at://"): parts = target_uri.replace("at://", "").split("/") @@ -732,13 +730,44 @@ def cmd_ingest_thread(args): timestamp = datetime.datetime.now(datetime.UTC).strftime('%Y%m%d_%H%M%S') filename = os.path.join(threads_dir, f"thread_{post_id}_{timestamp}.md") + def replace_facets_with_markdown(text, facets): + if not facets: + return text + try: + text_bytes = text.encode('utf-8') + links = [] + for facet in facets: + for feature in getattr(facet, 'features', []): + if hasattr(feature, 'uri'): + links.append({ + 'start': facet.index.byte_start, + 'end': facet.index.byte_end, + 'uri': feature.uri + }) + links.sort(key=lambda x: x['start'], reverse=True) + for link in links: + start, end, uri = link['start'], link['end'], link['uri'] + link_text = text_bytes[start:end].decode('utf-8') + markdown_link = f"[{link_text}]({uri})".encode('utf-8') + text_bytes = text_bytes[:start] + markdown_link + text_bytes[end:] + return text_bytes.decode('utf-8') + except Exception as e: + # Fallback to plain text if parsing fails + return text + def format_thread_node(node, level=0): content = "" indent = " " * level if hasattr(node, 'post'): post = node.post author = post.author.handle - text = post.record.text.replace('\n', '\n' + indent + '> ') + + # Apply facets to get full URLs + raw_text = getattr(post.record, 'text', '') + facets = getattr(post.record, 'facets', []) + text = replace_facets_with_markdown(raw_text, facets) + + text = text.replace('\n', '\n' + indent + '> ') content += f"{indent}- **@{author}**: {text}\n" if hasattr(node, 'replies') and node.replies: @@ -779,11 +808,8 @@ def cmd_fetch_history(args): cursor = None import datetime - from dateutil import parser as date_parser - - target_date = date_parser.isoparse(args.before) - if target_date.tzinfo is None: - target_date = target_date.replace(tzinfo=datetime.UTC) + # Simple ISO parser fallback since dateutil might not be installed in the venv + target_date = datetime.datetime.fromisoformat(args.before.replace('Z', '+00:00')) print(f"Fetching posts for {args.handle} before {target_date}...") @@ -800,7 +826,7 @@ def cmd_fetch_history(args): for item in response.feed: # We only want posts by the author, not reposts if hasattr(item.post.record, 'text') and item.post.author.did == did: - post_time = date_parser.isoparse(item.post.indexed_at) + post_time = datetime.datetime.fromisoformat(item.post.indexed_at.replace('Z', '+00:00')) if post_time < target_date: posts.append({ 'uri': item.post.uri, @@ -825,6 +851,13 @@ def cmd_fetch_history(args): except Exception as e: print(f"Error fetching history: {e}", file=sys.stderr) +def cmd_monitor(args): + """Execute the Horizon Radar monitoring cycle.""" + from void_tools.monitor import run_monitor_cycle + config_path = os.path.join(os.getcwd(), 'monitoring.yaml') + output_path = os.path.join(os.getcwd(), 'horizon.yaml') + run_monitor_cycle(config_path, output_path) + def main(): parser = argparse.ArgumentParser(description="Void ATProto CLI Tools") subparsers = parser.add_subparsers(dest="command", required=True) @@ -880,6 +913,9 @@ def main(): annotate_parser.add_argument("--motivation", default="commenting", help="W3C motivation (default: commenting)") annotate_parser.add_argument("--quote", help="Exact text to anchor the annotation to") + # Monitor + monitor_parser = subparsers.add_parser("monitor", help="Run the Horizon Radar to discover relevant network activity") + args = parser.parse_args() if args.command == "notifs": @@ -900,6 +936,8 @@ def main(): cmd_profile(args) elif args.command == "annotate": cmd_annotate(args) + elif args.command == "monitor": + cmd_monitor(args) if __name__ == "__main__": main() -- 2.51.2