repr-cli 0.2.17__tar.gz → 0.2.19__tar.gz
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_cli-0.2.17 → repr_cli-0.2.19}/PKG-INFO +1 -1
- {repr_cli-0.2.17 → repr_cli-0.2.19}/pyproject.toml +1 -1
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/__init__.py +1 -1
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/cli.py +185 -51
- repr_cli-0.2.19/repr/dashboard/dist/assets/index-B-aCjaCw.js +384 -0
- repr_cli-0.2.19/repr/dashboard/dist/assets/index-C7Gzxc4f.js +384 -0
- repr_cli-0.2.19/repr/dashboard/dist/assets/index-CQdMXo6g.js +391 -0
- repr_cli-0.2.19/repr/dashboard/dist/assets/index-Cs8ofFGd.js +384 -0
- repr_cli-0.2.19/repr/dashboard/dist/assets/index-DwN0SeMc.css +1 -0
- repr_cli-0.2.19/repr/dashboard/dist/assets/index-YFch_e0S.js +384 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/dist/index.html +2 -2
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/server.py +191 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr_cli.egg-info/PKG-INFO +1 -1
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr_cli.egg-info/SOURCES.txt +6 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/LICENSE +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/README.md +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/__main__.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/api.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/auth.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/change_synthesis.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/config.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/configure.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/cron.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/__init__.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/build.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/dist/assets/index-BYFVbEev.css +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/dist/assets/index-BrrhyJFO.css +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/dist/assets/index-CcEg74ts.js +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/dist/assets/index-Cerc-iA_.js +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/dist/assets/index-CjVcBW2L.css +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/dist/assets/index-Dfl3mR5E.js +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/dist/favicon.svg +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/dashboard/manager.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/db.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/discovery.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/doctor.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/extractor.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/hooks.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/keychain.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/llm.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/loaders/__init__.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/loaders/base.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/loaders/claude_code.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/loaders/clawdbot.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/loaders/gemini_antigravity.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/mcp_server.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/models.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/openai_analysis.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/privacy.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/session_extractor.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/storage.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/story_synthesis.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/telemetry.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/templates.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/timeline.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/tools.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/ui.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr/updater.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr_cli.egg-info/dependency_links.txt +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr_cli.egg-info/entry_points.txt +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr_cli.egg-info/requires.txt +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/repr_cli.egg-info/top_level.txt +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/setup.cfg +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/setup.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/tests/test_deduplication.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/tests/test_environment_variables.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/tests/test_network_sandboxing.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/tests/test_privacy_guarantees.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/tests/test_profile_export.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/tests/test_repo_identity.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/tests/test_stories_review.py +0 -0
- {repr_cli-0.2.17 → repr_cli-0.2.19}/tests/test_token_budget.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "repr-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.19"
|
|
8
8
|
description = "A beautiful, privacy-first CLI that analyzes your code repositories and generates a compelling developer profile"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -933,24 +933,40 @@ def generate(
|
|
|
933
933
|
console.print(" repr generate --local")
|
|
934
934
|
raise typer.Exit(1)
|
|
935
935
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
936
|
+
# Resolve LLM mode and model from config
|
|
937
|
+
llm_config = get_llm_config()
|
|
938
|
+
default_mode = llm_config.get("default", "local")
|
|
939
|
+
byok_provider = None
|
|
940
940
|
|
|
941
|
+
if not local and not cloud:
|
|
941
942
|
if default_mode == "local":
|
|
942
943
|
local = True
|
|
943
944
|
elif default_mode == "cloud" and is_authenticated() and is_cloud_allowed():
|
|
944
945
|
cloud = True
|
|
946
|
+
elif default_mode.startswith("byok:"):
|
|
947
|
+
byok_provider = default_mode.split(":", 1)[1]
|
|
945
948
|
else:
|
|
946
949
|
# Fallback: local if not signed in or cloud not allowed
|
|
947
950
|
local = True
|
|
948
|
-
|
|
951
|
+
|
|
952
|
+
# Determine model and provider string for display
|
|
953
|
+
if byok_provider:
|
|
954
|
+
from .config import get_byok_config, BYOK_PROVIDERS
|
|
955
|
+
byok_cfg = get_byok_config(byok_provider)
|
|
956
|
+
model_name = byok_cfg.get("model") if byok_cfg else None
|
|
957
|
+
provider_name = BYOK_PROVIDERS.get(byok_provider, {}).get("name", byok_provider)
|
|
958
|
+
mode_str = f"{provider_name}: {model_name}" if model_name else provider_name
|
|
959
|
+
elif local:
|
|
960
|
+
provider_name = llm_config.get("local_provider") or "Ollama"
|
|
961
|
+
model_name = llm_config.get("local_model") or "llama3.2"
|
|
962
|
+
mode_str = f"{provider_name}: {model_name}"
|
|
963
|
+
else:
|
|
964
|
+
model_name = llm_config.get("cloud_model") or "gpt-4o-mini"
|
|
965
|
+
mode_str = f"Cloud: {model_name}"
|
|
966
|
+
|
|
949
967
|
if not json_output:
|
|
950
968
|
print_header()
|
|
951
969
|
|
|
952
|
-
mode_str = "local LLM" if local else "cloud LLM"
|
|
953
|
-
|
|
954
970
|
# Build timeframe string for display
|
|
955
971
|
if commits:
|
|
956
972
|
timeframe_str = f"specific commits"
|
|
@@ -1073,13 +1089,8 @@ def generate(
|
|
|
1073
1089
|
if not json_output:
|
|
1074
1090
|
console.print(f" Batch {current}/{total}")
|
|
1075
1091
|
|
|
1076
|
-
#
|
|
1077
|
-
|
|
1078
|
-
use_local = local or (not cloud and llm_config.get("default") == "local")
|
|
1079
|
-
if use_local:
|
|
1080
|
-
model = llm_config.get("local_model") or "llama3.2"
|
|
1081
|
-
else:
|
|
1082
|
-
model = None # Use default cloud model
|
|
1092
|
+
# Use model_name resolved earlier (handles local, cloud, and BYOK)
|
|
1093
|
+
model = model_name
|
|
1083
1094
|
|
|
1084
1095
|
try:
|
|
1085
1096
|
# Run synthesis sync
|
|
@@ -1981,6 +1992,165 @@ def push(
|
|
|
1981
1992
|
print_success(f"Pushed {pushed}/{len(stories_payload)} stories")
|
|
1982
1993
|
|
|
1983
1994
|
|
|
1995
|
+
@app.command()
|
|
1996
|
+
def publish(
|
|
1997
|
+
story_id: Optional[str] = typer.Argument(None, help="Story ID to publish (omit for batch publish)"),
|
|
1998
|
+
all_stories: bool = typer.Option(False, "--all", "-a", help="Republish all stories, including already-pushed"),
|
|
1999
|
+
repo: Optional[str] = typer.Option(None, "--repo", "-r", help="Publish stories from specific repository"),
|
|
2000
|
+
visibility: Optional[str] = typer.Option(None, "--visibility", "-v", help="Override visibility: public, private, connections"),
|
|
2001
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Preview what would be published"),
|
|
2002
|
+
):
|
|
2003
|
+
"""
|
|
2004
|
+
Publish stories to repr.dev.
|
|
2005
|
+
|
|
2006
|
+
Publish = Upload to cloud with current visibility (or override).
|
|
2007
|
+
|
|
2008
|
+
Supports three scopes:
|
|
2009
|
+
- Global: Publish all unpushed stories (or all with --all)
|
|
2010
|
+
- Repo: Publish stories from a specific repository (--repo)
|
|
2011
|
+
- Story: Publish a single story by ID
|
|
2012
|
+
|
|
2013
|
+
Examples:
|
|
2014
|
+
repr publish # Unpushed stories only
|
|
2015
|
+
repr publish --all # All stories (re-publish)
|
|
2016
|
+
repr publish --repo myproject # Stories from specific repo
|
|
2017
|
+
repr publish 01HXYZ123 # Single story by ID
|
|
2018
|
+
repr publish --visibility public # Publish as public
|
|
2019
|
+
repr publish --repo myproject --dry-run # Preview
|
|
2020
|
+
"""
|
|
2021
|
+
from .privacy import check_cloud_permission, log_cloud_operation
|
|
2022
|
+
from .api import push_stories_batch, APIError, AuthError
|
|
2023
|
+
from .db import get_db
|
|
2024
|
+
|
|
2025
|
+
# Check authentication
|
|
2026
|
+
if not get_access_token():
|
|
2027
|
+
print_error("Not authenticated")
|
|
2028
|
+
print_info("Run 'repr login' to authenticate")
|
|
2029
|
+
raise typer.Exit(1)
|
|
2030
|
+
|
|
2031
|
+
allowed, reason = check_cloud_permission("push")
|
|
2032
|
+
if not allowed:
|
|
2033
|
+
print_error("Publishing blocked")
|
|
2034
|
+
print_info(reason)
|
|
2035
|
+
raise typer.Exit(1)
|
|
2036
|
+
|
|
2037
|
+
# Validate visibility if provided
|
|
2038
|
+
valid_visibilities = {"public", "private", "connections"}
|
|
2039
|
+
if visibility and visibility not in valid_visibilities:
|
|
2040
|
+
print_error(f"Invalid visibility: {visibility}")
|
|
2041
|
+
print_info(f"Valid options: {', '.join(valid_visibilities)}")
|
|
2042
|
+
raise typer.Exit(1)
|
|
2043
|
+
|
|
2044
|
+
db = get_db()
|
|
2045
|
+
|
|
2046
|
+
# Determine which stories to publish based on scope
|
|
2047
|
+
if story_id:
|
|
2048
|
+
# Single story mode
|
|
2049
|
+
story = db.get_story(story_id)
|
|
2050
|
+
if not story:
|
|
2051
|
+
print_error(f"Story not found: {story_id}")
|
|
2052
|
+
raise typer.Exit(1)
|
|
2053
|
+
all_stories_list = [story]
|
|
2054
|
+
scope_desc = f"story {story_id[:8]}..."
|
|
2055
|
+
elif repo:
|
|
2056
|
+
# Repo mode - get stories from specific repo
|
|
2057
|
+
projects = db.list_projects()
|
|
2058
|
+
project_ids = [p["id"] for p in projects if p["name"] == repo]
|
|
2059
|
+
if not project_ids:
|
|
2060
|
+
print_error(f"Repository not found: {repo}")
|
|
2061
|
+
print_info("Use 'repr repos list' to see tracked repositories")
|
|
2062
|
+
raise typer.Exit(1)
|
|
2063
|
+
all_stories_list = [s for s in db.list_stories(limit=10000) if s.project_id in project_ids]
|
|
2064
|
+
scope_desc = f"repo '{repo}'"
|
|
2065
|
+
else:
|
|
2066
|
+
# Global mode
|
|
2067
|
+
all_stories_list = db.list_stories(limit=10000)
|
|
2068
|
+
scope_desc = "all repositories"
|
|
2069
|
+
|
|
2070
|
+
if not all_stories_list:
|
|
2071
|
+
print_info(f"No stories found for {scope_desc}")
|
|
2072
|
+
raise typer.Exit()
|
|
2073
|
+
|
|
2074
|
+
# Filter to unpushed unless --all is specified
|
|
2075
|
+
# Note: For now we don't track pushed_at in the schema, so --all just means republish everything
|
|
2076
|
+
# In future, we could filter by pushed_at column
|
|
2077
|
+
stories_to_publish = all_stories_list
|
|
2078
|
+
|
|
2079
|
+
if not stories_to_publish:
|
|
2080
|
+
print_info("No stories to publish")
|
|
2081
|
+
raise typer.Exit()
|
|
2082
|
+
|
|
2083
|
+
console.print(f"Found [bold]{len(stories_to_publish)}[/] stories from {scope_desc}")
|
|
2084
|
+
console.print()
|
|
2085
|
+
|
|
2086
|
+
if dry_run:
|
|
2087
|
+
console.print("[dim]Preview (dry-run):[/]")
|
|
2088
|
+
for story in stories_to_publish[:20]: # Limit preview to 20
|
|
2089
|
+
vis = visibility or story.visibility or "private"
|
|
2090
|
+
console.print(f" • [{vis}] {story.title[:55]}...")
|
|
2091
|
+
if len(stories_to_publish) > 20:
|
|
2092
|
+
console.print(f" ... and {len(stories_to_publish) - 20} more")
|
|
2093
|
+
console.print()
|
|
2094
|
+
console.print("Run without --dry-run to publish")
|
|
2095
|
+
raise typer.Exit()
|
|
2096
|
+
|
|
2097
|
+
# Build batch payload
|
|
2098
|
+
vis_label = visibility or "default"
|
|
2099
|
+
console.print(f"Publishing with visibility: [bold]{vis_label}[/]...")
|
|
2100
|
+
|
|
2101
|
+
stories_payload = []
|
|
2102
|
+
for story in stories_to_publish:
|
|
2103
|
+
payload = story.model_dump(mode="json")
|
|
2104
|
+
# Use override visibility or story's current visibility
|
|
2105
|
+
payload["visibility"] = visibility or story.visibility or "private"
|
|
2106
|
+
payload["client_id"] = story.id
|
|
2107
|
+
stories_payload.append(payload)
|
|
2108
|
+
|
|
2109
|
+
# Push all stories in batch with progress
|
|
2110
|
+
console.print(f"Publishing {len(stories_payload)} stories...")
|
|
2111
|
+
console.print()
|
|
2112
|
+
|
|
2113
|
+
with BatchProgress() as progress:
|
|
2114
|
+
try:
|
|
2115
|
+
result = asyncio.run(push_stories_batch(stories_payload))
|
|
2116
|
+
pushed = result.get("pushed", 0)
|
|
2117
|
+
failed = result.get("failed", 0)
|
|
2118
|
+
results = result.get("results", [])
|
|
2119
|
+
|
|
2120
|
+
# Display results
|
|
2121
|
+
for i, story_result in enumerate(results):
|
|
2122
|
+
story_title = stories_to_publish[i].title[:50] if i < len(stories_to_publish) else "Unknown"
|
|
2123
|
+
if story_result.get("success"):
|
|
2124
|
+
console.print(f" [{BRAND_SUCCESS}]✓[/] {story_title}")
|
|
2125
|
+
else:
|
|
2126
|
+
error_msg = story_result.get("error", "Unknown error")
|
|
2127
|
+
console.print(f" [{BRAND_ERROR}]✗[/] {story_title}: {error_msg}")
|
|
2128
|
+
|
|
2129
|
+
except (APIError, AuthError) as e:
|
|
2130
|
+
print_error(f"Publish failed: {e}")
|
|
2131
|
+
raise typer.Exit(1)
|
|
2132
|
+
|
|
2133
|
+
# Log operation
|
|
2134
|
+
if pushed > 0:
|
|
2135
|
+
log_cloud_operation(
|
|
2136
|
+
operation="publish",
|
|
2137
|
+
destination="repr.dev",
|
|
2138
|
+
payload_summary={
|
|
2139
|
+
"stories_published": pushed,
|
|
2140
|
+
"visibility": visibility or "default",
|
|
2141
|
+
"scope": "story" if story_id else ("repo" if repo else "global"),
|
|
2142
|
+
"repo": repo,
|
|
2143
|
+
},
|
|
2144
|
+
bytes_sent=0,
|
|
2145
|
+
)
|
|
2146
|
+
|
|
2147
|
+
console.print()
|
|
2148
|
+
if failed > 0:
|
|
2149
|
+
print_warning(f"Published {pushed}/{len(stories_payload)} stories ({failed} failed)")
|
|
2150
|
+
else:
|
|
2151
|
+
print_success(f"Published {pushed} stories to repr.dev")
|
|
2152
|
+
|
|
2153
|
+
|
|
1984
2154
|
@app.command()
|
|
1985
2155
|
def sync():
|
|
1986
2156
|
"""
|
|
@@ -3724,45 +3894,9 @@ def profile_link():
|
|
|
3724
3894
|
|
|
3725
3895
|
|
|
3726
3896
|
# =============================================================================
|
|
3727
|
-
#
|
|
3897
|
+
# UNPUBLISH COMMAND
|
|
3728
3898
|
# =============================================================================
|
|
3729
3899
|
|
|
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
3900
|
@app.command("unpublish")
|
|
3767
3901
|
def unpublish_story(
|
|
3768
3902
|
story_id: str = typer.Argument(..., help="Story ID to unpublish"),
|