AI agent skills related to using social media
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

More of Claude's improvements

+448
+28
atproto/authenticate.sh
··· 1 + #!/usr/bin/env bash 2 + # Authenticate with an ATproto PDS and print an access JWT to stdout. 3 + # 4 + # Usage: authenticate.sh 5 + # 6 + # Requires: 7 + # - AGENT_ATPROTO_APP_PASSWORD env var pointing to app password file 8 + # - curl, python3 9 + # 10 + # Other scripts can use this via: JWT=$(bash authenticate.sh) 11 + 12 + set -euo pipefail 13 + 14 + PDS="https://agrocybe.us-west.host.bsky.network" 15 + DID="did:plc:36zqqogbeg4i3nsqfkq64idw" 16 + 17 + APP_PASS=$(cat "$AGENT_ATPROTO_APP_PASSWORD" | tr -d '\n') 18 + ACCESS_JWT=$(curl -s -X POST "${PDS}/xrpc/com.atproto.server.createSession" \ 19 + -H "Content-Type: application/json" \ 20 + -d "{\"identifier\": \"${DID}\", \"password\": \"${APP_PASS}\"}" \ 21 + | python3 -c "import sys,json; print(json.load(sys.stdin).get('accessJwt',''))") 22 + 23 + if [[ -z "$ACCESS_JWT" ]]; then 24 + echo "Error: authentication failed" >&2 25 + exit 1 26 + fi 27 + 28 + echo "$ACCESS_JWT"
+73
bluesky/create-post.sh
··· 1 + #!/usr/bin/env bash 2 + # Create a Bluesky post. 3 + # 4 + # Usage: create-post.sh <text> [facets-json] 5 + # 6 + # Arguments: 7 + # text - Post text (max 300 graphemes) 8 + # facets-json - Optional JSON array of facets for mentions/links 9 + # 10 + # Requires: 11 + # - AGENT_ATPROTO_APP_PASSWORD env var pointing to app password file 12 + # - curl, python3 13 + # 14 + # Examples: 15 + # create-post.sh "Hello world" 16 + # create-post.sh "Hey @notjack.space" '[{"index":{"byteStart":4,"byteEnd":20},"features":[{"$type":"app.bsky.richtext.facet#mention","did":"did:plc:7oyzfpde4xg23u447zkp3b2i"}]}]' 17 + 18 + set -euo pipefail 19 + 20 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 21 + PDS="https://agrocybe.us-west.host.bsky.network" 22 + DID="did:plc:36zqqogbeg4i3nsqfkq64idw" 23 + 24 + if [[ $# -lt 1 ]]; then 25 + echo "Usage: $0 <text> [facets-json]" >&2 26 + exit 1 27 + fi 28 + 29 + POST_TEXT="$1" 30 + FACETS_JSON="${2:-}" 31 + 32 + # Authenticate 33 + ACCESS_JWT=$(bash "${SCRIPT_DIR}/../atproto/authenticate.sh") 34 + 35 + # Build and send post 36 + TS=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) 37 + RESULT=$(python3 -c " 38 + import json, sys 39 + 40 + text = sys.argv[1] 41 + ts = sys.argv[2] 42 + facets_arg = sys.argv[3] if len(sys.argv) > 3 else '' 43 + 44 + record = { 45 + '\$type': 'app.bsky.feed.post', 46 + 'text': text, 47 + 'createdAt': ts 48 + } 49 + 50 + if facets_arg: 51 + record['facets'] = json.loads(facets_arg) 52 + 53 + payload = { 54 + 'repo': '${DID}', 55 + 'collection': 'app.bsky.feed.post', 56 + 'record': record 57 + } 58 + print(json.dumps(payload)) 59 + " "$POST_TEXT" "$TS" "$FACETS_JSON" | curl -s -X POST "${PDS}/xrpc/com.atproto.repo.createRecord" \ 60 + -H "Content-Type: application/json" \ 61 + -H "Authorization: Bearer ${ACCESS_JWT}" \ 62 + -d @-) 63 + 64 + echo "$RESULT" | python3 -c " 65 + import sys, json 66 + data = json.load(sys.stdin) 67 + uri = data.get('uri', '') 68 + if uri: 69 + print(f'Posted: {uri}') 70 + else: 71 + print(f'Error: {json.dumps(data)}') 72 + sys.exit(1) 73 + "
+89
bluesky/like-post.sh
··· 1 + #!/usr/bin/env bash 2 + # Like a Bluesky post, but only if not already liked. 3 + # 4 + # Usage: like-post.sh <post-uri> 5 + # 6 + # Requires: 7 + # - AGENT_ATPROTO_APP_PASSWORD env var pointing to app password file 8 + # - curl, python3 9 + # 10 + # Authenticates first, then checks the viewer.like field (which requires auth 11 + # to populate), and only creates a like record if it isn't already liked. 12 + 13 + set -euo pipefail 14 + 15 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 16 + PDS="https://agrocybe.us-west.host.bsky.network" 17 + DID="did:plc:36zqqogbeg4i3nsqfkq64idw" 18 + 19 + if [[ $# -ne 1 ]]; then 20 + echo "Usage: $0 <post-uri>" >&2 21 + exit 1 22 + fi 23 + 24 + POST_URI="$1" 25 + 26 + # Authenticate first (needed for viewer.like to populate) 27 + ACCESS_JWT=$(bash "${SCRIPT_DIR}/../atproto/authenticate.sh") 28 + 29 + # Fetch post with auth to get viewer.like and CID 30 + POST_DATA=$(curl -s "${PDS}/xrpc/app.bsky.feed.getPosts?uris=${POST_URI}" \ 31 + -H "Authorization: Bearer ${ACCESS_JWT}") 32 + 33 + VIEWER_LIKE=$(echo "$POST_DATA" | python3 -c " 34 + import sys, json 35 + data = json.load(sys.stdin) 36 + posts = data.get('posts', []) 37 + if posts: 38 + print(posts[0].get('viewer', {}).get('like', '')) 39 + ") 40 + 41 + if [[ -n "$VIEWER_LIKE" ]]; then 42 + echo "Already liked: $VIEWER_LIKE" 43 + exit 0 44 + fi 45 + 46 + POST_CID=$(echo "$POST_DATA" | python3 -c " 47 + import sys, json 48 + data = json.load(sys.stdin) 49 + posts = data.get('posts', []) 50 + if posts: 51 + print(posts[0].get('cid', '')) 52 + ") 53 + 54 + if [[ -z "$POST_CID" ]]; then 55 + echo "Error: could not find post $POST_URI" >&2 56 + exit 1 57 + fi 58 + 59 + # Create like 60 + TS=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) 61 + RESULT=$(python3 -c " 62 + import json 63 + print(json.dumps({ 64 + 'repo': '${DID}', 65 + 'collection': 'app.bsky.feed.like', 66 + 'record': { 67 + '\$type': 'app.bsky.feed.like', 68 + 'subject': { 69 + 'uri': '${POST_URI}', 70 + 'cid': '${POST_CID}' 71 + }, 72 + 'createdAt': '${TS}' 73 + } 74 + })) 75 + " | curl -s -X POST "${PDS}/xrpc/com.atproto.repo.createRecord" \ 76 + -H "Content-Type: application/json" \ 77 + -H "Authorization: Bearer ${ACCESS_JWT}" \ 78 + -d @-) 79 + 80 + echo "$RESULT" | python3 -c " 81 + import sys, json 82 + data = json.load(sys.stdin) 83 + uri = data.get('uri', '') 84 + if uri: 85 + print(f'Liked: {uri}') 86 + else: 87 + print(f'Error: {json.dumps(data)}') 88 + sys.exit(1) 89 + "
+50
bluesky/read-dms.sh
··· 1 + #!/usr/bin/env bash 2 + # Read recent DMs from a Bluesky conversation. 3 + # 4 + # Usage: read-dms.sh <convo-id> [limit] 5 + # 6 + # Arguments: 7 + # convo-id - Conversation ID 8 + # limit - Number of messages to fetch (default: 20) 9 + # 10 + # Requires: 11 + # - AGENT_ATPROTO_APP_PASSWORD env var pointing to app password file 12 + # - curl, python3 13 + # 14 + # Prints messages oldest-first: timestamp, sender DID, text. 15 + # Known conversation IDs: 16 + # Jacqueline: 3mf7dxxpa5c2j 17 + 18 + set -euo pipefail 19 + 20 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 21 + PDS="https://agrocybe.us-west.host.bsky.network" 22 + MY_DID="did:plc:36zqqogbeg4i3nsqfkq64idw" 23 + 24 + if [[ $# -lt 1 ]]; then 25 + echo "Usage: $0 <convo-id> [limit]" >&2 26 + exit 1 27 + fi 28 + 29 + CONVO_ID="$1" 30 + LIMIT="${2:-20}" 31 + 32 + # Authenticate 33 + ACCESS_JWT=$(bash "${SCRIPT_DIR}/../atproto/authenticate.sh") 34 + 35 + curl -s "${PDS}/xrpc/chat.bsky.convo.getMessages?convoId=${CONVO_ID}&limit=${LIMIT}" \ 36 + -H "Authorization: Bearer ${ACCESS_JWT}" \ 37 + -H "Atproto-Proxy: did:web:api.bsky.chat#bsky_chat" \ 38 + | python3 -c " 39 + import json, sys 40 + 41 + my_did = '${MY_DID}' 42 + data = json.load(sys.stdin) 43 + for m in reversed(data.get('messages', [])): 44 + sender = m.get('sender', {}).get('did', '?') 45 + who = 'Me' if sender == my_did else sender.split(':')[-1][:12] 46 + text = m.get('text', '') 47 + ts = m.get('sentAt', '') 48 + print(f'{ts} | {who}: {text}') 49 + print() 50 + "
+43
bluesky/read-notifications.sh
··· 1 + #!/usr/bin/env bash 2 + # Read recent Bluesky notifications. 3 + # 4 + # Usage: read-notifications.sh [limit] 5 + # 6 + # Arguments: 7 + # limit - Number of notifications to fetch (default: 30) 8 + # 9 + # Requires: 10 + # - AGENT_ATPROTO_APP_PASSWORD env var pointing to app password file 11 + # - curl, python3 12 + # 13 + # Prints notifications in a readable format: timestamp, type, author, text. 14 + 15 + set -euo pipefail 16 + 17 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 18 + PDS="https://agrocybe.us-west.host.bsky.network" 19 + 20 + LIMIT="${1:-30}" 21 + 22 + # Authenticate 23 + ACCESS_JWT=$(bash "${SCRIPT_DIR}/../atproto/authenticate.sh") 24 + 25 + curl -s "${PDS}/xrpc/app.bsky.notification.listNotifications?limit=${LIMIT}" \ 26 + -H "Authorization: Bearer ${ACCESS_JWT}" \ 27 + | python3 -c " 28 + import json, sys 29 + data = json.load(sys.stdin) 30 + for n in data.get('notifications', []): 31 + author = n.get('author', {}).get('handle', '?') 32 + reason = n.get('reason', '?') 33 + ts = n.get('indexedAt', '?') 34 + text = n.get('record', {}).get('text', '')[:200] 35 + uri = n.get('uri', '') 36 + is_read = n.get('isRead', False) 37 + read_marker = '' if is_read else ' [NEW]' 38 + print(f'{ts} | {reason} | @{author}{read_marker}') 39 + if text: 40 + print(f' {text}') 41 + print(f' {uri}') 42 + print() 43 + "
+116
bluesky/reply-to-post.sh
··· 1 + #!/usr/bin/env bash 2 + # Reply to a Bluesky post. 3 + # 4 + # Usage: reply-to-post.sh <parent-uri> <text> [facets-json] 5 + # 6 + # Arguments: 7 + # parent-uri - at:// URI of the post to reply to 8 + # text - Reply text (max 300 graphemes) 9 + # facets-json - Optional JSON array of facets for mentions/links 10 + # 11 + # The script resolves the parent post's CID and root automatically. 12 + # 13 + # Requires: 14 + # - AGENT_ATPROTO_APP_PASSWORD env var pointing to app password file 15 + # - curl, python3 16 + # 17 + # Example: 18 + # reply-to-post.sh "at://did:plc:.../app.bsky.feed.post/..." "Great point!" 19 + 20 + set -euo pipefail 21 + 22 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 23 + PDS="https://agrocybe.us-west.host.bsky.network" 24 + DID="did:plc:36zqqogbeg4i3nsqfkq64idw" 25 + 26 + if [[ $# -lt 2 ]]; then 27 + echo "Usage: $0 <parent-uri> <text> [facets-json]" >&2 28 + exit 1 29 + fi 30 + 31 + PARENT_URI="$1" 32 + POST_TEXT="$2" 33 + FACETS_JSON="${3:-}" 34 + 35 + # Authenticate 36 + ACCESS_JWT=$(bash "${SCRIPT_DIR}/../atproto/authenticate.sh") 37 + 38 + # Fetch parent post to get CID and thread root 39 + PARENT_DATA=$(curl -s "https://public.api.bsky.app/xrpc/app.bsky.feed.getPosts?uris=${PARENT_URI}") 40 + 41 + THREAD_INFO=$(echo "$PARENT_DATA" | python3 -c " 42 + import sys, json 43 + data = json.load(sys.stdin) 44 + posts = data.get('posts', []) 45 + if not posts: 46 + print('ERROR') 47 + sys.exit(0) 48 + post = posts[0] 49 + parent_cid = post['cid'] 50 + parent_uri = post['uri'] 51 + # If the parent is itself a reply, use its root; otherwise the parent IS the root 52 + record = post.get('record', {}) 53 + reply = record.get('reply', {}) 54 + if reply: 55 + root_uri = reply['root']['uri'] 56 + root_cid = reply['root']['cid'] 57 + else: 58 + root_uri = parent_uri 59 + root_cid = parent_cid 60 + print(json.dumps({ 61 + 'parent_uri': parent_uri, 62 + 'parent_cid': parent_cid, 63 + 'root_uri': root_uri, 64 + 'root_cid': root_cid 65 + })) 66 + ") 67 + 68 + if [[ "$THREAD_INFO" == "ERROR" ]]; then 69 + echo "Error: could not find post $PARENT_URI" >&2 70 + exit 1 71 + fi 72 + 73 + # Build and send reply 74 + TS=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) 75 + RESULT=$(python3 -c " 76 + import json, sys 77 + 78 + text = sys.argv[1] 79 + ts = sys.argv[2] 80 + thread = json.loads(sys.argv[3]) 81 + facets_arg = sys.argv[4] if len(sys.argv) > 4 else '' 82 + 83 + record = { 84 + '\$type': 'app.bsky.feed.post', 85 + 'text': text, 86 + 'createdAt': ts, 87 + 'reply': { 88 + 'root': {'uri': thread['root_uri'], 'cid': thread['root_cid']}, 89 + 'parent': {'uri': thread['parent_uri'], 'cid': thread['parent_cid']} 90 + } 91 + } 92 + 93 + if facets_arg: 94 + record['facets'] = json.loads(facets_arg) 95 + 96 + payload = { 97 + 'repo': '${DID}', 98 + 'collection': 'app.bsky.feed.post', 99 + 'record': record 100 + } 101 + print(json.dumps(payload)) 102 + " "$POST_TEXT" "$TS" "$THREAD_INFO" "$FACETS_JSON" | curl -s -X POST "${PDS}/xrpc/com.atproto.repo.createRecord" \ 103 + -H "Content-Type: application/json" \ 104 + -H "Authorization: Bearer ${ACCESS_JWT}" \ 105 + -d @-) 106 + 107 + echo "$RESULT" | python3 -c " 108 + import sys, json 109 + data = json.load(sys.stdin) 110 + uri = data.get('uri', '') 111 + if uri: 112 + print(f'Replied: {uri}') 113 + else: 114 + print(f'Error: {json.dumps(data)}') 115 + sys.exit(1) 116 + "
+49
bluesky/send-dm.sh
··· 1 + #!/usr/bin/env bash 2 + # Send a DM to a Bluesky conversation. 3 + # 4 + # Usage: send-dm.sh <convo-id> <message-text> 5 + # 6 + # Requires: 7 + # - AGENT_ATPROTO_APP_PASSWORD env var pointing to app password file 8 + # - curl, python3 9 + # 10 + # Uses python3 json.dumps for proper escaping of message text. 11 + 12 + set -euo pipefail 13 + 14 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 15 + PDS="https://agrocybe.us-west.host.bsky.network" 16 + DID="did:plc:36zqqogbeg4i3nsqfkq64idw" 17 + 18 + if [[ $# -ne 2 ]]; then 19 + echo "Usage: $0 <convo-id> <message-text>" >&2 20 + exit 1 21 + fi 22 + 23 + CONVO_ID="$1" 24 + MESSAGE_TEXT="$2" 25 + 26 + # Authenticate 27 + ACCESS_JWT=$(bash "${SCRIPT_DIR}/../atproto/authenticate.sh") 28 + 29 + # Send message using python3 for proper JSON escaping 30 + RESULT=$(python3 -c " 31 + import json, sys 32 + payload = {'convoId': sys.argv[1], 'message': {'text': sys.argv[2]}} 33 + print(json.dumps(payload)) 34 + " "$CONVO_ID" "$MESSAGE_TEXT" | curl -s -X POST "${PDS}/xrpc/chat.bsky.convo.sendMessage" \ 35 + -H "Content-Type: application/json" \ 36 + -H "Authorization: Bearer ${ACCESS_JWT}" \ 37 + -H "Atproto-Proxy: did:web:api.bsky.chat#bsky_chat" \ 38 + -d @-) 39 + 40 + echo "$RESULT" | python3 -c " 41 + import sys, json 42 + data = json.load(sys.stdin) 43 + msg_id = data.get('id', '') 44 + if msg_id: 45 + print(f'Sent: {msg_id}') 46 + else: 47 + print(f'Error: {json.dumps(data)}') 48 + sys.exit(1) 49 + "