Integrating Claude Code with Sublime Text
Given how much I love utilizing WatchExec as part of my development workflow. I wanted to find a way to integrate Claude code in a similar way. the goal is to create a watcher style plugin that will provide insights on the work I am doing in real time. This plug in will:
- Have the ability to be enabled/disabled
- Will run when a file is saved
- Will provide insights on the direct context of the work I am doing (only analyze the current file)
- Will run a prompt that guides the focus to provide the insights that I want, eliminating noise
- Open in a new tab in the editor so I do not have to switch windows/leave my current workflow to get the insights
A few details to consider.
- You will need a Claude code pro subscription to utilize the API the way I do in this thread.
- I built this for sublime text, which is my editor of choice because of how easily I find it to customize to the workflows i want to add.
Step 1: Install Claude Code
npm install -g @anthropic-ai/claude-codeThen verify it is installed.
claude-code --versionStep 2: Authenticate Claude Code
claude-code authStep 3: Configure Sublime Text to Use Claude Code
I will be utilize the `terminus` package to leverage Claude code from the terminal.
Install Terminus package (better terminal in Sublime):
- Press Ctrl+Shift+P / Cmd+Shift+P
- Type "Package Control: Install Package"
- Search for "Terminus" and install
Now configure a key binding to trigger terminus to open and run the Claude code command. Go to settings -> key bindings and drop this code in.
{
"keys": ["ctrl+alt+t"],
"command": "terminus_open",
"args": {
"cmd": ["/bin/zsh", "-i", "-c", "claude"],
"cwd": "${file_path:${folder}}"
}
},This command is saying, ""start zsh interactively and run the Claude command". Passing in the cwd ensures Claude has context about the current project/folder.
I also want to add key bindings for enabling/disabiling the plugin.
{
"keys": ["ctrl+alt+w"],
"command": "toggle_claude_watcher",
"caption": "Toggle Claude Watcher"
},Create Custom Plugin
Create a plugin for more seamless integration:
- Go to Tools → Developer → New Plugin
- My current plugin code:
import sublime
import sublime_plugin
import os
from threading import Timer
from datetime import datetime
# Global toggle state
claude_watcher_enabled = False
class ClaudeFileWatcher(sublime_plugin.EventListener):
WATCHED_EXTENSIONS = ['.rb', '.tsx', 'js']
WATCHED_PROJECTS = [
'/replace with your path/documents/projects/*',
'your_app_here'
]
DEBOUNCE_DELAY = 1.0
LOG_DIRECTORY = os.path.expanduser("~/Documents/projects/claude_logs")
pending_timer = None
def on_post_save_async(self, view):
global claude_watcher_enabled
if not claude_watcher_enabled:
return
file_path = view.file_name()
if not file_path:
return
normalized_path = os.path.normpath(file_path)
in_watched_project = any(
os.path.normpath(proj) in normalized_path
for proj in self.WATCHED_PROJECTS
)
if not in_watched_project:
return
_, ext = os.path.splitext(file_path)
if ext not in self.WATCHED_EXTENSIONS:
return
if self.pending_timer:
self.pending_timer.cancel()
self.pending_timer = Timer(self.DEBOUNCE_DELAY, self.review_file, [view, file_path])
self.pending_timer.start()
def review_file(self, view, file_path):
file_name = os.path.basename(file_path)
working_dir = os.path.dirname(file_path)
sublime.set_timeout(lambda: self.send_to_claude(view, file_name, working_dir), 0)
def send_to_claude(self, view, file_name, working_dir):
window = view.window()
if not window:
return
timestamp = datetime.now().strftime("%H:%M:%S")
date_stamp = datetime.now().strftime("%Y-%m-%d")
# Get project/app name from working directory
folders = window.folders()
if folders:
# Use the project folder name (root of what's open in Sublime)
project_name = os.path.basename(folders[0])
else:
# Fallback: traverse up to find a reasonable project root
# Look for common project markers
current = working_dir
for _ in range(5): # Go up max 5 levels
if any(os.path.exists(os.path.join(current, marker))
for marker in ['.git', 'Gemfile', 'package.json', '.ruby-version']):
project_name = os.path.basename(current)
break
parent = os.path.dirname(current)
if parent == current: # Reached root
break
current = parent
else:
project_name = "unknown_project"
# Find and log existing terminal content before closing
self.log_terminal_content(window, project_name, date_stamp, timestamp)
# Close existing terminal
window.run_command("terminus_close", {"tag": "claude_watcher"})
# Open fresh terminal in panel
window.run_command("terminus_open", {
"cmd": ["/bin/zsh", "-i", "-c",
f"claude '[{timestamp}] I just saved {file_name}. " \
"Quick review - any bugs, refactors, security issues, " \
"query optimizations or improvements? " \
"If none, say \"Looks good\". Be brief.'"],
"cwd": working_dir,
"tag": "claude_watcher",
"pre_window_hooks": [
["new_pane", {"direction": "down"}]
]
})
def log_terminal_content(self, window, project_name, date_stamp, timestamp):
"""Log terminal content before closing"""
# Find the Claude terminal
claude_view = None
for v in window.views():
if v.settings().get("terminus_view.tag") == "claude_watcher":
claude_view = v
break
if not claude_view:
return # No terminal to log
# Get all content from the terminal
content = claude_view.substr(sublime.Region(0, claude_view.size()))
if not content or len(content.strip()) == 0:
return # Nothing to log
# Create log directory if it doesn't exist
os.makedirs(self.LOG_DIRECTORY, exist_ok=True)
# Create log filename: project_name_YYYY-MM-DD.log
log_filename = f"{project_name}_{date_stamp}.log"
log_path = os.path.join(self.LOG_DIRECTORY, log_filename)
# Append to log file
try:
with open(log_path, 'a', encoding='utf-8') as f:
f.write(f"\n{'='*80}\n")
f.write(f"Session at {timestamp}\n")
f.write(f"{'='*80}\n")
f.write(content)
f.write(f"\n{'='*80}\n\n")
print(f"Logged Claude session to: {log_path}")
except Exception as e:
print(f"Error logging Claude session: {e}")
def find_claude_terminal(self, window):
"""Find existing Claude Watcher terminal view"""
for view in window.views():
# Check if this is a Terminus view with our tag
settings = view.settings()
if settings.get("terminus_view.tag") == "claude_watcher":
return view
# Also check by title as fallback
if view.name() == "Claude Watcher":
return view
return None
class ToggleClaudeWatcherCommand(sublime_plugin.ApplicationCommand):
def run(self):
global claude_watcher_enabled
claude_watcher_enabled = not claude_watcher_enabled
status = "enabled ✓" if claude_watcher_enabled else "disabled ✗"
sublime.status_message(f"Claude Watcher {status}")
class ClaudeWatcherStatusCommand(sublime_plugin.ApplicationCommand):
def run(self):
global claude_watcher_enabled
watcher = ClaudeFileWatcher()
status = "enabled ✓" if claude_watcher_enabled else "disabled ✗"
message = f"""
Claude Watcher Status: {status}
Settings:
- Debounce delay: {watcher.DEBOUNCE_DELAY} seconds
- Watched projects: {', '.join(watcher.WATCHED_PROJECTS)}
- Watched extensions: {', '.join(watcher.WATCHED_EXTENSIONS)}
"""
sublime.message_dialog(message.strip())I'm not going to discuss everything in here but want to point out the main takeaways as I see them.
Enabling/Disabling
With the key binding added I can now toggle it on/off and see that status in my code editor


Running the command on save, watcher style.
On save it will pass the command to Claude code:
`I just saved _app.tsx. Quick review - any bugs, refactors, security issues, query optimizations or improvements? If none, say "Looks good". Be brief.`
That command gets passed to Claude in a new panel in sublime text. Example below when adding Goggle analytics to my website.

Worth noting here that when disabled this window will not open, I can just save the file and move on as is.
There is an feature in the python code I am leaving out of the discussion here. Primarily that I also write the output to a log file. This is for my own benefit. Having the output allows me to process those logs and get other details from them after a session. One other reason is that in order to continually send the command to terminus I needed to open and close the window which loses the code we were discussing so this also allows me to keep track of everything that comes up.