Claude Code is the best coding AI I've ever used. But launching it 30 times a day — open terminal, cd to project directory, type command — that workflow drove me insane.
I built GroAsk to fix it. Press ⌥Space, launch Claude Code with your prompt, auto-navigate to the right project directory. No cd, no memorizing paths, no touching the terminal directly.
This post covers the technical implementation: automation strategies for 4 different terminals, the pitfalls of PATH detection on macOS, dependency chain handling for CLI installs, and why these seemingly simple problems are surprisingly hard to get right.
The Pain: Claude Code's Launch Overhead
Claude Code's interaction model means nontrivial startup friction.
Step one, open a terminal. Step two, cd ~/Projects/my-app — assuming you remember the path. I have dozens of projects scattered across different directories. Step three, type claude. Step four, type your prompt.
About 15 seconds each time. 30 times a day, that's 7 minutes. Seven minutes isn't a lot, but repetitive friction erodes focus. Every context switch to perform these mechanical steps costs more time getting back into flow.
And there are other CLI AIs too — Gemini CLI, Codex, CodeBuddy, Kimi Code, Qwen Code. Same launch ritual: cd, type command, type prompt.
What I wanted was simple: one hotkey, one sentence, start working.
The Solution: ⌥Space to Claude Code
GroAsk compresses all the intermediate steps into nothing.
Press ⌥Space, a text field appears. Select Claude Code, type your prompt, hit enter. GroAsk opens the terminal, cds to the right directory, and runs claude "your prompt".
How does it determine the "right directory"? Three-layer strategy:
- Path prefix in prompt. You can configure workspace aliases in settings — e.g.,
groaskmaps to~/Documents/GitHub/gro/GroAsk. When you type/groask fix this bug, GroAsk parses out the path and passes a clean prompt to Claude Code. Sub-path completion works:/groask/appnavigates to~/Documents/GitHub/gro/GroAsk/app. - Default workspace. Set a frequently used project path; used automatically when no prefix is given.
- Current Finder directory. If Finder has an open window, GroAsk reads its path via AppleScript.
Three layers of fallback, covering 95% of my daily scenarios.
The Hard Part: 4 Terminals, 4 Implementations
macOS users are split across terminals. Terminal.app, iTerm2, Ghostty, Warp — four terminals with no unified command execution interface. This was the most time-consuming part of the implementation.
Terminal.app: AppleScript do script
The traditional approach. Terminal.app has a full AppleScript dictionary. do script executes commands directly in a terminal window:
tell application "Terminal"
activate
do script "cd ~/my-project && claude \"fix the bug\""
end tell
One subtlety: if Terminal isn't running yet, do script executes in the default window. If it's already running, you need a new window. GroAsk detects Terminal's running state and waits for the window to be ready before executing, preventing lost commands.
When NSAppleScript fails due to TCC permission issues, GroAsk automatically falls back to an osascript subprocess.
iTerm2: AppleScript write text
iTerm2 also supports AppleScript, but with a different interface. It uses create window with default profile to open a window, then write text to input commands:
tell application id "com.googlecode.iterm2"
activate
create window with default profile
tell current session of current window
write text "cd ~/my-project && claude \"fix the bug\""
end tell
end tell
Referenced by bundle ID com.googlecode.iterm2 instead of app name, because some users have it named "iTerm" while others have "iTerm2."
Ghostty: Clipboard Paste
Ghostty has no AppleScript dictionary. I initially tried the -e flag for startup, which requires bash -l -c wrapping to load shell configuration, but it proved unreliable in practice.
The final approach: clipboard paste via System Events. Write the command to the clipboard, ⌘V to paste into the terminal, then press return.
tell application "System Events"
tell (first process whose bundle identifier is "com.mitchellh.ghostty")
keystroke "n" using command down -- new window
delay 0.5
keystroke "v" using command down -- paste command
delay 0.1
keystroke return -- execute
end tell
end tell
Why clipboard paste instead of keystroke simulation? Because keystroke goes through the macOS input method system. If the user has a Chinese (or other non-Latin) input method active, the command turns into garbled text. ⌘V paste writes directly to the terminal, bypassing the input method entirely.
After pasting, GroAsk waits 3 seconds before restoring the original clipboard content — enough time for the terminal to finish processing, without polluting the user's clipboard.
This approach requires Accessibility permissions. GroAsk prompts the user to grant access on first use.
Warp: Also Clipboard Paste
Warp's situation is similar to Ghostty — no AppleScript dictionary. I initially tried YAML Launch Config with URI schemes, but ultimately adopted the same clipboard paste approach, sharing the input method bypass logic with Ghostty.
The difference: Ghostty uses ⌘N for a new window; Warp uses ⌘T for a new tab.
Four terminals, two fundamentally different technical approaches: native AppleScript control (Terminal.app, iTerm2) and System Events clipboard injection (Ghostty, Warp). Users pick their terminal once in settings, and it works transparently from then on.
PATH Detection: More Complex Than You'd Think
Claude Code is installed. Can GroAsk find it?
Sounds simple — the command is somewhere in PATH, just look it up. But on macOS, PATH is a mess.
Different installation methods put tools in different places:
curlinstallers (Claude Code, CodeBuddy, Kimi Code) →~/.local/bin- Homebrew (Apple Silicon) →
/opt/homebrew/bin - Homebrew (Intel) →
/usr/local/bin - npm global install →
~/.npm-global/bin - nvm →
~/.nvm/versions/node/v<version>/bin - fnm →
~/Library/Application Support/fnm/node-versions/v<version>/installation/bin - Volta →
~/.volta/bin - Cargo →
~/.cargo/bin - Bun →
~/.bun/bin - mise →
~/.mise/shims
GroAsk's detection strategy has two layers:
Layer 1: Filesystem scan. On launch, scan all known paths above. Pure file checks, no shell dependency, 100% reliable. For version managers like nvm and fnm, it also enumerates all installed Node.js version directories.
Layer 2: Shell PATH snapshot. Read the complete PATH from the user's login shell:
let shell = ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/zsh"
process.arguments = ["-i", "-l", "-c", "printf '\(marker)%s' \"$PATH\""]
There's a trap here: tools like conda and pyenv print noise to stdout during shell startup. GroAsk uses a marker string __GROASK__ to isolate the signal — only the content after the marker is treated as the PATH value.
Both layers are merged and deduplicated. Checking if a command exists is a simple iteration over these paths, checking for an executable file — pure file operations, zero subprocess overhead.
Smart PATH Injection
Finding the command isn't enough. When GroAsk executes a command in the terminal, the terminal's shell environment might not include that path.
GroAsk's approach: at launch, it captures a snapshot of the shell's native PATH. If the command's directory is already in the native PATH, a new terminal window will find it too — execute directly. If it's not — for example, if it was discovered through fallback scanning — GroAsk injects export PATH="<dir>:$PATH" before the command, ensuring the execution environment is correct.
A redundant export is harmless. A missing export is fatal. Better to over-include than to miss.
One-Click Install: Graphical UI for CLI Tools
"Installing a CLI tool" might be one copied command for a developer. For non-command-line users, it's a wall.
GroAsk provides a one-click install button for each CLI AI tool in the settings interface. Clicking it executes the install command in the terminal and starts polling — every 2 seconds, a shell subprocess checks if the command is available, with a 3-minute timeout.
During installation, the channel chip shows an "Installing..." state. Once installation is detected, the status refreshes automatically. No manual action required from the user.
Automatic Dependency Chain Resolution
The most complex case involves tools that need npm — Gemini CLI and Codex.
Three situations, three paths:
- npm available: Execute
npm install -g @google/gemini-clidirectly. - fnm exists but no npm: Alert dialog, then run
fnm install --ltsto get Node.js, followed by the CLI install. - No fnm either: Alert offering "One-click install Node.js environment," then execute the full chain:
curl -fsSL https://fnm.vercel.app/install | bash -s -- --force-no-brew && \
source "${ZDOTDIR:-$HOME}/.zshrc" && \
fnm install --lts && \
eval "$(fnm env)" && \
npm install -g @google/gemini-cli
One button. Behind it: fnm installation → shell config reload → Node.js LTS installation → environment variable setup → CLI tool installation. The user watches terminal output scroll by, then the button changes to "Installed."
Why fnm over nvm? fnm is written in Rust, installs fast, doesn't need Homebrew, doesn't need sudo. The --force-no-brew flag ensures a clean standalone install.
For tools using curl installers (Claude Code, CodeBuddy, Kimi Code), GroAsk also auto-fixes PATH after installation — checking if ~/.zshrc already contains ~/.local/bin, appending it if not. This prevents the frustrating "installed but command not found" scenario.
How GroAsk Compares
There are a few similar tools out there:
Claude Code Now (VS Code extension). Launches Claude Code inside VS Code, tied to the editor. GroAsk is system-level — no editor dependency, works from any app.
Raycast plugins. Raycast is a powerful launcher, but its Claude Code plugin has limited functionality. GroAsk supports launching with a prompt (not just opening a terminal), workspace alias auto-completion, and graphical installation.
What GroAsk does differently:
- Launch with prompt: Not opening an empty Claude Code session, but starting with your question ready to go.
- Graphical one-click install: Users who've never touched CLI tools can get set up in one click, including the full Node.js dependency chain.
- Multi-AI switching: Same input box, 6 CLI AIs + 4 Web AIs, switch with one click. Claude Code isn't the only option.
- Select-and-ask: Highlight text, press hotkey, send directly to AI. No copy-paste.
- Automatic PATH detection: No matter how you installed your tools, GroAsk finds them.
By the Numbers
GroAsk from first line of code to now: 14 days. 5,600 lines of Swift + 1,200 lines of server-side code. 190 commits, 49% made by Claude Code.
Pure AppKit implementation. No SwiftUI, no Electron. macOS native, minimal memory footprint.
6 CLI AI channels: Claude Code, Gemini CLI, Codex, CodeBuddy, Kimi Code, Qwen Code. 4 Web AI channels: ChatGPT, Claude, Gemini, Monica. One tool for all major AIs.
Completely free, no sign-up required.
What's Next
The current implementation is stable — the four terminal automation strategies have been tested against a large number of edge cases. But there's more to do:
- Session management: remember each project's last AI conversation state
- Agent workflows: chain multi-step tasks together
- More CLI AI support: fast onboarding when new tools ship
GroAsk is iterating continuously. If you work with Claude Code every day, try replacing cd with ⌥Space.
Download GroAsk: groask.com
Feedback: Drop a comment, or reach out through the website