Raycast 가이드

macOS의 Spotlight는 앱과 파일은 잘 찾지만, 개발 중에 자주 하는 작업은 대부분 도구가 따로 필요하다. 클립보드 기록은 다른 앱으로, 창 배치는 또 다른 앱으로, 반복 입력하는 텍스트는 텍스트 확장 도구로 처리해야 한다. Raycast는 이 기능들을 키보드 런처(launcher) 하나에 모으고, 셸 스크립트나 TypeScript로 직접 명령을 추가할 수 있게 한다.

이 글에서는 Raycast의 핵심 기능, 동적 플레이스홀더(dynamic placeholder), Script Commands, TypeScript 확장(extension) 개발, Deeplink 연동을 정리한다. 공식 사이트는 raycast.com이다.

설치와 요금제

macOS(Apple silicon/Intel)와 Windows 10 21H2 이상·Windows 11을 지원한다. Windows 버전은 Microsoft Store 또는 WinGet으로 설치하며, 2026년 10월 기준 버전은 2.10.1.0이다.

플랜가격 (월/연)주요 차이
Free$0핵심 기능 전부, 커뮤니티 확장 전부, 클립보드 기록 3개월. AI 없음
Pro$10 / $96AI 크레딧 500/월, 클라우드 동기화(cloud sync), 클립보드 기록 무제한, 커스텀 테마, 커스텀 창 관리 명령
Plus / Max$20 / $50 (월)AI 크레딧 3,000 / 7,500, 상위 모델
TeamsFree ~ $65/사용자Snippets·Quicklinks·Commands 팀 공유

AI를 쓰지 않으면 Free 플랜만으로 이 글의 내용을 전부 쓸 수 있다.

핵심 기능

런처 단축키는 처음 실행할 때 지정한다. 결과 항목에서 ⌘ K를 누르면 그 항목에 쓸 수 있는 액션 패널(Action Panel)이 열린다.

기능 (Command)하는 일실무 활용
Clipboard History복사한 텍스트·이미지·파일 기록을 검색몇 분 전에 복사한 명령어·토큰을 다시 붙여넣기
Snippets키워드를 입력하면 정해진 텍스트로 확장커밋 메시지 템플릿, 회의록 머리말
QuicklinksURL·폴더·앱 열기에 인자(argument)를 붙여 저장사내 Jira·GitHub 코드 검색 바로가기
Window Management단축키로 창을 반/사분할·최대화·다른 디스플레이로 이동에디터와 터미널을 좌우로 배치
Calculator수식, 단위·통화·시간대 변환용량 단위 계산, 해외 팀과 회의 시각 맞추기
File Search로컬 파일·폴더 검색 후 열기·이동Finder를 열지 않고 로그 파일 찾기
Script Commands / Extensions직접 만든 명령을 런처에 추가아래 섹션에서 다룸

모든 명령에는 별칭(alias)과 전역 단축키(hotkey)를 붙일 수 있다. 설정 위치는 Settings → Extensions에서 명령을 선택한 뒤 오른쪽의 Alias·Hotkey 칸이다.

Snippets와 Quicklinks의 동적 플레이스홀더

Snippets·Quicklinks 본문에 중괄호 플레이스홀더를 넣으면 실행할 때 실제 값으로 바뀐다.

플레이스홀더값사용 가능 위치
{clipboard}현재 클립보드 (offset=1이면 한 단계 전 항목)Snippets, Quicklinks, AI Commands
{cursor}확장 후 커서가 놓일 위치Snippets
{date format="yyyy-MM-dd"}현재 날짜. offset="+1d"로 이동 가능Snippets, Quicklinks
{uuid}새 UUIDSnippets, Quicklinks
{selection}선택한 텍스트Quicklinks, AI Commands
{argument name="q"}실행할 때 입력받는 값 (최대 3개, default·options 지정 가능)Snippets, Quicklinks, AI Commands

값 뒤에 |로 수정자(modifier)를 붙인다. trim, uppercase, lowercase, percent-encode, json-stringify를 쓸 수 있고, {clipboard | trim | uppercase}처럼 이어 쓸 수 있다.

Snippet 예: 회의록 머리말

Keyword : ;mtg
Snippet :
## {date format="yyyy-MM-dd"} {argument name="topic"} 회의
- 참석:
- 안건: {cursor}

;mtg를 입력하면 주제(topic)를 묻는 입력창이 뜨고, 확장된 뒤 커서는 안건 줄 끝에 놓인다.

Quicklink 예: GitHub 코드 검색

Name : GitHub Code
Link : https://github.com/search?type=code&q={argument name="query"}+org:{argument name="org" default="torvalds"}

org 인자에 기본값이 있으므로 검색어만 입력하고 실행할 수 있다.

Script Commands

Script Commands는 메타데이터 주석이 붙은 스크립트 파일이다. 지정한 디렉터리에 두면 Raycast가 명령으로 등록한다. Bash 외에 Python, Node.js, Swift, AppleScript 등 셰뱅(shebang)으로 실행되는 언어는 모두 쓸 수 있다.

메타데이터필수설명
@raycast.schemaVersion필수현재 1만 있음
@raycast.title필수검색 결과에 표시할 이름
@raycast.mode필수출력 방식 (아래 표)
@raycast.packageName선택부제목. 생략하면 디렉터리 이름
@raycast.icon선택이모지, 파일 경로, https URL
@raycast.argument1~3선택입력 인자 JSON. type은 text/password/dropdown
@raycast.refreshTimeinline 필수자동 갱신 주기 (10s 이상, 1m, 1h, 1d)
@raycast.needsConfirmation선택true이면 실행 전 확인 창
mode출력 위치
fullOutput터미널 같은 별도 화면에 전체 출력
compact마지막 줄을 토스트(toast)로 표시
silentRaycast 창을 닫은 뒤 마지막 줄을 HUD로 표시
inline첫 줄을 검색 결과 항목에 바로 표시하고 refreshTime마다 갱신

인자는 순서대로 $1, $2로 들어온다. 종료 코드가 0이 아니면 Raycast는 마지막 출력 줄을 실패 메시지로 표시한다. 아래 스크립트는 모두 실제로 실행해 출력을 확인했다.

compact: epoch 변환

#!/bin/bash

# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Epoch to Date
# @raycast.mode compact

# Optional parameters:
# @raycast.icon ⏱
# @raycast.packageName Dev Utils
# @raycast.argument1 { "type": "text", "placeholder": "epoch (sec)" }

epoch="$1"
if ! [[ "$epoch" =~ ^[0-9]+$ ]]; then
  echo "숫자만 입력할 것: $epoch"
  exit 1
fi

# macOS(BSD date)는 -r, Linux(GNU date)는 -d @
date -r "$epoch" "+%Y-%m-%d %H:%M:%S %Z" 2>/dev/null \
  || date -d "@$epoch" "+%Y-%m-%d %H:%M:%S %Z"
$ ./epoch-to-date.sh 1761696000
2025-10-29 09:00:00 KST
$ ./epoch-to-date.sh 2026-10-29; echo "exit=$?"
숫자만 입력할 것: 2026-10-29
exit=1

GNU date의 -r은 파일 시각 옵션이라 실패하고, 바로 -d @로 넘어간다. 같은 스크립트가 macOS와 Linux에서 모두 동작한다.

fullOutput: HTTP 상태 확인

#!/bin/bash

# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title HTTP Status
# @raycast.mode fullOutput

# Optional parameters:
# @raycast.icon 🌐
# @raycast.packageName Dev Utils
# @raycast.argument1 { "type": "text", "placeholder": "URL" }

curl -sS -o /dev/null -L --max-time 10 \
  -w 'status    : %{http_code}\nredirects : %{num_redirects}\nfinal url : %{url_effective}\nip        : %{remote_ip}\ntime total: %{time_total}s\n' \
  "$1"
$ ./http-status.sh http://github.com
status    : 200
redirects : 1
final url : https://github.com/
ip        : 20.200.245.247
time total: 0.176944s

출력이 여러 줄이므로 fullOutput을 쓴다. compact로 바꾸면 마지막 줄(time total)만 보인다.

inline: 로드 애버리지 대시보드

#!/bin/bash

# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Load Average
# @raycast.mode inline
# @raycast.refreshTime 1m

# Optional parameters:
# @raycast.icon 📈
# @raycast.packageName System

load=$(uptime | sed -E 's/.*load averages?: //; s/,//g' | awk '{print $1}')

# 1분 평균이 코어 수를 넘으면 빨간색, 아니면 초록색
cores=$(getconf _NPROCESSORS_ONLN)
if awk -v l="$load" -v c="$cores" 'BEGIN { exit !(l > c) }'; then
  echo -e "\033[31m${load}\033[0m / ${cores} cores"
else
  echo -e "\033[32m${load}\033[0m / ${cores} cores"
fi
$ ./load-average.sh | cat -v
^[[32m0.05^[[0m / 12 cores

inline·fullOutput 모드는 ANSI 색 코드를 해석한다. cat -v로 보이는 ^[[32m 부분이 Raycast에서는 초록색 숫자로 표시된다.

dropdown 인자: Base64 인코딩/디코딩

#!/bin/bash

# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Base64
# @raycast.mode compact

# Optional parameters:
# @raycast.icon 🔤
# @raycast.packageName Dev Utils
# @raycast.argument1 { "type": "dropdown", "placeholder": "mode", "data": [{"title": "Encode", "value": "enc"}, {"title": "Decode", "value": "dec"}] }
# @raycast.argument2 { "type": "text", "placeholder": "text" }

case "$1" in
  enc) out=$(printf '%s' "$2" | base64) ;;
  dec) out=$(printf '%s' "$2" | base64 -d 2>/dev/null) || { echo "디코딩 실패"; exit 1; } ;;
esac

# 결과는 클립보드에도 복사 (macOS: pbcopy)
command -v pbcopy >/dev/null && printf '%s' "$out" | pbcopy
echo "$out"
$ ./base64-tool.sh enc "Raycast 가이드"
UmF5Y2FzdCDqsIDsnbTrk5w=
$ ./base64-tool.sh dec "UmF5Y2FzdCDqsIDsnbTrk5w="
Raycast 가이드
$ ./base64-tool.sh dec "%%%"; echo "exit=$?"
디코딩 실패
exit=1

dropdown 인자의 value가 $1로 전달된다. Linux에는 pbcopy가 없어서 클립보드 복사를 건너뛰고 출력만 남겼다.

등록

  1. 스크립트 파일을 한 디렉터리(예: ~/raycast-scripts)에 모은다.
  2. chmod +x로 실행 권한을 준다.
  3. Raycast에서 Settings → Extensions → + → Add Script Directory를 선택하고 그 디렉터리를 지정한다.
  4. 런처에서 스크립트 title로 검색해 실행한다.

빈 템플릿이 필요하면 런처에서 Create Script Command 명령을 실행한다. 커뮤니티 예제는 raycast/script-commands 저장소에 있다.

TypeScript 확장 개발

목록 UI, 폼, 액션 패널이 필요하면 확장을 만든다. 확장은 React + TypeScript로 작성하고, @raycast/api 패키지의 ray CLI로 빌드한다. 2.10.0 기준 Node.js 22.22.2 이상이 필요하다.

Raycast 앱에서 Create Extension 명령을 실행하면 템플릿을 만들어준다. 아래는 직접 작성한 최소 구성이며, view 명령 하나와 no-view 명령 하나로 이루어진다.

epoch-tools/
├── assets/icon.png      # 512x512 PNG
├── src/
│   ├── convert.tsx      # view 명령
│   ├── copy-uuid.ts     # no-view 명령
│   └── time.ts          # 변환 로직
├── package.json         # 매니페스트 겸 npm 설정
├── tsconfig.json
├── eslint.config.js
└── .prettierrc
{
  "$schema": "https://www.raycast.com/schemas/extension.json",
  "name": "epoch-tools",
  "title": "Epoch Tools",
  "description": "Convert Unix timestamps and generate UUIDs",
  "icon": "icon.png",
  "author": "junorion",
  "platforms": ["macOS", "Windows"],
  "categories": ["Developer Tools"],
  "license": "MIT",
  "commands": [
    {
      "name": "convert",
      "title": "Convert Timestamp",
      "description": "Convert epoch seconds/milliseconds to dates",
      "mode": "view"
    },
    {
      "name": "copy-uuid",
      "title": "Copy UUID",
      "description": "Copy a new UUID v4 to the clipboard",
      "mode": "no-view"
    }
  ],
  "scripts": {
    "build": "ray build -e dist",
    "dev": "ray develop",
    "lint": "ray lint",
    "fix-lint": "ray lint --fix"
  },
  "dependencies": {
    "@raycast/api": "^2.10.0"
  },
  "devDependencies": {
    "@raycast/eslint-config": "^2.2.0",
    "@types/node": "^22.10.0",
    "eslint": "^9.22.0",
    "prettier": "^3.8.1",
    "typescript": "^5.9.3"
  }
}

commands[].name은 src/ 아래 파일 이름과 같아야 한다. mode는 view, no-view, menu-bar 중 하나다.

export type Converted = { label: string; value: string };

// 10자리 이하는 초, 13자리 안팎은 밀리초로 본다
export function toMillis(input: string): number | undefined {
  const trimmed = input.trim();
  if (!/^\d+$/.test(trimmed)) return undefined;
  const n = Number(trimmed);
  return trimmed.length > 11 ? n : n * 1000;
}

export function convert(input: string): Converted[] {
  const ms = input.trim() === "" ? Date.now() : toMillis(input);
  if (ms === undefined) return [];
  const d = new Date(ms);
  return [
    { label: "ISO 8601 (UTC)", value: d.toISOString() },
    { label: "Local", value: d.toLocaleString("ko-KR", { timeZone: "Asia/Seoul" }) },
    { label: "Epoch (sec)", value: String(Math.floor(ms / 1000)) },
    { label: "Epoch (ms)", value: String(ms) },
  ];
}
import { Action, ActionPanel, List } from "@raycast/api";
import { useState } from "react";
import { convert } from "./time";

export default function Command() {
  const [text, setText] = useState("");
  const items = convert(text);

  return (
    <List searchBarPlaceholder="epoch 초/밀리초 입력 (비우면 현재 시각)" onSearchTextChange={setText} filtering={false}>
      <List.EmptyView title="숫자만 입력할 것" />
      {items.map((item) => (
        <List.Item
          key={item.label}
          title={item.value}
          subtitle={item.label}
          actions={
            <ActionPanel>
              <Action.CopyToClipboard content={item.value} />
              <Action.Paste content={item.value} />
            </ActionPanel>
          }
        />
      ))}
    </List>
  );
}

filtering={false}로 Raycast의 기본 검색 필터를 끄고, 검색창 입력값을 변환 입력으로만 쓴다.

import { Clipboard, showHUD } from "@raycast/api";
import { randomUUID } from "node:crypto";

export default async function Command() {
  const id = randomUUID();
  await Clipboard.copy(id);
  await showHUD(`Copied ${id}`);
}

no-view 명령은 UI 없이 실행되고, showHUD로 결과를 짧게 알린다.

로직 확인

UI와 분리한 time.ts는 Node.js의 타입 제거(type stripping)로 바로 실행할 수 있다.

import { convert, toMillis } from "./src/time.ts";
for (const s of ["1761696000", "1761696000123", "", "2026-10-29"]) {
  console.log(JSON.stringify(s), "->", toMillis(s), convert(s).slice(0, 2));
}
$ TZ=Asia/Seoul node try.mts
"1761696000" -> 1761696000000 [
  { label: 'ISO 8601 (UTC)', value: '2025-10-29T00:00:00.000Z' },
  { label: 'Local', value: '2025. 10. 29. 오전 9:00:00' }
]
"1761696000123" -> 1761696000123 [
  { label: 'ISO 8601 (UTC)', value: '2025-10-29T00:00:00.123Z' },
  { label: 'Local', value: '2025. 10. 29. 오전 9:00:00' }
]
"" -> undefined [
  { label: 'ISO 8601 (UTC)', value: '2026-10-28T17:05:46.223Z' },
  { label: 'Local', value: '2026. 10. 29. 오전 2:05:46' }
]
"2026-10-29" -> undefined []

빈 입력은 현재 시각을, 숫자가 아닌 입력은 빈 배열을 돌려준다. 빈 배열이면 List.EmptyView가 표시된다.

빌드와 린트

$ npm install
added 239 packages in 21s
$ npx ray build -e dist -o dist
info  - entry points ["src/convert.tsx","src/copy-uuid.ts"]
info  - compiled entry points
info  - generated extension's TypeScript definitions
info  - checked TypeScript
ready  - built extension successfully
$ ls dist
assets  convert.js  convert.js.map  copy-uuid.js  copy-uuid.js.map  package.json  plugin.json

빌드할 때 tsc --noEmit로 타입 검사도 한다. subtitle에 숫자를 넘기도록 코드를 바꾸면 빌드가 실패한다.

$ npx ray build -e dist -o dist
...
src/convert.tsx(16,11): error TS2322: Type 'number' is not assignable to type 'string | { value?: string | null | undefined; tooltip?: string | null | undefined; } | undefined'.
    Error: TypeScript check failed (Command failed with exit code 2:
    ./node_modules/.bin/tsc -p tsconfig.json --noEmit)

ray lint는 매니페스트, 아이콘, ESLint, Prettier를 차례로 검사한다. 첫 실행에서는 author 검사가 실패했다.

$ npx ray lint
wait  - validate package.json file
error  - validate package.json file
/home/noble/raycast-demo/epoch-tools/package.json
  7:12  error  Invalid author "junorion". error: 404 - Not found (https://www.raycast.com/api/v1/users/junorion ...)
wait  - validate extension icons
ready  - validate extension icons
...
ready  - run Prettier 3.9.10
 ›   Error: linting issues found (tip: try re-running with '--fix')

author를 Raycast 계정 사용자명으로 바꾸면 모든 단계가 ready로 통과한다.

개발 중에는 npm run dev(ray develop)를 실행해 둔다. 저장할 때마다 Raycast 앱에 다시 로드된다. 이 글의 빌드·린트는 Linux(Ubuntu 24.04) 테스트 호스트에서 실행했다. UI 동작 확인에는 macOS나 Windows의 Raycast 앱이 필요해서 화면 확인은 하지 않았다.

Deeplink로 다른 도구와 연결

모든 명령은 raycast:// URL로 실행할 수 있다. 명령에서 액션 패널을 열고 Copy Deeplink를 선택하면 URL을 복사한다.

raycast://extensions/<author-or-owner>/<extension-name>/<command-name>
쿼리 파라미터값용도
launchTypeuserInitiated / backgroundRaycast 창을 띄우지 않고 백그라운드 실행
argumentsURL 인코딩한 JSON명령 인자 전달
fallbackTextURL 인코딩한 문자열검색창 또는 첫 텍스트 입력 미리 채우기
contextURL 인코딩한 JSONLaunchContext 데이터 전달
# 위 확장의 변환 화면을 epoch 값이 채워진 상태로 연다
open "raycast://extensions/junorion/epoch-tools/convert?fallbackText=1761696000"

# 내장 명령 예: 오늘 일정
open "raycast://extensions/raycast/calendar/my-schedule"

터미널, Shortcuts.app, Stream Deck처럼 URL을 열 수 있는 도구라면 어디서든 Raycast 명령을 실행할 수 있다.

주의사항 및 팁

  • ray build -e dist의 -e는 출력 폴더가 아니다. 환경(dev|dist) 옵션이다. 출력 위치를 지정하지 않으면 ~/.config/raycast/extensions/<name>에 빌드된다. 폴더가 필요하면 -o dist를 함께 준다.
  • author에는 Raycast 계정 사용자명을 쓸 것. ray lint가 Raycast API로 사용자명을 조회하므로 없는 이름이면 404로 실패한다. Store에 올리지 않고 개인용으로만 쓰면 build·develop에는 영향이 없다.
  • Script Commands에서 OS별로 다른 명령을 쓰지 말 것. BSD date/sed와 GNU 버전은 옵션이 다르다. Windows에서는 @raycast.platform windows를 지정해야 한다.
  • inline 모드에 refreshTime이 없으면 compact로 동작한다. 자동 갱신은 처음 10개 inline 명령에만 적용되고, 실제 주기는 OS 스케줄에 따라 늘어날 수 있다.
  • compact/silent/inline 모드에서는 부분 출력을 많이 내는 장시간 작업을 실행하지 말 것. 예를 들어 zip은 -q를 붙여야 동작한다. 진행 로그가 필요하면 fullOutput을 쓴다.
  • Script Command 인자를 URL에 넣을 때는 "percentEncoded": true를 지정할 것. 그러지 않으면 공백·한글이 그대로 전달된다.
  • Deeplink로 실행하면 확인 창이 먼저 뜬다. 어떤 명령이 실행되는지 사용자가 알 수 있게 하는 동작이다.
  • Windows 버전은 macOS와 기능이 아직 같지 않다. 클라우드 동기화가 준비 중이고, 네이티브 코드(Swift 등)를 쓰는 확장은 동작하지 않는다.

마무리

Raycast는 기본 기능(클립보드 기록, Snippets, Quicklinks, 창 관리)만으로도 여러 보조 앱을 대신한다. 부족한 기능은 Bash 스크립트 하나로 Script Command를 만들어 추가하고, 목록·폼 UI가 필요할 때만 TypeScript 확장으로 넘어가면 된다. 반복 작업 하나를 골라 Script Command로 만드는 것부터 시작하는 것을 권한다.

참고

답글 남기기