repr-cli 0.2.17__py3-none-any.whl → 0.2.18__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
repr/__init__.py CHANGED
@@ -10,6 +10,6 @@ try:
10
10
  __version__ = version("repr-cli")
11
11
  except Exception:
12
12
  # Fallback for PyInstaller builds where metadata isn't available
13
- __version__ = "0.2.17"
13
+ __version__ = "0.2.18"
14
14
  __author__ = "Repr"
15
15
  __email__ = "hello@repr.dev"
repr/cli.py CHANGED
@@ -1981,6 +1981,165 @@ def push(
1981
1981
  print_success(f"Pushed {pushed}/{len(stories_payload)} stories")
1982
1982
 
1983
1983
 
1984
+ @app.command()
1985
+ def publish(
1986
+ story_id: Optional[str] = typer.Argument(None, help="Story ID to publish (omit for batch publish)"),
1987
+ all_stories: bool = typer.Option(False, "--all", "-a", help="Republish all stories, including already-pushed"),
1988
+ repo: Optional[str] = typer.Option(None, "--repo", "-r", help="Publish stories from specific repository"),
1989
+ visibility: Optional[str] = typer.Option(None, "--visibility", "-v", help="Override visibility: public, private, connections"),
1990
+ dry_run: bool = typer.Option(False, "--dry-run", help="Preview what would be published"),
1991
+ ):
1992
+ """
1993
+ Publish stories to repr.dev.
1994
+
1995
+ Publish = Upload to cloud with current visibility (or override).
1996
+
1997
+ Supports three scopes:
1998
+ - Global: Publish all unpushed stories (or all with --all)
1999
+ - Repo: Publish stories from a specific repository (--repo)
2000
+ - Story: Publish a single story by ID
2001
+
2002
+ Examples:
2003
+ repr publish # Unpushed stories only
2004
+ repr publish --all # All stories (re-publish)
2005
+ repr publish --repo myproject # Stories from specific repo
2006
+ repr publish 01HXYZ123 # Single story by ID
2007
+ repr publish --visibility public # Publish as public
2008
+ repr publish --repo myproject --dry-run # Preview
2009
+ """
2010
+ from .privacy import check_cloud_permission, log_cloud_operation
2011
+ from .api import push_stories_batch, APIError, AuthError
2012
+ from .db import get_db
2013
+
2014
+ # Check authentication
2015
+ if not get_access_token():
2016
+ print_error("Not authenticated")
2017
+ print_info("Run 'repr login' to authenticate")
2018
+ raise typer.Exit(1)
2019
+
2020
+ allowed, reason = check_cloud_permission("push")
2021
+ if not allowed:
2022
+ print_error("Publishing blocked")
2023
+ print_info(reason)
2024
+ raise typer.Exit(1)
2025
+
2026
+ # Validate visibility if provided
2027
+ valid_visibilities = {"public", "private", "connections"}
2028
+ if visibility and visibility not in valid_visibilities:
2029
+ print_error(f"Invalid visibility: {visibility}")
2030
+ print_info(f"Valid options: {', '.join(valid_visibilities)}")
2031
+ raise typer.Exit(1)
2032
+
2033
+ db = get_db()
2034
+
2035
+ # Determine which stories to publish based on scope
2036
+ if story_id:
2037
+ # Single story mode
2038
+ story = db.get_story(story_id)
2039
+ if not story:
2040
+ print_error(f"Story not found: {story_id}")
2041
+ raise typer.Exit(1)
2042
+ all_stories_list = [story]
2043
+ scope_desc = f"story {story_id[:8]}..."
2044
+ elif repo:
2045
+ # Repo mode - get stories from specific repo
2046
+ projects = db.list_projects()
2047
+ project_ids = [p["id"] for p in projects if p["name"] == repo]
2048
+ if not project_ids:
2049
+ print_error(f"Repository not found: {repo}")
2050
+ print_info("Use 'repr repos list' to see tracked repositories")
2051
+ raise typer.Exit(1)
2052
+ all_stories_list = [s for s in db.list_stories(limit=10000) if s.project_id in project_ids]
2053
+ scope_desc = f"repo '{repo}'"
2054
+ else:
2055
+ # Global mode
2056
+ all_stories_list = db.list_stories(limit=10000)
2057
+ scope_desc = "all repositories"
2058
+
2059
+ if not all_stories_list:
2060
+ print_info(f"No stories found for {scope_desc}")
2061
+ raise typer.Exit()
2062
+
2063
+ # Filter to unpushed unless --all is specified
2064
+ # Note: For now we don't track pushed_at in the schema, so --all just means republish everything
2065
+ # In future, we could filter by pushed_at column
2066
+ stories_to_publish = all_stories_list
2067
+
2068
+ if not stories_to_publish:
2069
+ print_info("No stories to publish")
2070
+ raise typer.Exit()
2071
+
2072
+ console.print(f"Found [bold]{len(stories_to_publish)}[/] stories from {scope_desc}")
2073
+ console.print()
2074
+
2075
+ if dry_run:
2076
+ console.print("[dim]Preview (dry-run):[/]")
2077
+ for story in stories_to_publish[:20]: # Limit preview to 20
2078
+ vis = visibility or story.visibility or "private"
2079
+ console.print(f" • [{vis}] {story.title[:55]}...")
2080
+ if len(stories_to_publish) > 20:
2081
+ console.print(f" ... and {len(stories_to_publish) - 20} more")
2082
+ console.print()
2083
+ console.print("Run without --dry-run to publish")
2084
+ raise typer.Exit()
2085
+
2086
+ # Build batch payload
2087
+ vis_label = visibility or "default"
2088
+ console.print(f"Publishing with visibility: [bold]{vis_label}[/]...")
2089
+
2090
+ stories_payload = []
2091
+ for story in stories_to_publish:
2092
+ payload = story.model_dump(mode="json")
2093
+ # Use override visibility or story's current visibility
2094
+ payload["visibility"] = visibility or story.visibility or "private"
2095
+ payload["client_id"] = story.id
2096
+ stories_payload.append(payload)
2097
+
2098
+ # Push all stories in batch with progress
2099
+ console.print(f"Publishing {len(stories_payload)} stories...")
2100
+ console.print()
2101
+
2102
+ with BatchProgress() as progress:
2103
+ try:
2104
+ result = asyncio.run(push_stories_batch(stories_payload))
2105
+ pushed = result.get("pushed", 0)
2106
+ failed = result.get("failed", 0)
2107
+ results = result.get("results", [])
2108
+
2109
+ # Display results
2110
+ for i, story_result in enumerate(results):
2111
+ story_title = stories_to_publish[i].title[:50] if i < len(stories_to_publish) else "Unknown"
2112
+ if story_result.get("success"):
2113
+ console.print(f" [{BRAND_SUCCESS}]✓[/] {story_title}")
2114
+ else:
2115
+ error_msg = story_result.get("error", "Unknown error")
2116
+ console.print(f" [{BRAND_ERROR}]✗[/] {story_title}: {error_msg}")
2117
+
2118
+ except (APIError, AuthError) as e:
2119
+ print_error(f"Publish failed: {e}")
2120
+ raise typer.Exit(1)
2121
+
2122
+ # Log operation
2123
+ if pushed > 0:
2124
+ log_cloud_operation(
2125
+ operation="publish",
2126
+ destination="repr.dev",
2127
+ payload_summary={
2128
+ "stories_published": pushed,
2129
+ "visibility": visibility or "default",
2130
+ "scope": "story" if story_id else ("repo" if repo else "global"),
2131
+ "repo": repo,
2132
+ },
2133
+ bytes_sent=0,
2134
+ )
2135
+
2136
+ console.print()
2137
+ if failed > 0:
2138
+ print_warning(f"Published {pushed}/{len(stories_payload)} stories ({failed} failed)")
2139
+ else:
2140
+ print_success(f"Published {pushed} stories to repr.dev")
2141
+
2142
+
1984
2143
  @app.command()
1985
2144
  def sync():
1986
2145
  """
@@ -3724,45 +3883,9 @@ def profile_link():
3724
3883
 
3725
3884
 
3726
3885
  # =============================================================================
3727
- # PUBLISH/UNPUBLISH COMMANDS
3886
+ # UNPUBLISH COMMAND
3728
3887
  # =============================================================================
3729
3888
 
3730
- @app.command("publish")
3731
- def publish_story(
3732
- story_id: str = typer.Argument(..., help="Story ID to publish"),
3733
- visibility: str = typer.Option("public", "--visibility", "-v", help="Visibility: public, friends, private"),
3734
- ):
3735
- """
3736
- Set story visibility.
3737
-
3738
- Examples:
3739
- repr publish abc123
3740
- repr publish abc123 --visibility friends
3741
- """
3742
- from .api import set_story_visibility, AuthError
3743
-
3744
- if visibility not in ("public", "friends", "private"):
3745
- print_error(f"Invalid visibility: {visibility}")
3746
- print_info("Valid options: public, friends, private")
3747
- raise typer.Exit(1)
3748
-
3749
- if not is_authenticated():
3750
- print_error("Not authenticated")
3751
- print_info("Run `repr login` first")
3752
- raise typer.Exit(1)
3753
-
3754
- try:
3755
- with create_spinner(f"Setting visibility to {visibility}..."):
3756
- result = asyncio.run(set_story_visibility(story_id, visibility))
3757
- print_success(f"Story visibility set to {visibility}")
3758
- except AuthError as e:
3759
- print_error(str(e))
3760
- raise typer.Exit(1)
3761
- except APIError as e:
3762
- print_error(f"API error: {e}")
3763
- raise typer.Exit(1)
3764
-
3765
-
3766
3889
  @app.command("unpublish")
3767
3890
  def unpublish_story(
3768
3891
  story_id: str = typer.Argument(..., help="Story ID to unpublish"),