repr-cli 0.2.15__py3-none-any.whl → 0.2.17__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 +1 -1
- repr/api.py +363 -62
- repr/auth.py +47 -38
- repr/change_synthesis.py +478 -0
- repr/cli.py +4103 -267
- repr/config.py +119 -11
- repr/configure.py +889 -0
- repr/cron.py +419 -0
- repr/dashboard/__init__.py +9 -0
- repr/dashboard/build.py +126 -0
- repr/dashboard/dist/assets/index-BYFVbEev.css +1 -0
- repr/dashboard/dist/assets/index-BrrhyJFO.css +1 -0
- repr/dashboard/dist/assets/index-CcEg74ts.js +270 -0
- repr/dashboard/dist/assets/index-Cerc-iA_.js +377 -0
- repr/dashboard/dist/assets/index-CjVcBW2L.css +1 -0
- repr/dashboard/dist/assets/index-Dfl3mR5E.js +377 -0
- repr/dashboard/dist/favicon.svg +4 -0
- repr/dashboard/dist/index.html +14 -0
- repr/dashboard/manager.py +234 -0
- repr/dashboard/server.py +1298 -0
- repr/db.py +980 -0
- repr/hooks.py +3 -2
- repr/loaders/__init__.py +22 -0
- repr/loaders/base.py +156 -0
- repr/loaders/claude_code.py +287 -0
- repr/loaders/clawdbot.py +313 -0
- repr/loaders/gemini_antigravity.py +381 -0
- repr/mcp_server.py +1196 -0
- repr/models.py +503 -0
- repr/openai_analysis.py +25 -0
- repr/session_extractor.py +481 -0
- repr/storage.py +360 -0
- repr/story_synthesis.py +1296 -0
- repr/templates.py +68 -4
- repr/timeline.py +710 -0
- repr/tools.py +17 -8
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/METADATA +50 -10
- repr_cli-0.2.17.dist-info/RECORD +52 -0
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/WHEEL +1 -1
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/entry_points.txt +1 -0
- repr_cli-0.2.15.dist-info/RECORD +0 -26
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/licenses/LICENSE +0 -0
- {repr_cli-0.2.15.dist-info → repr_cli-0.2.17.dist-info}/top_level.txt +0 -0
repr/config.py
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
Configuration management for ~/.repr/ directory.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import hashlib
|
|
5
6
|
import json
|
|
6
7
|
import os
|
|
8
|
+
import subprocess
|
|
7
9
|
import tempfile
|
|
8
10
|
from datetime import datetime
|
|
9
11
|
from pathlib import Path
|
|
@@ -108,11 +110,17 @@ DEFAULT_CONFIG = {
|
|
|
108
110
|
},
|
|
109
111
|
"generation": {
|
|
110
112
|
"batch_size": 5, # Commits per story
|
|
111
|
-
"auto_generate_on_hook": False, # Auto-generate when hook runs
|
|
113
|
+
"auto_generate_on_hook": False, # Auto-generate when hook runs (prefer cron)
|
|
112
114
|
"default_template": "resume", # Default story template
|
|
113
115
|
"token_limit": 100000, # Max tokens per cloud request
|
|
114
116
|
"max_commits_per_batch": 50, # Max commits per request
|
|
115
117
|
},
|
|
118
|
+
"cron": {
|
|
119
|
+
"installed": False,
|
|
120
|
+
"paused": False,
|
|
121
|
+
"interval_hours": None,
|
|
122
|
+
"min_commits": None,
|
|
123
|
+
},
|
|
116
124
|
"publish": {
|
|
117
125
|
"on_generate": "never", # "never", "prompt", "always"
|
|
118
126
|
"on_sync": "prompt", # "never", "prompt", "always"
|
|
@@ -120,7 +128,9 @@ DEFAULT_CONFIG = {
|
|
|
120
128
|
"privacy": {
|
|
121
129
|
"lock_local_only": False, # Disable cloud features entirely
|
|
122
130
|
"lock_permanent": False, # Make local-only lock irreversible
|
|
123
|
-
"profile_visibility": "
|
|
131
|
+
"profile_visibility": "private", # "public", "private", "connections"
|
|
132
|
+
"repos_default_visibility": "private", # Default visibility for new repos
|
|
133
|
+
"stories_default_visibility": "private", # Default visibility for new stories
|
|
124
134
|
"telemetry_enabled": False, # Opt-in telemetry
|
|
125
135
|
},
|
|
126
136
|
"tracked_repos": [], # List of {"path": str, "last_sync": str|None, "hook_installed": bool, "paused": bool}
|
|
@@ -278,29 +288,31 @@ def set_auth(
|
|
|
278
288
|
access_token: str,
|
|
279
289
|
user_id: str,
|
|
280
290
|
email: str,
|
|
291
|
+
username: str | None = None,
|
|
281
292
|
litellm_api_key: str | None = None,
|
|
282
293
|
) -> None:
|
|
283
294
|
"""Store authentication info securely."""
|
|
284
295
|
from .keychain import store_secret
|
|
285
|
-
|
|
296
|
+
|
|
286
297
|
config = load_config()
|
|
287
|
-
|
|
298
|
+
|
|
288
299
|
# Store token in keychain
|
|
289
300
|
keychain_ref = f"auth_token_{user_id[:8]}"
|
|
290
301
|
store_secret(keychain_ref, access_token)
|
|
291
|
-
|
|
302
|
+
|
|
292
303
|
config["auth"] = {
|
|
293
304
|
"token_keychain_ref": keychain_ref,
|
|
294
305
|
"user_id": user_id,
|
|
295
306
|
"email": email,
|
|
307
|
+
"username": username,
|
|
296
308
|
"authenticated_at": datetime.now().isoformat(),
|
|
297
309
|
}
|
|
298
|
-
|
|
310
|
+
|
|
299
311
|
if litellm_api_key:
|
|
300
312
|
litellm_ref = f"litellm_key_{user_id[:8]}"
|
|
301
313
|
store_secret(litellm_ref, litellm_api_key)
|
|
302
314
|
config["auth"]["litellm_keychain_ref"] = litellm_ref
|
|
303
|
-
|
|
315
|
+
|
|
304
316
|
save_config(config)
|
|
305
317
|
|
|
306
318
|
|
|
@@ -486,12 +498,17 @@ BYOK_PROVIDERS = {
|
|
|
486
498
|
},
|
|
487
499
|
"anthropic": {
|
|
488
500
|
"name": "Anthropic",
|
|
489
|
-
"default_model": "claude-
|
|
501
|
+
"default_model": "claude-sonnet-4-20250514",
|
|
490
502
|
"base_url": "https://api.anthropic.com/v1",
|
|
491
503
|
},
|
|
504
|
+
"gemini": {
|
|
505
|
+
"name": "Google Gemini",
|
|
506
|
+
"default_model": "gemini-1.5-flash",
|
|
507
|
+
"base_url": "https://generativelanguage.googleapis.com/v1beta",
|
|
508
|
+
},
|
|
492
509
|
"groq": {
|
|
493
510
|
"name": "Groq",
|
|
494
|
-
"default_model": "llama-3.
|
|
511
|
+
"default_model": "llama-3.3-70b-versatile",
|
|
495
512
|
"base_url": "https://api.groq.com/openai/v1",
|
|
496
513
|
},
|
|
497
514
|
"together": {
|
|
@@ -499,6 +516,11 @@ BYOK_PROVIDERS = {
|
|
|
499
516
|
"default_model": "meta-llama/Llama-3-70b-chat-hf",
|
|
500
517
|
"base_url": "https://api.together.xyz/v1",
|
|
501
518
|
},
|
|
519
|
+
"openrouter": {
|
|
520
|
+
"name": "OpenRouter",
|
|
521
|
+
"default_model": "anthropic/claude-3.5-sonnet",
|
|
522
|
+
"base_url": "https://openrouter.ai/api/v1",
|
|
523
|
+
},
|
|
502
524
|
}
|
|
503
525
|
|
|
504
526
|
|
|
@@ -617,14 +639,100 @@ def set_profile_config(**kwargs) -> None:
|
|
|
617
639
|
config = load_config()
|
|
618
640
|
if "profile" not in config:
|
|
619
641
|
config["profile"] = DEFAULT_CONFIG["profile"].copy()
|
|
620
|
-
|
|
642
|
+
|
|
621
643
|
for key, value in kwargs.items():
|
|
622
644
|
if key in DEFAULT_CONFIG["profile"]:
|
|
623
645
|
config["profile"][key] = value
|
|
624
|
-
|
|
646
|
+
|
|
625
647
|
save_config(config)
|
|
626
648
|
|
|
627
649
|
|
|
650
|
+
# Word lists for mnemonic username generation
|
|
651
|
+
_ADJECTIVES = [
|
|
652
|
+
"swift", "brave", "calm", "keen", "bold", "wise", "warm", "cool",
|
|
653
|
+
"bright", "quiet", "noble", "vivid", "lucid", "agile", "witty",
|
|
654
|
+
"gentle", "steady", "clever", "humble", "serene", "cosmic", "lunar",
|
|
655
|
+
"stellar", "golden", "silver", "crystal", "amber", "azure", "coral",
|
|
656
|
+
"crimson", "emerald", "ivory", "jade", "onyx", "pearl", "ruby",
|
|
657
|
+
]
|
|
658
|
+
|
|
659
|
+
_NOUNS = [
|
|
660
|
+
"falcon", "wolf", "bear", "hawk", "tiger", "lion", "eagle", "raven",
|
|
661
|
+
"phoenix", "dragon", "panther", "cobra", "viper", "lynx", "fox",
|
|
662
|
+
"mountain", "river", "ocean", "forest", "desert", "glacier", "canyon",
|
|
663
|
+
"meadow", "valley", "summit", "thunder", "storm", "aurora", "comet",
|
|
664
|
+
"nebula", "quasar", "pulsar", "nova", "orbit", "zenith", "vertex",
|
|
665
|
+
]
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def get_gpg_fingerprint() -> str | None:
|
|
669
|
+
"""Get the GPG signing key fingerprint from git config."""
|
|
670
|
+
try:
|
|
671
|
+
# First check if git has a signing key configured
|
|
672
|
+
result = subprocess.run(
|
|
673
|
+
["git", "config", "--get", "user.signingkey"],
|
|
674
|
+
capture_output=True,
|
|
675
|
+
text=True,
|
|
676
|
+
timeout=5,
|
|
677
|
+
)
|
|
678
|
+
if result.returncode != 0 or not result.stdout.strip():
|
|
679
|
+
return None
|
|
680
|
+
|
|
681
|
+
key_id = result.stdout.strip()
|
|
682
|
+
|
|
683
|
+
# Get the full fingerprint from GPG
|
|
684
|
+
result = subprocess.run(
|
|
685
|
+
["gpg", "--fingerprint", "--with-colons", key_id],
|
|
686
|
+
capture_output=True,
|
|
687
|
+
text=True,
|
|
688
|
+
timeout=5,
|
|
689
|
+
)
|
|
690
|
+
if result.returncode != 0:
|
|
691
|
+
return None
|
|
692
|
+
|
|
693
|
+
# Parse the fingerprint from colon-delimited output
|
|
694
|
+
for line in result.stdout.split("\n"):
|
|
695
|
+
if line.startswith("fpr:"):
|
|
696
|
+
return line.split(":")[9]
|
|
697
|
+
|
|
698
|
+
return None
|
|
699
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
|
|
700
|
+
return None
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def derive_mnemonic_from_key(fingerprint: str) -> str:
|
|
704
|
+
"""Derive a deterministic human-friendly name from a key fingerprint."""
|
|
705
|
+
# Hash the fingerprint to get consistent bytes
|
|
706
|
+
hash_bytes = hashlib.sha256(fingerprint.encode()).digest()
|
|
707
|
+
|
|
708
|
+
# Use first bytes to select adjective and noun
|
|
709
|
+
adj_idx = hash_bytes[0] % len(_ADJECTIVES)
|
|
710
|
+
noun_idx = hash_bytes[1] % len(_NOUNS)
|
|
711
|
+
|
|
712
|
+
return f"{_ADJECTIVES[adj_idx]}-{_NOUNS[noun_idx]}"
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def get_or_generate_username() -> str:
|
|
716
|
+
"""
|
|
717
|
+
Get username with fallback chain:
|
|
718
|
+
1. Profile username (claimed or set)
|
|
719
|
+
2. GPG-derived mnemonic (deterministic from signing key)
|
|
720
|
+
3. None (caller should use git author or other fallback)
|
|
721
|
+
"""
|
|
722
|
+
profile = get_profile_config()
|
|
723
|
+
|
|
724
|
+
# 1. Check for set username
|
|
725
|
+
if username := profile.get("username"):
|
|
726
|
+
return username
|
|
727
|
+
|
|
728
|
+
# 2. Try to derive from GPG key
|
|
729
|
+
if fingerprint := get_gpg_fingerprint():
|
|
730
|
+
return derive_mnemonic_from_key(fingerprint)
|
|
731
|
+
|
|
732
|
+
# 3. No deterministic identity available
|
|
733
|
+
return None
|
|
734
|
+
|
|
735
|
+
|
|
628
736
|
def get_skip_patterns() -> list[str]:
|
|
629
737
|
"""Get list of patterns to skip during discovery."""
|
|
630
738
|
config = load_config()
|