diff --git a/src/void_tools/cli.py b/src/void_tools/cli.py index 1373e05..adac28c 100644 --- a/src/void_tools/cli.py +++ b/src/void_tools/cli.py @@ -30,8 +30,6 @@ def mirror_to_letta(text, target_uri): "text": f"Margin Annotation attached to {target_uri}: {text}" } - # We need to handle Letta's expected JSON payload which might require a specific format. - # The standard endpoint takes {"text": "string"} resp = requests.post(url, headers=headers, json=payload) if resp.ok: print(" Mirrored annotation to Letta archival memory.") @@ -40,6 +38,50 @@ def mirror_to_letta(text, target_uri): except Exception as e: print(f" Warning: Failed to mirror to Letta: {e}") +def create_annotation(client, target_uri, text, motivation="commenting", quote=None): + """Helper function to format and push an at.margin.annotation record.""" + if target_uri.startswith("at://"): + parts = target_uri.replace("at://", "").split("/") + if len(parts) >= 3: + repo = parts[0] + rkey = parts[2] + target_uri = f"https://bsky.app/profile/{repo}/post/{rkey}" + + print(f" Dispatching annotation to {target_uri}...") + + try: + source_hash = hashlib.sha256(target_uri.encode()).hexdigest() + record = { + "$type": "at.margin.annotation", + "createdAt": client.get_current_time_iso(), + "target": { + "source": target_uri, + "sourceHash": source_hash, + }, + "body": { + "value": text + }, + "motivation": motivation + } + + if quote: + record["target"]["selector"] = { + "type": "TextQuoteSelector", + "exact": quote + } + + resp = client.com.atproto.repo.create_record({ + 'repo': client.me.did, + 'collection': 'at.margin.annotation', + 'record': record + }) + print(f" Successfully annotated: {resp.uri}") + mirror_to_letta(text, target_uri) + return resp.uri + except Exception as e: + print(f" Error annotating {target_uri}: {e}", file=sys.stderr) + return None + def cmd_sync(args): client = get_client() agent_id = os.getenv("AGENT_ID", "agent-c52e371d-cb89-4f99-8634-b606bdc69cad") @@ -134,14 +176,19 @@ def cmd_sync(args): # posts: # - "First post text" # - "Second post text (automatically threads)" +# annotation: # Optional: automatically annotates the first post in this block +# text: "Internal log message" +# motivation: "describing" # # 2. Reply # - action: reply # target_id: "notif_0" # Resolves from inbox.yaml # posts: # - "My reply text" +# annotation: # Optional: automatically annotates your reply +# text: "Memory trace" # -# 3. Margin Annotation +# 3. Margin Annotation (Standalone) # - action: annotate # target_id: "notif_1" # Or explicit at:// URI # text: "My archival observation" @@ -191,18 +238,24 @@ def cmd_dispatch(args): print("Dispatching standalone post/thread...") root_ref = None parent_ref = None + first_post_uri = None for text in posts: if root_ref is None: post = client.send_post(text=text) root_ref = models.ComAtprotoRepoStrongRef.Main(cid=post.cid, uri=post.uri) parent_ref = root_ref + first_post_uri = post.uri else: reply_ref = models.AppBskyFeedPost.ReplyRef(parent=parent_ref, root=root_ref) post = client.send_post(text=text, reply_to=reply_ref) parent_ref = models.ComAtprotoRepoStrongRef.Main(cid=post.cid, uri=post.uri) print(f" Posted: {post.uri}") + ann = act.get("annotation") + if ann and first_post_uri: + create_annotation(client, first_post_uri, ann.get("text"), ann.get("motivation", "commenting"), ann.get("quote")) + elif action_type == "reply": target_id = act.get("target_id") if target_id not in mapping: @@ -221,11 +274,18 @@ def cmd_dispatch(args): else: root_ref = parent_ref + first_reply_uri = None for text in posts: reply_ref = models.AppBskyFeedPost.ReplyRef(parent=parent_ref, root=root_ref) post = client.send_post(text=text, reply_to=reply_ref) parent_ref = models.ComAtprotoRepoStrongRef.Main(cid=post.cid, uri=post.uri) print(f" Replied: {post.uri}") + if first_reply_uri is None: + first_reply_uri = post.uri + + ann = act.get("annotation") + if ann and first_reply_uri: + create_annotation(client, first_reply_uri, ann.get("text"), ann.get("motivation", "commenting"), ann.get("quote")) except Exception as e: print(f"Error replying to {target_uri}: {e}")