<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: JackWu</title>
    <description>The latest articles on DEV Community by JackWu (@thinkerjack).</description>
    <link>https://dev.to/thinkerjack</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3758628%2F1d92d2f9-793d-4154-9505-b847e722bc69.jpg</url>
      <title>DEV Community: JackWu</title>
      <link>https://dev.to/thinkerjack</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thinkerjack"/>
    <language>en</language>
    <item>
      <title>6 Business Questions Every Solo Dev Skips (That Kill Products Before Launch)</title>
      <dc:creator>JackWu</dc:creator>
      <pubDate>Sat, 21 Feb 2026 14:23:47 +0000</pubDate>
      <link>https://dev.to/thinkerjack/6-business-questions-every-solo-dev-skips-that-kill-products-before-launch-5197</link>
      <guid>https://dev.to/thinkerjack/6-business-questions-every-solo-dev-skips-that-kill-products-before-launch-5197</guid>
      <description>&lt;p&gt;I spent two months building a macOS tool. Shipped it. Got 23 visitors. Zero paying users.&lt;/p&gt;

&lt;p&gt;Not because the code was broken. Not because the design was bad. Because I skipped everything that happens &lt;em&gt;outside&lt;/em&gt; the code editor.&lt;/p&gt;

&lt;p&gt;Here's the pattern I've watched repeat across dozens of indie products: developer gets an idea, gets excited, starts building, ships after a month, hears crickets, gets confused. The gap is between "idea" and "start building" — at minimum four things get skipped there.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 4 Things That Get Skipped Before Line 1
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Does this problem actually exist?&lt;/strong&gt; Not "do you think it exists" — is someone actively searching for a solution right now?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Who already built this?&lt;/strong&gt; Not just awareness that competitors exist, but a real breakdown of their tech, pricing, and user complaints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Where are your users?&lt;/strong&gt; Not "on the internet." Specific subreddits, Discord servers, forums, newsletters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Why you?&lt;/strong&gt; Not feature count. One dimension where you're clearly better.&lt;/p&gt;

&lt;p&gt;In a big company, a PM handles this. Product research teams validate it. Solo? All of it lands on you — and developers are wired to jump straight to the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Mistake: Competitive Research After Launch
&lt;/h2&gt;

&lt;p&gt;I built GroAsk — a native macOS menu bar launcher that gets you into Claude Code with a single hotkey (&lt;code&gt;⌥Space&lt;/code&gt;). Agent dispatch panel, multi-CLI support, 100% local.&lt;/p&gt;

&lt;p&gt;I did competitive research &lt;em&gt;after&lt;/em&gt; the product was feature-complete. What I found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 direct competitors&lt;/li&gt;
&lt;li&gt;The top one (opcode) has 20k+ GitHub stars&lt;/li&gt;
&lt;li&gt;Auto-Claude has 12k+ stars with 73 contributors&lt;/li&gt;
&lt;li&gt;Even the YC-backed Claudia chose AGPL open source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Every single competitor is free and open source. Not one charges a dollar.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If I'd done this research before writing line 1, my design and pricing strategy would have looked completely different.&lt;/p&gt;

&lt;p&gt;But here's where competitive research pays off in a non-obvious way: I noticed something in the tech stacks. Every competitor is built on Electron, Tauri, or pure web tech. Not one is native macOS.&lt;/p&gt;

&lt;p&gt;That's a real opening:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Electron / Tauri&lt;/th&gt;
&lt;th&gt;Native AppKit (GroAsk)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Launch time&lt;/td&gt;
&lt;td&gt;1–3 seconds&lt;/td&gt;
&lt;td&gt;&amp;lt; 0.3 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;200–500 MB&lt;/td&gt;
&lt;td&gt;&amp;lt; 50 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System integration&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Menu bar, global hotkeys, native notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Lesson: competitive research isn't for talking yourself out of building. It's for finding the dimension where you can actually win.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Pick One North Star Metric
&lt;/h2&gt;

&lt;p&gt;The scariest thing about indie products isn't failure. It's not knowing you're failing.&lt;/p&gt;

&lt;p&gt;If you're tracking downloads, DAU, retention, conversion rate, and NPS at the same time — you're tracking nothing. When everything matters, nothing matters.&lt;/p&gt;

&lt;p&gt;My approach: one north star metric per stage, everything else is context.&lt;/p&gt;

&lt;p&gt;Right now I'm tracking &lt;strong&gt;GitHub Stars&lt;/strong&gt;. Reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero friction — no signup, no download required&lt;/li&gt;
&lt;li&gt;Unambiguous — the number is the number&lt;/li&gt;
&lt;li&gt;Social proof — new visitors lower their guard when they see stars&lt;/li&gt;
&lt;li&gt;Real signal — starring means "I think this is useful, might use it later"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Secondary metric: WAU (weekly active users), defined as opened the app 3+ times in the past 7 days. Currently 12.&lt;/p&gt;

&lt;p&gt;The stage determines the metric. Cold start: track attention (stars, signups, saves). Growth: track engagement (DAU, retention). Monetization: track revenue (MRR, LTV). One primary metric per stage. Everything else is supporting data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Distribution Is a Loop, Not a Single Post
&lt;/h2&gt;

&lt;p&gt;Most solo devs think "marketing" means: write a post, share it to a few communities, wait.&lt;/p&gt;

&lt;p&gt;I did exactly that. Hit 15+ channels. Then nothing.&lt;/p&gt;

&lt;p&gt;What changed it: I started tracking actual referral data. Every download link gets a &lt;code&gt;?ref=xxx&lt;/code&gt; parameter. The site logs where visitors come from.&lt;/p&gt;

&lt;p&gt;Two weeks in:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Visitors&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Search&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X / Twitter&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Appinn (niche software community)&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eleduck (remote work community)&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W2Solo (indie dev community)&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two decisions came directly from this table:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cut the low-signal channels.&lt;/strong&gt; LinkedIn brought 1 visitor. Not worth the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Double down on X/Twitter.&lt;/strong&gt; Claude Code users cluster there. 13% from one social platform signals the high-frequency posting strategy is working. Shifted to 3–5 posts per week of Claude Code workflow content.&lt;/p&gt;

&lt;p&gt;Distribution isn't a one-shot. It's a feedback loop: post → track → find what works → concentrate there → post again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pricing When All Competitors Are Free
&lt;/h2&gt;

&lt;p&gt;Can you charge when everything similar is free?&lt;/p&gt;

&lt;p&gt;I spent a while stuck on this. The math that unstuck me:&lt;/p&gt;

&lt;p&gt;Claude Code has millions of users globally (conservative estimate). I need 0.01% of them — roughly 900 people — willing to pay $5/month. That's ~$54K/year.&lt;/p&gt;

&lt;p&gt;That reframe moves you off "how do I compete with free" and onto "who are the 900 people and what do they need."&lt;/p&gt;

&lt;p&gt;Strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free tier: match the best open-source competitor on core features.&lt;/strong&gt; Don't hide the good stuff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro tier: things power users actually pay for.&lt;/strong&gt; Multi-CLI support, advanced monitoring, custom themes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don't compete with free. Compete with "not good enough."&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pricing is never really about the number. It's about: who are you creating value for, what is that value worth to them? If your tool saves someone 10 minutes a day, $5/month is not the conversation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 6-Question Checklist
&lt;/h2&gt;

&lt;p&gt;Everything above compresses into six questions. Before you write a line of production code, spend half a day writing actual answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Who am I solving this for, exactly?&lt;/strong&gt;&lt;br&gt;
Not "developers." "macOS developers who switch between 3+ Claude Code projects daily and lose context every time." The more specific, the more useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Who already built this? How good is it?&lt;/strong&gt;&lt;br&gt;
List 5–10 competitors. Tech stack, pricing, user complaints. Find what users are actively complaining about — that's your opening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What's the one dimension I can win on?&lt;/strong&gt;&lt;br&gt;
Speed? Native feel? Price? Community? Integration depth? Pick one and go deep, don't spread across all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. How will I know if this is working?&lt;/strong&gt;&lt;br&gt;
One north star metric, matched to your current stage. Not five metrics — one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Where are my users? How do I reach them?&lt;/strong&gt;&lt;br&gt;
Name five specific communities. Track referrals after you post. Cut the low-performers within two weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What's the business model?&lt;/strong&gt;&lt;br&gt;
You don't need a final answer on day one, but you need a hypothesis. Freemium? Subscription? One-time? What's your revenue target, and how many paying users does that require?&lt;/p&gt;




&lt;p&gt;These questions don't need perfect answers. Writing them down is already 10x better than "I'll figure it out later."&lt;/p&gt;

&lt;p&gt;My product is still in cold-start. The answers are still getting revised. But I'm not "ship and pray" anymore.&lt;/p&gt;

&lt;p&gt;If you're building something solo right now — open a doc, spend 30 minutes on these six questions. You'll find that a lot of the fuzzy stuff gets concrete fast.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building &lt;a href="https://groask.com?ref=devto" rel="noopener noreferrer"&gt;GroAsk&lt;/a&gt; — a native macOS launcher for Claude Code. If you use Claude Code and want to try it, it's free to start.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the business question you wish you'd asked before you started building?&lt;/strong&gt; Drop it in the comments — I'm curious what others have learned the hard way.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>productivity</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>20 Days, 20,000 Conversations, 1.2 Billion Tokens: What I Learned from Heavy Claude Code Use</title>
      <dc:creator>JackWu</dc:creator>
      <pubDate>Fri, 20 Feb 2026 14:19:34 +0000</pubDate>
      <link>https://dev.to/thinkerjack/20-days-20000-conversations-12-billion-tokens-what-i-learned-from-heavy-claude-code-use-eek</link>
      <guid>https://dev.to/thinkerjack/20-days-20000-conversations-12-billion-tokens-what-i-learned-from-heavy-claude-code-use-eek</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I started using Claude Code in November thinking it was just a smarter Cursor. After 20,000 conversations and 1.2 billion tokens — verified by Anthropic as a top 1% user — here's the full retrospective.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The Mental Model Shift: From AI Assistant to AI Team
&lt;/h2&gt;

&lt;p&gt;When I first picked up Claude Code, I treated it like an autocomplete tool with better reasoning. A smarter linter. A more accurate Cursor.&lt;/p&gt;

&lt;p&gt;That framing was wrong.&lt;/p&gt;

&lt;p&gt;After enough usage and conversations with others doing the same, the better mental model clicked: &lt;strong&gt;Claude Code is not an AI assistant. It's a capable, low-cost, reliable team.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not an intern. A team.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What 20 Days of Output Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;Claude Code can now carry a product from zero to shipped — independently, end to end. Here's what I built in 20 days:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AI ChatBot&lt;/td&gt;
&lt;td&gt;Flutter&lt;/td&gt;
&lt;td&gt;2 days&lt;/td&gt;
&lt;td&gt;Voice input, resume upload, AI interviewer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS Launcher App&lt;/td&gt;
&lt;td&gt;Native iOS&lt;/td&gt;
&lt;td&gt;2 days&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://groask.com?ref=devto" rel="noopener noreferrer"&gt;GroAsk&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Native macOS&lt;/td&gt;
&lt;td&gt;2 weeks&lt;/td&gt;
&lt;td&gt;Claude Code session manager — the tooling behind everything in this post&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;All three were vibe-coded. No lines written by hand.&lt;/strong&gt; During the Opus 4.5 era I'd still glance at the code occasionally. By Opus 4.6, I stopped reading it entirely.&lt;/p&gt;

&lt;p&gt;GroAsk came out of my own frustration: constantly switching terminals, losing track of which session was doing what, context windows silently filling up with no visibility. I built it to scratch my own itch. More on that below.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Beyond Coding: What Else Claude Code Handles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-posting to Twitter&lt;/strong&gt; — via browser MCP + custom Skills&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Article writing and multi-platform distribution&lt;/strong&gt; — via custom Skills&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product analytics&lt;/strong&gt; — running against real operational data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market research&lt;/strong&gt; — gathering user pain points, validating assumptions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. My Iteration Loop
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Small changes: one sentence to shipped
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Describe the change → Claude Code executes → Ship → One-line fix if needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Large features: structured pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gather requirements → PRD → Market validation → Tech spec → Execution plan → Build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Requirements&lt;/strong&gt;: personal pain points + community feedback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PRD&lt;/strong&gt;: describe the raw need, let Claude Code draft the PRD, validate with research&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech spec&lt;/strong&gt;: Claude Code writes it, market research reviews it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution plan&lt;/strong&gt;: Claude Code uses TDD + subagents to break down tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt;: execute the plan&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A 3,000-word PRD → 10,000-word tech spec → 40,000-word execution plan. The AI handles the expansion. I handle the decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The principle that matters most
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Product decisions and technical decisions are the human's job.&lt;/strong&gt; Fully delegating decisions to AI still breaks down on complex tasks — it looks like it works, but the maintenance cost is brutal. Get the design right first.&lt;/p&gt;

&lt;p&gt;Break problems into small, concrete tasks. A focused, small prompt outperforms a large, vague one in reliability, speed, and outcome.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Techniques That Actually Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 Skills: The Highest-Leverage Feature
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Core idea: encoding your own workflows.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Skill has two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt&lt;/strong&gt;: natural language description of what needs model judgment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scripts&lt;/strong&gt;: code for the deterministic parts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only use prompts where judgment is required. Everything else should be a script.&lt;/p&gt;

&lt;p&gt;My regular Skills: atomic commits, auto-build-and-run, pulling operational metrics, posting drafts, writing articles from outlines.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Subagents: Context Management for Complex Tasks
&lt;/h3&gt;

&lt;p&gt;The built-in &lt;code&gt;Task&lt;/code&gt; tool is Claude Code's subagent mechanism. The main session delegates work to internal sessions and only cares about inputs and outputs. This keeps the primary context clean on long-running tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 MCP: Extending What Claude Code Can See
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;I run 8 MCPs personally, but I'd recommend fewer to start&lt;/li&gt;
&lt;li&gt;MCPs consume a lot of raw context — use parameters to expose minimal interfaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer Rust or Go compiled MCPs over Node&lt;/strong&gt; — significantly lower memory footprint&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.4 Hooks: The Safety Net
&lt;/h3&gt;

&lt;p&gt;The highest-value hook I've set up: &lt;strong&gt;back up anything untracked before Claude Code deletes it.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.5 The Most Effective Debugging Technique
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Write logs to disk. Let Claude Code read the logs and diagnose from there.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the most reliable way to resolve bugs — more reliable than anything else I've tried. When you think Claude Code is stuck, try logs. It will surprise you.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Long-Term Memory: Making It Stick
&lt;/h2&gt;

&lt;p&gt;Forgetting context across sessions is a real friction point. My solution: &lt;strong&gt;layered knowledge files&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Maintain a hierarchical &lt;code&gt;knowledge/&lt;/code&gt; folder. Tell Claude Code to store useful findings there.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;CLAUDE.md&lt;/code&gt; files per sub-project, plus a &lt;code&gt;rules/&lt;/code&gt; directory for principles&lt;/li&gt;
&lt;li&gt;The result: a local long-term memory that gets better the more you use it&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  7. Context Management: The Biggest Performance Variable
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load MCPs on-demand&lt;/td&gt;
&lt;td&gt;Expose minimal interfaces via parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layer CLAUDE.md by scope&lt;/td&gt;
&lt;td&gt;Avoid information overload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hard limit at 200k tokens&lt;/td&gt;
&lt;td&gt;Switch sessions before you hit this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Never compress context&lt;/td&gt;
&lt;td&gt;Open a new session instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitor in real time&lt;/td&gt;
&lt;td&gt;Know what each session is consuming&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;I keep context under 200k (roughly 20% of the 1M window). The difference between a fresh session and one near the limit is like the difference between a colleague at 9am versus 2am — technically still working, but the quality has dropped.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The "monitor in real time" row is the one most people skip. You keep going, context quietly fills up, and response quality drops off a cliff before you notice. &lt;strong&gt;That's actually the primary reason I built &lt;a href="https://groask.com?ref=devto" rel="noopener noreferrer"&gt;GroAsk&lt;/a&gt;&lt;/strong&gt; — a menu bar panel that shows each Claude Code terminal's context usage, current status, and queue at a glance. No more switching terminals to check.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Multi-Session: Stop Watching One Window
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Running a single Claude Code process at a time is leaving most of the throughput on the table.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comfortable baseline: &lt;strong&gt;2–3 parallel sessions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;High-output mode: &lt;strong&gt;5–10 sessions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Only actively manage sessions waiting for input; let the others run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical problem: once you're running 5+ terminals, just finding "which session is waiting for me" becomes its own overhead. My current setup uses GroAsk's dispatch panel — all Claude Code terminals in one view, with status (running / waiting / context usage) visible without switching windows. &lt;code&gt;⌥Space&lt;/code&gt; opens the panel, one click jumps to the right terminal.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're running multiple sessions regularly: &lt;a href="https://groask.com?ref=devto" rel="noopener noreferrer"&gt;groask.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  9. Where the Model Hits Its Limits
&lt;/h2&gt;

&lt;p&gt;The one thing models can't do well: &lt;strong&gt;genuinely novel work&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When there's no training data to draw from — when you're building something that hasn't been built before — the model is guessing. That's where human creativity and judgment still matter. Know where that line is.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Three Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code is not an AI assistant. It's an AI team.&lt;/strong&gt; Manage it like one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decisions are your job.&lt;/strong&gt; Product calls, architecture calls, novel problems — those stay with you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineer your AI usage.&lt;/strong&gt; Skills encode workflows. Subagents manage complexity. Knowledge files build memory. Context discipline keeps quality high.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;I use Claude Code daily to build &lt;a href="https://groask.com?ref=devto" rel="noopener noreferrer"&gt;GroAsk&lt;/a&gt; — a macOS menu bar launcher that opens any AI with ⌥Space and monitors all Claude Code terminal sessions in one panel. If you're running Claude Code heavily, it's worth a look.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Automating 4 macOS Terminals for Claude Code: AppleScript, Ghostty's `-e` Trap, and Warp's Missing API</title>
      <dc:creator>JackWu</dc:creator>
      <pubDate>Sun, 15 Feb 2026 15:11:34 +0000</pubDate>
      <link>https://dev.to/thinkerjack/automating-4-macos-terminals-for-claude-code-applescript-ghosttys-e-trap-and-warps-missing-3nk1</link>
      <guid>https://dev.to/thinkerjack/automating-4-macos-terminals-for-claude-code-applescript-ghosttys-e-trap-and-warps-missing-3nk1</guid>
      <description>&lt;p&gt;I launch Claude Code 30+ times a day. The workflow always looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Projects/my-app
claude &lt;span class="s2"&gt;"fix the login bug"&lt;/span&gt;

&lt;span class="c"&gt;# switch project&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Projects/api-server
claude &lt;span class="s2"&gt;"add rate limiting"&lt;/span&gt;

&lt;span class="c"&gt;# switch again...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four steps every time. Open terminal, cd, type command, wait. Multiply by 30 and you've burned 600 keystrokes on navigation alone.&lt;/p&gt;

&lt;p&gt;I wanted a single hotkey that takes a prompt and launches Claude Code in the right directory. So I built &lt;a href="https://groask.com?ref=devto" rel="noopener noreferrer"&gt;GroAsk&lt;/a&gt; -- a macOS menu bar launcher. Press &lt;code&gt;Option+Space&lt;/code&gt;, type your prompt, hit Enter. Terminal opens, cd's to the project, Claude Code starts with your prompt already loaded.&lt;/p&gt;

&lt;p&gt;The hard part wasn't the launcher UI. It was making it work across Terminal.app, iTerm2, Ghostty, and Warp -- because they all automate differently, and some barely automate at all.&lt;/p&gt;

&lt;p&gt;This post covers the technical implementation of each terminal integration, the PATH detection problem in GUI apps, and the one-click CLI installer with dependency chain resolution.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: 4 Terminals, 4 Different Automation Models
&lt;/h2&gt;

&lt;p&gt;Most Claude Code tools assume Terminal.app. But developers use different terminals. iTerm2 is the most popular third-party option. Ghostty (by Mitchell Hashimoto) is gaining traction fast. Warp has a dedicated following.&lt;/p&gt;

&lt;p&gt;Supporting "just Terminal.app" means excluding a large chunk of your users. So I implemented all four.&lt;/p&gt;

&lt;p&gt;Here's how they differ:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Terminal&lt;/th&gt;
&lt;th&gt;Automation Method&lt;/th&gt;
&lt;th&gt;Quirks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terminal.app&lt;/td&gt;
&lt;td&gt;AppleScript &lt;code&gt;do script&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Full scripting dictionary, straightforward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iTerm2&lt;/td&gt;
&lt;td&gt;AppleScript &lt;code&gt;write text&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Full scripting dictionary, different commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ghostty&lt;/td&gt;
&lt;td&gt;Clipboard paste via System Events&lt;/td&gt;
&lt;td&gt;No AppleScript dictionary, requires Accessibility permission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warp&lt;/td&gt;
&lt;td&gt;Clipboard paste via System Events&lt;/td&gt;
&lt;td&gt;No programmatic API at all, requires Accessibility permission&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's walk through each.&lt;/p&gt;




&lt;h2&gt;
  
  
  Terminal.app: The Baseline
&lt;/h2&gt;

&lt;p&gt;Terminal.app has a complete AppleScript dictionary. You can create windows, run commands, and control tabs programmatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight applescript"&gt;&lt;code&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Terminal"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;activate&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd ~/Projects/my-app &amp;amp;&amp;amp; claude \"fix the login bug\""&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One catch: if Terminal.app isn't running yet, there's no window to target. You need to wait for the initial window to appear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight applescript"&gt;&lt;code&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wasRunning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Terminal"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Terminal"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;activate&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wasRunning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd ~/Projects/my-app &amp;amp;&amp;amp; claude \"fix the bug\""&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c1"&gt;-- Wait for the default window to spawn&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;repeat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;times&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;repeat&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nb"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;repeat&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd ~/Projects/my-app &amp;amp;&amp;amp; claude \"fix the bug\""&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;front&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd ~/Projects/my-app &amp;amp;&amp;amp; claude \"fix the bug\""&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids opening a second tab when Terminal launches for the first time -- the initial blank window already exists, so we execute into it.&lt;/p&gt;

&lt;p&gt;I also added an &lt;code&gt;osascript&lt;/code&gt; subprocess fallback. &lt;code&gt;NSAppleScript&lt;/code&gt; can fail silently due to TCC permission issues on newer macOS versions, but spawning &lt;code&gt;/usr/bin/osascript&lt;/code&gt; as a child process sometimes bypasses the restriction.&lt;/p&gt;




&lt;h2&gt;
  
  
  iTerm2: Similar But Different Dictionary
&lt;/h2&gt;

&lt;p&gt;iTerm2 also has a full AppleScript dictionary, but the commands are different. Instead of &lt;code&gt;do script&lt;/code&gt;, you use &lt;code&gt;create window with default profile&lt;/code&gt; and &lt;code&gt;write text&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight applescript"&gt;&lt;code&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.googlecode.iterm2"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nb"&gt;activate&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;profile&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;window&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nb"&gt;write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cd ~/Projects/my-app &amp;amp;&amp;amp; claude \"fix the bug\""&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the use of bundle ID (&lt;code&gt;com.googlecode.iterm2&lt;/code&gt;) instead of the app name. Some users have the app named "iTerm" and others "iTerm2" -- the bundle ID is stable across installations.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;write text&lt;/code&gt; approach has a benefit: the shell stays alive after the command runs. With Terminal.app's &lt;code&gt;do script&lt;/code&gt;, the behavior is similar, but &lt;code&gt;write text&lt;/code&gt; is more predictable for interactive tools like Claude Code that keep running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ghostty: The Clipboard Paste Workaround
&lt;/h2&gt;

&lt;p&gt;Ghostty is where things get interesting. It's a relatively new terminal by Mitchell Hashimoto (of HashiCorp fame). It has no AppleScript dictionary.&lt;/p&gt;

&lt;p&gt;Ghostty does support a &lt;code&gt;-e&lt;/code&gt; flag for executing commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open &lt;span class="nt"&gt;-na&lt;/span&gt; Ghostty &lt;span class="nt"&gt;--args&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"claude &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;fix the bug&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But &lt;code&gt;-e&lt;/code&gt; &lt;em&gt;replaces&lt;/em&gt; the shell. It doesn't load &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt;, which means your PATH, aliases, and version manager initialization (nvm, fnm, etc.) are all missing. Claude Code itself likely won't be found.&lt;/p&gt;

&lt;p&gt;You can work around this by wrapping in a login shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open &lt;span class="nt"&gt;-na&lt;/span&gt; Ghostty &lt;span class="nt"&gt;--args&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; bash &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"cd ~/Projects/my-app &amp;amp;&amp;amp; claude &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;fix the bug&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in practice, this still had edge cases -- particularly with how Ghostty handles argument quoting and process lifecycle.&lt;/p&gt;

&lt;p&gt;The approach I settled on: &lt;strong&gt;clipboard paste via System Events&lt;/strong&gt;. It requires macOS Accessibility permission, but it's reliable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight applescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- First, put the command in the clipboard (done in Swift before this script runs)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wasRunning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.mitchellh.ghostty"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.mitchellh.ghostty"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;activate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"System Events"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;whose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bundle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;identifier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.mitchellh.ghostty"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wasRunning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nb"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;keystroke&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;down&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- new window&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nb"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nb"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- wait for first launch&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;keystroke&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;down&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- paste from clipboard&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nb"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;keystroke&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Swift side saves the original clipboard contents, writes the command, executes the AppleScript, then restores the clipboard after a 3-second delay (enough time for the terminal to process the paste).&lt;/p&gt;

&lt;p&gt;Why clipboard paste instead of &lt;code&gt;keystroke&lt;/code&gt; character-by-character? Because &lt;code&gt;keystroke&lt;/code&gt; goes through macOS's input method system. If the user has a CJK input method active, the command gets garbled. Cmd+V bypasses input methods entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Warp: No API, Period
&lt;/h2&gt;

&lt;p&gt;Warp is a Rust-based modern terminal. It has no AppleScript dictionary, no &lt;code&gt;-e&lt;/code&gt; flag equivalent, and no documented programmatic API.&lt;/p&gt;

&lt;p&gt;I initially tried its Launch Configuration feature -- YAML files that define startup commands, triggered via URI scheme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/.warp/launch_configurations/groask.yaml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;groask&lt;/span&gt;
&lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cd&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;~/Projects/my-app&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;claude&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bug&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then trigger with &lt;code&gt;warp://action/launch?config=groask&lt;/code&gt;. This works, but writing a temp file for every launch felt fragile, and the YAML parsing had quoting issues with complex prompts.&lt;/p&gt;

&lt;p&gt;The final implementation uses the same clipboard paste approach as Ghostty, but opens a new tab (&lt;code&gt;Cmd+T&lt;/code&gt;) instead of a new window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight applescript"&gt;&lt;code&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wasRunning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev.warp.Warp-Stable"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev.warp.Warp-Stable"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;activate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"System Events"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warp"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;wasRunning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nb"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;keystroke&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"t"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;down&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;-- new tab&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nb"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nb"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;keystroke&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;down&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nb"&gt;delay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;keystroke&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;tell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both Ghostty and Warp require the user to grant Accessibility permission to GroAsk. On first attempt, the app detects &lt;code&gt;AXIsProcessTrusted() == false&lt;/code&gt; and shows a dialog pointing the user to System Settings.&lt;/p&gt;




&lt;h2&gt;
  
  
  The PATH Problem in GUI Apps
&lt;/h2&gt;

&lt;p&gt;Here's a problem most terminal-based tools don't face: a macOS GUI app launched from Finder or Spotlight doesn't inherit your shell's PATH.&lt;/p&gt;

&lt;p&gt;When you open Terminal and type &lt;code&gt;which claude&lt;/code&gt;, it finds &lt;code&gt;~/.local/bin/claude&lt;/code&gt; because your &lt;code&gt;.zshrc&lt;/code&gt; added that directory to PATH. But a &lt;code&gt;.app&lt;/code&gt; bundle launched via LaunchServices gets a minimal PATH -- typically just &lt;code&gt;/usr/bin:/bin:/usr/sbin:/sbin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This means when GroAsk tries to check if Claude Code is installed, &lt;code&gt;which claude&lt;/code&gt; or &lt;code&gt;command -v claude&lt;/code&gt; would fail even though the tool is perfectly usable from a terminal.&lt;/p&gt;

&lt;p&gt;My solution has two layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: Read the user's actual shell PATH
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;marker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"__GROASK__"&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;shell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ProcessInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"SHELL"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"/bin/zsh"&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executableURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fileURLWithPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-l"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"printf '&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;%s' &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;$PATH&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launch a login shell subprocess and ask it what PATH looks like. The &lt;code&gt;printf&lt;/code&gt; with a marker is critical -- tools like conda, pyenv, and some version managers print extra output during shell initialization. The marker lets me find where the actual PATH value starts and ignore everything before it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: File system scanning as fallback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;discoverPaths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/.local/bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Claude Code, CodeBuddy, Kimi Code&lt;/span&gt;
        &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/.npm-global/bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// npm global prefix&lt;/span&gt;
        &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/.volta/bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Volta&lt;/span&gt;
        &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/.cargo/bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Rust / Cargo&lt;/span&gt;
        &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/.bun/bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// Bun&lt;/span&gt;
        &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/.mise/shims"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// mise&lt;/span&gt;
        &lt;span class="s"&gt;"/opt/homebrew/bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Homebrew (Apple Silicon)&lt;/span&gt;
        &lt;span class="s"&gt;"/usr/local/bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// Homebrew (Intel) / system&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;// Scan nvm versions: ~/.nvm/versions/node/v&amp;lt;version&amp;gt;/bin&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;nvmBase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/.nvm/versions/node"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;FileManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contentsOfDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;atPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nvmBase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;nvmBase&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/bin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Scan fnm versions (macOS and Linux paths)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fnmBase&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/Library/Application Support/fnm/node-versions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/.local/share/fnm/node-versions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;FileManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contentsOfDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;atPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fnmBase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;fnmBase&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/installation/bin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scans known installation directories directly on the filesystem. No subprocess needed, no shell rc loaded. It catches tools installed via nvm, fnm, Volta, Homebrew, Cargo, mise, and the standard &lt;code&gt;~/.local/bin&lt;/code&gt; location.&lt;/p&gt;

&lt;p&gt;The two layers merge: shell PATH takes priority (preserves user's ordering), filesystem scan fills in anything the shell missed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smart PATH injection
&lt;/h3&gt;

&lt;p&gt;When launching a command in a new terminal window, that window loads the user's shell rc files. So if &lt;code&gt;claude&lt;/code&gt; is in a directory that's already in the shell's PATH, we don't need to inject anything -- it'll just work.&lt;/p&gt;

&lt;p&gt;But if we found &lt;code&gt;claude&lt;/code&gt; via filesystem scanning in a directory &lt;em&gt;not&lt;/em&gt; in the shell's PATH (e.g., an unusual nvm version), we prepend an &lt;code&gt;export PATH=&lt;/code&gt; to the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pathPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;searchPaths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;FileManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isExecutableFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;atPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;shellNativePaths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pathPrefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"export PATH=&lt;/span&gt;&lt;span class="se"&gt;\"\(&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;:$PATH&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; &amp;amp;&amp;amp; "&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pathPrefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;fullCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;pathPrefix&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;cd &lt;/span&gt;&lt;span class="se"&gt;\"\(&lt;/span&gt;&lt;span class="n"&gt;safeDir&lt;/span&gt;&lt;span class="se"&gt;)\"&lt;/span&gt;&lt;span class="s"&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"\(&lt;/span&gt;&lt;span class="n"&gt;cleanPrompt&lt;/span&gt;&lt;span class="se"&gt;)\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids polluting the terminal session with unnecessary PATH modifications while ensuring the command always resolves.&lt;/p&gt;




&lt;h2&gt;
  
  
  One-Click CLI Installation with Dependency Chains
&lt;/h2&gt;

&lt;p&gt;The last piece: installing CLI tools without touching the terminal.&lt;/p&gt;

&lt;p&gt;Some tools have simple curl installers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://claude.ai/install.sh | bash

&lt;span class="c"&gt;# CodeBuddy&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://copilot.tencent.com/cli/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Others need npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Gemini CLI&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @google/gemini-cli

&lt;span class="c"&gt;# Codex&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @openai/codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem: if the user doesn't have npm, they need Node.js. If they don't have Node.js, they need a version manager. That's three installations before you can even start.&lt;/p&gt;

&lt;p&gt;GroAsk handles the full dependency chain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if &lt;code&gt;npm&lt;/code&gt; exists -&amp;gt; if yes, run &lt;code&gt;npm install -g @google/gemini-cli&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;No npm? Check if &lt;code&gt;fnm&lt;/code&gt; exists -&amp;gt; if yes, run &lt;code&gt;fnm install --lts &amp;amp;&amp;amp; npm install -g @google/gemini-cli&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;No fnm either? Run the full chain: install fnm -&amp;gt; install Node.js -&amp;gt; install the CLI tool&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All in one click. No sudo, no Homebrew, no Xcode Command Line Tools required.&lt;/p&gt;

&lt;p&gt;After triggering the install, the app polls every 2 seconds using &lt;code&gt;command -v&lt;/code&gt; through the user's login shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;isCommandAvailableViaShell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSatisfy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLetter&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isNumber&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"_"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// prevent command injection&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;shell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ProcessInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"SHELL"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"/bin/zsh"&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executableURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fileURLWithPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-l"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"command -v &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;terminationStatus&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The poll runs through a full login shell (&lt;code&gt;-i -l&lt;/code&gt;) so it picks up PATH changes from the installation. 3-minute timeout, then it stops. As soon as the tool is detected, the UI updates from "installing" to "ready."&lt;/p&gt;




&lt;h2&gt;
  
  
  The Resulting Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⌥Space pressed
    |
    v
Launcher UI (prompt input)
    |
    v
Router.dispatch(channel, prompt)
    |
    +--- Web AI? --&amp;gt; WebManager (Chrome AppleScript + JS injection)
    |
    +--- CLI AI? --&amp;gt; TerminalBridge.execute()
                        |
                        +-- Resolve working directory
                        |     (Finder path / alias / default)
                        |
                        +-- Find command binary
                        |     (shell PATH + filesystem scan)
                        |
                        +-- Build command string
                        |     (PATH prefix if needed)
                        |
                        +-- Dispatch to terminal
                              |
                              +-- Terminal.app: AppleScript do script
                              +-- iTerm2: AppleScript write text
                              +-- Ghostty: Clipboard paste + System Events
                              +-- Warp: Clipboard paste + System Events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole app is 5,600 lines of Swift (pure AppKit, no SwiftUI App framework, no Electron) + 1,200 lines for the server (Cloudflare Workers for license validation). Memory usage stays under 30MB.&lt;/p&gt;




&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;6 CLI AIs supported: Claude Code, Gemini CLI, Codex, CodeBuddy, Kimi Code, Qwen Code&lt;/li&gt;
&lt;li&gt;4 Web AIs: ChatGPT, Claude, Gemini, Monica&lt;/li&gt;
&lt;li&gt;4 terminal implementations&lt;/li&gt;
&lt;li&gt;190 commits, 49% co-authored with Claude Code&lt;/li&gt;
&lt;li&gt;14 days from first line of code to launch&lt;/li&gt;
&lt;li&gt;Free and open source&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Comparison with Existing Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Claude Code Now&lt;/th&gt;
&lt;th&gt;Raycast Plugin&lt;/th&gt;
&lt;th&gt;GroAsk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Launch with prompt&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes -- prompt goes straight to the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-terminal&lt;/td&gt;
&lt;td&gt;Terminal.app only&lt;/td&gt;
&lt;td&gt;Terminal/Alacritty/Ghostty/Warp&lt;/td&gt;
&lt;td&gt;Terminal/iTerm2/Ghostty/Warp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GUI install&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;One-click with dependency resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-AI&lt;/td&gt;
&lt;td&gt;Claude Code only&lt;/td&gt;
&lt;td&gt;Claude Code only&lt;/td&gt;
&lt;td&gt;6 CLI + 4 Web AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text selection send&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Select text -&amp;gt; hotkey -&amp;gt; sent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATH auto-detection&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Shell PATH + filesystem scan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not knocking the existing tools -- they solve real problems. GroAsk just goes further: it's not only a Claude Code launcher, it's a bridge to all your AIs.&lt;/p&gt;




&lt;p&gt;If you want to try it: &lt;a href="https://groask.com?ref=devto" rel="noopener noreferrer"&gt;groask.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's free, open source, and runs entirely on your machine. No data leaves your Mac -- GroAsk is a local bridge between your keyboard and whatever AI you're talking to.&lt;/p&gt;

&lt;p&gt;If you find it useful, a star on &lt;a href="https://github.com/ThinkerJack/groask-release" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps more than you'd think for a solo dev project.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I was switching browser tabs 50+ times a day to talk to AI. Here's how I fixed it.</title>
      <dc:creator>JackWu</dc:creator>
      <pubDate>Fri, 13 Feb 2026 18:25:07 +0000</pubDate>
      <link>https://dev.to/thinkerjack/i-was-switching-browser-tabs-50-times-a-day-to-talk-to-ai-heres-how-i-fixed-it-efp</link>
      <guid>https://dev.to/thinkerjack/i-was-switching-browser-tabs-50-times-a-day-to-talk-to-ai-heres-how-i-fixed-it-efp</guid>
      <description>&lt;p&gt;I counted yesterday: 47 times hunting Chrome tabs for the right AI. 37 times typing &lt;code&gt;cd&lt;/code&gt; in terminal to start Claude Code. That's 84 context switches — each one just a few seconds, but enough to derail a train of thought.&lt;/p&gt;

&lt;p&gt;So I built GroAsk — a macOS menu bar app that puts all your AIs behind one hotkey.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Web AI friction:&lt;/strong&gt; I'm subscribed to ChatGPT, Claude, Gemini, and more. Every time I need one: switch to Chrome → find the tab → navigate → start chatting. As a developer, my time is split across IDE, terminal, docs. Context-switching to the browser every time kills flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code friction:&lt;/strong&gt; Best coding AI I've used, but the launch sequence is: open terminal → &lt;code&gt;cd&lt;/code&gt; to project → type &lt;code&gt;claude&lt;/code&gt;. I have dozens of projects. I can't remember those paths. cd, cd, cd — dozens of times a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GroAsk does
&lt;/h2&gt;

&lt;p&gt;Press &lt;code&gt;⌥Space&lt;/code&gt; from anywhere. A launcher pops up. Type your question, hit Enter. Done.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One hotkey, all AIs.&lt;/strong&gt; &lt;code&gt;Tab&lt;/code&gt; switches between ChatGPT, Claude, Gemini, DeepSeek, and others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select text + hotkey = instant query.&lt;/strong&gt; Highlight text in your editor, press the hotkey, it goes straight to your selected AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code without cd.&lt;/strong&gt; Pick a project folder from the launcher, Claude Code opens right there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100% local.&lt;/strong&gt; GroAsk uses AppleScript to bridge your local browser and terminal. No data proxied, nothing stored.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The build
&lt;/h2&gt;

&lt;p&gt;Native Swift + AppKit (not Electron). ~30MB RAM. Two weeks, solo, with Claude Code as co-pilot.&lt;/p&gt;

&lt;p&gt;The web AI channel works by injecting JavaScript into Chrome tabs via AppleScript — each AI site has its own injection script that adapts to the DOM. These scripts update from the server, so when ChatGPT changes their interface, I push a fix without shipping a new app version.&lt;/p&gt;

&lt;p&gt;The CLI channel detects your local terminal (Terminal.app or iTerm2), constructs the &lt;code&gt;cd&lt;/code&gt; + &lt;code&gt;claude&lt;/code&gt; command, and executes it via AppleScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who it's for
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Heavy AI users&lt;/strong&gt; who talk to multiple AIs daily and want zero friction between thought and query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code newcomers&lt;/strong&gt; who want a graphical launcher instead of memorizing terminal paths.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Free to use. macOS only.&lt;/p&gt;




&lt;p&gt;Free, under 5MB download, no signup: &lt;a href="https://groask.com/?ref=devto" rel="noopener noreferrer"&gt;groask.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How do you manage your AI workflow? Still tab-hunting, or have you found a better way? Would love to hear in the comments.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>ai</category>
      <category>showdev</category>
      <category>swift</category>
    </item>
    <item>
      <title>I Built a macOS Menu Bar App That Sends Text to Any AI in 2 Seconds</title>
      <dc:creator>JackWu</dc:creator>
      <pubDate>Sat, 07 Feb 2026 15:08:03 +0000</pubDate>
      <link>https://dev.to/thinkerjack/i-built-a-macos-menu-bar-app-that-sends-text-to-any-ai-in-2-seconds-3ki7</link>
      <guid>https://dev.to/thinkerjack/i-built-a-macos-menu-bar-app-that-sends-text-to-any-ai-in-2-seconds-3ki7</guid>
      <description>&lt;p&gt;I kept switching between AI tabs all day — Claude for code reviews, ChatGPT for API lookups, Gemini for translations. The constant copy-paste-switch loop was killing my flow, so I built something to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is GroAsk?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GroAsk&lt;/strong&gt; is a native macOS menu bar app. Press &lt;code&gt;⌥Space&lt;/code&gt;, type your question, &lt;code&gt;Tab&lt;/code&gt; to pick an AI, hit Enter. The question gets sent to the AI's website automatically. The whole thing takes under 2 seconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhcxec400cdi2p2etohv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhcxec400cdi2p2etohv.gif" alt="GroAsk workflow" width="760" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Features I Use the Most
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Select &amp;amp; Ask
&lt;/h3&gt;

&lt;p&gt;Select any text on screen — error messages, code snippets, paragraphs — hit the shortcut, and it goes straight to the AI. No copy-paste needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code GUI
&lt;/h3&gt;

&lt;p&gt;Launch Claude Code from anywhere without opening Terminal. It auto-detects your project directory. If you use Claude Code daily, this alone is worth it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Questions
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Cmd+V&lt;/code&gt; to paste a screenshot and ask the AI about it. Great for asking "how do I implement this layout?" from a design mockup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Silent Mode
&lt;/h3&gt;

&lt;p&gt;Hold &lt;code&gt;Option&lt;/code&gt; when submitting. The question gets sent but your focus stays where it is — keep coding while the AI thinks.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Built with &lt;strong&gt;Swift + SwiftUI&lt;/strong&gt;, native and lightweight (~30MB RAM). It uses Chrome AppleScript to auto-fill and submit questions on AI websites.&lt;/p&gt;

&lt;p&gt;The clever part: when AI websites update their UI (which happens often), the injection scripts update from the server automatically — no app update needed.&lt;/p&gt;

&lt;p&gt;If the primary method fails, it gracefully degrades to clipboard mode (copies the question so you can paste it manually).&lt;/p&gt;

&lt;h2&gt;
  
  
  Free vs Pro
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Free&lt;/strong&gt; includes 4 channels: ChatGPT, Gemini, DeepSeek, Kimi — enough for daily use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro&lt;/strong&gt; unlocks Claude, Claude Code, and the productivity features above (Select &amp;amp; Ask, image upload, silent mode).&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://groask.com?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;groask.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ThinkerJack/groask-release" rel="noopener noreferrer"&gt;github.com/ThinkerJack/groask-release&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;macOS 13.0+, Apple Silicon &amp;amp; Intel supported.&lt;/p&gt;




&lt;p&gt;Happy to answer any questions or take feedback!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>productivity</category>
      <category>swift</category>
    </item>
  </channel>
</rss>
