Sync your Twitter/X bookmarks to Obsidian with AI-powered categorization.
- Fetches your Twitter bookmarks using the bird CLI
- Diffs against previously processed bookmarks (only processes new ones)
- Categorizes each tweet using Google Gemini AI
- Saves as markdown files in your Obsidian vault, organized into smart folders
- π Automatic folder categorization using Gemini AI
- π Incremental sync (only processes new bookmarks)
- π Rich markdown files with full metadata (likes, retweets, author, etc.)
- π·οΈ YAML frontmatter for Obsidian Dataview queries
- β‘ Pagination support for efficient API usage
- π Direct links back to original tweets
- π§Ή Vault Organizer - Reorganize existing notes without re-fetching
- π¨ Beautiful CLI - Interactive prompts, spinners, progress bars & colors
node --version # Should be v18 or higherSynapse includes the bird CLI automatically. Bird needs access to your Twitter session. The easiest way:
- Log into x.com in Safari/Chrome/Firefox
- Verify it works:
bird whoami
For more info on how to setup bird, visit here.
Get an API key from Google AI Studio
# Install globally
npm install -g @xorforce/synapse
# Now you can use the CLI commands anywhere
synapse --help
synapse-organize --help
synapse-likes --helpbrew tap xorforce/tap
brew install synapse# Clone the repo
git clone https://github.com/xorforce/synapse
cd synapse
# Install dependencies
npm install
# Run with npm scripts
npm run fetch
npm run organizeEdit config.js to customize:
export default {
// Path to your Obsidian vault
vaultPath: '<your-vault-path-here>',
// Folder inside vault for bookmarks
bookmarksFolder: '<your-twitter-bookmarks-folder-name here>',
// Fallback folder for uncategorizable tweets
uncategorizedFolder: '<folder-name-for-unactegorized-entries>',
// State file to track processed bookmarks
stateFile: '<path-to-state-file here>',
// Max pages to fetch (0 = unlimited, each page ~20 bookmarks)
// For daily sync, 2-3 pages is usually enough
maxPages: 2, // max page count
// Gemini model for categorization
geminiModel: 'gemini-2.0-flash',
// Batch size for Gemini API (tweets per request, reduces API calls)
batchSize: 10,
// Max tweets to process per run (0 = unlimited)
maxTweetsPerRun: 0,
// Include visible metadata table in markdown (frontmatter always included)
includeMetadataTable: true,
};
β οΈ Important: Always test synapse on an empty or dummy vault first before pointing it at your actual Obsidian vault. This ensures you understand how the tool organizes files and lets you verify everything works as expected without risking your real notes.
export GEMINI_API_KEY="your-api-key-here"npm run fetchThis fetches recent bookmarks (limited by maxPages in config, default ~40 bookmarks).
npm run fetch:fullThis fetches ALL bookmarks, ignoring the maxPages limit. Use this for:
- First-time setup to import your entire bookmark history
- Catching up after a long break
- Re-syncing if you think you missed some
npm run dry-run # Preview daily sync
npm run dry-run:full # Preview full sync| Flag | Description |
|---|---|
--dry-run |
Preview mode - no files written, no state saved |
--full |
Fetch ALL bookmarks (ignore maxPages limit) |
You can combine flags:
node src/index.js --dry-run --fullAlready have a vault with notes that need better organization? Use the vault organizer with its beautiful interactive CLI:
npm run organize # Interactive mode
npm run organize:dry-run # Preview without moving files
npm run organize:bookmarks # Organize bookmarks folder
npm run organize:likes # Organize likes folderThe organizer features a polished, user-friendly interface:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π§ SYNAPSE β
β Vault Organizer β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Select folder to organize
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β― π Twitter Bookmarks (bookmarks)
β€οΈ Twitter Likes (likes)
π Custom folder...
β Scanning vault for notes...
β Found 1911 markdown files
β Parsed 1911 notes (1.3 MB)
ββββββββββββββββββββββββββββββ 45% (86/192)
Features:
- π¨ Colorful output with intuitive icons
- β¨οΈ Arrow key navigation for folder selection
- π Animated spinners during processing
- π Progress bars with batch tracking
- π Category distribution chart at completion
The organizer will:
- Let you select which folder to organize (arrow keys to navigate)
- Ask for maximum folder depth (1 = flat categories, 2+ = nested)
- Prompt for backup before making changes
- Re-categorize all existing notes using Gemini AI
- Move files to their new category folders
- Clean up empty folders
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β Notes processed: 1911 β
β Files moved: 1850 β
β Categories used: 47 β
β New folders created: 12 β
ββββββββββββββββββββββββββββββββββββββββββββββββββ
Category Distribution
AI Development Tools ββββββββββββββββββββ 342
AI Agent Development ββββββββββββββββββββ 298
Swift Programming ββββββββββββββββββββ 156
Startup Advice ββββββββββββββββββββ 124
| Flag | Description |
|---|---|
--dry-run |
Preview mode - no files moved |
--no-backup |
Skip the backup prompt |
--target=FolderName |
Specify target folder directly |
--depth=N |
Set max folder depth (1-5) |
Example:
# Reorganize with max depth 2, no backup prompt
node src/organize.js --target="Twitter Bookmarks" --depth=2 --no-backupObsidian Vault/
βββ Twitter Bookmarks/
βββ AI Development/
β βββ 2026-01-05 @openai - Announcing GPT-5 with reasoning.md
β βββ 2026-01-04 @anthropic - Claude now supports.md
βββ Swift Packages/
β βββ 2026-01-05 @johnsundell - New Swift package alert.md
β βββ 2026-01-05 @pointfreeco - Composable Architecture 2.0.md
βββ Developer Resources/
β βββ 2026-01-03 @github - The only resource you need.md
βββ Uncategorized/
βββ 2026-01-02 @random - Could not categorize this.md
Each bookmark is saved as a markdown file with YAML frontmatter (for Obsidian Dataview) and optionally a visible metadata table.
Set includeMetadataTable: false in config for cleaner output (frontmatter is always kept).
Full format (default):
---
id: "1234567890"
author: "@username"
author_name: "Display Name"
created_at: "Mon Jan 05 12:00:00 +0000 2026"
bookmarked_at: "2026-01-06T10:30:00.000Z"
likes: 1234
retweets: 567
replies: 89
url: "https://twitter.com/username/status/1234567890"
conversation_id: "1234567890"
---
# Tweet by @username
Full tweet text here with links and hashtags preserved.
---
## Metadata
| Field | Value |
|-------|-------|
| **Author** | [@username](https://twitter.com/username) (Display Name) |
| **Posted** | 1/5/2026, 12:00:00 PM |
| **Likes** | 1234 |
| **Retweets** | 567 |
| **Replies** | 89 |
| **Link** | [View on Twitter](https://twitter.com/username/status/1234567890) |Minimal format (includeMetadataTable: false):
---
id: "1234567890"
author: "@username"
... (same frontmatter)
---
# Tweet by @username
Full tweet text here with links and hashtags preserved.
[View on Twitter](https://twitter.com/username/status/1234567890)crontab -eAdd (runs daily at 9 AM):
0 9 * * * cd /Users/bhagat/Desktop/bhagat/synapse && GEMINI_API_KEY="your-key" /usr/local/bin/node src/index.js >> /tmp/synapse.log 2>&1Verify:
crontab -lCreate ~/Library/LaunchAgents/com.synapse.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.synapse</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/node</string>
<string>/Users/bhagat/Desktop/bhagat/synapse/src/index.js</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>GEMINI_API_KEY</key>
<string>your-api-key-here</string>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>WorkingDirectory</key>
<string>/Users/bhagat/Desktop/bhagat/synapse</string>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/tmp/synapse.log</string>
<key>StandardErrorPath</key>
<string>/tmp/synapse-error.log</string>
</dict>
</plist>Load it:
launchctl load ~/Library/LaunchAgents/com.synapse.plist- Processed bookmark IDs are stored in
data/state.json - Only new bookmarks (not in state) are processed on each run
- Delete
data/state.jsonto reprocess everything
Tweets are categorized in batches (default: 10 per API call) for efficiency:
- 40 tweets = 4 API calls instead of 40
- ~80% faster than individual requests
- Reduces rate limiting issues
Gemini analyzes each batch and either:
- Reuses an existing category folder if it fits
- Creates a new category if none fit well
Categories are concise (2-4 words) and topic-focused like:
- "AI Development"
- "Swift Packages"
- "Startup Advice"
- "Web Development"
- "Career Tips"
- Uses
bird bookmarks --all --max-pages Nfor efficient fetching - Default: 2 pages (~40 bookmarks) - good for daily sync
- Use
npm run fetch:fullor--fullflag to fetch everything - Or set
maxPages: 0in config for always unlimited
synapse/
βββ config.js # Configuration options
βββ package.json # Dependencies
βββ README.md # This file
βββ data/
β βββ state.json # Tracks processed bookmarks
βββ src/
βββ index.js # Main entry point (bookmark sync)
βββ likes.js # Likes sync
βββ organize.js # Vault reorganizer (no fetch)
βββ bird.js # Twitter API via bird CLI
βββ gemini.js # AI categorization
βββ obsidian.js # Markdown file generation
βββ state.js # State management
Bird is bundled with synapse. If you installed synapse globally, bird should be available. Try reinstalling:
npm install -g @xorforce/synapse
which birdBird can't find Twitter cookies. Either:
- Log into x.com in Safari and grant Full Disk Access to Terminal
- Set
AUTH_TOKENandCT0environment variables manually
Set the API key before running:
export GEMINI_API_KEY="your-key"Gemini has rate limits. The script handles this gracefully by:
- Using "Uncategorized" folder for failed categorizations
- Adding small delays between API calls
Delete the state file to start fresh:
rm -rf data/
npm run fetchWith the YAML frontmatter, you can query your bookmarks:
TABLE author, likes, retweets
FROM "Twitter Bookmarks"
WHERE likes > 100
SORT likes DESC
Enable backlinks to see connections between tweets in the same conversation.
MIT
- bird CLI - Twitter API access (bundled)
- Google Gemini - AI categorization