gitcast 1.0.27__tar.gz → 1.0.28__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 (92) hide show
  1. {gitcast-1.0.27/gitcast.egg-info → gitcast-1.0.28}/PKG-INFO +1 -1
  2. {gitcast-1.0.27 → gitcast-1.0.28}/ai/prompts.py +1 -0
  3. {gitcast-1.0.27 → gitcast-1.0.28}/api/payload.py +79 -66
  4. gitcast-1.0.28/core/project_reader.py +125 -0
  5. {gitcast-1.0.27 → gitcast-1.0.28}/core/trigger.py +74 -4
  6. {gitcast-1.0.27 → gitcast-1.0.28/gitcast.egg-info}/PKG-INFO +1 -1
  7. {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/SOURCES.txt +1 -0
  8. {gitcast-1.0.27 → gitcast-1.0.28}/setup.py +1 -1
  9. {gitcast-1.0.27 → gitcast-1.0.28}/.env.example +0 -0
  10. {gitcast-1.0.27 → gitcast-1.0.28}/LICENSE +0 -0
  11. {gitcast-1.0.27 → gitcast-1.0.28}/MANIFEST.in +0 -0
  12. {gitcast-1.0.27 → gitcast-1.0.28}/README.md +0 -0
  13. {gitcast-1.0.27 → gitcast-1.0.28}/ai/__init__.py +0 -0
  14. {gitcast-1.0.27 → gitcast-1.0.28}/ai/formatter.py +0 -0
  15. {gitcast-1.0.27 → gitcast-1.0.28}/ai/generator.py +0 -0
  16. {gitcast-1.0.27 → gitcast-1.0.28}/ai/viral_patterns.py +0 -0
  17. {gitcast-1.0.27 → gitcast-1.0.28}/api/__init__.py +0 -0
  18. {gitcast-1.0.27 → gitcast-1.0.28}/api/analytics.py +0 -0
  19. {gitcast-1.0.27 → gitcast-1.0.28}/api/auth.py +0 -0
  20. {gitcast-1.0.27 → gitcast-1.0.28}/api/auth_middleware.py +0 -0
  21. {gitcast-1.0.27 → gitcast-1.0.28}/api/auth_routes.py +0 -0
  22. {gitcast-1.0.27 → gitcast-1.0.28}/api/monitoring.py +0 -0
  23. {gitcast-1.0.27 → gitcast-1.0.28}/api/ratelimit.py +0 -0
  24. {gitcast-1.0.27 → gitcast-1.0.28}/api/routes.py +0 -0
  25. {gitcast-1.0.27 → gitcast-1.0.28}/api/server.py +0 -0
  26. {gitcast-1.0.27 → gitcast-1.0.28}/api/validators.py +0 -0
  27. {gitcast-1.0.27 → gitcast-1.0.28}/assets/__init__.py +0 -0
  28. {gitcast-1.0.27 → gitcast-1.0.28}/assets/favicon-16x16.png +0 -0
  29. {gitcast-1.0.27 → gitcast-1.0.28}/assets/favicon-32x32.png +0 -0
  30. {gitcast-1.0.27 → gitcast-1.0.28}/assets/favicon-64x64.png +0 -0
  31. {gitcast-1.0.27 → gitcast-1.0.28}/assets/favicon.ico +0 -0
  32. {gitcast-1.0.27 → gitcast-1.0.28}/assets/icon.png +0 -0
  33. {gitcast-1.0.27 → gitcast-1.0.28}/cli/.env.example +0 -0
  34. {gitcast-1.0.27 → gitcast-1.0.28}/cli/__init__.py +0 -0
  35. {gitcast-1.0.27 → gitcast-1.0.28}/cli/gitcast.py +0 -0
  36. {gitcast-1.0.27 → gitcast-1.0.28}/config/__init__.py +0 -0
  37. {gitcast-1.0.27 → gitcast-1.0.28}/config/settings.py +0 -0
  38. {gitcast-1.0.27 → gitcast-1.0.28}/core/__init__.py +0 -0
  39. {gitcast-1.0.27 → gitcast-1.0.28}/core/capture.py +0 -0
  40. {gitcast-1.0.27 → gitcast-1.0.28}/core/cloud_client.py +0 -0
  41. {gitcast-1.0.27 → gitcast-1.0.28}/core/codebase_reader.py +0 -0
  42. {gitcast-1.0.27 → gitcast-1.0.28}/core/framing.py +0 -0
  43. {gitcast-1.0.27 → gitcast-1.0.28}/core/hotkey.py +0 -0
  44. {gitcast-1.0.27 → gitcast-1.0.28}/core/log_stream.py +0 -0
  45. {gitcast-1.0.27 → gitcast-1.0.28}/core/ocr.py +0 -0
  46. {gitcast-1.0.27 → gitcast-1.0.28}/core/screenshot_session.py +0 -0
  47. {gitcast-1.0.27 → gitcast-1.0.28}/core/security.py +0 -0
  48. {gitcast-1.0.27 → gitcast-1.0.28}/core/tray.py +0 -0
  49. {gitcast-1.0.27 → gitcast-1.0.28}/docs/api-keys.md +0 -0
  50. {gitcast-1.0.27 → gitcast-1.0.28}/docs/article-generator.md +0 -0
  51. {gitcast-1.0.27 → gitcast-1.0.28}/docs/capture-flow.md +0 -0
  52. {gitcast-1.0.27 → gitcast-1.0.28}/docs/changelog.md +0 -0
  53. {gitcast-1.0.27 → gitcast-1.0.28}/docs/contributing.md +0 -0
  54. {gitcast-1.0.27 → gitcast-1.0.28}/docs/dashboard.md +0 -0
  55. {gitcast-1.0.27 → gitcast-1.0.28}/docs/insights.md +0 -0
  56. {gitcast-1.0.27 → gitcast-1.0.28}/docs/introduction.md +0 -0
  57. {gitcast-1.0.27 → gitcast-1.0.28}/docs/post-formats.md +0 -0
  58. {gitcast-1.0.27 → gitcast-1.0.28}/docs/providers.md +0 -0
  59. {gitcast-1.0.27 → gitcast-1.0.28}/docs/pypi-errors.md +0 -0
  60. {gitcast-1.0.27 → gitcast-1.0.28}/docs/quickstart.md +0 -0
  61. {gitcast-1.0.27 → gitcast-1.0.28}/docs/sprint-mode.md +0 -0
  62. {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/dependency_links.txt +0 -0
  63. {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/entry_points.txt +0 -0
  64. {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/requires.txt +0 -0
  65. {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/top_level.txt +0 -0
  66. {gitcast-1.0.27 → gitcast-1.0.28}/publisher/__init__.py +0 -0
  67. {gitcast-1.0.27 → gitcast-1.0.28}/publisher/clipboard.py +0 -0
  68. {gitcast-1.0.27 → gitcast-1.0.28}/publisher/twitter.py +0 -0
  69. {gitcast-1.0.27 → gitcast-1.0.28}/pyproject.toml +0 -0
  70. {gitcast-1.0.27 → gitcast-1.0.28}/requirements.txt +0 -0
  71. {gitcast-1.0.27 → gitcast-1.0.28}/setup.cfg +0 -0
  72. {gitcast-1.0.27 → gitcast-1.0.28}/storage/__init__.py +0 -0
  73. {gitcast-1.0.27 → gitcast-1.0.28}/storage/cleanup.py +0 -0
  74. {gitcast-1.0.27 → gitcast-1.0.28}/storage/data/prompts.json +0 -0
  75. {gitcast-1.0.27 → gitcast-1.0.28}/storage/engagement.py +0 -0
  76. {gitcast-1.0.27 → gitcast-1.0.28}/storage/insights.py +0 -0
  77. {gitcast-1.0.27 → gitcast-1.0.28}/storage/key_manager.py +0 -0
  78. {gitcast-1.0.27 → gitcast-1.0.28}/storage/logger.py +0 -0
  79. {gitcast-1.0.27 → gitcast-1.0.28}/storage/metrics.py +0 -0
  80. {gitcast-1.0.27 → gitcast-1.0.28}/storage/sprint.py +0 -0
  81. {gitcast-1.0.27 → gitcast-1.0.28}/storage/streak.py +0 -0
  82. {gitcast-1.0.27 → gitcast-1.0.28}/storage/supabase_client.py +0 -0
  83. {gitcast-1.0.27 → gitcast-1.0.28}/storage/tone_memory.py +0 -0
  84. {gitcast-1.0.27 → gitcast-1.0.28}/tests/test_keys.py +0 -0
  85. {gitcast-1.0.27 → gitcast-1.0.28}/tests/test_ocr_fix.py +0 -0
  86. {gitcast-1.0.27 → gitcast-1.0.28}/tests/test_validation.py +0 -0
  87. {gitcast-1.0.27 → gitcast-1.0.28}/ui/__init__.py +0 -0
  88. {gitcast-1.0.27 → gitcast-1.0.28}/ui/popup.py +0 -0
  89. {gitcast-1.0.27 → gitcast-1.0.28}/ui/review.py +0 -0
  90. {gitcast-1.0.27 → gitcast-1.0.28}/web/__init__.py +0 -0
  91. {gitcast-1.0.27 → gitcast-1.0.28}/web/index.html +0 -0
  92. {gitcast-1.0.27 → gitcast-1.0.28}/web/landing.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitcast
3
- Version: 1.0.27
3
+ Version: 1.0.28
4
4
  Summary: git diff → published post. under 60 seconds.
5
5
  Home-page: https://github.com/drizzy765/gitcast
6
6
  Author: Timilehin Agoro
@@ -69,6 +69,7 @@ Rules:
69
69
  - Keep it grounded and specific. Vague posts get ignored.
70
70
  - If there is a code snippet in the context, reference
71
71
  the actual function name, variable, or error.
72
+ - The project README and tech stack are provided as context. Use them to write about the ACTUAL project the developer is working on — not about Gitcast or any other tool. Reference the real project name, real technologies, and real features described in the README.
72
73
  """
73
74
 
74
75
 
@@ -2,72 +2,57 @@ import base64
2
2
  from pathlib import Path
3
3
  from config.settings import get_project_narrative
4
4
  from core.log_stream import stream_log
5
-
6
-
7
- # ── Payload builder ───────────────────────────────────────────────────────────
8
-
9
- def build_payload(
10
- raw_thought: str,
11
- ocr_result: dict,
12
- capture_result: dict,
13
- format_keys: list = None,
14
- multi_screenshots: list = None,
15
- ) -> dict:
16
- """
17
- Assembles the full payload from all captured context.
18
- Supports single or multi-screenshot sessions.
19
- """
20
- if format_keys is None:
21
- format_keys = ["deep_tech", "linkedin", "pr_generator", "quick_win"]
22
-
23
- narrative = get_project_narrative()
24
- git_diff = capture_result.get("git_diff", {"diff": "", "success": False})
25
-
26
- # Handle single or multi-shot
27
- if multi_screenshots:
28
- screenshots = multi_screenshots
29
- primary_shot = screenshots[0]
30
- else:
31
- # Normalize single shot to a list of one
32
- primary_shot = capture_result["screenshot"]
33
- screenshots = [{
34
- "path": primary_shot["path"],
35
- "purpose": "general",
36
- "ocr_text": ocr_result.get("text") or ocr_result.get("raw_text", ""),
37
- "confidence": ocr_result.get("confidence", 0.0),
38
- "timestamp": primary_shot.get("timestamp", ""),
39
- "index": 1
40
- }]
41
-
42
- # Decide vision fallback based on primary shot or all shots
43
- # For now, we'll use OCR if any shot is reliable, but usually it's per-shot.
44
- # LLM will see OCR text for each shot.
45
- use_vision = ocr_result.get("use_vision_fallback", False) if not multi_screenshots else False
46
-
47
- # encode primary screenshot as base64 for vision fallback
48
- screenshot_b64 = None
49
- if use_vision and primary_shot.get("path"):
50
- screenshot_b64 = _encode_image(primary_shot["path"])
51
-
52
- # build the structured user message
53
- user_message = _build_user_message(
54
- raw_thought=raw_thought,
55
- screenshots=screenshots,
56
- git_diff=git_diff,
57
- narrative=narrative,
58
- use_vision=use_vision,
59
- )
60
-
61
- # joined OCR text for legacy/summary access
62
- all_ocr = "\n\n".join([s.get("ocr_text", "") for s in screenshots if s.get("ocr_text")])
63
-
64
- stream_log(
65
- "Payload",
66
- "OK",
67
- f"assembled payload: {len(screenshots)} screenshot(s), {len(all_ocr)} OCR chars",
68
- )
69
5
 
70
- return {
6
+
7
+ # ── Payload builder ───────────────────────────────────────────────────────────
8
+
9
+ def build_payload(
10
+ raw_thought: str,
11
+ ocr_result: dict,
12
+ capture_result: dict,
13
+ format_keys: list = None,
14
+ multi_screenshots: list = None,
15
+ ) -> dict:
16
+ """
17
+ Assembles the full payload from all captured context.
18
+ Supports single or multi-screenshot sessions.
19
+ """
20
+ if format_keys is None:
21
+ format_keys = ["deep_tech", "linkedin", "pr_generator", "quick_win"]
22
+
23
+ narrative = get_project_narrative()
24
+ git_diff = capture_result.get("git_diff", {"diff": "", "success": False})
25
+
26
+ # Handle single or multi-shot
27
+ if multi_screenshots:
28
+ screenshots = multi_screenshots
29
+ primary_shot = screenshots[0]
30
+ else:
31
+ # Normalize single shot to a list of one
32
+ primary_shot = capture_result["screenshot"]
33
+ screenshots = [{
34
+ "path": primary_shot["path"],
35
+ "purpose": "general",
36
+ "ocr_text": ocr_result.get("text") or ocr_result.get("raw_text", ""),
37
+ "confidence": ocr_result.get("confidence", 0.0),
38
+ "timestamp": primary_shot.get("timestamp", ""),
39
+ "index": 1
40
+ }]
41
+
42
+ # Decide vision fallback based on primary shot or all shots
43
+ # For now, we'll use OCR if any shot is reliable, but usually it's per-shot.
44
+ # LLM will see OCR text for each shot.
45
+ use_vision = ocr_result.get("use_vision_fallback", False) if not multi_screenshots else False
46
+
47
+ # encode primary screenshot as base64 for vision fallback
48
+ screenshot_b64 = None
49
+ if use_vision and primary_shot.get("path"):
50
+ screenshot_b64 = _encode_image(primary_shot["path"])
51
+
52
+ # joined OCR text for legacy/summary access
53
+ all_ocr = "\n\n".join([s.get("ocr_text", "") for s in screenshots if s.get("ocr_text")])
54
+
55
+ payload = {
71
56
  "raw_thought": raw_thought.strip(),
72
57
  "ocr_text": all_ocr,
73
58
  "screenshots": screenshots,
@@ -77,12 +62,30 @@ def build_payload(
77
62
  "use_vision_fallback": use_vision,
78
63
  "screenshot_b64": screenshot_b64,
79
64
  "screenshot_path": primary_shot.get("path", ""),
80
- "user_message": user_message,
81
65
  "format_keys": format_keys,
82
66
  "timestamp": primary_shot.get("timestamp", ""),
83
67
  "working_dir": capture_result.get("working_dir", ""),
84
68
  }
85
69
 
70
+ # build the structured user message
71
+ user_message = _build_user_message(
72
+ raw_thought=raw_thought,
73
+ screenshots=screenshots,
74
+ git_diff=git_diff,
75
+ narrative=narrative,
76
+ use_vision=use_vision,
77
+ payload=payload,
78
+ )
79
+ payload["user_message"] = user_message
80
+
81
+ stream_log(
82
+ "Payload",
83
+ "OK",
84
+ f"assembled payload: {len(screenshots)} screenshot(s), {len(all_ocr)} OCR chars",
85
+ )
86
+
87
+ return payload
88
+
86
89
 
87
90
  # ── User message builder ──────────────────────────────────────────────────────
88
91
 
@@ -92,6 +95,7 @@ def _build_user_message(
92
95
  git_diff: dict,
93
96
  narrative: str,
94
97
  use_vision: bool,
98
+ payload: dict = None,
95
99
  ) -> str:
96
100
  """
97
101
  Builds the user-turn message that gets sent to the LLM.
@@ -123,6 +127,15 @@ def _build_user_message(
123
127
  if narrative:
124
128
  parts.append(f"## Project context\n{narrative}")
125
129
 
130
+ if payload and payload.get("readme_content"):
131
+ parts.append(
132
+ f"## Project context\n"
133
+ f"Project name: {payload.get('project_name', 'Unknown')}\n"
134
+ f"Tech stack: {payload.get('tech_stack', 'Unknown')}\n"
135
+ f"README:\n"
136
+ f"{payload['readme_content'][:1500]}"
137
+ )
138
+
126
139
  parts.append(
127
140
  "\nUsing the context above, generate the post now. "
128
141
  "Follow the format and rules in the system prompt exactly."
@@ -0,0 +1,125 @@
1
+ import os
2
+ import json
3
+ from pathlib import Path
4
+
5
+ MAX_README_CHARS = 3000
6
+ MAX_STACK_CHARS = 500
7
+
8
+ def read_project_context(directory: str = None) -> dict:
9
+ """
10
+ Reads project context from the current working
11
+ directory. Extracts README, tech stack, and
12
+ project name to give the AI real grounding.
13
+ """
14
+ cwd = Path(directory or os.getcwd())
15
+
16
+ context = {
17
+ "project_name": cwd.name,
18
+ "readme_content": "",
19
+ "tech_stack": "",
20
+ "project_type": "",
21
+ "main_language": "",
22
+ }
23
+
24
+ # Read README
25
+ for readme_name in [
26
+ "README.md", "readme.md",
27
+ "README.txt", "README"]:
28
+ readme_path = cwd / readme_name
29
+ if readme_path.exists():
30
+ try:
31
+ content = readme_path.read_text(
32
+ encoding="utf-8", errors="ignore")
33
+ # cap at 3000 chars
34
+ context["readme_content"] = \
35
+ content[:MAX_README_CHARS]
36
+ if len(content) > MAX_README_CHARS:
37
+ context["readme_content"] += \
38
+ "\n... [truncated]"
39
+ break
40
+ except Exception:
41
+ pass
42
+
43
+ # Detect tech stack from config files
44
+ stack_signals = []
45
+
46
+ if (cwd / "requirements.txt").exists():
47
+ try:
48
+ reqs = (cwd / "requirements.txt"
49
+ ).read_text(errors="ignore")
50
+ stack_signals.append(
51
+ f"Python project. Dependencies: "
52
+ f"{reqs[:300]}")
53
+ context["main_language"] = "Python"
54
+ except Exception:
55
+ pass
56
+
57
+ if (cwd / "package.json").exists():
58
+ try:
59
+ pkg = json.loads(
60
+ (cwd / "package.json").read_text())
61
+ name = pkg.get("name", "")
62
+ deps = list(pkg.get(
63
+ "dependencies", {}).keys())[:10]
64
+ stack_signals.append(
65
+ f"JavaScript/Node project: {name}. "
66
+ f"Dependencies: {', '.join(deps)}")
67
+ context["main_language"] = "JavaScript"
68
+ except Exception:
69
+ pass
70
+
71
+ if (cwd / "Cargo.toml").exists():
72
+ stack_signals.append("Rust project")
73
+ context["main_language"] = "Rust"
74
+
75
+ if (cwd / "go.mod").exists():
76
+ stack_signals.append("Go project")
77
+ context["main_language"] = "Go"
78
+
79
+ if (cwd / "pom.xml").exists():
80
+ stack_signals.append("Java/Maven project")
81
+ context["main_language"] = "Java"
82
+
83
+ if (cwd / "Dockerfile").exists():
84
+ stack_signals.append("Dockerized")
85
+
86
+ if (cwd / ".github").exists():
87
+ stack_signals.append("GitHub Actions CI")
88
+
89
+ context["tech_stack"] = " | ".join(
90
+ stack_signals)[:MAX_STACK_CHARS]
91
+
92
+ # detect project type
93
+ if (cwd / "manage.py").exists():
94
+ context["project_type"] = "Django web app"
95
+ elif (cwd / "app.py").exists() or \
96
+ (cwd / "main.py").exists():
97
+ context["project_type"] = "Python app"
98
+ elif (cwd / "index.js").exists() or \
99
+ (cwd / "index.ts").exists():
100
+ context["project_type"] = "Node.js app"
101
+ elif (cwd / "src").exists():
102
+ context["project_type"] = "Source project"
103
+
104
+ return context
105
+
106
+ def detect_gitcast_window(ocr_text: str) -> bool:
107
+ """
108
+ Detects if the captured window is the Gitcast
109
+ dashboard itself (localhost:8000).
110
+ Returns True if the screenshot is of Gitcast.
111
+ """
112
+ gitcast_signals = [
113
+ "localhost:8000",
114
+ "127.0.0.1:8000",
115
+ "DRAFT_ROOM",
116
+ "X_POST",
117
+ "GITCAST",
118
+ "git diff → published post",
119
+ "NEW_CAPTURE",
120
+ "QUICK_WIN",
121
+ ]
122
+ ocr_upper = ocr_text.upper()
123
+ matches = sum(1 for s in gitcast_signals
124
+ if s.upper() in ocr_upper)
125
+ return matches >= 2
@@ -41,7 +41,7 @@ def _show_tray_notification(title, message):
41
41
  print(f"[Trigger] {title}: {message}")
42
42
 
43
43
 
44
- def _generate_and_open(raw_thought, capture, ocr):
44
+ def _generate_and_open(raw_thought, capture, ocr, project_ctx):
45
45
  payload = build_payload(
46
46
  raw_thought=raw_thought,
47
47
  ocr_result=ocr,
@@ -50,6 +50,25 @@ def _generate_and_open(raw_thought, capture, ocr):
50
50
  payload["use_vision_fallback"] = False
51
51
  payload["screenshot_b64"] = None
52
52
 
53
+ # inject project context into payload
54
+ payload["project_name"] = project_ctx["project_name"]
55
+ payload["readme_content"] = project_ctx["readme_content"]
56
+ payload["tech_stack"] = project_ctx["tech_stack"]
57
+ payload["project_type"] = project_ctx["project_type"]
58
+ payload["main_language"] = project_ctx["main_language"]
59
+
60
+ # build enriched user message
61
+ if project_ctx["readme_content"]:
62
+ payload["user_message"] = (
63
+ payload.get("user_message", "") +
64
+ f"\n\n## Project: "
65
+ f"{project_ctx['project_name']}\n"
66
+ f"## Tech stack: "
67
+ f"{project_ctx['tech_stack']}\n"
68
+ f"## README context:\n"
69
+ f"{project_ctx['readme_content'][:1500]}"
70
+ )
71
+
53
72
  def generate_and_show():
54
73
  try:
55
74
  print('[Trigger] calling generate_posts...')
@@ -108,13 +127,63 @@ def _generate_and_open(raw_thought, capture, ocr):
108
127
  def _run_trigger():
109
128
  """The actual trigger logic — moved from on_trigger."""
110
129
  print("[Trigger] on_trigger fired successfully")
111
- print("[Gitcast] Hotkey fired — starting capture...")
112
130
 
131
+ # read project context FIRST before capture
132
+ from core.project_reader import (
133
+ read_project_context, detect_gitcast_window)
134
+ from core.capture import detect_working_directory
135
+
136
+ working_dir = detect_working_directory()
137
+ project_ctx = read_project_context(working_dir)
138
+
139
+ if project_ctx["readme_content"]:
140
+ print(f"[Trigger] README found for: "
141
+ f"{project_ctx['project_name']}")
142
+ if project_ctx["tech_stack"]:
143
+ print(f"[Trigger] Stack: "
144
+ f"{project_ctx['tech_stack'][:60]}")
145
+
146
+ # run capture
147
+ print("[Gitcast] Hotkey fired — starting capture...")
113
148
  capture = run_capture()
114
149
  print("[Trigger] capture complete")
115
150
  ocr = run_ocr(capture["screenshot"]["path"])
116
151
  print("[Trigger] OCR complete")
117
152
 
153
+ # detect if user captured Gitcast dashboard
154
+ ocr_text = ocr.get("text", "") or \
155
+ ocr.get("raw_text", "")
156
+ if detect_gitcast_window(ocr_text):
157
+ print("""
158
+ > [!!] You captured the Gitcast dashboard.
159
+ >
160
+ > For best results:
161
+ > 1. Switch to your code editor (VS Code,
162
+ > PyCharm, terminal with your code)
163
+ > 2. Press Ctrl+Shift+P again
164
+ >
165
+ > The 5-second countdown gives you time to switch.
166
+ > Gitcast will still generate using your README
167
+ > and project narrative as context.
168
+ """)
169
+
170
+ # show tray notification
171
+ try:
172
+ from plyer import notification
173
+ notification.notify(
174
+ title="Gitcast",
175
+ message="Switch to your code editor "
176
+ "then press Ctrl+Shift+P again",
177
+ timeout=5,
178
+ )
179
+ except Exception:
180
+ pass
181
+
182
+ # clear OCR text — don't send dashboard content to AI
183
+ ocr["text"] = ""
184
+ ocr["raw_text"] = ""
185
+ ocr["reliable"] = False
186
+
118
187
  if is_sprint_mode():
119
188
  log_sprint_capture(
120
189
  git_diff=capture["git_diff"].get("diff", ""),
@@ -132,7 +201,7 @@ def _run_trigger():
132
201
  # use narrative as the raw thought
133
202
  raw_thought = narrative
134
203
  print("[Trigger] using project narrative — skipping popup")
135
- _generate_and_open(raw_thought, capture, ocr)
204
+ _generate_and_open(raw_thought, capture, ocr, project_ctx)
136
205
 
137
206
  else:
138
207
  # first time or no narrative — show popup once
@@ -145,7 +214,7 @@ def _run_trigger():
145
214
  # save as project narrative for future
146
215
  set_project_narrative(thought)
147
216
  print(f"[Trigger] narrative saved: '{thought}'")
148
- _generate_and_open(thought, capture, ocr)
217
+ _generate_and_open(thought, capture, ocr, project_ctx)
149
218
 
150
219
  def on_dismiss():
151
220
  print("[Trigger] popup dismissed")
@@ -156,3 +225,4 @@ def _run_trigger():
156
225
  on_dismiss=on_dismiss,
157
226
  prompt_text="What are you building? (saved for future captures)"
158
227
  )
228
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitcast
3
- Version: 1.0.27
3
+ Version: 1.0.28
4
4
  Summary: git diff → published post. under 60 seconds.
5
5
  Home-page: https://github.com/drizzy765/gitcast
6
6
  Author: Timilehin Agoro
@@ -40,6 +40,7 @@ core/framing.py
40
40
  core/hotkey.py
41
41
  core/log_stream.py
42
42
  core/ocr.py
43
+ core/project_reader.py
43
44
  core/screenshot_session.py
44
45
  core/security.py
45
46
  core/tray.py
@@ -50,7 +50,7 @@ if os.path.exists(requirements_path):
50
50
 
51
51
  setup(
52
52
  name="gitcast",
53
- version="1.0.27",
53
+ version="1.0.28",
54
54
  description="git diff → published post. under 60 seconds.",
55
55
  long_description=long_description,
56
56
  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
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