gitcast 1.0.9__tar.gz → 1.0.11__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.
Files changed (90) hide show
  1. gitcast-1.0.11/.env.example +40 -0
  2. {gitcast-1.0.9/gitcast.egg-info → gitcast-1.0.11}/PKG-INFO +1 -1
  3. {gitcast-1.0.9 → gitcast-1.0.11}/api/routes.py +15 -24
  4. {gitcast-1.0.9 → gitcast-1.0.11}/api/server.py +20 -9
  5. gitcast-1.0.11/cli/.env.example +40 -0
  6. {gitcast-1.0.9 → gitcast-1.0.11}/cli/gitcast.py +18 -0
  7. {gitcast-1.0.9 → gitcast-1.0.11}/config/settings.py +23 -3
  8. {gitcast-1.0.9 → gitcast-1.0.11}/core/hotkey.py +1 -0
  9. {gitcast-1.0.9 → gitcast-1.0.11}/docs/pypi-errors.md +8 -0
  10. {gitcast-1.0.9 → gitcast-1.0.11/gitcast.egg-info}/PKG-INFO +1 -1
  11. {gitcast-1.0.9 → gitcast-1.0.11}/setup.py +1 -1
  12. {gitcast-1.0.9 → gitcast-1.0.11}/web/index.html +38 -6
  13. gitcast-1.0.9/.env.example +0 -26
  14. gitcast-1.0.9/cli/.env.example +0 -26
  15. {gitcast-1.0.9 → gitcast-1.0.11}/LICENSE +0 -0
  16. {gitcast-1.0.9 → gitcast-1.0.11}/MANIFEST.in +0 -0
  17. {gitcast-1.0.9 → gitcast-1.0.11}/README.md +0 -0
  18. {gitcast-1.0.9 → gitcast-1.0.11}/ai/__init__.py +0 -0
  19. {gitcast-1.0.9 → gitcast-1.0.11}/ai/formatter.py +0 -0
  20. {gitcast-1.0.9 → gitcast-1.0.11}/ai/generator.py +0 -0
  21. {gitcast-1.0.9 → gitcast-1.0.11}/ai/prompts.py +0 -0
  22. {gitcast-1.0.9 → gitcast-1.0.11}/ai/viral_patterns.py +0 -0
  23. {gitcast-1.0.9 → gitcast-1.0.11}/api/__init__.py +0 -0
  24. {gitcast-1.0.9 → gitcast-1.0.11}/api/analytics.py +0 -0
  25. {gitcast-1.0.9 → gitcast-1.0.11}/api/auth.py +0 -0
  26. {gitcast-1.0.9 → gitcast-1.0.11}/api/auth_middleware.py +0 -0
  27. {gitcast-1.0.9 → gitcast-1.0.11}/api/auth_routes.py +0 -0
  28. {gitcast-1.0.9 → gitcast-1.0.11}/api/monitoring.py +0 -0
  29. {gitcast-1.0.9 → gitcast-1.0.11}/api/payload.py +0 -0
  30. {gitcast-1.0.9 → gitcast-1.0.11}/api/ratelimit.py +0 -0
  31. {gitcast-1.0.9 → gitcast-1.0.11}/api/validators.py +0 -0
  32. {gitcast-1.0.9 → gitcast-1.0.11}/assets/__init__.py +0 -0
  33. {gitcast-1.0.9 → gitcast-1.0.11}/assets/favicon-16x16.png +0 -0
  34. {gitcast-1.0.9 → gitcast-1.0.11}/assets/favicon-32x32.png +0 -0
  35. {gitcast-1.0.9 → gitcast-1.0.11}/assets/favicon-64x64.png +0 -0
  36. {gitcast-1.0.9 → gitcast-1.0.11}/assets/favicon.ico +0 -0
  37. {gitcast-1.0.9 → gitcast-1.0.11}/assets/icon.png +0 -0
  38. {gitcast-1.0.9 → gitcast-1.0.11}/cli/__init__.py +0 -0
  39. {gitcast-1.0.9 → gitcast-1.0.11}/config/__init__.py +0 -0
  40. {gitcast-1.0.9 → gitcast-1.0.11}/core/__init__.py +0 -0
  41. {gitcast-1.0.9 → gitcast-1.0.11}/core/capture.py +0 -0
  42. {gitcast-1.0.9 → gitcast-1.0.11}/core/codebase_reader.py +0 -0
  43. {gitcast-1.0.9 → gitcast-1.0.11}/core/framing.py +0 -0
  44. {gitcast-1.0.9 → gitcast-1.0.11}/core/log_stream.py +0 -0
  45. {gitcast-1.0.9 → gitcast-1.0.11}/core/ocr.py +0 -0
  46. {gitcast-1.0.9 → gitcast-1.0.11}/core/screenshot_session.py +0 -0
  47. {gitcast-1.0.9 → gitcast-1.0.11}/core/security.py +0 -0
  48. {gitcast-1.0.9 → gitcast-1.0.11}/core/tray.py +0 -0
  49. {gitcast-1.0.9 → gitcast-1.0.11}/core/trigger.py +0 -0
  50. {gitcast-1.0.9 → gitcast-1.0.11}/docs/api-keys.md +0 -0
  51. {gitcast-1.0.9 → gitcast-1.0.11}/docs/article-generator.md +0 -0
  52. {gitcast-1.0.9 → gitcast-1.0.11}/docs/capture-flow.md +0 -0
  53. {gitcast-1.0.9 → gitcast-1.0.11}/docs/changelog.md +0 -0
  54. {gitcast-1.0.9 → gitcast-1.0.11}/docs/contributing.md +0 -0
  55. {gitcast-1.0.9 → gitcast-1.0.11}/docs/dashboard.md +0 -0
  56. {gitcast-1.0.9 → gitcast-1.0.11}/docs/insights.md +0 -0
  57. {gitcast-1.0.9 → gitcast-1.0.11}/docs/introduction.md +0 -0
  58. {gitcast-1.0.9 → gitcast-1.0.11}/docs/post-formats.md +0 -0
  59. {gitcast-1.0.9 → gitcast-1.0.11}/docs/providers.md +0 -0
  60. {gitcast-1.0.9 → gitcast-1.0.11}/docs/quickstart.md +0 -0
  61. {gitcast-1.0.9 → gitcast-1.0.11}/docs/sprint-mode.md +0 -0
  62. {gitcast-1.0.9 → gitcast-1.0.11}/gitcast.egg-info/SOURCES.txt +0 -0
  63. {gitcast-1.0.9 → gitcast-1.0.11}/gitcast.egg-info/dependency_links.txt +0 -0
  64. {gitcast-1.0.9 → gitcast-1.0.11}/gitcast.egg-info/entry_points.txt +0 -0
  65. {gitcast-1.0.9 → gitcast-1.0.11}/gitcast.egg-info/requires.txt +0 -0
  66. {gitcast-1.0.9 → gitcast-1.0.11}/gitcast.egg-info/top_level.txt +0 -0
  67. {gitcast-1.0.9 → gitcast-1.0.11}/publisher/__init__.py +0 -0
  68. {gitcast-1.0.9 → gitcast-1.0.11}/publisher/clipboard.py +0 -0
  69. {gitcast-1.0.9 → gitcast-1.0.11}/publisher/twitter.py +0 -0
  70. {gitcast-1.0.9 → gitcast-1.0.11}/pyproject.toml +0 -0
  71. {gitcast-1.0.9 → gitcast-1.0.11}/requirements.txt +0 -0
  72. {gitcast-1.0.9 → gitcast-1.0.11}/setup.cfg +0 -0
  73. {gitcast-1.0.9 → gitcast-1.0.11}/storage/__init__.py +0 -0
  74. {gitcast-1.0.9 → gitcast-1.0.11}/storage/cleanup.py +0 -0
  75. {gitcast-1.0.9 → gitcast-1.0.11}/storage/engagement.py +0 -0
  76. {gitcast-1.0.9 → gitcast-1.0.11}/storage/insights.py +0 -0
  77. {gitcast-1.0.9 → gitcast-1.0.11}/storage/key_manager.py +0 -0
  78. {gitcast-1.0.9 → gitcast-1.0.11}/storage/logger.py +0 -0
  79. {gitcast-1.0.9 → gitcast-1.0.11}/storage/metrics.py +0 -0
  80. {gitcast-1.0.9 → gitcast-1.0.11}/storage/sprint.py +0 -0
  81. {gitcast-1.0.9 → gitcast-1.0.11}/storage/streak.py +0 -0
  82. {gitcast-1.0.9 → gitcast-1.0.11}/storage/supabase_client.py +0 -0
  83. {gitcast-1.0.9 → gitcast-1.0.11}/storage/tone_memory.py +0 -0
  84. {gitcast-1.0.9 → gitcast-1.0.11}/tests/test_keys.py +0 -0
  85. {gitcast-1.0.9 → gitcast-1.0.11}/tests/test_ocr_fix.py +0 -0
  86. {gitcast-1.0.9 → gitcast-1.0.11}/ui/__init__.py +0 -0
  87. {gitcast-1.0.9 → gitcast-1.0.11}/ui/popup.py +0 -0
  88. {gitcast-1.0.9 → gitcast-1.0.11}/ui/review.py +0 -0
  89. {gitcast-1.0.9 → gitcast-1.0.11}/web/__init__.py +0 -0
  90. {gitcast-1.0.9 → gitcast-1.0.11}/web/landing.html +0 -0
@@ -0,0 +1,40 @@
1
+ # ─────────────────────────────────────────────
2
+ # YOUR API KEYS (optional)
3
+ # Gitcast works out of the box with shared keys.
4
+ # Add your own keys for higher rate limits.
5
+ # Your key always takes priority over shared key.
6
+ # ─────────────────────────────────────────────
7
+ # Groq — primary AI inference
8
+ GROQ_API_KEY=
9
+
10
+ # DeepSeek & Moonshot — specialized/free-tier fallbacks
11
+ DEEPSEEK_API_KEY=
12
+ MOONSHOT_API_KEY=
13
+ CEREBRAS_API_KEY=
14
+ OPENROUTER_API_KEY=
15
+
16
+ # Gemini — vision fallback (when OCR confidence is too low)
17
+ GEMINI_API_KEY=
18
+
19
+ # Optional model overrides
20
+ GROQ_MODEL=llama-3.3-70b-versatile
21
+ DEEPSEEK_MODEL=deepseek-chat
22
+ MOONSHOT_MODEL=moonshot-v1-8k
23
+ GEMINI_MODEL=gemini-2.0-flash
24
+ CEREBRAS_MODEL=llama-3.3-70b
25
+ OPENROUTER_MODEL=meta-llama/llama-3.3-70b-instruct:free
26
+
27
+ # X (Twitter) API v2 — all five are required for posting
28
+ TWITTER_API_KEY=your_twitter_api_key_here
29
+ TWITTER_API_SECRET=your_twitter_api_secret_here
30
+ TWITTER_ACCESS_TOKEN=your_twitter_access_token_here
31
+ TWITTER_ACCESS_SECRET=your_twitter_access_secret_here
32
+ TWITTER_BEARER_TOKEN=your_twitter_bearer_token_here
33
+
34
+ # ─────────────────────────────────────────────
35
+ # DO NOT EDIT — Maintainer base keys
36
+ # These are set automatically by the package
37
+ # ─────────────────────────────────────────────
38
+ GITCAST_BASE_GROQ_KEY=
39
+ GITCAST_BASE_DEEPSEEK_KEY=
40
+ GITCAST_BASE_GEMINI_KEY=
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitcast
3
- Version: 1.0.9
3
+ Version: 1.0.11
4
4
  Summary: git diff → published post. under 60 seconds.
5
5
  Home-page: https://github.com/drizzy765/gitcast
6
6
  Author: Timilehin Agoro
@@ -542,32 +542,23 @@ def _update_settings_for_user(user_id: str, values: dict) -> dict:
542
542
 
543
543
  @router.get("/keys/status")
544
544
  def get_ai_keys_status(user_id: str = Depends(get_current_user)):
545
- if _is_local_user(user_id):
546
- env_values = dotenv_values(_env_path())
547
- return {
548
- provider: {
549
- "configured": bool((env_values.get(env_name) or os.getenv(env_name) or "").strip()),
550
- "key_preview": "",
551
- "last_used_at": None,
552
- }
553
- for provider, env_name in API_KEY_ENV_MAP.items()
554
- }
555
-
556
- response = (
557
- get_client()
558
- .table("api_keys")
559
- .select("provider,key_preview,last_used_at,created_at")
560
- .eq("user_id", user_id)
561
- .execute()
545
+ from config.settings import (
546
+ GROQ_API_KEY,
547
+ DEEPSEEK_API_KEY,
548
+ GEMINI_API_KEY,
549
+ MOONSHOT_API_KEY,
550
+ CEREBRAS_API_KEY,
551
+ OPENROUTER_API_KEY,
552
+ USING_BASE_KEYS,
562
553
  )
563
- saved = {row["provider"]: row for row in response.data or []}
564
554
  return {
565
- provider: {
566
- "configured": provider in saved,
567
- "key_preview": saved.get(provider, {}).get("key_preview", ""),
568
- "last_used_at": saved.get(provider, {}).get("last_used_at"),
569
- }
570
- for provider in API_KEY_ENV_MAP
555
+ "groq": bool(GROQ_API_KEY),
556
+ "deepseek": bool(DEEPSEEK_API_KEY),
557
+ "gemini": bool(GEMINI_API_KEY),
558
+ "kimi": bool(MOONSHOT_API_KEY),
559
+ "cerebras": bool(CEREBRAS_API_KEY),
560
+ "openrouter": bool(OPENROUTER_API_KEY),
561
+ "using_base_keys": USING_BASE_KEYS,
571
562
  }
572
563
 
573
564
 
@@ -11,7 +11,7 @@ from api.auth_routes import router as auth_router
11
11
  from config.settings import missing_api_keys, BASE_DIR, STORAGE_DIR, CONFIG_DIR
12
12
  from api.monitoring import init_sentry
13
13
  from api.ratelimit import limiter
14
- from api.auth import get_token
14
+ from api.auth import get_token as get_auth_token
15
15
 
16
16
  # ── App setup ─────────────────────────────────────────────────────────────────
17
17
 
@@ -86,7 +86,7 @@ app.include_router(auth_router, prefix="/auth")
86
86
 
87
87
  @app.on_event("startup")
88
88
  def startup_event():
89
- print(f"\n[Auth] Session Token: {get_token()}")
89
+ print(f"\n[Auth] Session Token: {get_auth_token()}")
90
90
  print("[Server] Starting Gitcast API on http://127.0.0.1:8000")
91
91
 
92
92
 
@@ -126,12 +126,23 @@ def health_check():
126
126
 
127
127
  # ── Auth token retrieval (Localhost only) ───────────────────────────────────────
128
128
 
129
+ def is_localhost(host: str) -> bool:
130
+ return host in [
131
+ "127.0.0.1",
132
+ "::1",
133
+ "localhost",
134
+ "::ffff:127.0.0.1",
135
+ ]
136
+
129
137
  @app.get("/api/token")
130
- def get_session_token(request: Request):
131
- client_host = request.client.host if request.client else None
132
- if client_host not in ["127.0.0.1", "localhost", "::1"]:
133
- raise HTTPException(status_code=403, detail="Forbidden: Access allowed only from localhost")
134
- return {"token": get_token()}
138
+ async def get_token(request: Request):
139
+ host = request.client.host \
140
+ if request.client else ""
141
+ if not is_localhost(host):
142
+ raise HTTPException(status_code=403,
143
+ detail="localhost only")
144
+ from api.auth import get_token as _get_token
145
+ return {"token": _get_token()}
135
146
 
136
147
 
137
148
  # ── Entry point ───────────────────────────────────────────────────────────────
@@ -141,11 +152,11 @@ def start_server():
141
152
  # Write session token to config/session_token.txt
142
153
  try:
143
154
  token_file = CONFIG_DIR / "session_token.txt"
144
- token_file.write_text(get_token(), encoding="utf-8")
155
+ token_file.write_text(get_auth_token(), encoding="utf-8")
145
156
  except Exception as e:
146
157
  print(f"[Server] Failed to write session token to file: {e}")
147
158
 
148
- print(f"\n[Auth] Session Token: {get_token()}")
159
+ print(f"\n[Auth] Session Token: {get_auth_token()}")
149
160
  print("[Server] Starting Gitcast API on http://127.0.0.1:8000")
150
161
  uvicorn.run(
151
162
  "api.server:app",
@@ -0,0 +1,40 @@
1
+ # ─────────────────────────────────────────────
2
+ # YOUR API KEYS (optional)
3
+ # Gitcast works out of the box with shared keys.
4
+ # Add your own keys for higher rate limits.
5
+ # Your key always takes priority over shared key.
6
+ # ─────────────────────────────────────────────
7
+ # Groq — primary AI inference
8
+ GROQ_API_KEY=
9
+
10
+ # DeepSeek & Moonshot — specialized/free-tier fallbacks
11
+ DEEPSEEK_API_KEY=
12
+ MOONSHOT_API_KEY=
13
+ CEREBRAS_API_KEY=
14
+ OPENROUTER_API_KEY=
15
+
16
+ # Gemini — vision fallback (when OCR confidence is too low)
17
+ GEMINI_API_KEY=
18
+
19
+ # Optional model overrides
20
+ GROQ_MODEL=llama-3.3-70b-versatile
21
+ DEEPSEEK_MODEL=deepseek-chat
22
+ MOONSHOT_MODEL=moonshot-v1-8k
23
+ GEMINI_MODEL=gemini-2.0-flash
24
+ CEREBRAS_MODEL=llama-3.3-70b
25
+ OPENROUTER_MODEL=meta-llama/llama-3.3-70b-instruct:free
26
+
27
+ # X (Twitter) API v2 — all five are required for posting
28
+ TWITTER_API_KEY=your_twitter_api_key_here
29
+ TWITTER_API_SECRET=your_twitter_api_secret_here
30
+ TWITTER_ACCESS_TOKEN=your_twitter_access_token_here
31
+ TWITTER_ACCESS_SECRET=your_twitter_access_secret_here
32
+ TWITTER_BEARER_TOKEN=your_twitter_bearer_token_here
33
+
34
+ # ─────────────────────────────────────────────
35
+ # DO NOT EDIT — Maintainer base keys
36
+ # These are set automatically by the package
37
+ # ─────────────────────────────────────────────
38
+ GITCAST_BASE_GROQ_KEY=
39
+ GITCAST_BASE_DEEPSEEK_KEY=
40
+ GITCAST_BASE_GEMINI_KEY=
@@ -67,6 +67,24 @@ def main():
67
67
  # open browser
68
68
  webbrowser.open("http://localhost:8000")
69
69
 
70
+ # ASCII Logo
71
+ print(" ██████╗ ██╗████████╗ ██████╗ █████╗ ███████╗████████╗")
72
+ print("██╔════╝ ██║╚══██╔══╝██╔════╝ ██╔══██╗ ██╔════╝╚══██╔══╝")
73
+ print("██║ ███╗██║ ██║ ██║ ███████║ ███████╗ ██║ ")
74
+ print("██║ ██║██║ ██║ ██║ ██╔══██║ ╚════██║ ██║ ")
75
+ print("╚██████╔╝██║ ██║ ╚██████╗ ██║ ██║ ███████║ ██║ ")
76
+ print(" ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ")
77
+
78
+ from config.settings import USING_BASE_KEYS
79
+
80
+ if USING_BASE_KEYS:
81
+ print("> [OK] using Gitcast shared API key")
82
+ print("> // want your own key? run: "
83
+ "gitcast --setup")
84
+ print("> // get free key: console.groq.com")
85
+ else:
86
+ print("> [OK] using your configured API keys")
87
+
70
88
  print("[OK] server running at http://localhost:8000")
71
89
  print("[OK] browser opened")
72
90
 
@@ -84,13 +84,16 @@ API_KEY_ENV_MAP = {
84
84
  "kimi": "MOONSHOT_API_KEY",
85
85
  "cerebras": "CEREBRAS_API_KEY",
86
86
  "openrouter": "OPENROUTER_API_KEY",
87
+ "deepseek": "DEEPSEEK_API_KEY",
87
88
  }
88
89
 
89
90
  GROQ_API_KEY = ""
91
+ DEEPSEEK_API_KEY = ""
90
92
  MOONSHOT_API_KEY = ""
91
93
  GEMINI_API_KEY = ""
92
94
  CEREBRAS_API_KEY = ""
93
95
  OPENROUTER_API_KEY = ""
96
+ USING_BASE_KEYS = False
94
97
  GROQ_MODEL = ""
95
98
  MOONSHOT_MODEL = ""
96
99
  GEMINI_MODEL = ""
@@ -100,10 +103,12 @@ OPENROUTER_MODEL = ""
100
103
 
101
104
  def reload_api_keys() -> None:
102
105
  global GROQ_API_KEY
106
+ global DEEPSEEK_API_KEY
103
107
  global MOONSHOT_API_KEY
104
108
  global GEMINI_API_KEY
105
109
  global CEREBRAS_API_KEY
106
110
  global OPENROUTER_API_KEY
111
+ global USING_BASE_KEYS
107
112
  global GROQ_MODEL
108
113
  global MOONSHOT_MODEL
109
114
  global GEMINI_MODEL
@@ -116,11 +121,26 @@ def reload_api_keys() -> None:
116
121
  global SUPABASE_JWT_AUDIENCE
117
122
 
118
123
  load_all_dotenvs(override=True)
119
- GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
124
+
125
+ # Base keys — maintainer's keys as default fallback
126
+ # Users can override by setting their own in .env
127
+ _BASE_GROQ = os.getenv("GITCAST_BASE_GROQ_KEY", "")
128
+ _BASE_DEEPSEEK = os.getenv("GITCAST_BASE_DEEPSEEK_KEY", "")
129
+ _BASE_GEMINI = os.getenv("GITCAST_BASE_GEMINI_KEY", "")
130
+
131
+ # User key takes priority, falls back to base key
132
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY") or _BASE_GROQ
133
+ DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY") or _BASE_DEEPSEEK
134
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or _BASE_GEMINI
120
135
  MOONSHOT_API_KEY = os.getenv("MOONSHOT_API_KEY", "")
121
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
122
- CEREBRAS_API_KEY = os.getenv("CEREBRAS_API_KEY", "")
123
136
  OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
137
+ CEREBRAS_API_KEY = os.getenv("CEREBRAS_API_KEY", "")
138
+
139
+ # Flag so the app knows which mode it's in
140
+ USING_BASE_KEYS = (
141
+ not os.getenv("GROQ_API_KEY") and bool(_BASE_GROQ)
142
+ )
143
+
124
144
  GROQ_MODEL = os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
125
145
  MOONSHOT_MODEL = os.getenv("MOONSHOT_MODEL", "moonshot-v1-8k")
126
146
  GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.0-flash")
@@ -8,6 +8,7 @@ def start_hotkey_listener(callback):
8
8
  callback()
9
9
 
10
10
  _listener = keyboard.GlobalHotKeys({
11
+ '<ctrl>+<shift>+p': on_activate,
11
12
  '<ctrl>+<alt>+s': on_activate
12
13
  })
13
14
  _listener.start()
@@ -80,4 +80,12 @@ This document records the issues faced during the testing, packaging, and execut
80
80
  ```
81
81
  * **Status**: Logged and waiting for future triage. The application starts, but key bindings or endpoints triggers are failing to generate the actual post output.
82
82
 
83
+ ### Issue 2.4: 401 Unauthorized API Errors and Failure to Generate Posts on Directory/Venv Switch (gitcast==1.0.10)
84
+ * **Symptom**: Running `gitcast` from a different project directory (`snakegame`) using a different virtual environment leads to `api/keys/status:1 Failed to load resource: 401 (Unauthorized)` in the browser console, and post generation does not trigger.
85
+ * **Root Cause**:
86
+ 1. **Token Authentication Bypass Failure**: The browser's attempt to retrieve the active session token from `/api/token` returns `403 Forbidden` if the request host is mapped as an IPv4-mapped IPv6 address (e.g. `::ffff:127.0.0.1`). The frontend then falls back to using the old `localStorage` token from a previous run (e.g. in `context-engine`), which the FastAPI backend rejects with a `401 Unauthorized` status.
87
+ 2. **Wrong Working Directory Resolution**: If the target directory (`snakegame`) is not a Git repository, `detect_working_directory()` falls back to candidates in the list, matching the parent `context-engine` directory. Diffs and history are scanned from the wrong project, preventing post generation for the active context.
88
+ * **Status**: Logged in detail in [pypit-error.md](file:///mnt/c/Users/USER/Documents/context-engine/pypit-error.md) and awaiting resolution.
89
+
90
+
83
91
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitcast
3
- Version: 1.0.9
3
+ Version: 1.0.11
4
4
  Summary: git diff → published post. under 60 seconds.
5
5
  Home-page: https://github.com/drizzy765/gitcast
6
6
  Author: Timilehin Agoro
@@ -51,7 +51,7 @@ if os.path.exists(requirements_path):
51
51
 
52
52
  setup(
53
53
  name="gitcast",
54
- version="1.0.9",
54
+ version="1.0.11",
55
55
  description="git diff → published post. under 60 seconds.",
56
56
  long_description=long_description,
57
57
  long_description_content_type="text/markdown",
@@ -1984,8 +1984,11 @@
1984
1984
  <span class="provider-name">OPENROUTER</span>
1985
1985
  <span class="provider-status" id="status-openrouter">[ACTIVE]</span>
1986
1986
  </div>
1987
- </div>
1988
- </div>
1987
+ </div>
1988
+ <div id="shared-key-note" class="shared-key-note hidden" style="font-size: 10px; color: #ffb800; margin-top: 8px; font-family: var(--font-mono);">
1989
+ // using shared key · add yours: gitcast --setup
1990
+ </div>
1991
+ </div>
1989
1992
 
1990
1993
  <div class="sidebar-spacer"></div>
1991
1994
  <div class="sidebar-auth-container">
@@ -2653,20 +2656,49 @@
2653
2656
  if (keys) {
2654
2657
  state.keys = keys;
2655
2658
  const onlineProviders = [];
2659
+ const usingBaseKeys = !!keys.using_base_keys;
2660
+
2656
2661
  KEY_PROVIDERS.forEach(k => {
2657
2662
  const el = document.getElementById(`l-${k}`);
2658
2663
  const lbl = document.getElementById(`status-${k}`);
2659
2664
  const active = !!keys[k];
2660
2665
 
2661
- if (el) el.classList.toggle('online', active);
2666
+ if (el) {
2667
+ el.classList.toggle('online', active);
2668
+ if (active) {
2669
+ if (k === 'groq' && usingBaseKeys) {
2670
+ el.style.color = '#ffb800'; // Amber for shared key
2671
+ } else {
2672
+ el.style.color = ''; // Use CSS default
2673
+ }
2674
+ } else {
2675
+ el.style.color = '';
2676
+ }
2677
+ }
2662
2678
  if (lbl) {
2663
- lbl.innerText = active ? '[ACTIVE]' : '[MISSING]';
2664
- lbl.style.color = active ? 'var(--accent-green)' : 'var(--text-secondary)';
2679
+ if (k === 'groq' && usingBaseKeys) {
2680
+ lbl.innerText = '[SHARED]';
2681
+ lbl.style.color = '#ffb800';
2682
+ } else {
2683
+ lbl.innerText = active ? '[ACTIVE]' : '[MISSING]';
2684
+ lbl.style.color = active ? 'var(--accent-green)' : 'var(--text-secondary)';
2685
+ }
2665
2686
  }
2666
2687
  if (active) {
2667
2688
  onlineProviders.push(k.toUpperCase());
2668
2689
  }
2669
2690
  });
2691
+
2692
+ // Toggle note display
2693
+ const noteEl = document.getElementById('shared-key-note');
2694
+ if (noteEl) {
2695
+ if (usingBaseKeys) {
2696
+ noteEl.classList.remove('hidden');
2697
+ } else {
2698
+ noteEl.classList.add('hidden');
2699
+ }
2700
+ }
2701
+
2670
2702
  document.getElementById('status-bar-providers').innerText = onlineProviders.length > 0 ? `[${onlineProviders.join('/')}]` : '[NO_AI]';
2671
2703
  }
2672
2704
  const guide = await api('/api/keys/guide');
@@ -3626,7 +3658,7 @@
3626
3658
  function renderApiKeyRows() {
3627
3659
  return KEY_PROVIDERS.map(provider => {
3628
3660
  const guide = state.keyGuide[provider] || {};
3629
- const active = !!state.keys[provider]?.configured;
3661
+ const active = state.keys[provider]?.configured !== undefined ? !!state.keys[provider]?.configured : !!state.keys[provider];
3630
3662
  const status = active ? '[ACTIVE ✓]' : '[NOT_CONFIGURED]';
3631
3663
  const statusClass = active ? 'active' : 'missing';
3632
3664
  const preview = active ? '[●●●●●●●●...set]' : '[no_key_saved]';
@@ -1,26 +0,0 @@
1
- # Groq — primary AI inference
2
- GROQ_API_KEY=your_groq_api_key_here
3
-
4
- # DeepSeek & Moonshot — specialized/free-tier fallbacks
5
- DEEPSEEK_API_KEY=your_deepseek_api_key_here
6
- MOONSHOT_API_KEY=your_moonshot_api_key_here
7
- CEREBRAS_API_KEY=your_cerebras_api_key_here
8
- OPENROUTER_API_KEY=your_openrouter_api_key_here
9
-
10
- # Gemini — vision fallback (when OCR confidence is too low)
11
- GEMINI_API_KEY=your_gemini_api_key_here
12
-
13
- # Optional model overrides
14
- GROQ_MODEL=llama-3.3-70b-versatile
15
- DEEPSEEK_MODEL=deepseek-chat
16
- MOONSHOT_MODEL=moonshot-v1-8k
17
- GEMINI_MODEL=gemini-2.0-flash
18
- CEREBRAS_MODEL=llama-3.3-70b
19
- OPENROUTER_MODEL=meta-llama/llama-3.3-70b-instruct:free
20
-
21
- # X (Twitter) API v2 — all five are required for posting
22
- TWITTER_API_KEY=your_twitter_api_key_here
23
- TWITTER_API_SECRET=your_twitter_api_secret_here
24
- TWITTER_ACCESS_TOKEN=your_twitter_access_token_here
25
- TWITTER_ACCESS_SECRET=your_twitter_access_secret_here
26
- TWITTER_BEARER_TOKEN=your_twitter_bearer_token_here
@@ -1,26 +0,0 @@
1
- # Groq — primary AI inference
2
- GROQ_API_KEY=your_groq_api_key_here
3
-
4
- # DeepSeek & Moonshot — specialized/free-tier fallbacks
5
- DEEPSEEK_API_KEY=your_deepseek_api_key_here
6
- MOONSHOT_API_KEY=your_moonshot_api_key_here
7
- CEREBRAS_API_KEY=your_cerebras_api_key_here
8
- OPENROUTER_API_KEY=your_openrouter_api_key_here
9
-
10
- # Gemini — vision fallback (when OCR confidence is too low)
11
- GEMINI_API_KEY=your_gemini_api_key_here
12
-
13
- # Optional model overrides
14
- GROQ_MODEL=llama-3.3-70b-versatile
15
- DEEPSEEK_MODEL=deepseek-chat
16
- MOONSHOT_MODEL=moonshot-v1-8k
17
- GEMINI_MODEL=gemini-2.0-flash
18
- CEREBRAS_MODEL=llama-3.3-70b
19
- OPENROUTER_MODEL=meta-llama/llama-3.3-70b-instruct:free
20
-
21
- # X (Twitter) API v2 — all five are required for posting
22
- TWITTER_API_KEY=your_twitter_api_key_here
23
- TWITTER_API_SECRET=your_twitter_api_secret_here
24
- TWITTER_ACCESS_TOKEN=your_twitter_access_token_here
25
- TWITTER_ACCESS_SECRET=your_twitter_access_secret_here
26
- TWITTER_BEARER_TOKEN=your_twitter_bearer_token_here
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes