Last active
June 14, 2025 01:49
-
-
Save jeremiahlukus/dc72786a72bea94883af16338eaf6198 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'json' | |
require 'logger' | |
require 'time' | |
require 'fileutils' | |
require 'rb-scpt' # For better AppleScript integration | |
require 'tmpdir' | |
require 'tempfile' | |
class CursorAutomation | |
def initialize | |
@project_path = Dir.pwd | |
@tasks_json = File.join(@project_path, '.taskmaster/tasks/tasks.json') | |
@log_file = File.join(@project_path, 'cursor_auto.log') | |
@max_iterations = 100 | |
@check_interval = 120 # 2 minutes | |
@current_iteration = 0 | |
@last_message_time = 0 | |
@max_retries = 3 | |
setup_logger | |
end | |
def setup_logger | |
@logger = Logger.new(File.open(@log_file, 'a')) | |
@logger.formatter = proc do |severity, datetime, progname, msg| | |
time_str = datetime.strftime('%H:%M:%S') | |
"[#{time_str}] #{msg}\n" | |
end | |
end | |
def log(msg) | |
puts "\e[34m[#{Time.now.strftime('%H:%M:%S')}] #{msg}\e[0m" | |
@logger.info(msg) | |
end | |
def log_success(msg) | |
puts "\e[32m[#{Time.now.strftime('%H:%M:%S')}] SUCCESS: #{msg}\e[0m" | |
@logger.info("SUCCESS: #{msg}") | |
end | |
def log_warning(msg) | |
puts "\e[33m[#{Time.now.strftime('%H:%M:%S')}] WARNING: #{msg}\e[0m" | |
@logger.warn(msg) | |
end | |
def get_cursor_window | |
app = Appscript.app('System Events').processes['Cursor'] | |
return nil unless app.exists | |
windows = app.windows.get | |
return nil if windows.empty? | |
windows.first | |
end | |
def run_applescript(script) | |
file = Tempfile.new(['script', '.applescript']) | |
begin | |
file.write(script) | |
file.close | |
system("osascript #{file.path}") | |
ensure | |
file.unlink | |
end | |
end | |
def is_chat_open? | |
log "Checking if chat is already open..." | |
script = <<~APPLESCRIPT | |
tell application "System Events" | |
tell process "Cursor" | |
-- Try multiple methods to detect chat | |
try | |
-- Method 1: Look for chat-specific UI elements | |
set chatElements to UI elements of window 1 whose description contains "chat" | |
if (count of chatElements) > 0 then | |
return "true" | |
end if | |
-- Method 2: Look for message input area | |
set messageElements to UI elements of window 1 whose description contains "message" | |
if (count of messageElements) > 0 then | |
return "true" | |
end if | |
-- Method 3: Look for specific chat panel elements | |
set panelElements to groups of window 1 whose description contains "Chat Panel" | |
if (count of panelElements) > 0 then | |
return "true" | |
end if | |
-- Method 4: Check for specific chat-related buttons | |
set buttons to buttons of window 1 whose description contains "Clear chat" | |
if (count of buttons) > 0 then | |
return "true" | |
end if | |
end try | |
return "false" | |
end tell | |
end tell | |
APPLESCRIPT | |
result = run_applescript(script) | |
is_open = (result.to_s.strip == "true") | |
log(is_open ? "Chat panel is open" : "Chat panel is closed") | |
is_open | |
end | |
def open_chat_panel | |
log "Opening chat panel..." | |
script = <<~APPLESCRIPT | |
tell application "System Events" | |
tell process "Cursor" | |
-- Use command palette to open chat | |
keystroke "p" using {command down, shift down} | |
delay 1 | |
-- Type "chat" to find the chat command | |
keystroke "chat" | |
delay 1 | |
-- Press return to select | |
key code 36 | |
delay 2 | |
end tell | |
end tell | |
APPLESCRIPT | |
run_applescript(script) | |
sleep 2 | |
end | |
def ensure_chat_open | |
log "Ensuring chat is open..." | |
@max_retries.times do |attempt| | |
if is_chat_open? | |
return true | |
else | |
log "Opening chat panel (attempt #{attempt + 1}/#{@max_retries})..." | |
open_chat_panel | |
sleep 5 # Wait for chat to open | |
end | |
end | |
log_warning "Failed to open chat panel after #{@max_retries} attempts" | |
false | |
end | |
def focus_chat_input | |
script = <<~APPLESCRIPT | |
tell application "System Events" | |
tell process "Cursor" | |
-- Use command palette | |
keystroke "p" using {command down, shift down} | |
delay 1 | |
-- Type "focus chat" to find the focus command | |
keystroke "focus chat" | |
delay 1 | |
-- Press return to select | |
key code 36 | |
delay 1 | |
-- Press return again to ensure we're in input mode | |
key code 36 | |
delay 0.5 | |
end tell | |
end tell | |
APPLESCRIPT | |
run_applescript(script) | |
sleep 1 | |
end | |
def clear_chat_input | |
script = <<~APPLESCRIPT | |
tell application "System Events" | |
tell process "Cursor" | |
-- Press Escape to exit any mode | |
key code 53 | |
delay 0.5 | |
-- Press 'i' to enter insert mode | |
keystroke "i" | |
delay 0.5 | |
-- Select all text | |
keystroke "a" using {command down} | |
delay 0.2 | |
-- Delete selected text | |
key code 51 | |
delay 0.2 | |
end tell | |
end tell | |
APPLESCRIPT | |
run_applescript(script) | |
sleep 1 | |
end | |
def type_with_delay(text) | |
# Escape newlines and quotes for AppleScript | |
escaped_text = text.gsub('"', '\\"').gsub("\n", "\\n") | |
script = <<~APPLESCRIPT | |
tell application "System Events" | |
tell process "Cursor" | |
-- Use command palette to ensure chat focus | |
keystroke "p" using {command down, shift down} | |
delay 1 | |
keystroke "focus chat" | |
delay 1 | |
key code 36 | |
delay 1 | |
-- Type the complete message | |
keystroke "#{escaped_text}" | |
delay 1 | |
-- Send message | |
key code 36 | |
delay 2 | |
end tell | |
end tell | |
APPLESCRIPT | |
run_applescript(script) | |
sleep 1 | |
end | |
def send_messages | |
log "Attempting to send messages to Cursor..." | |
return unless ensure_chat_open | |
main_dir = File.basename(@project_path) | |
messages = [ | |
"Let's continue working on the tasks in @tasks.json " + | |
"Available Resources: " + | |
"- Task Master Documentation: @https://www.npmjs.com/package/task-master-ai " + | |
"- Project Code: @/#{main_dir} " + | |
"Task Status Updates: " + | |
"After completing each task, please update its status using either: " + | |
"1. MCP Tool Method: " + | |
"set_task_status with: " + | |
"- id='<task_id>' " + | |
"- status='done' " + | |
"2. CLI Method: " + | |
"task-master set-status --id=<task_id> --status=done " + | |
"Remember to update the status right after completing each task." | |
] | |
messages.each_with_index do |msg, index| | |
# Extra delay between messages | |
sleep 2 if index > 0 | |
# Send the complete message | |
type_with_delay(msg) | |
sleep 2 | |
end | |
log_success "Messages sent successfully" | |
end | |
def run | |
log "Starting Cursor automation..." | |
# Ensure Cursor is running | |
Appscript.app('Cursor').activate | |
sleep 5 | |
send_messages | |
end | |
def is_chat_working? | |
script = <<~APPLESCRIPT | |
tell application "System Events" | |
tell process "Cursor" | |
-- Check for stop button or progress indicators | |
try | |
set allButtons to every button of window 1 | |
repeat with btn in allButtons | |
try | |
if description of btn contains "Stop" or description of btn contains "Generating" then | |
return true | |
end if | |
end try | |
end repeat | |
end try | |
-- Check progress indicators | |
try | |
if (count of (every progress indicator of window 1)) > 0 then | |
return true | |
end if | |
end try | |
-- Check for working/thinking text | |
try | |
set allTexts to every static text of window 1 | |
repeat with txt in allTexts | |
try | |
set txtContent to value of txt | |
if txtContent contains "thinking" or txtContent contains "working" or txtContent contains "generating" then | |
return true | |
end if | |
end try | |
end repeat | |
end try | |
return false | |
end tell | |
end tell | |
APPLESCRIPT | |
result = run_applescript(script) | |
is_working = (result == "true") | |
log "Chat working status: #{is_working}" | |
is_working | |
end | |
def get_task_counts | |
return [0, 0, 0, 0] unless File.exist?(@tasks_json) | |
data = JSON.parse(File.read(@tasks_json)) | |
tasks = data['tasks'] | |
total = tasks.size | |
completed = tasks.count { |t| t['status'] == 'done' } | |
pending = tasks.count { |t| t['status'] == 'pending' } | |
in_progress = tasks.count { |t| t['status'] == 'in-progress' } | |
[total, completed, pending, in_progress] | |
end | |
def print_status | |
total, completed, pending, in_progress = get_task_counts | |
puts "Total: #{total} | Done: #{completed} | Pending: #{pending} | In-Progress: #{in_progress}" | |
end | |
def run_automation | |
log "Opening Cursor with project..." | |
system("open -a Cursor \"#{@project_path}\"") | |
sleep 5 | |
print_status | |
log "Waiting for Cursor to initialize..." | |
sleep 10 | |
while @current_iteration < @max_iterations | |
log "Iteration #{@current_iteration} of #{@max_iterations}" | |
unless is_chat_working? | |
log "Chat appears finished, sending next task..." | |
send_messages | |
@last_message_time = Time.now.to_i | |
sleep 10 | |
else | |
log "Chat still working, waiting..." | |
end | |
sleep @check_interval | |
@current_iteration += 1 | |
end | |
end | |
end | |
# Parse command line arguments | |
command = ARGV[0] || 'run' | |
automation = CursorAutomation.new | |
case command | |
when 'run', '' | |
automation.run_automation | |
when 'status' | |
automation.print_status | |
when 'send' | |
automation.run | |
else | |
puts "Unknown command: #{command}" | |
puts "Usage: #{$0} [run|status|send]" | |
exit 1 | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment