Skip to content

feat: add sync all feature with collection flags (#69) - #71

Merged
tfriedel merged 3 commits into
mainfrom
feat/sync-all-flags
Jan 18, 2026
Merged

feat: add sync all feature with collection flags (#69)#71
tfriedel merged 3 commits into
mainfrom
feat/sync-all-flags

Conversation

@tfriedel

@tfriedel tfriedel commented Jan 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add tweethoarder sync command (no subcommand) that syncs all collections by default
  • Add inclusion flags: --likes, --bookmarks, --tweets, --reposts, --replies, --feed
  • --feed is excluded from default sync (must be explicitly requested)
  • Add --with-threads and --full options support
  • Display progress bars during sync
  • Remove deprecated sync posts and sync threads subcommands (use flags instead)
  • Update SPEC.md documentation

Test plan

  • All 25 new tests in tests/cli/test_sync_all.py pass
  • tweethoarder sync --help shows all flags
  • tweethoarder sync --likes syncs only likes with progress bar
  • tweethoarder sync syncs all collections except feed

Closes #69

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Unified sync command that runs all collections in one flow (recommended), with flags to select subsets and control resync behavior.
  • Documentation

    • Updated CLI docs and examples to emphasize the unified sync flow and clarify flag semantics and defaults.
  • Refactor

    • Removed legacy subcommands (posts, threads) and consolidated sync handling for simpler UX.
  • Tests

    • Added comprehensive tests for the unified sync path; updated tests to reflect removal of posts/threads commands.

✏️ Tip: You can customize this high-level summary in your review settings.

tfriedel and others added 2 commits January 18, 2026 17:54
Add `tweethoarder sync` command that syncs all collections by default.
Users can specify which collections to sync using inclusion flags:
- `sync` - syncs likes, bookmarks, tweets, reposts, replies (not feed)
- `sync --likes` - syncs only likes
- `sync --likes --bookmarks` - syncs likes and bookmarks
- `sync --feed` - syncs feed (excluded from default)

Also adds common options: --count, --with-threads, --full

Breaking changes:
- Removed `sync posts` subcommand (use `sync --tweets --reposts`)
- Removed `sync threads` subcommand (use `sync --threads` flag)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Pass with_threads flag from callback to sync_all_async
- Add progress parameter to sync_all_async and pass to individual sync functions
- Wrap callback in create_sync_progress() for progress bar display
- Output "Sync complete." message after sync finishes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jan 18, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The changes add a unified "sync all" CLI entrypoint that routes flags to a new async coordinator which sequentially runs per-collection syncs (likes, bookmarks, tweets, reposts, replies, optional feed). The posts and threads subcommands were removed, docs updated, and tests adjusted/added for the new flow.

Changes

Cohort / File(s) Summary
Configuration
\.claude/settings\.local\.json
Added Bash(gh issue list:*) to the allow list.
Documentation
SPEC\.md
Reworked sync docs to emphasize a single tweethoarder sync flow; updated flags (--count, --all, --full, --store-raw defaults) and examples; removed legacy threads example.
Core Implementation
src/tweethoarder/cli/sync\.py
Added sync_callback() CLI entrypoint and sync_all_async() coordinator to run multiple collection syncs sequentially; centralized progress handling; removed posts and threads commands.
Tests — New
tests/cli/test_sync_all\.py
Added comprehensive tests for the new sync callback: flag handling, propagation to sync_all_async, per-collection async call mocking, progress output, and signature checks.
Tests — Modified
tests/cli/test_sync_replies\.py, tests/cli/test_sync\.py
Removed/updated assertions for removed posts command; deleted the test_posts_command_accepts_full_flag test.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as "tweethoarder CLI"
  participant Coordinator as "sync_all_async"
  participant PerType as "sync_*_async"
  participant DB as "Database / Storage"
  participant Progress as "Progress"

  User->>CLI: run `tweethoarder sync [--likes|--bookmarks|...] [--count N] [--full]`
  CLI->>Coordinator: call with include_* flags, count, with_threads, full
  Coordinator->>Progress: create/start progress context
  loop for each enabled collection
    Coordinator->>PerType: call sync_<type>_async(count, with_threads, full, progress)
    PerType->>DB: read/update storage (sync results)
    PerType->>Progress: update progress
  end
  Coordinator->>Progress: finalize/close
  Coordinator->>CLI: return completion
  CLI->>User: print completion message
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a unified sync command with collection flags, directly matching the primary feature introduced in the PR.
Linked Issues check ✅ Passed The PR addresses all core objectives from issue #69: provides easy sync of all collections via single 'sync' command, implements inclusion flags (--likes, --bookmarks, --tweets, --reposts, --replies, --feed), excludes feed by default, and removes deprecated posts/threads commands.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #69 requirements: the new sync callback, sync_all_async coordinator, flag handling, documentation updates, deprecated command removal, and comprehensive tests are all in scope and purposeful.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
src/tweethoarder/cli/sync.py (2)

175-188: Use Google-style docstring for sync_callback.

Docstring at Line 188 doesn’t follow the required Google style (Args/Returns). Please update to match the standard. As per coding guidelines.

♻️ Proposed docstring update
 def sync_callback(
@@
 ) -> None:
-    """Sync Twitter/X data. Run without subcommand to sync all collections."""
+    """Sync Twitter/X data.
+
+    Args:
+        ctx: Typer context.
+        likes: Whether to sync likes.
+        bookmarks: Whether to sync bookmarks.
+        tweets_flag: Whether to sync tweets.
+        reposts: Whether to sync reposts.
+        replies: Whether to sync replies.
+        feed: Whether to sync feed.
+        count: Limit items per collection; None means unlimited.
+        with_threads: Whether to expand threads during sync.
+        full: Whether to force complete resync.
+    """

220-233: Use Google-style docstring for sync_all_async.

Docstring at Line 233 doesn’t follow the required Google style (Args/Returns). Please update to match the standard. As per coding guidelines.

♻️ Proposed docstring update
 async def sync_all_async(
@@
 ) -> None:
-    """Sync all collection types."""
+    """Sync all collection types.
+
+    Args:
+        db_path: Path to the SQLite database file.
+        include_likes: Whether to sync likes.
+        include_bookmarks: Whether to sync bookmarks.
+        include_tweets: Whether to sync tweets.
+        include_reposts: Whether to sync reposts.
+        include_replies: Whether to sync replies.
+        include_feed: Whether to sync feed.
+        count: Maximum items per collection; float("inf") means unlimited.
+        with_threads: Whether to expand threads during sync.
+        full: Whether to force complete resync.
+        progress: Optional Rich progress instance.
+    """
📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 781099a and 36b0ead.

📒 Files selected for processing (2)
  • src/tweethoarder/cli/sync.py
  • tests/cli/test_sync_all.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/cli/test_sync_all.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Port Twitter API patterns from bird reference implementation: headers must include x-twitter-auth-type: OAuth2Session, x-twitter-active-user: yes, x-twitter-client-language: en; feature flags need ~40 entries; query IDs should have fallback lists for resilience
Type hints required for all code
Use Google style for docstrings

**/*.py: Type hints required for all code
Use Google style for docstrings

Files:

  • src/tweethoarder/cli/sync.py
🧠 Learnings (1)
📚 Learning: 2026-01-16T23:14:31.941Z
Learnt from: CR
Repo: tfriedel/tweethoarder PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-16T23:14:31.941Z
Learning: Applies to **/*.py : Port Twitter API patterns from bird reference implementation: headers must include x-twitter-auth-type: OAuth2Session, x-twitter-active-user: yes, x-twitter-client-language: en; feature flags need ~40 entries; query IDs should have fallback lists for resilience

Applied to files:

  • src/tweethoarder/cli/sync.py
🧬 Code graph analysis (1)
src/tweethoarder/cli/sync.py (1)
src/tweethoarder/config.py (1)
  • get_data_dir (15-18)

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/tweethoarder/cli/sync.py`:
- Around line 183-184: The CLI accepts --feed and --threads but never uses them;
update the callback so these flags actually control sync behavior: modify
sync_all_async to accept an include_feed: bool parameter (and an optional
expand_threads/batch_threads: bool parameter) and pass the feed and threads
variables into it from the CLI handler, or if you prefer keeping feed logic
separate call sync_feed_async when feed is True; ensure the CLI callback calls
sync_all_async(..., include_feed=feed, expand_threads=threads) or calls
sync_feed_async() when feed is set and uses the threads flag to enable batch
thread expansion inside sync_all_async or the thread-expansion routine.
- Around line 185-187: The CLI options count and full are never forwarded to the
sync implementation; update the sync_all_async function signature
(sync_all_async(..., count: int | float = float("inf"), full: bool = False)) and
all calls to it so the CLI-provided count and full are passed through, and then
propagate these parameters into where collections are synced (e.g., calls to
sync_collection_async and any per-source sync functions like
sync_twitter_collection_async) replacing hardcoded count=float("inf") and
always-false full usage; ensure sync_collection_async and downstream sync
functions accept and use the count and full arguments.
- Line 199: The sync command is writing to a different DB file than the
subcommands; update the db path used in sync_callback so it matches the other
commands (use get_data_dir() and the same filename "tweethoarder.db"). Locate
the db_path assignment in sync_callback (db_path = get_config_dir() /
"tweets.db") and replace it to build the path with get_data_dir() /
"tweethoarder.db" so all commands (likes(), bookmarks(), etc.) use the same
database file.
🧹 Nitpick comments (4)
src/tweethoarder/cli/sync.py (1)

216-246: Consider returning aggregated results and adding error isolation.

The function has -> None return type but individual sync functions return {"synced_count": N}. Consider:

  1. Returning aggregated counts so callers can report what was synced
  2. Wrapping each sync call in try/except so one failure doesn't prevent syncing other collections
♻️ Example improvement
async def sync_all_async(...) -> dict[str, int]:
    """Sync all collection types."""
    results: dict[str, int] = {}
    
    if include_likes:
        try:
            r = await sync_likes_async(...)
            results["likes"] = r["synced_count"]
        except Exception as e:
            typer.echo(f"Warning: likes sync failed: {e}")
            results["likes"] = 0
    # ... similar for other collections
    
    return results
tests/cli/test_sync_all.py (3)

14-21: Test assertion could be more precise.

The assertion "Missing command" not in clean_output or result.exit_code == 0 is permissive and could pass even when the command fails for unexpected reasons. Consider a stricter check once the implementation is stable.

♻️ Suggested improvement
 def test_sync_without_subcommand_is_handled() -> None:
     """Running 'sync' without a subcommand should work (not show help)."""
-    # For now, just verify it doesn't error with "Missing command"
     result = runner.invoke(app, ["sync"])
-    # Strip ANSI escape codes
     clean_output = re.sub(r"\x1b\[[0-9;]*m", "", result.output)
-    # Should not show "Missing command" error - we'll implement the callback
-    assert "Missing command" not in clean_output or result.exit_code == 0
+    # Should complete without "Missing command" error
+    assert "Missing command" not in clean_output

233-241: Consider verifying call arguments in test_sync_callback_calls_sync_all_async.

The test only verifies assert_called_once() but doesn't check that default parameters were passed correctly. This is partially covered by test_sync_callback_with_no_flags_syncs_all_except_feed but verifying here too would strengthen the test.


63-67: Add tests to verify --count and --full are passed to sync_all_async.

While test_sync_accepts_full_option verifies the flag is accepted, there's no test verifying the value is passed through to sync_all_async. Given the implementation currently ignores these parameters, adding such tests would help catch the regression.

💡 Example test
def test_sync_callback_passes_count_to_sync_all_async() -> None:
    """The sync callback should pass count to sync_all_async."""
    from unittest.mock import AsyncMock, patch

    with patch("tweethoarder.cli.sync.sync_all_async", new_callable=AsyncMock) as mock:
        runner.invoke(app, ["sync", "--likes", "--count", "50"])

        mock.assert_called_once()
        call_kwargs = mock.call_args[1]
        assert call_kwargs.get("count") == 50


def test_sync_callback_passes_full_to_sync_all_async() -> None:
    """The sync callback should pass full flag to sync_all_async."""
    from unittest.mock import AsyncMock, patch

    with patch("tweethoarder.cli.sync.sync_all_async", new_callable=AsyncMock) as mock:
        runner.invoke(app, ["sync", "--likes", "--full"])

        mock.assert_called_once()
        call_kwargs = mock.call_args[1]
        assert call_kwargs.get("full") is True
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07a7ef9 and 781099a.

📒 Files selected for processing (6)
  • .claude/settings.local.json
  • SPEC.md
  • src/tweethoarder/cli/sync.py
  • tests/cli/test_sync.py
  • tests/cli/test_sync_all.py
  • tests/cli/test_sync_replies.py
💤 Files with no reviewable changes (1)
  • tests/cli/test_sync.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Port Twitter API patterns from bird reference implementation: headers must include x-twitter-auth-type: OAuth2Session, x-twitter-active-user: yes, x-twitter-client-language: en; feature flags need ~40 entries; query IDs should have fallback lists for resilience
Type hints required for all code
Use Google style for docstrings

**/*.py: Type hints required for all code
Use Google style for docstrings

Files:

  • tests/cli/test_sync_replies.py
  • tests/cli/test_sync_all.py
  • src/tweethoarder/cli/sync.py
**/*test*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*test*.py: Import shared logic from production code in tests, never duplicate test logic
Create test data factories that generate test data with sensible defaults
Use business-focused test names that describe business value, not technical details

Files:

  • tests/cli/test_sync_replies.py
  • tests/cli/test_sync_all.py
**/test_*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/test_*.py: Import shared logic from production code in tests, never duplicate in tests
Create test data factory functions that generate test data with sensible defaults
Test names should describe business value, not technical details
Test edge cases and errors in addition to happy paths
New features require tests; bug fixes require regression tests

Files:

  • tests/cli/test_sync_replies.py
  • tests/cli/test_sync_all.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: tfriedel/tweethoarder PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-16T23:14:31.941Z
Learning: Applies to **/*.py : Port Twitter API patterns from bird reference implementation: headers must include x-twitter-auth-type: OAuth2Session, x-twitter-active-user: yes, x-twitter-client-language: en; feature flags need ~40 entries; query IDs should have fallback lists for resilience
📚 Learning: 2026-01-16T23:14:31.941Z
Learnt from: CR
Repo: tfriedel/tweethoarder PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-16T23:14:31.941Z
Learning: When working on Twitter API issues, consult the ~/projects/bird directory TypeScript reference implementation for headers (x-twitter-auth-type, x-twitter-active-user, user-agent, origin, referer), feature flag builders, and query ID patterns

Applied to files:

  • .claude/settings.local.json
🧬 Code graph analysis (2)
tests/cli/test_sync_all.py (2)
src/tweethoarder/cli/sync.py (1)
  • sync_all_async (216-246)
src/tweethoarder/storage/database.py (1)
  • init_database (129-142)
src/tweethoarder/cli/sync.py (1)
src/tweethoarder/config.py (1)
  • get_config_dir (9-12)
🔇 Additional comments (5)
.claude/settings.local.json (1)

24-25: LGTM!

The new gh issue list permission is a reasonable addition for development workflows.

SPEC.md (2)

219-227: LGTM - documentation structure is clear.

The documentation clearly explains the unified sync flow and distinguishes it from individual subcommands. Note: ensure the --count and --full flags work as documented once the implementation issues flagged in sync.py are resolved.


275-277: Documentation is correct but implementation doesn't match.

The --count and --full flags are correctly documented here, but as noted in the sync.py review, these parameters are not forwarded to the sync functions. Once the implementation is fixed, this documentation will be accurate.

tests/cli/test_sync_replies.py (1)

159-169: LGTM!

Test correctly validates that the posts command was removed, with a clear docstring indicating the replacement approach (--tweets --reposts). The assertion handles both possible failure modes appropriately.

tests/cli/test_sync_all.py (1)

1-11: LGTM - well-structured test file with comprehensive coverage.

Good use of CliRunner for CLI testing and clear module-level setup. Test names describe business behavior rather than technical details.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/tweethoarder/cli/sync.py Outdated
Comment thread src/tweethoarder/cli/sync.py
Comment thread src/tweethoarder/cli/sync.py Outdated
@tfriedel

Copy link
Copy Markdown
Owner Author

Code review

Found 4 issues:

  1. Database path inconsistency: sync_callback() uses get_config_dir() / "tweets.db" but all individual sync commands use get_data_dir() / "tweethoarder.db". This will cause the unified sync command to write to a different database than individual commands, leading to data duplication and user confusion.

db_path = get_config_dir() / "tweets.db"
with create_sync_progress() as progress:

  1. Missing --full parameter propagation: The sync_callback() accepts a --full flag but never passes it to sync_all_async(). The sync_all_async() function also lacks a full parameter, so the --full flag is silently ignored.

asyncio.run(
sync_all_async(
db_path=db_path,
include_likes=likes,
include_bookmarks=bookmarks,
include_tweets=tweets_flag,
include_reposts=reposts,
include_replies=replies,
with_threads=with_threads,
progress=progress,
)
)

  1. Missing --count parameter propagation: The sync_callback() accepts --count option but never passes it to sync_all_async(). The function hardcodes count=float("inf") for all sync operations, making the --count flag non-functional.

if include_likes:
await sync_likes_async(
db_path=db_path, count=float("inf"), with_threads=with_threads, progress=progress
)
if include_bookmarks:
await sync_bookmarks_async(
db_path=db_path, count=float("inf"), with_threads=with_threads, progress=progress
)
if include_tweets:
await sync_tweets_async(
db_path=db_path, count=float("inf"), with_threads=with_threads, progress=progress
)
if include_reposts:
await sync_reposts_async(
db_path=db_path, count=float("inf"), with_threads=with_threads, progress=progress
)
if include_replies:
await sync_replies_async(
db_path=db_path, count=float("inf"), with_threads=with_threads, progress=progress
)

  1. Missing --feed flag implementation: The sync_callback() accepts a --feed flag but sync_all_async() has no include_feed parameter and never calls sync_feed_async(). Users running tweethoarder sync --feed will see no error but feed will not sync.

async def sync_all_async(
db_path: Path,
include_likes: bool = True,
include_bookmarks: bool = True,
include_tweets: bool = True,
include_reposts: bool = True,
include_replies: bool = True,
with_threads: bool = False,
progress: Progress | None = None,
) -> None:
"""Sync all collection types."""

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Fix database path to use get_data_dir()/tweethoarder.db (not tweets.db)
- Pass full parameter through to individual sync functions
- Pass count parameter through to individual sync functions
- Add include_feed parameter and call sync_feed_async when enabled
- Remove unused --threads flag (use --with-threads instead)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@tfriedel
tfriedel merged commit 23cc8fe into main Jan 18, 2026
2 checks passed
@tfriedel
tfriedel deleted the feat/sync-all-flags branch January 18, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add "sync all" feature

1 participant