Skip to content

Instantly share code, notes, and snippets.

@sulco
Created May 13, 2026 12:35
Show Gist options
  • Select an option

  • Save sulco/227f144feb7ebac6da2a4d9d33a3e7ab to your computer and use it in GitHub Desktop.

Select an option

Save sulco/227f144feb7ebac6da2a4d9d33a3e7ab to your computer and use it in GitHub Desktop.
npm run dev script for macos
#!/bin/bash
# @raycast.schemaVersion 1
# @raycast.title Run npm dev (Finder selection)
# @raycast.mode compact
# @raycast.icon πŸš€
# @raycast.packageName Developer
# @raycast.description Open iTerm2 in the folder currently selected in Finder and run `npm run dev` (falls back to `npm start`).
set -euo pipefail
# 1. Resolve target folder.
# - If $1 is provided (e.g. from a Finder Quick Action), use it.
# - Else query Finder: selected folder, parent of selected file, or front window's target.
if [ $# -ge 1 ] && [ -n "$1" ]; then
folder="$1"
else
folder=$(osascript <<'APPLESCRIPT'
tell application "Finder"
set sel to selection as list
if (count of sel) is greater than 0 then
set theItem to item 1 of sel
set itemClass to class of theItem
if itemClass is folder or itemClass is disk then
return POSIX path of (theItem as alias)
else
return POSIX path of ((container of theItem) as alias)
end if
end if
try
set theTarget to (target of front Finder window) as alias
return POSIX path of theTarget
on error
return ""
end try
end tell
APPLESCRIPT
)
fi
# Strip any trailing slash for cleaner paths.
folder="${folder%/}"
if [ -z "$folder" ]; then
echo "No folder selected in Finder."
exit 1
fi
if [ ! -f "$folder/package.json" ]; then
echo "No package.json in $folder"
exit 1
fi
# 2. Pick the npm script: prefer "dev", fall back to "start", otherwise fail.
# Parsing via node avoids brittle grep on package.json.
script_name=$(NODE_PATH= node -e '
const path = require("path");
const pkg = require(path.join(process.argv[1], "package.json"));
const s = pkg.scripts || {};
if (s.dev) console.log("dev");
else if (s.start) console.log("start");
' "$folder" 2>/dev/null || true)
if [ -z "$script_name" ]; then
echo "Neither 'dev' nor 'start' script found in $folder/package.json"
exit 1
fi
# 3. Open a new iTerm2 window, cd into the folder, and run the script.
# Escape backslashes and double quotes for safe AppleScript string interpolation.
esc_folder=$(printf '%s' "$folder" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')
osascript <<APPLESCRIPT
tell application "iTerm"
activate
set newWindow to (create window with default profile)
tell current session of newWindow
write text "cd \"$esc_folder\" && npm run $script_name"
end tell
end tell
APPLESCRIPT
echo "Started npm run $script_name in $folder"
@sulco

sulco commented May 13, 2026

Copy link
Copy Markdown
Author

run-npm-dev.sh

A small bash script that opens a new iTerm2 window in a given project folder and runs its dev server. Designed to be
triggered two ways: from Raycast (keyboard) or from Finder right-click β†’ Quick Actions (mouse).

What it does

Given a folder (passed as an argument, or resolved from the current Finder selection):

  1. Verifies the folder contains a package.json.
  2. Picks an npm script to run β€” prefers dev, falls back to start, errors out if neither exists.
  3. Opens a fresh iTerm2 window, cds into the folder, and runs npm run <script>.

Selection resolution when no argument is passed:

  • Folder selected in Finder β†’ uses it.
  • File selected in Finder β†’ uses the file's parent folder.
  • Nothing selected β†’ uses the front Finder window's current directory.

Each invocation opens a new iTerm2 window so multiple projects don't fight over a single tab.

Requirements

  • macOS
  • iTerm2
  • npm available in your iTerm2 login shell (nvm, Homebrew, Volta, whatever β€” it just needs to work when you open a
    new iTerm2 window and type npm)

Install

  1. Save the script anywhere stable, e.g. ~/raycast-scripts/run-npm-dev.sh.
  2. Make it executable:
    chmod +x ~/raycast-scripts/run-npm-dev.sh
  3. The first time you run it, macOS will prompt for permission to control Finder and iTerm via AppleScript.
    Approve both (System Settings β†’ Privacy & Security β†’ Automation).

Use it from Raycast (keyboard)

The script is already a valid Raycast Script Command β€” the @raycast.* directives are in the header.

  1. Open Raycast β†’ ⌘, β†’ Extensions β†’ Script Commands β†’ + under "Script Directories" β†’ pick the folder
    containing the script (e.g. ~/raycast-scripts).
  2. Raycast picks up "Run npm dev (Finder selection)" automatically.
  3. Optional: assign a hotkey to it from Raycast.
  4. Click a folder in Finder, trigger Raycast, run the command.

Use it from Finder right-click (Quick Action)

Create a one-step Quick Action that calls the script with the selected folder:

  1. Open Automator β†’ New Document β†’ Quick Action.
  2. Set:
    • Workflow receives current: folders
    • in: Finder.app
  3. Drag in the Run Shell Script action (Library β†’ Utilities). Set:
    • Shell: /bin/zsh Β (important β€” see "Gotchas" below)
    • Pass input: as arguments
    • Body:
      for f in "$@"; do
          ~/raycast-scripts/run-npm-dev.sh "$f"
      done
  4. File β†’ Save β†’ name it Run npm dev.

It now lives at ~/Library/Services/Run npm dev.workflow and appears under right-click β†’ Quick Actions β†’ Run
npm dev
.

Optional hotkey: System Settings β†’ Keyboard β†’ Keyboard Shortcuts β†’ Services β†’ Files and Folders β†’ assign a shortcut to
Run npm dev.

Gotchas

  • Use /bin/zsh, not /bin/bash, in the Automator step. Automator's /bin/bash runs with PATH=/usr/bin:/bin
    and doesn't source any init files, so nvm-managed node/npm will be invisible inside the script. /bin/zsh picks
    up ~/.zshenv/~/.zshrc and restores your real PATH. Symptom if you get this wrong: Automator pops up an error
    dialog with an empty message ("").
  • First run AppleScript prompts. macOS will ask the calling app (Raycast or Automator) for permission to control
    Finder and iTerm the first time. If you accidentally deny, re-enable in System Settings β†’ Privacy & Security β†’
    Automation.
  • Script detection uses grep, not node/jq. This is intentional so the script works in stripped-PATH
    contexts (Quick Actions, login items, cron, etc.) without needing a node toolchain on PATH.

Customising

  • Different terminal? Replace the final osascript block. Equivalents for Terminal.app, Ghostty, Warp, etc. are
    short β€” search for "AppleScript open new window run command ".
  • Different script preference order? Edit the two grep -qE lines β€” e.g. add a check for "start:dev" before
    "dev", or swap the fallback.
  • Want it to auto-install deps? Append npm install && before npm run in the write text line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment