Codex Skills 직접 만들어 쓰기: SKILL.md와 scripts 폴더로 내 작업 방식 자동화하기

이경규·2026년 5월 7일

Codex를 쓰다 보면 어느 순간 이런 생각이 듭니다.

이 작업은 매번 비슷하게 하는데, 왜 매번 다시 설명하고 있지?

예를 들어 프로젝트 상태를 점검하거나, 특정 형식의 문서를 만들거나, 자주 쓰는 스크립트를 실행하는 작업이 그렇습니다. 한두 번은 그냥 말로 설명해도 괜찮습니다. 그런데 같은 패턴이 반복되면 조금 아깝습니다.

Codex에게 매번 이렇게 말하게 됩니다.

이런 방식으로 확인해줘.
이 스크립트를 실행해줘.
결과는 이렇게 요약해줘.
불필요한 설명은 빼줘.

이럴 때 쓸 수 있는 것이 바로 Codex Skills입니다.

Skills는 어렵게 말하면 Codex에게 추가로 알려 주는 작업 매뉴얼입니다. 쉽게 말하면 “이런 요청이 들어오면 이 폴더 안의 설명과 스크립트를 참고해서 처리해”라고 알려 주는 작은 도구 상자입니다.

이번 글에서는 실제로 system-status라는 간단한 스킬을 만들어 보겠습니다.

이 스킬은 사용자가 “내 맥 상태 좀 확인해줘”, “디스크 용량 괜찮아?”, “시스템 상태 봐줘”라고 요청했을 때, Codex가 직접 로컬 스크립트를 실행해서 CPU, 메모리, 디스크, 업타임 정보를 가져오고 요약할 수 있게 해 줍니다.

완성되는 구조는 이렇게 됩니다.

~/.codex/skills/system-status/
├── SKILL.md
└── scripts/
    └── system_status.py

복잡한 플러그인이나 서버를 만드는 것은 아닙니다. 폴더 하나, Markdown 파일 하나, Python 스크립트 하나면 시작할 수 있습니다.


Codex Skills는 언제 쓰면 좋을까

처음에는 Skills가 꼭 필요한지 헷갈릴 수 있습니다. 모든 작업을 Skills로 만들 필요는 없습니다.

Skills가 특히 잘 맞는 경우는 다음과 같습니다.

  • 같은 절차를 자주 반복할 때
  • 매번 같은 명령어나 스크립트를 실행할 때
  • 특정 결과 형식으로 요약해야 할 때
  • 프로젝트나 업무마다 고정된 규칙이 있을 때
  • Codex에게 매번 설명하기 귀찮은 작업 방식이 있을 때

반대로 한 번만 할 작업이라면 굳이 Skill로 만들 필요가 없습니다. 그냥 대화로 처리하는 편이 빠릅니다.

예를 들어 이런 작업은 Skill로 만들기 좋습니다.

시스템 상태 점검
앱스토어 스크린샷 생성
릴리즈 체크리스트 점검
프로젝트 FIXME 검색
블로그 글 초안 포맷팅
반복되는 이미지 리사이즈
자주 쓰는 빌드 로그 요약

핵심은 반복성입니다. 한 번 만들고 여러 번 쓸수록 Skills의 가치가 커집니다.


준비물

이 글은 macOS 기준으로 설명합니다. 터미널은 zsh 기준입니다.

준비물은 많지 않습니다.

  • Codex가 설치되어 있어야 합니다.
  • 터미널을 사용할 수 있어야 합니다.
  • Python 3가 설치되어 있으면 좋습니다.
  • 홈 폴더 아래 .codex 폴더에 파일을 만들 수 있어야 합니다.

Python 3가 있는지 먼저 확인해 봅니다.

python3 --version

버전이 나오면 괜찮습니다.

Python 3.11.6

만약 command not found: python3가 나온다면 Python을 먼저 설치해야 합니다. macOS에서는 Homebrew를 쓰는 경우 아래처럼 설치할 수 있습니다.

brew install python

다만 이번 글의 핵심은 Python 설치가 아니라 Codex Skill 구조입니다. 이미 Python이 있다면 바로 다음 단계로 넘어가면 됩니다.


1단계: Skills 폴더 위치 이해하기

Codex는 기본적으로 아래 위치의 Skills를 읽을 수 있습니다.

~/.codex/skills

~는 현재 사용자의 홈 폴더입니다. 실제 경로로 보면 보통 이런 식입니다.

/Users/사용자이름/.codex/skills

터미널에서 아래 명령어로 폴더를 만들어 둡니다.

mkdir -p ~/.codex/skills

여기서 -p는 중간 폴더가 없어도 한 번에 만들어 주는 옵션입니다. 이미 폴더가 있어도 에러가 나지 않습니다.

확인해 봅니다.

ls -la ~/.codex

skills 폴더가 보이면 준비가 된 것입니다.


2단계: system-status 스킬 폴더 만들기

이제 실제 Skill 폴더를 만듭니다.

mkdir -p ~/.codex/skills/system-status/scripts

여기서 폴더 이름은 system-status입니다. Skills 이름은 가능하면 영어 소문자와 하이픈을 사용하는 것이 좋습니다.

좋은 이름 예시는 다음과 같습니다.

system-status
appstore-screenshot
release-checklist
swift-build-review
blog-draft-helper

피하는 것이 좋은 이름은 이런 형태입니다.

System Status
내 시스템 확인
system_status!

Codex Skills는 사람이 보기에도 좋아야 하지만, 도구가 읽기에도 단순한 이름이 좋습니다. 그래서 소문자, 숫자, 하이픈 조합을 추천합니다.

폴더 구조를 확인합니다.

find ~/.codex/skills/system-status -maxdepth 3 -type d

대략 이렇게 나오면 됩니다.

/Users/사용자이름/.codex/skills/system-status
/Users/사용자이름/.codex/skills/system-status/scripts

3단계: SKILL.md 만들기

Skill에서 가장 중요한 파일은 SKILL.md입니다.

이 파일은 두 가지 역할을 합니다.

  1. 이 Skill이 언제 사용되는지 알려 줍니다.
  2. Skill이 사용될 때 Codex가 따라야 할 절차를 적어 둡니다.

먼저 파일을 만듭니다.

touch ~/.codex/skills/system-status/SKILL.md

그리고 편한 에디터로 엽니다.

open ~/.codex/skills/system-status/SKILL.md

터미널에서 바로 작성하고 싶다면 아래처럼 해도 됩니다.

cat > ~/.codex/skills/system-status/SKILL.md <<'EOF'
---
name: system-status
description: Run the bundled system_status.py script to collect CPU, memory, disk, uptime, and host information, then summarize the local machine status. Use when the user asks about this Mac/PC's system diagnostics, machine status, resource usage, CPU load, memory pressure, disk usage, or quick health check.
---

# System Status

## Workflow

1. Run `scripts/system_status.py` with Python 3.
2. Parse the JSON printed to stdout.
3. Summarize the important numbers in natural language.
4. Call out only meaningful concerns, such as high CPU, high memory pressure, or low free disk space.

## Script

Use this command from the skill folder:

```bash
python3 scripts/system_status.py
```

## Output

The script prints JSON with these keys:

- `host`: machine name
- `platform`: operating system summary
- `uptime_seconds`: approximate uptime
- `cpu_percent`: CPU usage percentage when available
- `memory_percent`: memory usage percentage
- `disk_percent`: root filesystem usage percentage
- `disk_free_gb`: free storage on the root filesystem
- `source`: `psutil` when available, otherwise `stdlib-macos-fallback`

## Response Guidance

Keep the user-facing summary short and practical. Mention whether the system looks normal, mildly busy, or needs attention. If a metric is unavailable, do not over-explain it.
EOF

여기서 맨 위의 ---로 감싼 부분을 frontmatter라고 부릅니다.

---
name: system-status
description: ...
---

초보자라면 일단 두 가지만 기억하면 됩니다.

  • name은 폴더 이름과 같게 둡니다.
  • description에는 “무엇을 하는지”와 “언제 써야 하는지”를 같이 적습니다.

특히 description이 중요합니다. Codex는 이 설명을 보고 어떤 요청에서 이 Skill을 쓸지 판단합니다.

너무 짧게 이렇게만 쓰면 애매합니다.

description: Check system status.

조금 더 구체적으로 쓰는 편이 좋습니다.

description: Collect CPU, memory, disk, uptime, and host information. Use when the user asks for local system status, machine diagnostics, resource usage, or a quick health check.

길어 보이지만, 이 정도는 오히려 좋습니다. “시스템 상태”, “CPU”, “메모리”, “디스크”, “quick health check” 같은 트리거 표현이 들어 있기 때문입니다.


4단계: scripts 폴더에 Python 스크립트 만들기

이제 실제로 실행할 스크립트를 만듭니다.

cat > ~/.codex/skills/system-status/scripts/system_status.py <<'EOF'
#!/usr/bin/env python3
"""Collect local system status and print JSON for Codex."""

from __future__ import annotations

import json
import os
import platform
import shutil
import socket
import subprocess
import time
from pathlib import Path
from typing import Any


def _round(value: float | int | None, digits: int = 1) -> float | None:
    if value is None:
        return None
    return round(float(value), digits)


def _uptime_seconds() -> int | None:
    try:
        completed = subprocess.run(
            ["sysctl", "-n", "kern.boottime"],
            check=True,
            capture_output=True,
            text=True,
        )
        marker = "sec ="
        if marker in completed.stdout:
            boot = int(completed.stdout.split(marker, 1)[1].split(",", 1)[0].strip())
            return max(0, int(time.time()) - boot)
    except Exception:
        return None
    return None


def _vm_stat_memory_percent() -> float | None:
    try:
        page_size = os.sysconf("SC_PAGE_SIZE")
        completed = subprocess.run(
            ["vm_stat"],
            check=True,
            capture_output=True,
            text=True,
        )
        values: dict[str, int] = {}
        for line in completed.stdout.splitlines():
            if ":" not in line:
                continue
            key, raw = line.split(":", 1)
            raw_number = raw.strip().rstrip(".").replace(".", "")
            if raw_number.isdigit():
                values[key.strip()] = int(raw_number)

        active = values.get("Pages active", 0)
        wired = values.get("Pages wired down", 0)
        compressed = values.get("Pages occupied by compressor", 0)
        inactive = values.get("Pages inactive", 0)
        free = values.get("Pages free", 0)
        speculative = values.get("Pages speculative", 0)
        used_pages = active + wired + compressed
        total_pages = used_pages + inactive + free + speculative
        if total_pages <= 0 or page_size <= 0:
            return None
        return used_pages / total_pages * 100
    except Exception:
        return None


def _collect_with_psutil() -> dict[str, Any] | None:
    try:
        import psutil  # type: ignore
    except Exception:
        return None

    disk = psutil.disk_usage(str(Path("/")))
    return {
        "host": socket.gethostname(),
        "platform": platform.platform(),
        "uptime_seconds": int(time.time() - psutil.boot_time()),
        "cpu_percent": _round(psutil.cpu_percent(interval=0.2)),
        "memory_percent": _round(psutil.virtual_memory().percent),
        "disk_percent": _round(disk.percent),
        "disk_free_gb": _round(disk.free / (1024 ** 3), 2),
        "source": "psutil",
    }


def _collect_with_stdlib() -> dict[str, Any]:
    disk = shutil.disk_usage("/")
    disk_percent = disk.used / disk.total * 100 if disk.total else None
    return {
        "host": socket.gethostname(),
        "platform": platform.platform(),
        "uptime_seconds": _uptime_seconds(),
        "cpu_percent": None,
        "memory_percent": _round(_vm_stat_memory_percent()),
        "disk_percent": _round(disk_percent),
        "disk_free_gb": _round(disk.free / (1024 ** 3), 2),
        "source": "stdlib-macos-fallback",
    }


def main() -> None:
    status = _collect_with_psutil() or _collect_with_stdlib()
    print(json.dumps(status, ensure_ascii=False, sort_keys=True))


if __name__ == "__main__":
    main()
EOF

스크립트에 실행 권한을 줍니다.

chmod +x ~/.codex/skills/system-status/scripts/system_status.py

이 스크립트는 psutil이 설치되어 있으면 psutil을 사용합니다. psutil은 시스템 정보를 가져올 때 자주 쓰는 Python 패키지입니다.

하지만 초보자 입장에서는 패키지 설치부터 막힐 수 있습니다. 그래서 psutil이 없어도 macOS 기본 명령인 vm_stat, sysctl과 Python 표준 라이브러리로 어느 정도 동작하도록 fallback을 넣었습니다.

즉, 처음에는 따로 패키지를 설치하지 않아도 실행됩니다.


5단계: 스크립트 직접 실행해 보기

Skill을 만들었으면 바로 Codex에게 맡기기보다 스크립트를 직접 실행해 보는 것이 좋습니다.

python3 ~/.codex/skills/system-status/scripts/system_status.py

정상이라면 JSON이 출력됩니다.

예시는 이런 식입니다.

{
  "cpu_percent": null,
  "disk_free_gb": 15.21,
  "disk_percent": 96.7,
  "host": "My-MacBook.local",
  "memory_percent": 75.1,
  "platform": "macOS-26.3.1-arm64-arm-64bit",
  "source": "stdlib-macos-fallback",
  "uptime_seconds": 882156
}

여기서 cpu_percentnull이어도 실패는 아닙니다. psutil이 없을 때 fallback 모드에서는 CPU 사용률을 계산하지 않도록 했기 때문입니다.

더 자세한 CPU 값까지 보고 싶다면 psutil을 설치하면 됩니다.

python3 -m pip install psutil

다시 실행하면 sourcepsutil로 바뀌고 CPU 사용률도 표시될 수 있습니다.


6단계: Codex에게 사용해 보기

이제 Codex에게 자연스럽게 요청해 보면 됩니다.

내 맥 시스템 상태 한번 점검해줘.

또는 이렇게 말할 수도 있습니다.

디스크 용량이랑 메모리 상태 괜찮은지 봐줘.

Codex가 system-status Skill을 사용하면, SKILL.md를 읽고 scripts/system_status.py를 실행한 뒤 결과를 요약할 수 있습니다.

좋은 응답은 이런 느낌입니다.

현재 디스크 사용량이 96.7%로 꽤 높습니다. 남은 공간은 약 15GB라 Xcode DerivedData나 빌드 캐시가 쌓이면 금방 부족해질 수 있어요.
메모리는 75% 정도 사용 중이라 약간 높은 편이지만 당장 위험해 보이진 않습니다.

중요한 것은 Codex가 단순히 JSON을 그대로 보여 주는 것이 아니라, 사람이 판단하기 쉽게 요약해 주는 것입니다.

그래서 SKILL.md 마지막에 이런 지침을 넣어 두었습니다.

Keep the user-facing summary short and practical.

스킬은 결국 “Codex가 어떻게 행동했으면 좋겠는지”를 적어 두는 곳입니다.


SKILL.md를 쓸 때 중요한 요령

처음 Skills를 만들 때 가장 많이 하는 실수는 SKILL.md를 너무 길게 쓰는 것입니다.

Codex는 이미 똑똑합니다. 모든 개념을 교과서처럼 설명할 필요는 없습니다. 대신 아래 내용을 명확히 적는 것이 좋습니다.

  • 이 Skill이 언제 쓰이는지
  • 어떤 스크립트를 실행해야 하는지
  • 결과를 어떻게 해석해야 하는지
  • 사용자에게 어떤 형식으로 답해야 하는지

예를 들어 이번 Skill에서는 이 정도면 충분합니다.

## Workflow

1. Run `scripts/system_status.py` with Python 3.
2. Parse the JSON printed to stdout.
3. Summarize the important numbers in natural language.
4. Call out only meaningful concerns.

너무 많은 배경 설명을 넣으면 오히려 매번 읽어야 할 내용이 늘어납니다. Skill은 블로그 글이 아니라 작업 지침서에 가깝습니다.


scripts 폴더는 언제 쓰면 좋을까

scripts 폴더는 Skills의 진짜 장점 중 하나입니다.

Codex가 매번 코드를 새로 쓰게 하지 않고, 검증된 스크립트를 계속 재사용할 수 있기 때문입니다.

예를 들어 이런 작업은 scripts 폴더에 넣기 좋습니다.

이미지 리사이즈
스크린샷 분할
로그 요약 전처리
CSV 정리
앱 아이콘 생성
빌드 결과 파싱
시스템 상태 수집

반대로 단순한 설명 규칙만 필요한 Skill이라면 scripts 폴더가 없어도 됩니다.

예를 들어 “블로그 문체 가이드” 같은 Skill은 SKILL.md만 있어도 충분할 수 있습니다.

중요한 기준은 이것입니다.

같은 코드를 Codex가 반복해서 쓰고 있다면 scripts 폴더로 빼는 것이 좋다.

스크립트로 빼두면 결과가 더 일정하고, 실수도 줄어듭니다.


references와 assets 폴더는 언제 필요할까

이번 예제에서는 scripts만 만들었습니다. 하지만 Skills에는 필요에 따라 다른 폴더도 둘 수 있습니다.

대표적으로 세 가지가 있습니다.

scripts/      실행할 코드
references/   필요할 때 읽을 문서
assets/       결과물에 사용할 리소스

예를 들어 앱스토어 스크린샷 생성 Skill을 만든다면 이런 구조가 될 수 있습니다.

appstore-screenshot/
├── SKILL.md
├── scripts/
│   └── generate_screenshots.swift
├── references/
│   └── screenshot-guidelines.md
└── assets/
    └── background-template.png

문서 작성 Skill이라면 이런 구조도 가능합니다.

blog-writer/
├── SKILL.md
└── references/
    └── writing-style.md

처음부터 모든 폴더를 만들 필요는 없습니다. 필요한 것만 만들면 됩니다.

이번 글의 예제처럼 스크립트 하나면 충분한 경우도 많습니다.


스킬이 잘 만들어졌는지 확인하는 방법

Codex Skill은 기본적으로 폴더와 파일 구조가 중요합니다.

먼저 파일이 있는지 확인합니다.

find ~/.codex/skills/system-status -maxdepth 3 -type f

이렇게 나오면 됩니다.

/Users/사용자이름/.codex/skills/system-status/SKILL.md
/Users/사용자이름/.codex/skills/system-status/scripts/system_status.py

그리고 SKILL.md 맨 위에 frontmatter가 있는지 확인합니다.

head -n 8 ~/.codex/skills/system-status/SKILL.md

아래처럼 나오면 됩니다.

---
name: system-status
description: Run the bundled system_status.py script...
---

마지막으로 스크립트가 실행되는지 확인합니다.

python3 ~/.codex/skills/system-status/scripts/system_status.py

이 세 가지가 통과되면 기본적인 구조는 맞게 만든 것입니다.


PyYAML 검증 에러가 날 수 있다

Codex의 Skill 생성/검증 도구를 쓰다 보면 이런 에러를 볼 수도 있습니다.

ModuleNotFoundError: No module named 'yaml'

이건 보통 Python에 PyYAML 패키지가 없어서 생깁니다. Skill 자체가 잘못되었다는 뜻은 아닙니다.

필요하다면 설치하면 됩니다.

python3 -m pip install pyyaml

다만 단순히 직접 폴더와 SKILL.md를 만든 경우라면, 앞에서 확인한 것처럼 파일 구조와 frontmatter를 수동으로 점검해도 충분합니다.

초보자에게는 오히려 이 방식이 더 이해하기 쉽습니다.


자주 생기는 실수

1. SKILL.md 파일 이름을 다르게 씀

반드시 SKILL.md여야 합니다.

아래 이름은 피하세요.

skill.md
Skill.md
README.md
system-status.md

2. frontmatter를 빼먹음

SKILL.md 맨 위에는 반드시 이런 형태가 있어야 합니다.

---
name: system-status
description: ...
---

3. description이 너무 애매함

description은 Codex가 Skill 사용 여부를 판단하는 중요한 단서입니다.

나쁜 예시는 이렇습니다.

description: Useful helper.

이러면 언제 써야 하는지 알기 어렵습니다.

좋은 예시는 이렇습니다.

description: Collect CPU, memory, disk, uptime, and host information. Use when the user asks for local system status, machine diagnostics, resource usage, or a quick health check.

4. 스크립트를 만들고 실행 테스트를 안 함

scripts 폴더에 넣은 코드는 반드시 한 번 직접 실행해 보는 것이 좋습니다.

python3 scripts/system_status.py

실행해 보지 않은 스크립트는 Skill 안에 넣어도 나중에 Codex가 사용할 때 실패할 가능성이 큽니다.

5. 너무 많은 내용을 Skill에 넣음

Skill은 짧고 명확한 편이 좋습니다.

긴 설명, 설치 후기, 개인 메모, 사용하지 않는 예제까지 모두 넣으면 오히려 Codex가 읽어야 할 내용이 늘어납니다.

블로그 글은 자세해도 좋지만, SKILL.md는 실전 매뉴얼처럼 간결한 것이 좋습니다.


system-status 스킬을 조금 더 개선한다면

이번 예제는 일부러 단순하게 만들었습니다. 하지만 필요하다면 더 확장할 수 있습니다.

예를 들어 이런 기능을 추가할 수 있습니다.

  • 배터리 상태 확인
  • 네트워크 연결 상태 확인
  • Xcode DerivedData 용량 확인
  • 큰 파일 상위 20개 찾기
  • Docker 사용량 확인
  • 특정 프로젝트의 빌드 캐시 확인

예를 들어 macOS에서 디스크가 자주 부족하다면 scriptsdisk_cleanup_candidates.py 같은 파일을 추가할 수 있습니다.

구조는 이렇게 됩니다.

system-status/
├── SKILL.md
└── scripts/
    ├── system_status.py
    └── disk_cleanup_candidates.py

그리고 SKILL.md에 언제 어떤 스크립트를 쓸지 적어 두면 됩니다.

## Disk Cleanup Candidates

When the user asks what can be deleted safely, run:

```bash
python3 scripts/disk_cleanup_candidates.py
```

이런 식으로 하나의 Skill 안에 관련 스크립트를 조금씩 늘려 갈 수 있습니다.


개인적으로 추천하는 Skill 만들기 순서

처음부터 거창한 Skill을 만들려고 하면 금방 복잡해집니다. 저는 아래 순서를 추천합니다.

  1. 자주 반복하는 작업 하나를 고릅니다.
  2. 그 작업을 설명하는 SKILL.md를 짧게 만듭니다.
  3. 반복되는 명령이나 코드는 scripts로 뺍니다.
  4. 스크립트를 직접 실행해서 결과를 확인합니다.
  5. Codex에게 자연어로 요청해 봅니다.
  6. 답변이 애매하면 SKILL.md를 조금 수정합니다.

Skills는 한 번에 완벽하게 만드는 것보다, 실제로 쓰면서 다듬는 쪽이 좋습니다.

처음에는 이런 간단한 Skill부터 시작해 보세요.

system-status
fixme-finder
release-checklist
screenshot-splitter
blog-outline-writer

이 중 하나만 제대로 만들어도 Codex를 쓰는 느낌이 꽤 달라집니다.


마무리

Codex Skills는 거창한 확장 기능이라기보다, 내 작업 습관을 Codex에게 알려 주는 작은 매뉴얼입니다.

SKILL.md는 Codex에게 “언제, 어떻게 작업할지” 알려 주고, scripts 폴더는 반복되는 코드를 안정적으로 실행하게 해 줍니다.

처음에는 system-status처럼 작고 단순한 것부터 만들면 충분합니다.

~/.codex/skills/system-status/
├── SKILL.md
└── scripts/
    └── system_status.py

이 구조 하나만 이해하면, 이후에는 앱스토어 스크린샷 생성, 프로젝트 점검, 릴리즈 체크리스트, 블로그 초안 작성 같은 작업도 같은 방식으로 확장할 수 있습니다.

AI 도구를 잘 쓰는 핵심은 매번 더 길게 설명하는 것이 아니라, 반복되는 설명을 좋은 구조로 남겨 두는 것입니다.

Codex Skills는 그 반복을 줄여 주는 꽤 실용적인 방법입니다.

profile
iOS 앱 개발자

0개의 댓글