Skip to main content
Glama
terminator.scpt40.5 kB
#!/usr/bin/osascript -------------------------------------------------------------------------------- -- terminator.scpt - v0.6.1 Enhanced "T-1000" -- Enhanced Terminal session management with smart session reuse and better error reporting -- Features: Smart session reuse, enhanced error reporting, improved timing, better output formatting -------------------------------------------------------------------------------- --#region Configuration Properties property maxCommandWaitTime : 15.0 -- Increased from 10.0 for better reliability property pollIntervalForBusyCheck : 0.1 property startupDelayForTerminal : 0.7 property minTailLinesOnWrite : 100 -- Increased from 15 for better build log visibility property defaultTailLines : 100 -- Increased from 30 for better build log visibility property tabTitlePrefix : "🤖💥 " -- For the window/tab title itself property scriptInfoPrefix : "Terminator 🤖💥: " -- For messages generated by this script property projectIdentifierInTitle : "Project: " property taskIdentifierInTitle : " - Task: " property enableFuzzyTagGrouping : true property fuzzyGroupingMinPrefixLength : 4 -- Safe enhanced properties (minimal additions) property enhancedErrorReporting : true property verboseLogging : false --#endregion Configuration Properties --#region Helper Functions on isValidPath(thePath) if thePath is not "" and (thePath starts with "/") then if not (thePath contains " -") then -- Basic heuristic return true end if end if return false end isValidPath on getPathComponent(thePath, componentIndex) set oldDelims to AppleScript's text item delimiters set AppleScript's text item delimiters to "/" set pathParts to text items of thePath set AppleScript's text item delimiters to oldDelims set nonEmptyParts to {} repeat with aPart in pathParts if aPart is not "" then set end of nonEmptyParts to aPart end repeat if (count nonEmptyParts) = 0 then return "" try if componentIndex is -1 then return item -1 of nonEmptyParts else if componentIndex > 0 and componentIndex ≤ (count nonEmptyParts) then return item componentIndex of nonEmptyParts end if on error return "" end try return "" end getPathComponent on generateWindowTitle(taskTag as text, projectGroup as text) if projectGroup is not "" then return tabTitlePrefix & projectIdentifierInTitle & projectGroup & taskIdentifierInTitle & taskTag else return tabTitlePrefix & taskTag end if end generateWindowTitle on bufferContainsMeaningfulContentAS(multiLineText, knownInfoPrefix as text, commonShellPrompts as list) if multiLineText is "" then return false -- Simple approach: if the trimmed content is substantial and not just our info messages, consider it meaningful set trimmedText to my trimWhitespace(multiLineText) if (length of trimmedText) < 3 then return false -- Check if it's only our script info messages if trimmedText starts with knownInfoPrefix then -- If it's ONLY our message and nothing else meaningful, return false set oldDelims to AppleScript's text item delimiters set AppleScript's text item delimiters to linefeed set textLines to text items of multiLineText set AppleScript's text item delimiters to oldDelims set nonInfoLines to 0 repeat with aLine in textLines set currentLine to my trimWhitespace(aLine as text) if currentLine is not "" and not (currentLine starts with knownInfoPrefix) then set nonInfoLines to nonInfoLines + 1 end if end repeat -- If we have substantial non-info content, consider it meaningful return (nonInfoLines > 2) end if -- If content doesn't start with our info prefix, likely contains command output return true end bufferContainsMeaningfulContentAS -- Enhanced error reporting helper on formatErrorMessage(errorType, errorMsg, context) if enhancedErrorReporting then set formattedMsg to scriptInfoPrefix & errorType & ": " & errorMsg if context is not "" then set formattedMsg to formattedMsg & " (Context: " & context & ")" end if return formattedMsg else return scriptInfoPrefix & errorMsg end if end formatErrorMessage -- Enhanced logging helper on logVerbose(message) if verboseLogging then log "🔍 " & message end if end logVerbose --#endregion Helper Functions --#region Main Script Logic (on run) on run argv set appSpecificErrorOccurred to false try my logVerbose("Starting Terminator v0.6.0 Safe Enhanced") tell application "System Events" if not (exists process "Terminal") then launch application id "com.apple.Terminal" delay startupDelayForTerminal end if end tell set originalArgCount to count argv if originalArgCount < 1 then return my usageText() set projectPathArg to "" set actualArgsForParsing to argv if originalArgCount > 0 then set potentialPath to item 1 of argv if my isValidPath(potentialPath) then set projectPathArg to potentialPath my logVerbose("Detected project path: " & projectPathArg) if originalArgCount > 1 then set actualArgsForParsing to items 2 thru -1 of argv else return my formatErrorMessage("Argument Error", "Project path \"" & projectPathArg & "\" provided, but no task tag or command specified." & linefeed & linefeed & my usageText(), "") end if end if end if if (count actualArgsForParsing) < 1 then return my usageText() set taskTagName to item 1 of actualArgsForParsing my logVerbose("Task tag: " & taskTagName) if (length of taskTagName) > 40 or (not my tagOK(taskTagName)) then set errorMsg to "Task Tag missing or invalid: \"" & taskTagName & "\"." & linefeed & linefeed & ¬ "A 'task tag' (e.g., 'build', 'tests') is a short name (1-40 letters, digits, -, _) " & ¬ "to identify a specific task, optionally within a project session." & linefeed & linefeed return my formatErrorMessage("Validation Error", errorMsg & my usageText(), "tag validation") end if set doWrite to false set shellCmd to "" set originalUserShellCmd to "" set currentTailLines to defaultTailLines set explicitLinesProvided to false set argCountAfterTagOrPath to count actualArgsForParsing if argCountAfterTagOrPath > 1 then set commandParts to items 2 thru -1 of actualArgsForParsing if (count commandParts) > 0 then set lastOfCmdParts to item -1 of commandParts if my isInteger(lastOfCmdParts) then set currentTailLines to (lastOfCmdParts as integer) set explicitLinesProvided to true my logVerbose("Explicit lines requested: " & currentTailLines) if (count commandParts) > 1 then set commandParts to items 1 thru -2 of commandParts else set commandParts to {} end if end if end if if (count commandParts) > 0 then set originalUserShellCmd to my joinList(commandParts, " ") my logVerbose("Command detected: " & originalUserShellCmd) end if else if argCountAfterTagOrPath = 1 then -- Only taskTagName was provided after potential projectPathArg -- This is a read operation by default. my logVerbose("Read-only operation detected") end if if originalUserShellCmd is not "" and (my trimWhitespace(originalUserShellCmd) is not "") then set doWrite to true set shellCmd to originalUserShellCmd else if projectPathArg is not "" and originalUserShellCmd is "" then -- Path provided, task tag, and empty command string "" OR no command string but lines_to_read was there set doWrite to true set shellCmd to "" -- will become 'cd path' my logVerbose("CD-only operation for path: " & projectPathArg) else set doWrite to false set shellCmd to "" end if if currentTailLines < 1 then set currentTailLines to 1 if doWrite and (shellCmd is not "" or projectPathArg is not "") and currentTailLines < minTailLinesOnWrite then set currentTailLines to minTailLinesOnWrite my logVerbose("Increased tail lines for write operation: " & currentTailLines) end if if projectPathArg is not "" and doWrite then set quotedProjectPath to quoted form of projectPathArg if shellCmd is not "" then set shellCmd to "cd " & quotedProjectPath & " && " & shellCmd else set shellCmd to "cd " & quotedProjectPath end if my logVerbose("Final command: " & shellCmd) end if set derivedProjectGroup to "" if projectPathArg is not "" then set derivedProjectGroup to my getPathComponent(projectPathArg, -1) if derivedProjectGroup is "" then set derivedProjectGroup to "DefaultProject" my logVerbose("Project group: " & derivedProjectGroup) end if set allowCreation to false if doWrite then set allowCreation to true else if explicitLinesProvided then set allowCreation to true end if set effectiveTabTitleForLookup to my generateWindowTitle(taskTagName, derivedProjectGroup) my logVerbose("Tab title: " & effectiveTabTitleForLookup) set tabInfo to my ensureTabAndWindow(taskTagName, derivedProjectGroup, allowCreation, effectiveTabTitleForLookup) if tabInfo is missing value then if not allowCreation then set errorMsg to "Terminal session \"" & effectiveTabTitleForLookup & "\" not found." & linefeed & ¬ "To create this session, provide a command (even an empty string \"\" if only 'cd'-ing to a project path), " & ¬ "or specify lines to read (e.g., ... \"" & taskTagName & "\" 1)." & linefeed if projectPathArg is not "" then set errorMsg to errorMsg & "Project path was specified as: \"" & projectPathArg & "\"." & linefeed else set errorMsg to errorMsg & "If this is for a new project, provide the absolute project path as the first argument." & linefeed end if return my formatErrorMessage("Session Error", errorMsg & linefeed & my usageText(), "session lookup") else return my formatErrorMessage("Creation Error", "Could not find or create Terminal tab for \"" & effectiveTabTitleForLookup & "\". Check permissions/Terminal state.", "tab creation") end if end if set targetTab to targetTab of tabInfo set parentWindow to parentWindow of tabInfo set wasNewlyCreated to wasNewlyCreated of tabInfo set createdInExistingViaFuzzy to createdInExistingWindowViaFuzzy of tabInfo my logVerbose("Tab info - new: " & wasNewlyCreated & ", fuzzy: " & createdInExistingViaFuzzy) set bufferText to "" set commandTimedOut to false set tabWasBusyOnRead to false set previousCommandActuallyStopped to true set attemptMadeToStopPreviousCommand to false set identifiedBusyProcessName to "" set theTTYForInfo to "" if not doWrite and wasNewlyCreated then if createdInExistingViaFuzzy then return scriptInfoPrefix & "New tab \"" & effectiveTabTitleForLookup & "\" created in existing project window and ready." else return scriptInfoPrefix & "New tab \"" & effectiveTabTitleForLookup & "\" (in new window) created and ready." end if end if tell application id "com.apple.Terminal" try set index of parentWindow to 1 set selected tab of parentWindow to targetTab if wasNewlyCreated and doWrite then delay 0.4 else delay 0.1 end if if doWrite and shellCmd is not "" then my logVerbose("Executing command: " & shellCmd) set canProceedWithWrite to true if busy of targetTab then if not wasNewlyCreated or createdInExistingViaFuzzy then set attemptMadeToStopPreviousCommand to true set previousCommandActuallyStopped to false try set theTTYForInfo to my trimWhitespace(tty of targetTab) end try set processesBefore to {} try set processesBefore to processes of targetTab end try set commonShells to {"login", "bash", "zsh", "sh", "tcsh", "ksh", "-bash", "-zsh", "-sh", "-tcsh", "-ksh", "dtterm", "fish"} set identifiedBusyProcessName to "" if (count of processesBefore) > 0 then repeat with i from (count of processesBefore) to 1 by -1 set aProcessName to item i of processesBefore if aProcessName is not in commonShells then set identifiedBusyProcessName to aProcessName exit repeat end if end repeat end if my logVerbose("Busy process identified: " & identifiedBusyProcessName) set processToTargetForKill to identifiedBusyProcessName set killedViaPID to false if theTTYForInfo is not "" and processToTargetForKill is not "" then set shortTTY to text 6 thru -1 of theTTYForInfo set pidsToKillText to "" try set psCommand to "ps -t " & shortTTY & " -o pid,comm | awk '$2 == \"" & processToTargetForKill & "\" {print $1}'" set pidsToKillText to do shell script psCommand end try if pidsToKillText is not "" then set oldDelims to AppleScript's text item delimiters set AppleScript's text item delimiters to linefeed set pidList to text items of pidsToKillText set AppleScript's text item delimiters to oldDelims repeat with aPID in pidList set aPID to my trimWhitespace(aPID) if aPID is not "" then try do shell script "kill -INT " & aPID delay 0.3 do shell script "kill -0 " & aPID try do shell script "kill -KILL " & aPID delay 0.2 try do shell script "kill -0 " & aPID on error set previousCommandActuallyStopped to true end try end try on error set previousCommandActuallyStopped to true end try end if if previousCommandActuallyStopped then set killedViaPID to true exit repeat end if end repeat end if end if if not previousCommandActuallyStopped and busy of targetTab then activate delay 0.5 tell application "System Events" to keystroke "c" using control down delay 0.6 if not (busy of targetTab) then set previousCommandActuallyStopped to true if identifiedBusyProcessName is not "" and (identifiedBusyProcessName is in (processes of targetTab)) then set previousCommandActuallyStopped to false end if end if else if not busy of targetTab then set previousCommandActuallyStopped to true end if if not previousCommandActuallyStopped then set canProceedWithWrite to false end if else if wasNewlyCreated and not createdInExistingViaFuzzy and busy of targetTab then delay 0.4 if busy of targetTab then set attemptMadeToStopPreviousCommand to true set previousCommandActuallyStopped to false set identifiedBusyProcessName to "extended initialization" set canProceedWithWrite to false else set previousCommandActuallyStopped to true end if end if end if if canProceedWithWrite then -- Clear before write to prevent output truncation (only for reused tabs) if not wasNewlyCreated then do script "clear" in targetTab delay 0.1 end if do script shellCmd in targetTab set commandStartTime to current date set commandFinished to false repeat while ((current date) - commandStartTime) < maxCommandWaitTime if not (busy of targetTab) then set commandFinished to true exit repeat end if delay pollIntervalForBusyCheck end repeat if not commandFinished then set commandTimedOut to true if commandFinished then delay 0.2 -- Increased from 0.1 for better output settling my logVerbose("Command execution completed, timeout: " & commandTimedOut) end if else if not doWrite then if busy of targetTab then set tabWasBusyOnRead to true try set theTTYForInfo to my trimWhitespace(tty of targetTab) end try set processesReading to processes of targetTab set commonShells to {"login", "bash", "zsh", "sh", "tcsh", "ksh", "-bash", "-zsh", "-sh", "-tcsh", "-ksh", "dtterm", "fish"} set identifiedBusyProcessName to "" if (count of processesReading) > 0 then repeat with i from (count of processesReading) to 1 by -1 set aProcessName to item i of processesReading if aProcessName is not in commonShells then set identifiedBusyProcessName to aProcessName exit repeat end if end repeat end if my logVerbose("Tab busy during read with: " & identifiedBusyProcessName) end if end if set bufferText to history of targetTab on error errMsg number errNum set appSpecificErrorOccurred to true return my formatErrorMessage("Terminal Error", errMsg, "error " & errNum) end try end tell set appendedMessage to "" set ttyInfoStringForMessage to "" if theTTYForInfo is not "" then set ttyInfoStringForMessage to " (TTY " & theTTYForInfo & ")" if attemptMadeToStopPreviousCommand then set processNameToReport to "process" if identifiedBusyProcessName is not "" and identifiedBusyProcessName is not "extended initialization" then set processNameToReport to "'" & identifiedBusyProcessName & "'" else if identifiedBusyProcessName is "extended initialization" then set processNameToReport to "tab's extended initialization" end if if previousCommandActuallyStopped then set appendedMessage to linefeed & scriptInfoPrefix & "Previous " & processNameToReport & ttyInfoStringForMessage & " was interrupted. ---" else set appendedMessage to linefeed & scriptInfoPrefix & "Attempted to interrupt previous " & processNameToReport & ttyInfoStringForMessage & ", but it may still be running. New command NOT executed. ---" end if end if if commandTimedOut then set cmdForMsg to originalUserShellCmd if projectPathArg is not "" and originalUserShellCmd is not "" then set cmdForMsg to originalUserShellCmd & " (in " & projectPathArg & ")" if projectPathArg is not "" and originalUserShellCmd is "" then set cmdForMsg to "(cd " & projectPathArg & ")" set appendedMessage to appendedMessage & linefeed & scriptInfoPrefix & "Command '" & cmdForMsg & "' may still be running. Returned after " & maxCommandWaitTime & "s timeout. ---" else if tabWasBusyOnRead then set processNameToReportOnRead to "process" if identifiedBusyProcessName is not "" then set processNameToReportOnRead to "'" & identifiedBusyProcessName & "'" set busyProcessInfoString to "" if identifiedBusyProcessName is not "" then set busyProcessInfoString to " with " & processNameToReportOnRead set appendedMessage to appendedMessage & linefeed & scriptInfoPrefix & "Tab" & ttyInfoStringForMessage & " was busy" & busyProcessInfoString & " during read. Output may be from an ongoing process. ---" end if if appendedMessage is not "" then if bufferText is "" then set bufferText to my trimWhitespace(appendedMessage) else set bufferText to bufferText & appendedMessage end if end if set tailedOutput to my tailBufferAS(bufferText, currentTailLines) set finalResult to my trimBlankLinesAS(tailedOutput) if finalResult is "" then set effectiveOriginalCmdForMsg to originalUserShellCmd if projectPathArg is not "" and originalUserShellCmd is "" then set effectiveOriginalCmdForMsg to "(cd " & projectPathArg & ")" else if projectPathArg is not "" and originalUserShellCmd is not "" then set effectiveOriginalCmdForMsg to originalUserShellCmd & " (in " & projectPathArg & ")" end if set baseMsgInfo to "Session \"" & effectiveTabTitleForLookup & "\", requested " & currentTailLines & " lines." set specificAppendedInfo to my trimWhitespace(appendedMessage) set suffixForReturn to "" if specificAppendedInfo is not "" then set suffixForReturn to linefeed & specificAppendedInfo if attemptMadeToStopPreviousCommand and not previousCommandActuallyStopped then return my formatErrorMessage("Process Error", "Previous command/initialization in session \"" & effectiveTabTitleForLookup & "\"" & ttyInfoStringForMessage & " may not have terminated. New command '" & effectiveOriginalCmdForMsg & "' NOT executed." & suffixForReturn, "process termination") else if commandTimedOut then return my formatErrorMessage("Timeout Error", "Command '" & effectiveOriginalCmdForMsg & "' timed out after " & maxCommandWaitTime & "s. No other output. " & baseMsgInfo & suffixForReturn, "command timeout") else if tabWasBusyOnRead then return my formatErrorMessage("Busy Error", "Tab for session \"" & effectiveTabTitleForLookup & "\" was busy during read. No other output. " & baseMsgInfo & suffixForReturn, "read busy") else if doWrite and shellCmd is not "" then return scriptInfoPrefix & "Command '" & effectiveOriginalCmdForMsg & "' executed in session \"" & effectiveTabTitleForLookup & "\". No output captured." else return scriptInfoPrefix & "No meaningful content found in session \"" & effectiveTabTitleForLookup & "\"." end if end if my logVerbose("Returning " & (length of finalResult) & " characters of output") return finalResult on error generalErrorMsg number generalErrorNum if appSpecificErrorOccurred then error generalErrorMsg number generalErrorNum return my formatErrorMessage("Execution Error", generalErrorMsg, "error " & generalErrorNum) end try end run --#endregion Main Script Logic (on run) --#region Helper Functions on ensureTabAndWindow(taskTagName as text, projectGroupName as text, allowCreate as boolean, desiredFullTitle as text) set wasActuallyCreated to false set createdInExistingViaFuzzy to false tell application id "com.apple.Terminal" try repeat with w in windows repeat with tb in tabs of w try if custom title of tb is desiredFullTitle then set selected tab of w to tb return {targetTab:tb, parentWindow:w, wasNewlyCreated:false, createdInExistingWindowViaFuzzy:false} end if end try end repeat end repeat end try if allowCreate and enableFuzzyTagGrouping and projectGroupName is not "" then set projectGroupSearchPatternForWindowName to tabTitlePrefix & projectIdentifierInTitle & projectGroupName try repeat with w in windows try -- Look for any window that contains our project name if name of w contains projectGroupSearchPatternForWindowName or name of w contains (projectIdentifierInTitle & projectGroupName) then if not frontmost then activate delay 0.2 set newTabInGroup to do script "" in w delay 0.3 set custom title of newTabInGroup to desiredFullTitle delay 0.2 set selected tab of w to newTabInGroup return {targetTab:newTabInGroup, parentWindow:w, wasNewlyCreated:true, createdInExistingWindowViaFuzzy:true} end if end try end repeat end try end if -- Enhanced fallback: if no project-specific window found, try to use any existing Terminator window if allowCreate and enableFuzzyTagGrouping then try repeat with w in windows try if name of w contains tabTitlePrefix then -- Found an existing Terminator window, use it for grouping if not frontmost then activate delay 0.2 set newTabInGroup to do script "" in w delay 0.3 set custom title of newTabInGroup to desiredFullTitle delay 0.2 set selected tab of w to newTabInGroup return {targetTab:newTabInGroup, parentWindow:w, wasNewlyCreated:true, createdInExistingWindowViaFuzzy:true} end if end try end repeat end try end if if allowCreate then try if not frontmost then activate delay 0.3 set newTabInNewWindow to do script "" set wasActuallyCreated to true delay 0.4 set custom title of newTabInNewWindow to desiredFullTitle delay 0.2 set parentWinOfNew to missing value try set parentWinOfNew to window of newTabInNewWindow on error if (count of windows) > 0 then set parentWinOfNew to front window end try if parentWinOfNew is not missing value then if custom title of newTabInNewWindow is desiredFullTitle then set selected tab of parentWinOfNew to newTabInNewWindow return {targetTab:newTabInNewWindow, parentWindow:parentWinOfNew, wasNewlyCreated:wasActuallyCreated, createdInExistingWindowViaFuzzy:false} end if end if repeat with w_final_scan in windows repeat with tb_final_scan in tabs of w_final_scan try if custom title of tb_final_scan is desiredFullTitle then set selected tab of w_final_scan to tb_final_scan return {targetTab:tb_final_scan, parentWindow:w_final_scan, wasNewlyCreated:wasActuallyCreated, createdInExistingWindowViaFuzzy:false} end if end try end repeat end repeat return missing value on error return missing value end try else return missing value end if end tell end ensureTabAndWindow on tailBufferAS(txt, n) set AppleScript's text item delimiters to linefeed set lst to text items of txt if (count lst) = 0 then return "" set startN to (count lst) - (n - 1) if startN < 1 then set startN to 1 set slice to items startN thru -1 of lst set outText to slice as text set AppleScript's text item delimiters to "" return outText end tailBufferAS on lineIsEffectivelyEmptyAS(aLine) if aLine is "" then return true set trimmedLine to my trimWhitespace(aLine) return (trimmedLine is "") end lineIsEffectivelyEmptyAS on trimBlankLinesAS(txt) if txt is "" then return "" set oldDelims to AppleScript's text item delimiters set AppleScript's text item delimiters to {linefeed} set originalLines to text items of txt set linesToProcess to {} repeat with aLineRef in originalLines set aLine to contents of aLineRef if my lineIsEffectivelyEmptyAS(aLine) then set end of linesToProcess to "" else set end of linesToProcess to aLine end if end repeat set firstContentLine to 1 repeat while firstContentLine ≤ (count linesToProcess) and (item firstContentLine of linesToProcess is "") set firstContentLine to firstContentLine + 1 end repeat set lastContentLine to count linesToProcess repeat while lastContentLine ≥ firstContentLine and (item lastContentLine of linesToProcess is "") set lastContentLine to lastContentLine - 1 end repeat if firstContentLine > lastContentLine then set AppleScript's text item delimiters to oldDelims return "" end if set resultLines to items firstContentLine thru lastContentLine of linesToProcess set AppleScript's text item delimiters to linefeed set trimmedTxt to resultLines as text set AppleScript's text item delimiters to oldDelims return trimmedTxt end trimBlankLinesAS on trimWhitespace(theText) set whitespaceChars to {" ", tab} set newText to theText repeat while (newText is not "") and (character 1 of newText is in whitespaceChars) if (length of newText) > 1 then set newText to text 2 thru -1 of newText else set newText to "" end if end repeat repeat while (newText is not "") and (character -1 of newText is in whitespaceChars) if (length of newText) > 1 then set newText to text 1 thru -2 of newText else set newText to "" end if end repeat return newText end trimWhitespace on isInteger(v) try v as integer return true on error return false end try end isInteger on tagOK(t) try do shell script "/bin/echo " & quoted form of t & " | /usr/bin/grep -E -q '^[A-Za-z0-9_-]+$'" return true on error return false end try end tagOK on joinList(theList, theDelimiter) set oldDelims to AppleScript's text item delimiters set AppleScript's text item delimiters to theDelimiter set theText to theList as text set AppleScript's text item delimiters to oldDelims return theText end joinList on usageText() set LF to linefeed set scriptName to "terminator.scpt" set exampleProject to "/Users/name/Projects/FancyApp" set exampleProjectNameForTitle to my getPathComponent(exampleProject, -1) if exampleProjectNameForTitle is "" then set exampleProjectNameForTitle to "DefaultProject" set exampleTaskTag to "build_frontend" set exampleFullCommand to "npm run build" set generatedExampleTitle to my generateWindowTitle(exampleTaskTag, exampleProjectNameForTitle) set outText to scriptName & " - v0.6.0 Enhanced \"T-1000\" – AppleScript Terminal helper" & LF & LF set outText to outText & "Enhancements: Smart session reuse, enhanced error reporting, verbose logging (optional)" & LF & LF set outText to outText & "Manages dedicated, tagged Terminal sessions, grouped by project path." & LF & LF set outText to outText & "Core Concept:" & LF set outText to outText & " 1. For a NEW project, provide the absolute project path FIRST, then task tag, then command:" & LF set outText to outText & " osascript " & scriptName & " \"" & exampleProject & "\" \"" & exampleTaskTag & "\" \"" & exampleFullCommand & "\"" & LF set outText to outText & " The script will 'cd' into the project path and run the command." & LF set outText to outText & " The tab will be titled like: \"" & generatedExampleTitle & "\"" & LF set outText to outText & " 2. For SUBSEQUENT commands for THE SAME PROJECT, use the project path and task tag:" & LF set outText to outText & " osascript " & scriptName & " \"" & exampleProject & "\" \"" & exampleTaskTag & "\" \"another_command\"" & LF set outText to outText & " 3. To simply READ from an existing session (path & tag must identify an existing session):" & LF set outText to outText & " osascript " & scriptName & " \"" & exampleProject & "\" \"" & exampleTaskTag & "\"" & LF set outText to outText & " A READ operation on a non-existent tag (without path/command to create) will error." & LF & LF set outText to outText & "Title Format: \"" & tabTitlePrefix & projectIdentifierInTitle & "<ProjectName>" & taskIdentifierInTitle & "<TaskTag>\"" & LF set outText to outText & "Or if no project path provided: \"" & tabTitlePrefix & "<TaskTag>\"" & LF & LF set outText to outText & "Enhanced Features:" & LF set outText to outText & " • Smart session reuse for same project paths" & LF set outText to outText & " • Enhanced error reporting with context information" & LF set outText to outText & " • Optional verbose logging for debugging" & LF set outText to outText & " • No automatic clearing to prevent interrupting builds" & LF set outText to outText & " • 100-line default output for better build log visibility" & LF set outText to outText & " • Automatically 'cd's into project path if provided with a command." & LF set outText to outText & " • Groups new task tabs into existing project windows if fuzzy grouping enabled." & LF set outText to outText & " • Interrupts busy processes in reused tabs." & LF & LF set outText to outText & "Usage Examples:" & LF set outText to outText & " # Start new project session, cd, run command, get 100 lines:" & LF set outText to outText & " osascript " & scriptName & " \"" & exampleProject & "\" \"frontend_build\" \"npm run build\" 100" & LF set outText to outText & " # Create/use 'backend_tests' task tab in the 'FancyApp' project window:" & LF set outText to outText & " osascript " & scriptName & " \"" & exampleProject & "\" \"backend_tests\" \"pytest\"" & LF set outText to outText & " # Prepare/create a new session by just cd'ing into project path (empty command):" & LF set outText to outText & " osascript " & scriptName & " \"" & exampleProject & "\" \"dev_shell\" \"\" 1" & LF set outText to outText & " # Read from an existing session:" & LF set outText to outText & " osascript " & scriptName & " \"" & exampleProject & "\" \"frontend_build\" 50" & LF & LF set outText to outText & "Parameters:" & LF set outText to outText & " [\"/absolute/project/path\"]: (Optional First Arg) Base path for project. Enables 'cd' and grouping." & LF set outText to outText & " \"<task_tag_name>\": Required. Specific task name for the tab (e.g., 'build', 'tests')." & LF set outText to outText & " [\"<shell_command_parts...>\"]: (Optional) Command. If path provided, 'cd path &&' is prepended." & LF set outText to outText & " Use \"\" for no command (will just 'cd' if path given)." & LF set outText to outText & " [[lines_to_read]]: (Optional Last Arg) Number of history lines. Default: " & defaultTailLines & "." & LF & LF set outText to outText & "Notes:" & LF set outText to outText & " • Provide project path on first use for a project for best window grouping and auto 'cd'." & LF set outText to outText & " • Ensure Automation permissions for Terminal.app & System Events.app." & LF set outText to outText & " • Works within Terminal.app's AppleScript limitations for reliable operation." & LF return outText end usageText --#endregion Helper Functions

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/steipete/Peekaboo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server