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.
- {gitcast-1.0.27/gitcast.egg-info → gitcast-1.0.28}/PKG-INFO +1 -1
- {gitcast-1.0.27 → gitcast-1.0.28}/ai/prompts.py +1 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/payload.py +79 -66
- gitcast-1.0.28/core/project_reader.py +125 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/trigger.py +74 -4
- {gitcast-1.0.27 → gitcast-1.0.28/gitcast.egg-info}/PKG-INFO +1 -1
- {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/SOURCES.txt +1 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/setup.py +1 -1
- {gitcast-1.0.27 → gitcast-1.0.28}/.env.example +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/LICENSE +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/MANIFEST.in +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/README.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/ai/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/ai/formatter.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/ai/generator.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/ai/viral_patterns.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/analytics.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/auth.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/auth_middleware.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/auth_routes.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/monitoring.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/ratelimit.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/routes.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/server.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/api/validators.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/assets/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/assets/favicon-16x16.png +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/assets/favicon-32x32.png +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/assets/favicon-64x64.png +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/assets/favicon.ico +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/assets/icon.png +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/cli/.env.example +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/cli/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/cli/gitcast.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/config/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/config/settings.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/capture.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/cloud_client.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/codebase_reader.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/framing.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/hotkey.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/log_stream.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/ocr.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/screenshot_session.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/security.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/core/tray.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/api-keys.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/article-generator.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/capture-flow.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/changelog.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/contributing.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/dashboard.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/insights.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/introduction.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/post-formats.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/providers.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/pypi-errors.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/quickstart.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/docs/sprint-mode.md +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/dependency_links.txt +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/entry_points.txt +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/requires.txt +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/gitcast.egg-info/top_level.txt +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/publisher/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/publisher/clipboard.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/publisher/twitter.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/pyproject.toml +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/requirements.txt +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/setup.cfg +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/cleanup.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/data/prompts.json +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/engagement.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/insights.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/key_manager.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/logger.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/metrics.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/sprint.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/streak.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/supabase_client.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/storage/tone_memory.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/tests/test_keys.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/tests/test_ocr_fix.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/tests/test_validation.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/ui/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/ui/popup.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/ui/review.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/web/__init__.py +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/web/index.html +0 -0
- {gitcast-1.0.27 → gitcast-1.0.28}/web/landing.html +0 -0
|
@@ -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
|
-
|
|
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
|
+
|
|
@@ -50,7 +50,7 @@ if os.path.exists(requirements_path):
|
|
|
50
50
|
|
|
51
51
|
setup(
|
|
52
52
|
name="gitcast",
|
|
53
|
-
version="1.0.
|
|
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
|
|
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
|