ai-screenshooter 1.7.0__tar.gz → 1.7.1__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.
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/PKG-INFO +1 -1
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/ai_screenshooter.egg-info/PKG-INFO +1 -1
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/ai_screenshot.py +77 -11
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/setup.py +1 -1
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/README.md +0 -0
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/ai_screenshooter.egg-info/SOURCES.txt +0 -0
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/ai_screenshooter.egg-info/dependency_links.txt +0 -0
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/ai_screenshooter.egg-info/entry_points.txt +0 -0
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/ai_screenshooter.egg-info/requires.txt +0 -0
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/ai_screenshooter.egg-info/top_level.txt +0 -0
- {ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/setup.cfg +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import json
|
|
2
3
|
import os
|
|
3
4
|
import sys
|
|
4
5
|
import signal
|
|
@@ -18,6 +19,7 @@ from pynput import keyboard
|
|
|
18
19
|
# Constants
|
|
19
20
|
PID_FILE = Path.home() / ".ai-screenshooter.pid"
|
|
20
21
|
LOG_FILE = Path.home() / ".ai-screenshooter.log"
|
|
22
|
+
META_FILE = Path.home() / ".ai-screenshooter.meta.json"
|
|
21
23
|
SCREENSHOT_DIR = Path.home() / ".ai-screenshooter" / "screenshots"
|
|
22
24
|
AUDIO_DIR = Path.home() / ".ai-screenshooter" / "audio"
|
|
23
25
|
TIMEOUT_SECONDS = 5 * 60 * 60 # 5 hours
|
|
@@ -97,6 +99,31 @@ def cleanup_pid_file():
|
|
|
97
99
|
PID_FILE.unlink()
|
|
98
100
|
except Exception:
|
|
99
101
|
pass
|
|
102
|
+
try:
|
|
103
|
+
if META_FILE.exists():
|
|
104
|
+
META_FILE.unlink()
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def write_meta_file(server_mode, server_url):
|
|
110
|
+
"""Write process metadata for status command."""
|
|
111
|
+
meta = {
|
|
112
|
+
"started_at": time.time(),
|
|
113
|
+
"server_mode": server_mode,
|
|
114
|
+
"server_url": server_url,
|
|
115
|
+
}
|
|
116
|
+
META_FILE.write_text(json.dumps(meta))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def read_meta_file():
|
|
120
|
+
"""Read process metadata, return None if invalid."""
|
|
121
|
+
if not META_FILE.exists():
|
|
122
|
+
return None
|
|
123
|
+
try:
|
|
124
|
+
return json.loads(META_FILE.read_text())
|
|
125
|
+
except (ValueError, IOError):
|
|
126
|
+
return None
|
|
100
127
|
|
|
101
128
|
|
|
102
129
|
# ============ Process Management ============
|
|
@@ -482,11 +509,14 @@ def send_transcribed_text(text):
|
|
|
482
509
|
def on_press(key):
|
|
483
510
|
global last_esc_time, is_recording
|
|
484
511
|
|
|
485
|
-
current_keys.add(key)
|
|
486
|
-
|
|
487
512
|
try:
|
|
488
513
|
# Double-tap ESC detection for voice recording
|
|
489
514
|
if key == keyboard.Key.esc:
|
|
515
|
+
# Ignore repeated key events from holding ESC
|
|
516
|
+
if keyboard.Key.esc in current_keys:
|
|
517
|
+
return
|
|
518
|
+
current_keys.add(key)
|
|
519
|
+
|
|
490
520
|
current_time = time.time()
|
|
491
521
|
time_since_last = current_time - last_esc_time
|
|
492
522
|
|
|
@@ -496,8 +526,12 @@ def on_press(key):
|
|
|
496
526
|
|
|
497
527
|
last_esc_time = current_time
|
|
498
528
|
|
|
529
|
+
# Track non-ESC keys for combo detection
|
|
530
|
+
else:
|
|
531
|
+
current_keys.add(key)
|
|
532
|
+
|
|
499
533
|
# Other hotkeys (ESC + arrow keys)
|
|
500
|
-
|
|
534
|
+
if key == keyboard.Key.down and keyboard.Key.esc in current_keys:
|
|
501
535
|
logger.info("Capturing screenshot...")
|
|
502
536
|
capture_screenshot()
|
|
503
537
|
elif key == keyboard.Key.up and keyboard.Key.esc in current_keys:
|
|
@@ -530,19 +564,20 @@ def cmd_start(args):
|
|
|
530
564
|
"""Handle the start command."""
|
|
531
565
|
global API_TOKEN, API_URL
|
|
532
566
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
567
|
+
is_daemon = getattr(args, 'daemon', False)
|
|
568
|
+
|
|
569
|
+
# Kill any existing instance (unless this is the daemon subprocess itself)
|
|
570
|
+
if not is_daemon:
|
|
536
571
|
killed = kill_existing_process()
|
|
537
572
|
if killed:
|
|
538
|
-
print("
|
|
573
|
+
print("Replaced existing instance.")
|
|
539
574
|
|
|
575
|
+
# If --background flag, spawn a new process and exit
|
|
576
|
+
if args.background:
|
|
577
|
+
print("Starting in background mode...")
|
|
540
578
|
start_background_process(args.token, args.local)
|
|
541
579
|
return
|
|
542
580
|
|
|
543
|
-
# If --daemon flag (internal), this is the actual daemon process
|
|
544
|
-
is_daemon = getattr(args, 'daemon', False)
|
|
545
|
-
|
|
546
581
|
if is_daemon:
|
|
547
582
|
# Write PID file
|
|
548
583
|
write_pid_file()
|
|
@@ -565,6 +600,10 @@ def cmd_start(args):
|
|
|
565
600
|
API_URL = LOCAL_URL if args.local else PROD_URL
|
|
566
601
|
|
|
567
602
|
server_mode = "LOCAL" if args.local else "PRODUCTION"
|
|
603
|
+
|
|
604
|
+
# Write metadata for status command
|
|
605
|
+
write_meta_file(server_mode, API_URL)
|
|
606
|
+
|
|
568
607
|
logger.info("AI Screenshot CLI started.")
|
|
569
608
|
logger.info(f"Server: {server_mode} ({API_URL})")
|
|
570
609
|
logger.info("Press ESC + Down to capture a screenshot.")
|
|
@@ -584,11 +623,38 @@ def cmd_status(args):
|
|
|
584
623
|
pid = get_pid_from_file()
|
|
585
624
|
if pid and is_process_running(pid):
|
|
586
625
|
print(f"ai-screenshooter is running (PID: {pid})")
|
|
626
|
+
|
|
627
|
+
meta = read_meta_file()
|
|
628
|
+
if meta:
|
|
629
|
+
# Uptime
|
|
630
|
+
elapsed = time.time() - meta.get("started_at", time.time())
|
|
631
|
+
hours, remainder = divmod(int(elapsed), 3600)
|
|
632
|
+
minutes, seconds = divmod(remainder, 60)
|
|
633
|
+
print(f" Uptime: {hours}h {minutes}m {seconds}s")
|
|
634
|
+
|
|
635
|
+
# Time remaining
|
|
636
|
+
remaining = TIMEOUT_SECONDS - elapsed
|
|
637
|
+
if remaining > 0:
|
|
638
|
+
rh, rr = divmod(int(remaining), 3600)
|
|
639
|
+
rm, rs = divmod(rr, 60)
|
|
640
|
+
print(f" Expires: {rh}h {rm}m {rs}s remaining")
|
|
641
|
+
|
|
642
|
+
# Server
|
|
643
|
+
print(f" Server: {meta.get('server_mode', 'UNKNOWN')} ({meta.get('server_url', '')})")
|
|
644
|
+
|
|
645
|
+
print()
|
|
646
|
+
print(" Listening for hotkeys:")
|
|
647
|
+
print(" ESC + Down Capture screenshot")
|
|
648
|
+
print(" ESC + Up Send all screenshots")
|
|
649
|
+
print(" ESC + Right Send clipboard text to Code tab")
|
|
650
|
+
print(" Double-tap ESC Record voice, transcribe and send")
|
|
651
|
+
|
|
587
652
|
return 0
|
|
588
653
|
else:
|
|
589
654
|
print("ai-screenshooter is not running")
|
|
590
655
|
if PID_FILE.exists():
|
|
591
|
-
print(f"(stale PID file
|
|
656
|
+
print(f"(stale PID file found, cleaning up)")
|
|
657
|
+
cleanup_pid_file()
|
|
592
658
|
return 1
|
|
593
659
|
|
|
594
660
|
|
|
File without changes
|
|
File without changes
|
{ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/ai_screenshooter.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ai_screenshooter-1.7.0 → ai_screenshooter-1.7.1}/ai_screenshooter.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|