Skip to content

Instantly share code, notes, and snippets.

@jeremiahlukus
Last active June 14, 2025 01:49
Show Gist options
  • Save jeremiahlukus/dc72786a72bea94883af16338eaf6198 to your computer and use it in GitHub Desktop.
Save jeremiahlukus/dc72786a72bea94883af16338eaf6198 to your computer and use it in GitHub Desktop.
#!/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