gitcast 1.0.25__tar.gz → 1.0.26__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 (99) hide show
  1. gitcast-1.0.26/.env.example +26 -0
  2. gitcast-1.0.26/PKG-INFO +183 -0
  3. gitcast-1.0.26/README.md +142 -0
  4. gitcast-1.0.26/ai/generator.py +165 -0
  5. {gitcast-1.0.25 → gitcast-1.0.26}/api/routes.py +142 -185
  6. {gitcast-1.0.25 → gitcast-1.0.26}/api/server.py +4 -6
  7. gitcast-1.0.26/cli/.env.example +26 -0
  8. gitcast-1.0.26/cli/gitcast.py +221 -0
  9. {gitcast-1.0.25 → gitcast-1.0.26}/config/settings.py +21 -81
  10. gitcast-1.0.26/core/cloud_client.py +163 -0
  11. gitcast-1.0.26/gitcast.egg-info/PKG-INFO +183 -0
  12. {gitcast-1.0.25 → gitcast-1.0.26}/gitcast.egg-info/SOURCES.txt +1 -0
  13. {gitcast-1.0.25 → gitcast-1.0.26}/gitcast.egg-info/requires.txt +1 -3
  14. {gitcast-1.0.25 → gitcast-1.0.26}/requirements.txt +5 -7
  15. {gitcast-1.0.25 → gitcast-1.0.26}/setup.py +2 -3
  16. {gitcast-1.0.25 → gitcast-1.0.26}/storage/logger.py +68 -33
  17. gitcast-1.0.26/tests/test_keys.py +30 -0
  18. {gitcast-1.0.25 → gitcast-1.0.26}/web/index.html +50 -93
  19. gitcast-1.0.25/.env.example +0 -40
  20. gitcast-1.0.25/PKG-INFO +0 -108
  21. gitcast-1.0.25/README.md +0 -65
  22. gitcast-1.0.25/ai/generator.py +0 -612
  23. gitcast-1.0.25/cli/.env.example +0 -40
  24. gitcast-1.0.25/cli/gitcast.py +0 -132
  25. gitcast-1.0.25/gitcast.egg-info/PKG-INFO +0 -108
  26. gitcast-1.0.25/tests/test_keys.py +0 -86
  27. {gitcast-1.0.25 → gitcast-1.0.26}/LICENSE +0 -0
  28. {gitcast-1.0.25 → gitcast-1.0.26}/MANIFEST.in +0 -0
  29. {gitcast-1.0.25 → gitcast-1.0.26}/ai/__init__.py +0 -0
  30. {gitcast-1.0.25 → gitcast-1.0.26}/ai/formatter.py +0 -0
  31. {gitcast-1.0.25 → gitcast-1.0.26}/ai/prompts.py +0 -0
  32. {gitcast-1.0.25 → gitcast-1.0.26}/ai/viral_patterns.py +0 -0
  33. {gitcast-1.0.25 → gitcast-1.0.26}/api/__init__.py +0 -0
  34. {gitcast-1.0.25 → gitcast-1.0.26}/api/analytics.py +0 -0
  35. {gitcast-1.0.25 → gitcast-1.0.26}/api/auth.py +0 -0
  36. {gitcast-1.0.25 → gitcast-1.0.26}/api/auth_middleware.py +0 -0
  37. {gitcast-1.0.25 → gitcast-1.0.26}/api/auth_routes.py +0 -0
  38. {gitcast-1.0.25 → gitcast-1.0.26}/api/monitoring.py +0 -0
  39. {gitcast-1.0.25 → gitcast-1.0.26}/api/payload.py +0 -0
  40. {gitcast-1.0.25 → gitcast-1.0.26}/api/ratelimit.py +0 -0
  41. {gitcast-1.0.25 → gitcast-1.0.26}/api/validators.py +0 -0
  42. {gitcast-1.0.25 → gitcast-1.0.26}/assets/__init__.py +0 -0
  43. {gitcast-1.0.25 → gitcast-1.0.26}/assets/favicon-16x16.png +0 -0
  44. {gitcast-1.0.25 → gitcast-1.0.26}/assets/favicon-32x32.png +0 -0
  45. {gitcast-1.0.25 → gitcast-1.0.26}/assets/favicon-64x64.png +0 -0
  46. {gitcast-1.0.25 → gitcast-1.0.26}/assets/favicon.ico +0 -0
  47. {gitcast-1.0.25 → gitcast-1.0.26}/assets/icon.png +0 -0
  48. {gitcast-1.0.25 → gitcast-1.0.26}/cli/__init__.py +0 -0
  49. {gitcast-1.0.25 → gitcast-1.0.26}/config/__init__.py +0 -0
  50. {gitcast-1.0.25 → gitcast-1.0.26}/core/__init__.py +0 -0
  51. {gitcast-1.0.25 → gitcast-1.0.26}/core/capture.py +0 -0
  52. {gitcast-1.0.25 → gitcast-1.0.26}/core/codebase_reader.py +0 -0
  53. {gitcast-1.0.25 → gitcast-1.0.26}/core/framing.py +0 -0
  54. {gitcast-1.0.25 → gitcast-1.0.26}/core/hotkey.py +0 -0
  55. {gitcast-1.0.25 → gitcast-1.0.26}/core/log_stream.py +0 -0
  56. {gitcast-1.0.25 → gitcast-1.0.26}/core/ocr.py +0 -0
  57. {gitcast-1.0.25 → gitcast-1.0.26}/core/screenshot_session.py +0 -0
  58. {gitcast-1.0.25 → gitcast-1.0.26}/core/security.py +0 -0
  59. {gitcast-1.0.25 → gitcast-1.0.26}/core/tray.py +0 -0
  60. {gitcast-1.0.25 → gitcast-1.0.26}/core/trigger.py +0 -0
  61. {gitcast-1.0.25 → gitcast-1.0.26}/docs/api-keys.md +0 -0
  62. {gitcast-1.0.25 → gitcast-1.0.26}/docs/article-generator.md +0 -0
  63. {gitcast-1.0.25 → gitcast-1.0.26}/docs/capture-flow.md +0 -0
  64. {gitcast-1.0.25 → gitcast-1.0.26}/docs/changelog.md +0 -0
  65. {gitcast-1.0.25 → gitcast-1.0.26}/docs/contributing.md +0 -0
  66. {gitcast-1.0.25 → gitcast-1.0.26}/docs/dashboard.md +0 -0
  67. {gitcast-1.0.25 → gitcast-1.0.26}/docs/insights.md +0 -0
  68. {gitcast-1.0.25 → gitcast-1.0.26}/docs/introduction.md +0 -0
  69. {gitcast-1.0.25 → gitcast-1.0.26}/docs/post-formats.md +0 -0
  70. {gitcast-1.0.25 → gitcast-1.0.26}/docs/providers.md +0 -0
  71. {gitcast-1.0.25 → gitcast-1.0.26}/docs/pypi-errors.md +0 -0
  72. {gitcast-1.0.25 → gitcast-1.0.26}/docs/quickstart.md +0 -0
  73. {gitcast-1.0.25 → gitcast-1.0.26}/docs/sprint-mode.md +0 -0
  74. {gitcast-1.0.25 → gitcast-1.0.26}/gitcast.egg-info/dependency_links.txt +0 -0
  75. {gitcast-1.0.25 → gitcast-1.0.26}/gitcast.egg-info/entry_points.txt +0 -0
  76. {gitcast-1.0.25 → gitcast-1.0.26}/gitcast.egg-info/top_level.txt +0 -0
  77. {gitcast-1.0.25 → gitcast-1.0.26}/publisher/__init__.py +0 -0
  78. {gitcast-1.0.25 → gitcast-1.0.26}/publisher/clipboard.py +0 -0
  79. {gitcast-1.0.25 → gitcast-1.0.26}/publisher/twitter.py +0 -0
  80. {gitcast-1.0.25 → gitcast-1.0.26}/pyproject.toml +0 -0
  81. {gitcast-1.0.25 → gitcast-1.0.26}/setup.cfg +0 -0
  82. {gitcast-1.0.25 → gitcast-1.0.26}/storage/__init__.py +0 -0
  83. {gitcast-1.0.25 → gitcast-1.0.26}/storage/cleanup.py +0 -0
  84. {gitcast-1.0.25 → gitcast-1.0.26}/storage/data/prompts.json +0 -0
  85. {gitcast-1.0.25 → gitcast-1.0.26}/storage/engagement.py +0 -0
  86. {gitcast-1.0.25 → gitcast-1.0.26}/storage/insights.py +0 -0
  87. {gitcast-1.0.25 → gitcast-1.0.26}/storage/key_manager.py +0 -0
  88. {gitcast-1.0.25 → gitcast-1.0.26}/storage/metrics.py +0 -0
  89. {gitcast-1.0.25 → gitcast-1.0.26}/storage/sprint.py +0 -0
  90. {gitcast-1.0.25 → gitcast-1.0.26}/storage/streak.py +0 -0
  91. {gitcast-1.0.25 → gitcast-1.0.26}/storage/supabase_client.py +0 -0
  92. {gitcast-1.0.25 → gitcast-1.0.26}/storage/tone_memory.py +0 -0
  93. {gitcast-1.0.25 → gitcast-1.0.26}/tests/test_ocr_fix.py +0 -0
  94. {gitcast-1.0.25 → gitcast-1.0.26}/tests/test_validation.py +0 -0
  95. {gitcast-1.0.25 → gitcast-1.0.26}/ui/__init__.py +0 -0
  96. {gitcast-1.0.25 → gitcast-1.0.26}/ui/popup.py +0 -0
  97. {gitcast-1.0.25 → gitcast-1.0.26}/ui/review.py +0 -0
  98. {gitcast-1.0.25 → gitcast-1.0.26}/web/__init__.py +0 -0
  99. {gitcast-1.0.25 → gitcast-1.0.26}/web/landing.html +0 -0
@@ -0,0 +1,26 @@
1
+ # ────────────────────────────────────────────
2
+ # GITCAST CONFIGURATION
3
+ # ────────────────────────────────────────────
4
+ # Gitcast works out of the box — no keys needed.
5
+ # The shared key is built in.
6
+ #
7
+ # Add your own key only if you want unlimited
8
+ # personal usage without shared rate limits.
9
+ # ────────────────────────────────────────────
10
+
11
+ # Your optional API key (BYOK)
12
+ # Get free at console.groq.com (2 minutes)
13
+ # Leave empty to use Gitcast shared key
14
+ BYOK_KEY=
15
+ BYOK_PROVIDER=groq
16
+
17
+ # Twitter — only needed for direct X publishing
18
+ # Get from developer.twitter.com
19
+ TWITTER_API_KEY=
20
+ TWITTER_API_SECRET=
21
+ TWITTER_ACCESS_TOKEN=
22
+ TWITTER_ACCESS_SECRET=
23
+ TWITTER_BEARER_TOKEN=
24
+
25
+ # Cloud server (do not change)
26
+ GITCAST_API_URL=https://gitcast-api.onrender.com
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.4
2
+ Name: gitcast
3
+ Version: 1.0.26
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: tweepy
19
+ Requires-Dist: mss
20
+ Requires-Dist: pytesseract
21
+ Requires-Dist: httpx>=0.27.0
22
+ Requires-Dist: cryptography
23
+ Requires-Dist: python-jose[cryptography]
24
+ Requires-Dist: rich
25
+ Requires-Dist: keyboard
26
+ Requires-Dist: slowapi
27
+ Requires-Dist: limits
28
+ Requires-Dist: sentry-sdk[fastapi]
29
+ Requires-Dist: posthog
30
+ Requires-Dist: plyer
31
+ Dynamic: author
32
+ Dynamic: author-email
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: license
37
+ Dynamic: license-file
38
+ Dynamic: requires-dist
39
+ Dynamic: requires-python
40
+ Dynamic: summary
41
+
42
+ # Gitcast
43
+
44
+ > git diff -> published post. under 60 seconds.
45
+
46
+ Privacy-first developer tool that captures your coding session via hotkey, generates AI-powered social media posts, and publishes them without breaking your flow.
47
+
48
+ Works immediately after install - no API key setup required. Gitcast's shared key is built in.
49
+
50
+ ---
51
+
52
+ ## Quick Start
53
+
54
+ ### Step 1 - Install Tesseract OCR
55
+
56
+ Required for local screenshot text extraction. Runs entirely on your machine - your code never leaves without your permission.
57
+
58
+ **Windows:**
59
+
60
+ 1. Download: github.com/UB-Mannheim/tesseract/wiki
61
+ 2. Run installer
62
+ 3. Check "Add to PATH" during installation
63
+ 4. Restart your terminal
64
+
65
+ **Mac:**
66
+
67
+ ```bash
68
+ brew install tesseract
69
+ ```
70
+
71
+ **Linux:**
72
+
73
+ ```bash
74
+ sudo apt install tesseract-ocr
75
+ ```
76
+
77
+ ### Step 2 - Install Gitcast
78
+
79
+ ```bash
80
+ pip install gitcast
81
+ ```
82
+
83
+ ### Step 3 - Run
84
+
85
+ ```bash
86
+ gitcast
87
+ ```
88
+
89
+ Dashboard opens automatically at http://localhost:8000
90
+
91
+ Press **Ctrl+Shift+P** or **Ctrl+Alt+S** from anywhere - VS Code, terminal, browser - to capture your screen and generate a post.
92
+
93
+ Works immediately. No configuration needed. Gitcast's shared API key is built in.
94
+
95
+ ### Step 4 - Add your own key (optional)
96
+
97
+ The shared key has rate limits shared across users. For unlimited personal usage, add your own free key:
98
+
99
+ ```bash
100
+ gitcast --setup
101
+ ```
102
+
103
+ Or go to **[BYOK]** in the dashboard sidebar.
104
+
105
+ Get a free Groq key in 2 minutes at console.groq.com
106
+
107
+ Your key takes priority - shared key stays as fallback.
108
+
109
+ ---
110
+
111
+ ## What Gitcast generates
112
+
113
+ From a single hotkey press:
114
+
115
+ - **X (Twitter) post** - published directly via API
116
+ - **LinkedIn draft** - with framed screenshot
117
+ - **PR description** - paste into GitHub
118
+ - **Quick win** - short punchy update
119
+ - **Article** - Medium-ready markdown
120
+
121
+ ---
122
+
123
+ ## How it works
124
+
125
+ ```text
126
+ Ctrl+Shift+P (or Ctrl+Alt+S)
127
+
128
+ -> screenshot captured locally
129
+
130
+ -> git diff read locally
131
+
132
+ -> OCR runs on your machine (Tesseract)
133
+
134
+ -> payload sent to Gitcast cloud
135
+
136
+ -> AI generates 4 post variations
137
+
138
+ -> dashboard shows results
139
+
140
+ -> you review, refine, publish
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Privacy
146
+
147
+ - OCR runs locally - text extracted on your machine
148
+ - Screenshots processed locally before any upload
149
+ - Sensitive content (API keys, tokens) auto-blocked
150
+ - Nothing always-on - you trigger every capture
151
+ - No account required to use
152
+
153
+ ---
154
+
155
+ ## CLI
156
+
157
+ ```text
158
+ gitcast Start dashboard + hotkey
159
+
160
+ gitcast --setup Add your own API key (optional)
161
+
162
+ gitcast capture Interactive screenshot session
163
+
164
+ gitcast --version Show version
165
+ ```
166
+
167
+ ---
168
+
169
+ ## Stack
170
+
171
+ - Python 3.10+, FastAPI, pynput, mss
172
+ - Tesseract OCR (local)
173
+ - Groq, Gemini, OpenRouter, Cerebras (cloud)
174
+ - Tweepy (X API v2)
175
+ - Supabase (cloud storage)
176
+
177
+ ---
178
+
179
+ ## License
180
+
181
+ MIT - free forever. Open source.
182
+
183
+ Built by Timilehin Agoro (@drizzy765) in Nigeria.
@@ -0,0 +1,142 @@
1
+ # Gitcast
2
+
3
+ > git diff -> published post. under 60 seconds.
4
+
5
+ Privacy-first developer tool that captures your coding session via hotkey, generates AI-powered social media posts, and publishes them without breaking your flow.
6
+
7
+ Works immediately after install - no API key setup required. Gitcast's shared key is built in.
8
+
9
+ ---
10
+
11
+ ## Quick Start
12
+
13
+ ### Step 1 - Install Tesseract OCR
14
+
15
+ Required for local screenshot text extraction. Runs entirely on your machine - your code never leaves without your permission.
16
+
17
+ **Windows:**
18
+
19
+ 1. Download: github.com/UB-Mannheim/tesseract/wiki
20
+ 2. Run installer
21
+ 3. Check "Add to PATH" during installation
22
+ 4. Restart your terminal
23
+
24
+ **Mac:**
25
+
26
+ ```bash
27
+ brew install tesseract
28
+ ```
29
+
30
+ **Linux:**
31
+
32
+ ```bash
33
+ sudo apt install tesseract-ocr
34
+ ```
35
+
36
+ ### Step 2 - Install Gitcast
37
+
38
+ ```bash
39
+ pip install gitcast
40
+ ```
41
+
42
+ ### Step 3 - Run
43
+
44
+ ```bash
45
+ gitcast
46
+ ```
47
+
48
+ Dashboard opens automatically at http://localhost:8000
49
+
50
+ Press **Ctrl+Shift+P** or **Ctrl+Alt+S** from anywhere - VS Code, terminal, browser - to capture your screen and generate a post.
51
+
52
+ Works immediately. No configuration needed. Gitcast's shared API key is built in.
53
+
54
+ ### Step 4 - Add your own key (optional)
55
+
56
+ The shared key has rate limits shared across users. For unlimited personal usage, add your own free key:
57
+
58
+ ```bash
59
+ gitcast --setup
60
+ ```
61
+
62
+ Or go to **[BYOK]** in the dashboard sidebar.
63
+
64
+ Get a free Groq key in 2 minutes at console.groq.com
65
+
66
+ Your key takes priority - shared key stays as fallback.
67
+
68
+ ---
69
+
70
+ ## What Gitcast generates
71
+
72
+ From a single hotkey press:
73
+
74
+ - **X (Twitter) post** - published directly via API
75
+ - **LinkedIn draft** - with framed screenshot
76
+ - **PR description** - paste into GitHub
77
+ - **Quick win** - short punchy update
78
+ - **Article** - Medium-ready markdown
79
+
80
+ ---
81
+
82
+ ## How it works
83
+
84
+ ```text
85
+ Ctrl+Shift+P (or Ctrl+Alt+S)
86
+
87
+ -> screenshot captured locally
88
+
89
+ -> git diff read locally
90
+
91
+ -> OCR runs on your machine (Tesseract)
92
+
93
+ -> payload sent to Gitcast cloud
94
+
95
+ -> AI generates 4 post variations
96
+
97
+ -> dashboard shows results
98
+
99
+ -> you review, refine, publish
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Privacy
105
+
106
+ - OCR runs locally - text extracted on your machine
107
+ - Screenshots processed locally before any upload
108
+ - Sensitive content (API keys, tokens) auto-blocked
109
+ - Nothing always-on - you trigger every capture
110
+ - No account required to use
111
+
112
+ ---
113
+
114
+ ## CLI
115
+
116
+ ```text
117
+ gitcast Start dashboard + hotkey
118
+
119
+ gitcast --setup Add your own API key (optional)
120
+
121
+ gitcast capture Interactive screenshot session
122
+
123
+ gitcast --version Show version
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Stack
129
+
130
+ - Python 3.10+, FastAPI, pynput, mss
131
+ - Tesseract OCR (local)
132
+ - Groq, Gemini, OpenRouter, Cerebras (cloud)
133
+ - Tweepy (X API v2)
134
+ - Supabase (cloud storage)
135
+
136
+ ---
137
+
138
+ ## License
139
+
140
+ MIT - free forever. Open source.
141
+
142
+ Built by Timilehin Agoro (@drizzy765) in Nigeria.
@@ -0,0 +1,165 @@
1
+ import httpx
2
+ from ai.prompts import sprint_summary_prompt
3
+ from core.log_stream import stream_log
4
+
5
+ # ── Constants ─────────────────────────────────────────────────────────────────
6
+
7
+ TIMEOUT = 15
8
+
9
+ _last_call_meta = {"provider_used": "", "used_fallback": False}
10
+
11
+
12
+ def refresh_provider_keys(user_id: str = "") -> None:
13
+ from config.settings import reload_api_keys
14
+
15
+ reload_api_keys()
16
+
17
+
18
+ # ── Main generator ────────────────────────────────────────────────────────────
19
+
20
+ async def generate_posts(payload: dict, user_id: str = "") -> dict:
21
+ from core.cloud_client import cloud_generate
22
+
23
+ stream_log("GENERATOR", "INFO", "sending to Gitcast cloud server...")
24
+
25
+ results = await cloud_generate(payload)
26
+
27
+ if not results:
28
+ stream_log("GENERATOR", "ERROR", "cloud server returned no results")
29
+ return {
30
+ k: (
31
+ "[Error] Could not reach generation "
32
+ "server. Check your internet connection."
33
+ )
34
+ for k in payload.get(
35
+ "format_keys",
36
+ ["x_post", "linkedin", "pr_desc", "quick_win"],
37
+ )
38
+ }
39
+
40
+ for key in results:
41
+ if isinstance(results[key], str) and not results[key].startswith("[Error]"):
42
+ stream_log("GENERATOR", "OK", f"{key} generated successfully")
43
+
44
+ return results
45
+
46
+
47
+ async def refine_post(
48
+ current_post: str,
49
+ instruction: str,
50
+ format_key: str,
51
+ **kwargs,
52
+ ) -> str:
53
+ from core.cloud_client import cloud_refine
54
+
55
+ return await cloud_refine(
56
+ current_post=current_post,
57
+ instruction=instruction,
58
+ format_key=format_key,
59
+ )
60
+
61
+
62
+ # ── Sprint generator ──────────────────────────────────────────────────────────
63
+
64
+ async def generate_sprint_summary(entries: list, narrative: str = "", user_id: str = "") -> str:
65
+ """
66
+ Synthesizes multiple sprint captures into a single cohesive thread.
67
+ """
68
+ from api.payload import build_sprint_payload
69
+
70
+ stream_log("Generator", "AI", f"synthesizing {len(entries)} captures into sprint thread")
71
+
72
+ payload = build_sprint_payload(entries)
73
+ if narrative:
74
+ payload["narrative"] = narrative
75
+ system_prompt = sprint_summary_prompt(len(entries))
76
+
77
+ try:
78
+ return await _ai_call("sprint_summary", system_prompt, payload["user_message"], user_id=user_id)
79
+ except Exception as e:
80
+ stream_log("Generator", "ERROR", f"sprint synthesis failed: {e}")
81
+ return f"[Error] {str(e)}"
82
+
83
+
84
+ # ── Provider Routing & Fallback ────────────────────────────────────────────────
85
+
86
+ async def _ai_call(
87
+ format_key: str,
88
+ system_prompt: str,
89
+ user_message: str,
90
+ user_id: str = "",
91
+ unavailable: dict[str, str] | None = None,
92
+ client: httpx.AsyncClient = None,
93
+ meta: dict | None = None,
94
+ ) -> str:
95
+ """Compatibility wrapper that routes legacy route calls through the cloud."""
96
+ results = await generate_posts(
97
+ {
98
+ "format_keys": [format_key],
99
+ "system_prompt": system_prompt,
100
+ "user_message": user_message,
101
+ },
102
+ user_id=user_id,
103
+ )
104
+ content = results.get(format_key, "")
105
+ if not content:
106
+ return "[Error] Could not reach generation server. Check your internet connection."
107
+ return content
108
+
109
+
110
+ async def _groq_call(system_prompt: str, user_message: str, retries: int = 2) -> str:
111
+ return await _ai_call("deep_tech", system_prompt, user_message)
112
+
113
+
114
+ async def _gemini_text_call(system_prompt: str, user_message: str, client: httpx.AsyncClient = None) -> str:
115
+ return await _ai_call("deep_tech", system_prompt, user_message)
116
+
117
+ async def _gemini_vision_call(
118
+ system_prompt: str,
119
+ user_message: str,
120
+ screenshots: list,
121
+ client: httpx.AsyncClient = None,
122
+ ) -> str:
123
+ return await _ai_call("deep_tech", system_prompt, user_message)
124
+
125
+
126
+ def _encode_image(image_path: str) -> str:
127
+ import base64
128
+ try:
129
+ with open(image_path, "rb") as f:
130
+ return base64.b64encode(f.read()).decode("utf-8")
131
+ except Exception:
132
+ return ""
133
+
134
+
135
+ # ── Test ──────────────────────────────────────────────────────────────────────
136
+
137
+ if __name__ == "__main__":
138
+ import asyncio
139
+ from core.capture import run_capture
140
+ from core.ocr import run_ocr
141
+ from api.payload import build_payload
142
+ from config.settings import set_project_narrative
143
+
144
+ set_project_narrative("an AI-powered build-in-public automation tool for developers")
145
+
146
+ async def test():
147
+ print("[Generator] Running multi-provider routing test...")
148
+ capture = run_capture()
149
+ ocr = run_ocr(capture["screenshot"]["path"])
150
+ payload = build_payload(
151
+ raw_thought="testing the new multi-provider fallback chain with Cerebras and OpenRouter",
152
+ )
153
+
154
+ payload["use_vision_fallback"] = False
155
+ payload["screenshot_b64"] = None
156
+
157
+ print("[Generator] Firing routed AI calls...")
158
+ results = await generate_posts(payload)
159
+
160
+ print("\n=== GENERATED POSTS ===")
161
+ for format_key, content in results.items():
162
+ print(f"\n--- {format_key.upper()} ---")
163
+ print(content)
164
+
165
+ asyncio.run(test())