+26
-25
ARCH.md
+26
-25
ARCH.md
·········
+6
-5
pyproject.toml
+6
-5
pyproject.toml
·········
+2
-2
src/thicket/cli/commands/__init__.py
+2
-2
src/thicket/cli/commands/__init__.py
···-__all__ = ["add", "duplicates", "index_cmd", "info_cmd", "init", "links_cmd", "list_cmd", "sync"]
+44
-9
src/thicket/cli/commands/add.py
+44
-9
src/thicket/cli/commands/add.py
···-display_name: Optional[str] = typer.Option(None, "--display-name", "-d", help="User display name"),···-user_display_name = display_name or (discovered_metadata.author_name or discovered_metadata.title if discovered_metadata else None)-user_homepage = homepage or (str(discovered_metadata.author_uri or discovered_metadata.link) if discovered_metadata else None)-user_icon = icon or (str(discovered_metadata.logo or discovered_metadata.icon or discovered_metadata.image_url) if discovered_metadata else None)
+7
-3
src/thicket/cli/commands/duplicates.py
+7
-3
src/thicket/cli/commands/duplicates.py
······-def add_duplicate(git_store: GitStore, duplicate_id: Optional[str], canonical_id: Optional[str]) -> None:···
-396
src/thicket/cli/commands/index_cmd.py
-396
src/thicket/cli/commands/index_cmd.py
···-console.print(f"Found {len(user_domains)} users with {sum(len(d) for d in user_domains.values())} total domains")
+105
-112
src/thicket/cli/commands/info_cmd.py
+105
-112
src/thicket/cli/commands/info_cmd.py
··················-console.print(f"[red]Entry with {'URL' if is_url else 'atom ID'} '{identifier}' not found for user '{username}'[/red]")+f"[red]Entry with {'URL' if is_url else 'atom ID'} '{identifier}' not found for user '{username}'[/red]"-console.print(f"[red]Entry with {'URL' if is_url else 'atom ID'} '{identifier}' not found in any user's entries[/red]")+f"[red]Entry with {'URL' if is_url else 'atom ID'} '{identifier}' not found in any user's entries[/red]"-console.print("\n[yellow]No reference index found. Run 'thicket index' to build cross-reference data.[/yellow]")······-target_info = f"{ref.target_username}:{ref.target_entry_id}" if ref.target_username and ref.target_entry_id else "External"-console.print(f"\n[bold]Summary:[/bold] {len(outbound_refs)} outbound, {len(inbound_refs)} inbound references")+f"\n[bold]Summary:[/bold] {len(outbound_links)} outbound links, {len(backlinks)} inbound backlinks"-def _display_entry_info_tsv(entry, username: str, ref_index: Optional[ReferenceIndex], show_content: bool) -> None:-print(f"Title\t{entry.title.replace(chr(9), ' ').replace(chr(10), ' ').replace(chr(13), ' ')}")···-target_info = f"{ref.target_username}:{ref.target_entry_id}" if ref.target_username and ref.target_entry_id else "External"
+5
-6
src/thicket/cli/commands/init.py
+5
-6
src/thicket/cli/commands/init.py
·········
-422
src/thicket/cli/commands/links_cmd.py
-422
src/thicket/cli/commands/links_cmd.py
···-self.link_pattern = re.compile(r'<a[^>]+href="([^"]+)"[^>]*>(.*?)</a>', re.IGNORECASE | re.DOTALL)-console.print(f"Found {len(user_domains)} users with {sum(len(d) for d in user_domains.values())} total domains")-console.print(f"Found {len(link_dict)} total links, {len(filtered_link_dict)} links to registered posts")-table.add_row("Internal", str(len(link_categories["internal"])), "Links to same user's domain")-table.add_row("Cross-references", str(len(filtered_link_dict)), "Links to registered posts only")-for link_pair, count in sorted(user_link_counts.items(), key=lambda x: x[1], reverse=True)[:10]:-for link_pair, count in sorted(user_link_counts.items(), key=lambda x: x[1], reverse=True)[:10]:
+11
-11
src/thicket/cli/commands/list_cmd.py
+11
-11
src/thicket/cli/commands/list_cmd.py
·········-def list_entries(git_store: GitStore, username: Optional[str] = None, limit: Optional[int] = None) -> None:······
+15
-5
src/thicket/cli/commands/sync.py
+15
-5
src/thicket/cli/commands/sync.py
···-for feed_url in track(user_metadata.feeds, description=f"Syncing {user_metadata.username}'s feeds"):···-print_info(f"User {user_metadata.username}: {user_new_entries} new, {user_updated_entries} updated")···-print_info(f"Dry run complete: would sync {total_new_entries} new entries, {total_updated_entries} updated")+f"Dry run complete: would sync {total_new_entries} new entries, {total_updated_entries} updated"-print_success(f"Sync complete: {total_new_entries} new entries, {total_updated_entries} updated")-async def sync_feed(git_store: GitStore, username: str, feed_url, dry_run: bool) -> tuple[int, int]:
+1
-1
src/thicket/cli/main.py
+1
-1
src/thicket/cli/main.py
+32
-20
src/thicket/cli/utils.py
+32
-20
src/thicket/cli/utils.py
························-print(f"{user.username}\t{user.display_name or ''}\t{user.email or ''}\t{user.homepage or ''}\t{feeds_str}")+f"{user.username}\t{user.display_name or ''}\t{user.email or ''}\t{user.homepage or ''}\t{feeds_str}"···-print(f"{user.username}\t{user.display_name or ''}\t{user.email or ''}\t{user.homepage or ''}\t{feeds_str}")+f"{user.username}\t{user.display_name or ''}\t{user.email or ''}\t{user.homepage or ''}\t{feeds_str}"······
+84
-55
src/thicket/core/feed_parser.py
+84
-55
src/thicket/core/feed_parser.py
······-def parse_feed(self, content: str, source_url: Optional[HttpUrl] = None) -> tuple[FeedMetadata, list[AtomEntry]]:·········-def _normalize_entry(self, entry: feedparser.FeedParserDict, source_url: Optional[HttpUrl] = None) -> AtomEntry:···············
+45
-18
src/thicket/core/git_store.py
+45
-18
src/thicket/core/git_store.py
·································
-301
src/thicket/core/reference_parser.py
-301
src/thicket/core/reference_parser.py
···-"""Resolve target_entry_id for references that have target_username but no target_entry_id."""
+24
src/thicket/models/config.py
+24
src/thicket/models/config.py
···+"""Add a feed to an existing user. Returns True if added, False if user not found or feed already exists."""
+2
-2
src/thicket/models/feed.py
+2
-2
src/thicket/models/feed.py
······
+1
-3
src/thicket/models/user.py
+1
-3
src/thicket/models/user.py
···
+9
-1
uv.lock
+9
-1
uv.lock
·········