gitcast 1.0.0__tar.gz → 1.0.2__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 (85) hide show
  1. {gitcast-1.0.0 → gitcast-1.0.2}/MANIFEST.in +1 -0
  2. {gitcast-1.0.0/gitcast.egg-info → gitcast-1.0.2}/PKG-INFO +1 -1
  3. gitcast-1.0.2/assets/__init__.py +1 -0
  4. gitcast-1.0.2/cli/.env.example +26 -0
  5. gitcast-1.0.2/cli/gitcast.py +110 -0
  6. {gitcast-1.0.0 → gitcast-1.0.2}/core/capture.py +7 -3
  7. {gitcast-1.0.0 → gitcast-1.0.2}/core/screenshot_session.py +6 -3
  8. {gitcast-1.0.0 → gitcast-1.0.2/gitcast.egg-info}/PKG-INFO +1 -1
  9. {gitcast-1.0.0 → gitcast-1.0.2}/gitcast.egg-info/SOURCES.txt +3 -0
  10. {gitcast-1.0.0 → gitcast-1.0.2}/gitcast.egg-info/top_level.txt +2 -0
  11. {gitcast-1.0.0 → gitcast-1.0.2}/setup.py +1 -1
  12. gitcast-1.0.2/web/__init__.py +1 -0
  13. gitcast-1.0.0/cli/gitcast.py +0 -56
  14. {gitcast-1.0.0 → gitcast-1.0.2}/.env.example +0 -0
  15. {gitcast-1.0.0 → gitcast-1.0.2}/LICENSE +0 -0
  16. {gitcast-1.0.0 → gitcast-1.0.2}/README.md +0 -0
  17. {gitcast-1.0.0 → gitcast-1.0.2}/ai/__init__.py +0 -0
  18. {gitcast-1.0.0 → gitcast-1.0.2}/ai/formatter.py +0 -0
  19. {gitcast-1.0.0 → gitcast-1.0.2}/ai/generator.py +0 -0
  20. {gitcast-1.0.0 → gitcast-1.0.2}/ai/prompts.py +0 -0
  21. {gitcast-1.0.0 → gitcast-1.0.2}/ai/viral_patterns.py +0 -0
  22. {gitcast-1.0.0 → gitcast-1.0.2}/api/__init__.py +0 -0
  23. {gitcast-1.0.0 → gitcast-1.0.2}/api/analytics.py +0 -0
  24. {gitcast-1.0.0 → gitcast-1.0.2}/api/auth.py +0 -0
  25. {gitcast-1.0.0 → gitcast-1.0.2}/api/auth_middleware.py +0 -0
  26. {gitcast-1.0.0 → gitcast-1.0.2}/api/auth_routes.py +0 -0
  27. {gitcast-1.0.0 → gitcast-1.0.2}/api/monitoring.py +0 -0
  28. {gitcast-1.0.0 → gitcast-1.0.2}/api/payload.py +0 -0
  29. {gitcast-1.0.0 → gitcast-1.0.2}/api/ratelimit.py +0 -0
  30. {gitcast-1.0.0 → gitcast-1.0.2}/api/routes.py +0 -0
  31. {gitcast-1.0.0 → gitcast-1.0.2}/api/server.py +0 -0
  32. {gitcast-1.0.0 → gitcast-1.0.2}/api/validators.py +0 -0
  33. {gitcast-1.0.0 → gitcast-1.0.2}/assets/favicon-16x16.png +0 -0
  34. {gitcast-1.0.0 → gitcast-1.0.2}/assets/favicon-32x32.png +0 -0
  35. {gitcast-1.0.0 → gitcast-1.0.2}/assets/favicon-64x64.png +0 -0
  36. {gitcast-1.0.0 → gitcast-1.0.2}/assets/favicon.ico +0 -0
  37. {gitcast-1.0.0 → gitcast-1.0.2}/assets/icon.png +0 -0
  38. {gitcast-1.0.0 → gitcast-1.0.2}/cli/__init__.py +0 -0
  39. {gitcast-1.0.0 → gitcast-1.0.2}/config/__init__.py +0 -0
  40. {gitcast-1.0.0 → gitcast-1.0.2}/config/settings.py +0 -0
  41. {gitcast-1.0.0 → gitcast-1.0.2}/core/__init__.py +0 -0
  42. {gitcast-1.0.0 → gitcast-1.0.2}/core/codebase_reader.py +0 -0
  43. {gitcast-1.0.0 → gitcast-1.0.2}/core/framing.py +0 -0
  44. {gitcast-1.0.0 → gitcast-1.0.2}/core/hotkey.py +0 -0
  45. {gitcast-1.0.0 → gitcast-1.0.2}/core/log_stream.py +0 -0
  46. {gitcast-1.0.0 → gitcast-1.0.2}/core/ocr.py +0 -0
  47. {gitcast-1.0.0 → gitcast-1.0.2}/core/security.py +0 -0
  48. {gitcast-1.0.0 → gitcast-1.0.2}/core/tray.py +0 -0
  49. {gitcast-1.0.0 → gitcast-1.0.2}/docs/api-keys.md +0 -0
  50. {gitcast-1.0.0 → gitcast-1.0.2}/docs/article-generator.md +0 -0
  51. {gitcast-1.0.0 → gitcast-1.0.2}/docs/capture-flow.md +0 -0
  52. {gitcast-1.0.0 → gitcast-1.0.2}/docs/changelog.md +0 -0
  53. {gitcast-1.0.0 → gitcast-1.0.2}/docs/contributing.md +0 -0
  54. {gitcast-1.0.0 → gitcast-1.0.2}/docs/dashboard.md +0 -0
  55. {gitcast-1.0.0 → gitcast-1.0.2}/docs/insights.md +0 -0
  56. {gitcast-1.0.0 → gitcast-1.0.2}/docs/introduction.md +0 -0
  57. {gitcast-1.0.0 → gitcast-1.0.2}/docs/post-formats.md +0 -0
  58. {gitcast-1.0.0 → gitcast-1.0.2}/docs/providers.md +0 -0
  59. {gitcast-1.0.0 → gitcast-1.0.2}/docs/quickstart.md +0 -0
  60. {gitcast-1.0.0 → gitcast-1.0.2}/docs/sprint-mode.md +0 -0
  61. {gitcast-1.0.0 → gitcast-1.0.2}/gitcast.egg-info/dependency_links.txt +0 -0
  62. {gitcast-1.0.0 → gitcast-1.0.2}/gitcast.egg-info/entry_points.txt +0 -0
  63. {gitcast-1.0.0 → gitcast-1.0.2}/gitcast.egg-info/requires.txt +0 -0
  64. {gitcast-1.0.0 → gitcast-1.0.2}/publisher/__init__.py +0 -0
  65. {gitcast-1.0.0 → gitcast-1.0.2}/publisher/clipboard.py +0 -0
  66. {gitcast-1.0.0 → gitcast-1.0.2}/publisher/twitter.py +0 -0
  67. {gitcast-1.0.0 → gitcast-1.0.2}/pyproject.toml +0 -0
  68. {gitcast-1.0.0 → gitcast-1.0.2}/requirements.txt +0 -0
  69. {gitcast-1.0.0 → gitcast-1.0.2}/setup.cfg +0 -0
  70. {gitcast-1.0.0 → gitcast-1.0.2}/storage/__init__.py +0 -0
  71. {gitcast-1.0.0 → gitcast-1.0.2}/storage/cleanup.py +0 -0
  72. {gitcast-1.0.0 → gitcast-1.0.2}/storage/engagement.py +0 -0
  73. {gitcast-1.0.0 → gitcast-1.0.2}/storage/insights.py +0 -0
  74. {gitcast-1.0.0 → gitcast-1.0.2}/storage/key_manager.py +0 -0
  75. {gitcast-1.0.0 → gitcast-1.0.2}/storage/logger.py +0 -0
  76. {gitcast-1.0.0 → gitcast-1.0.2}/storage/metrics.py +0 -0
  77. {gitcast-1.0.0 → gitcast-1.0.2}/storage/sprint.py +0 -0
  78. {gitcast-1.0.0 → gitcast-1.0.2}/storage/streak.py +0 -0
  79. {gitcast-1.0.0 → gitcast-1.0.2}/storage/supabase_client.py +0 -0
  80. {gitcast-1.0.0 → gitcast-1.0.2}/storage/tone_memory.py +0 -0
  81. {gitcast-1.0.0 → gitcast-1.0.2}/tests/test_keys.py +0 -0
  82. {gitcast-1.0.0 → gitcast-1.0.2}/tests/test_ocr_fix.py +0 -0
  83. {gitcast-1.0.0 → gitcast-1.0.2}/ui/__init__.py +0 -0
  84. {gitcast-1.0.0 → gitcast-1.0.2}/web/index.html +0 -0
  85. {gitcast-1.0.0 → gitcast-1.0.2}/web/landing.html +0 -0
@@ -1,6 +1,7 @@
1
1
  include README.md
2
2
  include LICENSE
3
3
  include .env.example
4
+ include cli/.env.example
4
5
  include requirements.txt
5
6
  recursive-include web *
6
7
  recursive-include assets *
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitcast
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: git diff → published post. under 60 seconds.
5
5
  Home-page: https://github.com/drizzy765/gitcast
6
6
  Author: Timilehin Agoro
@@ -0,0 +1 @@
1
+ # Make assets directory a Python package so it gets packaged and installed to site-packages.
@@ -0,0 +1,26 @@
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
@@ -0,0 +1,110 @@
1
+ import sys
2
+ import os
3
+ import shutil
4
+ import subprocess
5
+ import httpx
6
+ import webbrowser
7
+
8
+ def main():
9
+ if "--setup" in sys.argv:
10
+ env_path = os.path.join(
11
+ os.path.dirname(__file__), '..', '.env')
12
+ example_path = os.path.join(
13
+ os.path.dirname(__file__), '..', '.env.example')
14
+ if not os.path.exists(example_path):
15
+ example_path = os.path.join(
16
+ os.path.dirname(__file__), '.env.example')
17
+
18
+ if not os.path.exists(env_path):
19
+ if os.path.exists(example_path):
20
+ shutil.copy(example_path, env_path)
21
+ else:
22
+ # If neither is found, create an empty or minimal env file
23
+ with open(env_path, 'w') as f:
24
+ f.write("# Gitcast Environment Variables\nGROQ_API_KEY=\nGEMINI_API_KEY=\n")
25
+
26
+ try:
27
+ if sys.platform == "win32":
28
+ subprocess.run(["notepad", env_path])
29
+ elif sys.platform == "darwin":
30
+ subprocess.run(["open", "-t", env_path])
31
+ else:
32
+ try:
33
+ subprocess.run(["xdg-open", env_path])
34
+ except Exception:
35
+ print(f"[Gitcast] Created/configured .env at: {env_path}")
36
+ print("Please open it in an editor to add your API keys.")
37
+ except Exception as e:
38
+ print(f"[Gitcast] Created/configured .env at: {env_path}")
39
+ print(f"Could not automatically open editor: {e}")
40
+ sys.exit(0)
41
+
42
+ if len(sys.argv) < 2:
43
+ # start FastAPI server in background thread
44
+ import threading
45
+ import webbrowser
46
+ import time
47
+ import urllib.request
48
+
49
+ def start_server():
50
+ from api.server import start_server as run
51
+ run()
52
+
53
+ server_thread = threading.Thread(
54
+ target=start_server, daemon=True)
55
+ server_thread.start()
56
+
57
+ # wait for server to be ready (max 5 seconds)
58
+ for _ in range(10):
59
+ try:
60
+ urllib.request.urlopen(
61
+ "http://localhost:8000/health",
62
+ timeout=1)
63
+ break
64
+ except Exception:
65
+ time.sleep(0.5)
66
+
67
+ # open browser
68
+ webbrowser.open("http://localhost:8000")
69
+
70
+ # start tray and hotkey
71
+ from core.tray import run_tray
72
+ import sys as _sys
73
+ import os as _os
74
+ _sys.path.insert(0, _os.path.dirname(
75
+ _os.path.dirname(_os.path.abspath(__file__))))
76
+ from main import on_trigger
77
+ run_tray(trigger_callback=on_trigger)
78
+ return
79
+
80
+ command = sys.argv[1]
81
+
82
+ if command == "capture":
83
+ from core.screenshot_session import ScreenshotSession
84
+ session = ScreenshotSession()
85
+ session.run()
86
+ return
87
+
88
+ thought = " ".join(sys.argv[1:])
89
+ print(f"[Gitcast] Initializing capture with thought: '{thought}'")
90
+
91
+ try:
92
+ # Call the internal trigger endpoint
93
+ response = httpx.post(
94
+ "http://localhost:8000/api/cli/trigger",
95
+ json={"thought": thought},
96
+ timeout=30
97
+ )
98
+
99
+ if response.status_code == 200:
100
+ print("[Gitcast] Context captured. Opening Draft Room...")
101
+ webbrowser.open("http://localhost:8000")
102
+ else:
103
+ print(f"[Gitcast Error] Failed to trigger: {response.text}")
104
+
105
+ except Exception as e:
106
+ print(f"[Gitcast Error] Connection failed: {e}")
107
+ print("Is Gitcast running? (python -m gitcast or python main.py)")
108
+
109
+ if __name__ == "__main__":
110
+ main()
@@ -7,6 +7,8 @@ from config.settings import STORAGE_DIR
7
7
  from core.log_stream import stream_log
8
8
  from api.analytics import track
9
9
  BASE_DIR = STORAGE_DIR.parent.parent
10
+ screenshot_dir = STORAGE_DIR / "screenshots"
11
+ screenshot_dir.mkdir(parents=True, exist_ok=True)
10
12
  from .framing import add_programming_frame
11
13
 
12
14
 
@@ -218,11 +220,13 @@ def run_capture(delay: float = 5.0) -> dict:
218
220
  # Apply programming frame if capture succeeded
219
221
  if screenshot.get("success") and screenshot.get("path"):
220
222
  try:
221
- framed_path = add_programming_frame(screenshot["path"])
222
- screenshot["framed_path"] = framed_path
223
+ abs_path = str(screenshot_dir / Path(screenshot["path"]).name)
224
+ framed_path = add_programming_frame(abs_path)
225
+ rel_framed_path = str(Path(framed_path).relative_to(BASE_DIR)).replace("\\", "/")
226
+ screenshot["framed_path"] = rel_framed_path
223
227
  # By default, use the framed path for downstream preview/publishing
224
228
  screenshot["raw_path"] = screenshot["path"]
225
- screenshot["path"] = framed_path
229
+ screenshot["path"] = rel_framed_path
226
230
  except Exception as e:
227
231
  stream_log("Capture", "WARN", f"framing failed: {e}")
228
232
  screenshot["framed_path"] = ""
@@ -17,6 +17,8 @@ from core.ocr import run_ocr
17
17
  from core.security import scan_for_secrets, delete_capture
18
18
  from config.settings import STORAGE_DIR, CURRENT_DRAFT, get_project_narrative
19
19
 
20
+ screenshot_dir = STORAGE_DIR / "screenshots"
21
+
20
22
  console = Console()
21
23
 
22
24
  class ScreenshotSession:
@@ -127,12 +129,13 @@ class ScreenshotSession:
127
129
  return
128
130
 
129
131
  # OCR and Security (Silent progress)
130
- ocr = run_ocr(shot["path"])
132
+ abs_shot_path = str(screenshot_dir / Path(shot["path"]).name)
133
+ ocr = run_ocr(abs_shot_path)
131
134
  security = scan_for_secrets(ocr["text"] or ocr["raw_text"])
132
135
 
133
136
  if not security["clean"]:
134
137
  console.print(f"\n[red]⚠️ Shot {len(self.screenshots)+1} blocked (sensitive content).[/red]")
135
- delete_capture(shot["path"])
138
+ delete_capture(abs_shot_path)
136
139
  return
137
140
 
138
141
  # Add to list with default purpose
@@ -212,7 +215,7 @@ class ScreenshotSession:
212
215
  if keep:
213
216
  final_shots.append(s)
214
217
  else:
215
- delete_capture(s["path"])
218
+ delete_capture(str(screenshot_dir / Path(s["path"]).name))
216
219
 
217
220
  self.screenshots = final_shots
218
221
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitcast
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: git diff → published post. under 60 seconds.
5
5
  Home-page: https://github.com/drizzy765/gitcast
6
6
  Author: Timilehin Agoro
@@ -21,11 +21,13 @@ api/ratelimit.py
21
21
  api/routes.py
22
22
  api/server.py
23
23
  api/validators.py
24
+ assets/__init__.py
24
25
  assets/favicon-16x16.png
25
26
  assets/favicon-32x32.png
26
27
  assets/favicon-64x64.png
27
28
  assets/favicon.ico
28
29
  assets/icon.png
30
+ cli/.env.example
29
31
  cli/__init__.py
30
32
  cli/gitcast.py
31
33
  config/__init__.py
@@ -75,5 +77,6 @@ storage/tone_memory.py
75
77
  tests/test_keys.py
76
78
  tests/test_ocr_fix.py
77
79
  ui/__init__.py
80
+ web/__init__.py
78
81
  web/index.html
79
82
  web/landing.html
@@ -1,8 +1,10 @@
1
1
  ai
2
2
  api
3
+ assets
3
4
  cli
4
5
  config
5
6
  core
6
7
  publisher
7
8
  storage
8
9
  ui
10
+ web
@@ -19,7 +19,7 @@ if os.path.exists(requirements_path):
19
19
 
20
20
  setup(
21
21
  name="gitcast",
22
- version="1.0.0",
22
+ version="1.0.2",
23
23
  description="git diff → published post. under 60 seconds.",
24
24
  long_description=long_description,
25
25
  long_description_content_type="text/markdown",
@@ -0,0 +1 @@
1
+ # Make web directory a Python package so it gets packaged and installed to site-packages.
@@ -1,56 +0,0 @@
1
- import sys
2
- import os
3
- import shutil
4
- import subprocess
5
- import httpx
6
- import webbrowser
7
-
8
- def main():
9
- if "--setup" in sys.argv:
10
- env_path = os.path.join(
11
- os.path.dirname(__file__), '..', '.env')
12
- example_path = os.path.join(
13
- os.path.dirname(__file__), '..', '.env.example')
14
- if not os.path.exists(env_path):
15
- shutil.copy(example_path, env_path)
16
- subprocess.run(["notepad", env_path])
17
- sys.exit(0)
18
-
19
- if len(sys.argv) < 2:
20
- print("Usage:")
21
- print(" gitcast \"your thought here\" -> Quick single capture")
22
- print(" gitcast capture -> Start interactive multi-shot session")
23
- print(" gitcast --setup -> Setup environment variables")
24
- sys.exit(1)
25
-
26
- command = sys.argv[1]
27
-
28
- if command == "capture":
29
- from core.screenshot_session import ScreenshotSession
30
- session = ScreenshotSession()
31
- session.run()
32
- return
33
-
34
- thought = " ".join(sys.argv[1:])
35
- print(f"[Gitcast] Initializing capture with thought: '{thought}'")
36
-
37
- try:
38
- # Call the internal trigger endpoint
39
- response = httpx.post(
40
- "http://127.0.0.1:8000/api/cli/trigger",
41
- json={"thought": thought},
42
- timeout=30
43
- )
44
-
45
- if response.status_code == 200:
46
- print("[Gitcast] Context captured. Opening Draft Room...")
47
- webbrowser.open("http://127.0.0.1:8000")
48
- else:
49
- print(f"[Gitcast Error] Failed to trigger: {response.text}")
50
-
51
- except Exception as e:
52
- print(f"[Gitcast Error] Connection failed: {e}")
53
- print("Is Gitcast running? (python -m gitcast or python main.py)")
54
-
55
- if __name__ == "__main__":
56
- main()
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