Repository navigation
Expand file tree
/
Copy pathcapture.py
More file actions
223 lines (179 loc) · 7.88 KB
/
Copy pathcapture.py
File metadata and controls
223 lines (179 loc) · 7.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"""
cortex/capture.py
-----------------
Global hotkey capture tool. Lives in your Mac menu bar.
Press Cmd+Shift+C anywhere to save selected text to Cortex.
How it works:
1. You select text in any app (ChatGPT, Slack, browser, Notes, anywhere)
2. Press Cmd+Shift+C
3. It copies the selection to clipboard (simulates Cmd+C)
4. Grabs the clipboard text
5. Runs it through the Cortex pipeline (extract → GitHub → Redis)
6. Shows a macOS notification with what was captured
Run: python capture.py
"""
import os
import sys
import time
import threading
import subprocess
from datetime import datetime
from dotenv import load_dotenv
load_dotenv()
import rumps
import pyperclip
from pynput import keyboard
from ingest import extract_context, format_extraction_summary
from github_store import save_extracted_context
try:
from redis_store import embed_and_store
REDIS_AVAILABLE = True
except Exception:
REDIS_AVAILABLE = False
# ── Hotkey config ─────────────────────────────────────────────────────────────
HOTKEY = {keyboard.Key.cmd, keyboard.Key.shift, keyboard.KeyCode.from_char('v')}
current_keys = set()
hotkey_triggered = False
# ── Notification ──────────────────────────────────────────────────────────────
def notify(title: str, message: str):
"""Show a macOS notification."""
subprocess.run([
"osascript", "-e",
f'display notification "{message}" with title "{title}"'
], capture_output=True)
# ── Core capture function ─────────────────────────────────────────────────────
def capture_and_save(app_instance=None):
"""
Grab selected text and save to Cortex.
Called when hotkey is triggered.
"""
# Update menu bar icon to show we're working
if app_instance:
app_instance.title = "🧠 ..."
try:
# Just read whatever is currently in the clipboard
# Flow: user selects text → Cmd+C → then Cmd+Shift+V to save to Cortex
selected_text = pyperclip.paste()
if not selected_text or not selected_text.strip():
notify("Cortex", "⚠️ Clipboard is empty — copy something first (Cmd+C)")
if app_instance:
app_instance.title = "🧠"
return
if len(selected_text.strip()) < 20:
notify("Cortex", "⚠️ Too short to capture")
if app_instance:
app_instance.title = "🧠"
return
# Detect source app
source = detect_source_app()
print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Capturing from {source}")
print(f"Text: {selected_text[:100]}{'...' if len(selected_text) > 100 else ''}")
# Extract context with Claude
extracted = extract_context(selected_text, source=source)
summary = format_extraction_summary(extracted)
print(summary)
# Save to GitHub
saved_files = save_extracted_context(extracted, raw_text=selected_text)
# Embed in Redis
if REDIS_AVAILABLE:
try:
embed_and_store(extracted, raw_text=selected_text)
except Exception:
pass
# Build notification message
counts = []
if extracted.get("KEY_INSIGHTS"):
counts.append(f"{len(extracted['KEY_INSIGHTS'])} insights")
if extracted.get("DECISIONS"):
counts.append(f"{len(extracted['DECISIONS'])} decisions")
if extracted.get("OPEN_QUESTIONS"):
counts.append(f"{len(extracted['OPEN_QUESTIONS'])} questions")
if extracted.get("ACTION_ITEMS"):
counts.append(f"{len(extracted['ACTION_ITEMS'])} actions")
msg = ", ".join(counts) if counts else "summary saved"
notify("✅ Cortex", f"Captured from {source}: {msg}")
print(f"✅ Saved {len(saved_files)} files to GitHub")
except Exception as e:
notify("Cortex", f"❌ Error: {str(e)[:60]}")
print(f"Error: {e}")
finally:
if app_instance:
app_instance.title = "🧠"
def detect_source_app() -> str:
"""Try to detect which app is frontmost."""
try:
result = subprocess.run(
["osascript", "-e", 'tell application "System Events" to get name of first application process whose frontmost is true'],
capture_output=True, text=True
)
app_name = result.stdout.strip().lower()
# Map common apps to source labels
app_map = {
"google chrome": "chrome",
"safari": "safari",
"firefox": "firefox",
"slack": "slack",
"notes": "apple-notes",
"messages": "imessage",
"mail": "email",
"notion": "notion",
"arc": "arc",
}
for key, label in app_map.items():
if key in app_name:
return label
return app_name or "unknown"
except Exception:
return "unknown"
# ── Menu bar app ──────────────────────────────────────────────────────────────
class CortexApp(rumps.App):
def __init__(self):
super().__init__("🧠", quit_button="Quit Cortex")
self.menu = [
rumps.MenuItem("Cortex — Universal Context Layer", callback=None),
None, # separator
rumps.MenuItem("Capture selection (Cmd+Shift+C)", callback=self.manual_capture),
None,
rumps.MenuItem("Open GitHub repo", callback=self.open_repo),
rumps.MenuItem("Status", callback=self.show_status),
]
# Start global hotkey listener in background thread
self.listener_thread = threading.Thread(target=self.start_hotkey_listener, daemon=True)
self.listener_thread.start()
print("🧠 Cortex capture running. Press Cmd+Shift+C anywhere to capture selected text.")
def start_hotkey_listener(self):
pressed = set()
def on_press(key):
pressed.add(key)
# Check if Cmd+Shift+C is all held
if (
keyboard.Key.cmd in pressed
and keyboard.Key.shift in pressed
and keyboard.KeyCode.from_char('v') in pressed
):
# Run capture in a separate thread to not block the listener
threading.Thread(target=capture_and_save, args=(self,), daemon=True).start()
pressed.clear() # prevent repeat triggers
def on_release(key):
pressed.discard(key)
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
@rumps.clicked("Capture selection (Cmd+Shift+C)")
def manual_capture(self, _):
threading.Thread(target=capture_and_save, args=(self,), daemon=True).start()
@rumps.clicked("Open GitHub repo")
def open_repo(self, _):
repo = os.environ.get("CORTEX_REPO", "")
if repo:
subprocess.run(["open", f"https://github.com/{repo}"])
@rumps.clicked("Status")
def show_status(self, _):
redis_status = "✅ connected" if REDIS_AVAILABLE else "❌ not running"
repo = os.environ.get("CORTEX_REPO", "not set")
rumps.alert(
title="Cortex Status",
message=f"Redis: {redis_status}\nRepo: {repo}\nHotkey: Cmd+Shift+C"
)
# ── Entry point ───────────────────────────────────────────────────────────────
if __name__ == "__main__":
CortexApp().run()