gitcast 1.0.4__tar.gz → 1.0.5__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 (84) hide show
  1. {gitcast-1.0.4 → gitcast-1.0.5}/.env.example +0 -0
  2. {gitcast-1.0.4 → gitcast-1.0.5}/LICENSE +0 -0
  3. {gitcast-1.0.4 → gitcast-1.0.5}/MANIFEST.in +0 -0
  4. {gitcast-1.0.4/gitcast.egg-info → gitcast-1.0.5}/PKG-INFO +50 -77
  5. {gitcast-1.0.4 → gitcast-1.0.5}/README.md +0 -0
  6. {gitcast-1.0.4 → gitcast-1.0.5}/ai/__init__.py +0 -0
  7. {gitcast-1.0.4 → gitcast-1.0.5}/ai/formatter.py +0 -0
  8. {gitcast-1.0.4 → gitcast-1.0.5}/ai/generator.py +0 -0
  9. {gitcast-1.0.4 → gitcast-1.0.5}/ai/prompts.py +0 -0
  10. {gitcast-1.0.4 → gitcast-1.0.5}/ai/viral_patterns.py +0 -0
  11. {gitcast-1.0.4 → gitcast-1.0.5}/api/__init__.py +0 -0
  12. {gitcast-1.0.4 → gitcast-1.0.5}/api/analytics.py +0 -0
  13. {gitcast-1.0.4 → gitcast-1.0.5}/api/auth.py +0 -0
  14. {gitcast-1.0.4 → gitcast-1.0.5}/api/auth_middleware.py +0 -0
  15. {gitcast-1.0.4 → gitcast-1.0.5}/api/auth_routes.py +0 -0
  16. {gitcast-1.0.4 → gitcast-1.0.5}/api/monitoring.py +0 -0
  17. {gitcast-1.0.4 → gitcast-1.0.5}/api/payload.py +0 -0
  18. {gitcast-1.0.4 → gitcast-1.0.5}/api/ratelimit.py +0 -0
  19. {gitcast-1.0.4 → gitcast-1.0.5}/api/routes.py +2 -1
  20. {gitcast-1.0.4 → gitcast-1.0.5}/api/server.py +0 -0
  21. {gitcast-1.0.4 → gitcast-1.0.5}/api/validators.py +0 -0
  22. {gitcast-1.0.4 → gitcast-1.0.5}/assets/__init__.py +0 -0
  23. {gitcast-1.0.4 → gitcast-1.0.5}/assets/favicon-16x16.png +0 -0
  24. {gitcast-1.0.4 → gitcast-1.0.5}/assets/favicon-32x32.png +0 -0
  25. {gitcast-1.0.4 → gitcast-1.0.5}/assets/favicon-64x64.png +0 -0
  26. {gitcast-1.0.4 → gitcast-1.0.5}/assets/favicon.ico +0 -0
  27. {gitcast-1.0.4 → gitcast-1.0.5}/assets/icon.png +0 -0
  28. {gitcast-1.0.4 → gitcast-1.0.5}/cli/.env.example +0 -0
  29. {gitcast-1.0.4 → gitcast-1.0.5}/cli/__init__.py +0 -0
  30. {gitcast-1.0.4 → gitcast-1.0.5}/cli/gitcast.py +2 -2
  31. {gitcast-1.0.4 → gitcast-1.0.5}/config/__init__.py +0 -0
  32. {gitcast-1.0.4 → gitcast-1.0.5}/config/settings.py +59 -4
  33. {gitcast-1.0.4 → gitcast-1.0.5}/core/__init__.py +0 -0
  34. {gitcast-1.0.4 → gitcast-1.0.5}/core/capture.py +0 -0
  35. {gitcast-1.0.4 → gitcast-1.0.5}/core/codebase_reader.py +0 -0
  36. {gitcast-1.0.4 → gitcast-1.0.5}/core/framing.py +0 -0
  37. {gitcast-1.0.4 → gitcast-1.0.5}/core/hotkey.py +0 -0
  38. {gitcast-1.0.4 → gitcast-1.0.5}/core/log_stream.py +0 -0
  39. {gitcast-1.0.4 → gitcast-1.0.5}/core/ocr.py +0 -0
  40. {gitcast-1.0.4 → gitcast-1.0.5}/core/screenshot_session.py +0 -0
  41. {gitcast-1.0.4 → gitcast-1.0.5}/core/security.py +0 -0
  42. {gitcast-1.0.4 → gitcast-1.0.5}/core/tray.py +0 -0
  43. {gitcast-1.0.4 → gitcast-1.0.5}/docs/api-keys.md +0 -0
  44. {gitcast-1.0.4 → gitcast-1.0.5}/docs/article-generator.md +0 -0
  45. {gitcast-1.0.4 → gitcast-1.0.5}/docs/capture-flow.md +0 -0
  46. {gitcast-1.0.4 → gitcast-1.0.5}/docs/changelog.md +0 -0
  47. {gitcast-1.0.4 → gitcast-1.0.5}/docs/contributing.md +0 -0
  48. {gitcast-1.0.4 → gitcast-1.0.5}/docs/dashboard.md +0 -0
  49. {gitcast-1.0.4 → gitcast-1.0.5}/docs/insights.md +0 -0
  50. {gitcast-1.0.4 → gitcast-1.0.5}/docs/introduction.md +0 -0
  51. {gitcast-1.0.4 → gitcast-1.0.5}/docs/post-formats.md +0 -0
  52. {gitcast-1.0.4 → gitcast-1.0.5}/docs/providers.md +0 -0
  53. {gitcast-1.0.4 → gitcast-1.0.5}/docs/quickstart.md +0 -0
  54. {gitcast-1.0.4 → gitcast-1.0.5}/docs/sprint-mode.md +0 -0
  55. {gitcast-1.0.4 → gitcast-1.0.5/gitcast.egg-info}/PKG-INFO +50 -77
  56. {gitcast-1.0.4 → gitcast-1.0.5}/gitcast.egg-info/SOURCES.txt +0 -2
  57. {gitcast-1.0.4 → gitcast-1.0.5}/gitcast.egg-info/entry_points.txt +1 -0
  58. {gitcast-1.0.4 → gitcast-1.0.5}/publisher/__init__.py +0 -0
  59. {gitcast-1.0.4 → gitcast-1.0.5}/publisher/clipboard.py +0 -0
  60. {gitcast-1.0.4 → gitcast-1.0.5}/publisher/twitter.py +0 -0
  61. {gitcast-1.0.4 → gitcast-1.0.5}/pyproject.toml +0 -0
  62. {gitcast-1.0.4 → gitcast-1.0.5}/requirements.txt +0 -0
  63. {gitcast-1.0.4 → gitcast-1.0.5}/setup.cfg +4 -4
  64. {gitcast-1.0.4 → gitcast-1.0.5}/setup.py +1 -1
  65. {gitcast-1.0.4 → gitcast-1.0.5}/storage/__init__.py +0 -0
  66. {gitcast-1.0.4 → gitcast-1.0.5}/storage/cleanup.py +0 -0
  67. {gitcast-1.0.4 → gitcast-1.0.5}/storage/engagement.py +0 -0
  68. {gitcast-1.0.4 → gitcast-1.0.5}/storage/insights.py +0 -0
  69. {gitcast-1.0.4 → gitcast-1.0.5}/storage/key_manager.py +0 -0
  70. {gitcast-1.0.4 → gitcast-1.0.5}/storage/logger.py +0 -0
  71. {gitcast-1.0.4 → gitcast-1.0.5}/storage/metrics.py +0 -0
  72. {gitcast-1.0.4 → gitcast-1.0.5}/storage/sprint.py +0 -0
  73. {gitcast-1.0.4 → gitcast-1.0.5}/storage/streak.py +0 -0
  74. {gitcast-1.0.4 → gitcast-1.0.5}/storage/supabase_client.py +14 -4
  75. {gitcast-1.0.4 → gitcast-1.0.5}/storage/tone_memory.py +0 -0
  76. {gitcast-1.0.4 → gitcast-1.0.5}/ui/__init__.py +0 -0
  77. {gitcast-1.0.4 → gitcast-1.0.5}/web/__init__.py +0 -0
  78. {gitcast-1.0.4 → gitcast-1.0.5}/web/index.html +0 -0
  79. {gitcast-1.0.4 → gitcast-1.0.5}/web/landing.html +0 -0
  80. gitcast-1.0.4/tests/test_keys.py +0 -86
  81. gitcast-1.0.4/tests/test_ocr_fix.py +0 -77
  82. {gitcast-1.0.4 → gitcast-1.0.5}/gitcast.egg-info/dependency_links.txt +0 -0
  83. {gitcast-1.0.4 → gitcast-1.0.5}/gitcast.egg-info/requires.txt +0 -0
  84. {gitcast-1.0.4 → gitcast-1.0.5}/gitcast.egg-info/top_level.txt +0 -0
File without changes
File without changes
File without changes
@@ -1,77 +1,50 @@
1
- Metadata-Version: 2.4
2
- Name: gitcast
3
- Version: 1.0.4
4
- Summary: git diff → published post. under 60 seconds.
5
- Home-page: https://github.com/drizzy765/gitcast
6
- Author: Timilehin Agoro
7
- Author-email: agorotimilehi05@gmail.com
8
- License: MIT
9
- Requires-Python: >=3.10
10
- Description-Content-Type: text/markdown
11
- License-File: LICENSE
12
- Requires-Dist: pynput
13
- Requires-Dist: pystray
14
- Requires-Dist: Pillow
15
- Requires-Dist: fastapi
16
- Requires-Dist: uvicorn
17
- Requires-Dist: python-dotenv
18
- Requires-Dist: groq
19
- Requires-Dist: tweepy
20
- Requires-Dist: mss
21
- Requires-Dist: pytesseract
22
- Requires-Dist: httpx
23
- Requires-Dist: supabase
24
- Requires-Dist: cryptography
25
- Requires-Dist: python-jose[cryptography]
26
- Requires-Dist: rich
27
- Requires-Dist: keyboard
28
- Requires-Dist: slowapi
29
- Requires-Dist: limits
30
- Requires-Dist: sentry-sdk[fastapi]
31
- Requires-Dist: posthog
32
- Dynamic: author
33
- Dynamic: author-email
34
- Dynamic: description
35
- Dynamic: description-content-type
36
- Dynamic: home-page
37
- Dynamic: license
38
- Dynamic: license-file
39
- Dynamic: requires-dist
40
- Dynamic: requires-python
41
- Dynamic: summary
42
-
43
- # Gitcast
44
-
45
- On-demand, privacy-first desktop utility that captures active window context,
46
- extracts code changes, and generates platform-ready X (Twitter) posts with one keystroke.
47
-
48
- ## Install
49
-
50
- ```bash
51
- pip install gitcast
52
- ```
53
-
54
- ## Setup
55
-
56
- 1. Install Tesseract OCR:
57
- - Windows: https://github.com/UB-Mannheim/tesseract/wiki
58
- - Mac: `brew install tesseract`
59
- - Linux: `sudo apt install tesseract-ocr`
60
-
61
- 2. Add your API keys:
62
- ```bash
63
- gitcast --setup
64
- ```
65
- (opens `.env` file for editing)
66
-
67
- 3. Run:
68
- ```bash
69
- gitcast
70
- ```
71
-
72
- Dashboard opens at http://127.0.0.1:8000
73
- Press Ctrl+Shift+P from anywhere to capture.
74
-
75
- ## Stack
76
- - Python 3.11+
77
- - FastAPI, pynput, mss, Tesseract, Groq API, Tweepy
1
+ Metadata-Version: 2.1
2
+ Name: gitcast
3
+ Version: 1.0.5
4
+ Summary: git diff → published post. under 60 seconds.
5
+ Home-page: https://github.com/drizzy765/gitcast
6
+ Author: Timilehin Agoro
7
+ Author-email: agorotimilehi05@gmail.com
8
+ License: MIT
9
+ Platform: UNKNOWN
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+
14
+ # Gitcast
15
+
16
+ On-demand, privacy-first desktop utility that captures active window context,
17
+ extracts code changes, and generates platform-ready X (Twitter) posts with one keystroke.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install gitcast
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ 1. Install Tesseract OCR:
28
+ - Windows: https://github.com/UB-Mannheim/tesseract/wiki
29
+ - Mac: `brew install tesseract`
30
+ - Linux: `sudo apt install tesseract-ocr`
31
+
32
+ 2. Add your API keys:
33
+ ```bash
34
+ gitcast --setup
35
+ ```
36
+ (opens `.env` file for editing)
37
+
38
+ 3. Run:
39
+ ```bash
40
+ gitcast
41
+ ```
42
+
43
+ Dashboard opens at http://127.0.0.1:8000
44
+ Press Ctrl+Shift+P from anywhere to capture.
45
+
46
+ ## Stack
47
+ - Python 3.11+
48
+ - FastAPI, pynput, mss, Tesseract, Groq API, Tweepy
49
+
50
+
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
@@ -445,7 +445,8 @@ KEY_GUIDE = {
445
445
 
446
446
 
447
447
  def _env_path():
448
- path = BASE_DIR / ".env"
448
+ from config.settings import get_active_env_path
449
+ path = get_active_env_path(for_write=True)
449
450
  path.touch(exist_ok=True)
450
451
  return path
451
452
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -7,8 +7,8 @@ import webbrowser
7
7
 
8
8
  def main():
9
9
  if "--setup" in sys.argv:
10
- env_path = os.path.join(
11
- os.path.dirname(__file__), '..', '.env')
10
+ from config.settings import get_active_env_path
11
+ env_path = str(get_active_env_path(for_write=True))
12
12
  example_path = os.path.join(
13
13
  os.path.dirname(__file__), '..', '.env.example')
14
14
  if not os.path.exists(example_path):
File without changes
@@ -5,11 +5,55 @@ from pathlib import Path
5
5
  from dotenv import load_dotenv
6
6
 
7
7
 
8
+ import sys
9
+
8
10
  BASE_DIR = Path(__file__).resolve().parent.parent
9
11
  CONFIG_DIR = BASE_DIR / "config"
10
- load_dotenv(BASE_DIR / ".env")
11
- if not os.getenv("SUPABASE_URL"):
12
- load_dotenv(Path(os.getcwd()) / ".env")
12
+
13
+ def get_env_paths() -> list:
14
+ paths = []
15
+ # 1. Custom env path
16
+ custom_path = os.getenv("GITCAST_ENV_PATH")
17
+ if custom_path:
18
+ paths.append(Path(custom_path))
19
+ # 2. Current working directory
20
+ paths.append(Path(os.getcwd()) / ".env")
21
+ # 3. Virtual environment parent
22
+ if hasattr(sys, "prefix") and sys.prefix:
23
+ paths.append(Path(sys.prefix).parent / ".env")
24
+ # 4. User home directories
25
+ paths.append(Path.home() / ".gitcast" / ".env")
26
+ paths.append(Path.home() / ".gitcast.env")
27
+ # 5. Base directory
28
+ paths.append(BASE_DIR / ".env")
29
+ return [p for p in paths if p]
30
+
31
+ def load_all_dotenvs(override: bool = False) -> None:
32
+ paths = get_env_paths()
33
+ if override:
34
+ for p in reversed(paths):
35
+ if p.exists() and p.is_file():
36
+ load_dotenv(p, override=True)
37
+ else:
38
+ for p in paths:
39
+ if p.exists() and p.is_file():
40
+ load_dotenv(p, override=False)
41
+
42
+ def get_active_env_path(for_write: bool = False) -> Path:
43
+ paths = get_env_paths()
44
+ for p in paths:
45
+ if p.exists() and p.is_file():
46
+ return p
47
+ if for_write:
48
+ home_env = Path.home() / ".gitcast" / ".env"
49
+ try:
50
+ home_env.parent.mkdir(parents=True, exist_ok=True)
51
+ return home_env
52
+ except Exception:
53
+ pass
54
+ return BASE_DIR / ".env"
55
+
56
+ load_all_dotenvs()
13
57
  STORAGE_DIR = BASE_DIR / "storage" / "data"
14
58
  POSTHOG_API_KEY = os.getenv("POSTHOG_API_KEY", "")
15
59
  SENTRY_DSN = os.getenv("SENTRY_DSN", "")
@@ -65,8 +109,13 @@ def reload_api_keys() -> None:
65
109
  global GEMINI_MODEL
66
110
  global CEREBRAS_MODEL
67
111
  global OPENROUTER_MODEL
112
+ global SUPABASE_URL
113
+ global SUPABASE_SERVICE_KEY
114
+ global SUPABASE_ANON_KEY
115
+ global SUPABASE_JWT_SECRET
116
+ global SUPABASE_JWT_AUDIENCE
68
117
 
69
- load_dotenv(BASE_DIR / ".env", override=True)
118
+ load_all_dotenvs(override=True)
70
119
  GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
71
120
  MOONSHOT_API_KEY = os.getenv("MOONSHOT_API_KEY", "")
72
121
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
@@ -77,6 +126,12 @@ def reload_api_keys() -> None:
77
126
  GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.0-flash")
78
127
  CEREBRAS_MODEL = os.getenv("CEREBRAS_MODEL", "gpt-oss-120b")
79
128
  OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL", "qwen/qwen3-coder:free")
129
+
130
+ SUPABASE_URL = os.getenv("SUPABASE_URL", "")
131
+ SUPABASE_SERVICE_KEY = os.getenv("SUPABASE_SERVICE_KEY", "")
132
+ SUPABASE_ANON_KEY = os.getenv("SUPABASE_ANON_KEY", "")
133
+ SUPABASE_JWT_SECRET = os.getenv("SUPABASE_JWT_SECRET", "")
134
+ SUPABASE_JWT_AUDIENCE = os.getenv("SUPABASE_JWT_AUDIENCE", "authenticated")
80
135
 
81
136
 
82
137
  reload_api_keys()
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
@@ -1,77 +1,50 @@
1
- Metadata-Version: 2.4
2
- Name: gitcast
3
- Version: 1.0.4
4
- Summary: git diff → published post. under 60 seconds.
5
- Home-page: https://github.com/drizzy765/gitcast
6
- Author: Timilehin Agoro
7
- Author-email: agorotimilehi05@gmail.com
8
- License: MIT
9
- Requires-Python: >=3.10
10
- Description-Content-Type: text/markdown
11
- License-File: LICENSE
12
- Requires-Dist: pynput
13
- Requires-Dist: pystray
14
- Requires-Dist: Pillow
15
- Requires-Dist: fastapi
16
- Requires-Dist: uvicorn
17
- Requires-Dist: python-dotenv
18
- Requires-Dist: groq
19
- Requires-Dist: tweepy
20
- Requires-Dist: mss
21
- Requires-Dist: pytesseract
22
- Requires-Dist: httpx
23
- Requires-Dist: supabase
24
- Requires-Dist: cryptography
25
- Requires-Dist: python-jose[cryptography]
26
- Requires-Dist: rich
27
- Requires-Dist: keyboard
28
- Requires-Dist: slowapi
29
- Requires-Dist: limits
30
- Requires-Dist: sentry-sdk[fastapi]
31
- Requires-Dist: posthog
32
- Dynamic: author
33
- Dynamic: author-email
34
- Dynamic: description
35
- Dynamic: description-content-type
36
- Dynamic: home-page
37
- Dynamic: license
38
- Dynamic: license-file
39
- Dynamic: requires-dist
40
- Dynamic: requires-python
41
- Dynamic: summary
42
-
43
- # Gitcast
44
-
45
- On-demand, privacy-first desktop utility that captures active window context,
46
- extracts code changes, and generates platform-ready X (Twitter) posts with one keystroke.
47
-
48
- ## Install
49
-
50
- ```bash
51
- pip install gitcast
52
- ```
53
-
54
- ## Setup
55
-
56
- 1. Install Tesseract OCR:
57
- - Windows: https://github.com/UB-Mannheim/tesseract/wiki
58
- - Mac: `brew install tesseract`
59
- - Linux: `sudo apt install tesseract-ocr`
60
-
61
- 2. Add your API keys:
62
- ```bash
63
- gitcast --setup
64
- ```
65
- (opens `.env` file for editing)
66
-
67
- 3. Run:
68
- ```bash
69
- gitcast
70
- ```
71
-
72
- Dashboard opens at http://127.0.0.1:8000
73
- Press Ctrl+Shift+P from anywhere to capture.
74
-
75
- ## Stack
76
- - Python 3.11+
77
- - FastAPI, pynput, mss, Tesseract, Groq API, Tweepy
1
+ Metadata-Version: 2.1
2
+ Name: gitcast
3
+ Version: 1.0.5
4
+ Summary: git diff → published post. under 60 seconds.
5
+ Home-page: https://github.com/drizzy765/gitcast
6
+ Author: Timilehin Agoro
7
+ Author-email: agorotimilehi05@gmail.com
8
+ License: MIT
9
+ Platform: UNKNOWN
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+
14
+ # Gitcast
15
+
16
+ On-demand, privacy-first desktop utility that captures active window context,
17
+ extracts code changes, and generates platform-ready X (Twitter) posts with one keystroke.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install gitcast
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ 1. Install Tesseract OCR:
28
+ - Windows: https://github.com/UB-Mannheim/tesseract/wiki
29
+ - Mac: `brew install tesseract`
30
+ - Linux: `sudo apt install tesseract-ocr`
31
+
32
+ 2. Add your API keys:
33
+ ```bash
34
+ gitcast --setup
35
+ ```
36
+ (opens `.env` file for editing)
37
+
38
+ 3. Run:
39
+ ```bash
40
+ gitcast
41
+ ```
42
+
43
+ Dashboard opens at http://127.0.0.1:8000
44
+ Press Ctrl+Shift+P from anywhere to capture.
45
+
46
+ ## Stack
47
+ - Python 3.11+
48
+ - FastAPI, pynput, mss, Tesseract, Groq API, Tweepy
49
+
50
+
@@ -74,8 +74,6 @@ storage/sprint.py
74
74
  storage/streak.py
75
75
  storage/supabase_client.py
76
76
  storage/tone_memory.py
77
- tests/test_keys.py
78
- tests/test_ocr_fix.py
79
77
  ui/__init__.py
80
78
  web/__init__.py
81
79
  web/index.html
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  gitcast = cli.gitcast:main
3
+
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,4 +1,4 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -19,7 +19,7 @@ if os.path.exists(requirements_path):
19
19
 
20
20
  setup(
21
21
  name="gitcast",
22
- version="1.0.4",
22
+ version="1.0.5",
23
23
  description="git diff → published post. under 60 seconds.",
24
24
  long_description=long_description,
25
25
  long_description_content_type="text/markdown",
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
@@ -1,22 +1,32 @@
1
1
  import threading
2
2
  from supabase import Client, create_client, ClientOptions
3
- from config.settings import SUPABASE_SERVICE_KEY, SUPABASE_URL
3
+ import config.settings
4
4
 
5
5
  _thread_local = threading.local()
6
6
 
7
7
  def get_client() -> Client:
8
- if not SUPABASE_URL or not SUPABASE_SERVICE_KEY:
8
+ url = getattr(config.settings, "SUPABASE_URL", "")
9
+ key = getattr(config.settings, "SUPABASE_SERVICE_KEY", "")
10
+ if not url or not key:
9
11
  raise RuntimeError("SUPABASE_URL and SUPABASE_SERVICE_KEY must be configured")
10
12
 
13
+ # Check if config changed and recreate if needed
14
+ if hasattr(_thread_local, "client"):
15
+ if getattr(_thread_local, "client_url", None) != url or getattr(_thread_local, "client_key", None) != key:
16
+ if hasattr(_thread_local, "client"):
17
+ delattr(_thread_local, "client")
18
+
11
19
  if not hasattr(_thread_local, "client"):
12
20
  _thread_local.client = create_client(
13
- SUPABASE_URL,
14
- SUPABASE_SERVICE_KEY,
21
+ url,
22
+ key,
15
23
  options=ClientOptions(
16
24
  postgrest_client_timeout=10,
17
25
  storage_client_timeout=10
18
26
  )
19
27
  )
28
+ _thread_local.client_url = url
29
+ _thread_local.client_key = key
20
30
  return _thread_local.client
21
31
 
22
32
 
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,86 +0,0 @@
1
- import asyncio
2
- import httpx
3
- import os
4
- import sys
5
- from pathlib import Path
6
-
7
- # Add project root to path so we can import config
8
- sys.path.append(str(Path(__file__).resolve().parent.parent))
9
-
10
- from config.settings import (
11
- GROQ_API_KEY, GEMINI_API_KEY,
12
- MOONSHOT_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY
13
- )
14
-
15
- async def test_provider(name, url, headers, body, is_gemini=False):
16
- print(f"Testing {name.upper()}...", end=" ", flush=True)
17
- async with httpx.AsyncClient(timeout=10) as client:
18
- try:
19
- if is_gemini:
20
- res = await client.post(f"{url}?key={GEMINI_API_KEY}", json=body)
21
- else:
22
- res = await client.post(url, headers=headers, json=body)
23
-
24
- if res.status_code == 200:
25
- print("✅ WORKING")
26
- return True
27
- else:
28
- print(f"❌ FAILED ({res.status_code})")
29
- try:
30
- error_msg = res.json().get('error', {}).get('message', res.text[:100])
31
- print(f" Reason: {error_msg}")
32
- except:
33
- print(f" Reason: {res.text[:100]}")
34
- return False
35
- except Exception as e:
36
- print(f"❌ ERROR: {str(e)}")
37
- return False
38
-
39
- async def main():
40
- print("=== Context Engine API Key Connectivity Test ===\n")
41
-
42
- # 1. Groq
43
- await test_provider(
44
- "Groq",
45
- "https://api.groq.com/openai/v1/chat/completions",
46
- {"Authorization": f"Bearer {GROQ_API_KEY}"},
47
- {"model": "llama-3.3-70b-versatile", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 5}
48
- )
49
-
50
- # 2. OpenRouter
51
- await test_provider(
52
- "OpenRouter",
53
- "https://openrouter.ai/api/v1/chat/completions",
54
- {"Authorization": f"Bearer {OPENROUTER_API_KEY}"},
55
- {"model": "qwen/qwen3-coder:free", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 5}
56
- )
57
-
58
- # 3. Kimi (Moonshot)
59
- await test_provider(
60
- "Moonshot (Kimi)",
61
- "https://api.moonshot.cn/v1/chat/completions",
62
- {"Authorization": f"Bearer {MOONSHOT_API_KEY}"},
63
- {"model": "moonshot-v1-8k", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 5}
64
- )
65
-
66
- # 4. Cerebras
67
- await test_provider(
68
- "Cerebras",
69
- "https://api.cerebras.ai/v1/chat/completions",
70
- {"Authorization": f"Bearer {CEREBRAS_API_KEY}"},
71
- {"model": "llama3.3-70b", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 5}
72
- )
73
-
74
- # 5. Gemini
75
- await test_provider(
76
- "Gemini",
77
- "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent",
78
- {},
79
- {"contents": [{"parts": [{"text": "hi"}]}]},
80
- is_gemini=True
81
- )
82
-
83
- print("\nTest complete.")
84
-
85
- if __name__ == "__main__":
86
- asyncio.run(main())
@@ -1,77 +0,0 @@
1
- import json
2
- import asyncio
3
- import sys
4
- from pathlib import Path
5
-
6
- sys.path.append(str(Path(__file__).resolve().parent.parent))
7
-
8
- from api.payload import build_payload
9
- from api.routes import generate_article, ArticleGenerateRequest
10
- from config.settings import CURRENT_DRAFT
11
- from fastapi import HTTPException
12
-
13
- async def test_ocr_text_in_payload():
14
- print("Testing ocr_text in build_payload...")
15
- ocr_result = {"text": "Detected Text", "confidence": 0.9}
16
- capture_result = {
17
- "screenshot": {"path": "test.png", "timestamp": "123"},
18
- "git_diff": {"diff": "some diff", "success": True}
19
- }
20
-
21
- payload = build_payload(
22
- raw_thought="my thought",
23
- ocr_result=ocr_result,
24
- capture_result=capture_result
25
- )
26
-
27
- assert "ocr_text" in payload
28
- assert payload["ocr_text"] == "Detected Text"
29
- print("✅ build_payload includes ocr_text")
30
-
31
- async def test_generate_article_robustness():
32
- print("Testing generate_article robustness with missing ocr_text in draft...")
33
- original_draft = CURRENT_DRAFT.read_text(encoding="utf-8") if CURRENT_DRAFT.exists() else None
34
- # Mock CURRENT_DRAFT
35
- draft_data = {
36
- "payload": {
37
- "user_message": "hello",
38
- # "ocr_text" is missing
39
- "git_diff": "diff"
40
- },
41
- "variations": {},
42
- "timestamp": "123",
43
- "status": "ready"
44
- }
45
-
46
- try:
47
- with open(CURRENT_DRAFT, "w", encoding="utf-8") as f:
48
- json.dump(draft_data, f)
49
-
50
- request = ArticleGenerateRequest(include_codebase=False)
51
-
52
- try:
53
- # This will still try to call AI, but we want to see if it passes the user_msg construction
54
- # We can't easily mock the AI call here without more setup,
55
- # but the KeyError would happen BEFORE the AI call.
56
-
57
- # To avoid actual AI call, we might just check the logic in routes.py
58
- # by calling the function and expecting it to fail at AI call but NOT with KeyError.
59
- await generate_article(request)
60
- except HTTPException as e:
61
- if "Article generation failed" in e.detail:
62
- print("✅ generate_article passed KeyError (failed at AI call as expected)")
63
- else:
64
- print(f"❌ generate_article failed with: {e.detail}")
65
- except KeyError as e:
66
- print(f"❌ generate_article still has KeyError: {e}")
67
- except Exception as e:
68
- print(f"✅ generate_article passed KeyError (failed with: {e})")
69
- finally:
70
- if original_draft is None:
71
- CURRENT_DRAFT.unlink(missing_ok=True)
72
- else:
73
- CURRENT_DRAFT.write_text(original_draft, encoding="utf-8")
74
-
75
- if __name__ == "__main__":
76
- asyncio.run(test_ocr_text_in_payload())
77
- asyncio.run(test_generate_article_robustness())