Replace sops+age encryption with plain dotenv and agent-native credential resolution

Agent environments (OpenClaw, Claude Code, Codex) assume users delegate
credentials to the agent. sops+age added setup friction without real
security benefit since the agent decrypts on every call anyway.

New model: skills declare required env var names; how they are supplied
is up to the agent (own vault, shell env, or ~/.config/k-skill/secrets.env
as the default fallback with 0600 permissions).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffrey (Dongkyu) Kim 2026-03-30 22:12:04 +09:00
commit ef2c69b81c
20 changed files with 203 additions and 486 deletions

View file

@ -38,8 +38,8 @@ Claude code, codex, opencode 등 각종 코딩 에이전트 지원합니다.
## 처음 시작하는 순서
1. [설치 방법](docs/install.md)을 따라 `k-skill` 전체 스킬을 먼저 설치합니다.
2. 설치가 끝나면 `k-skill-setup` 스킬을 사용해 `sops + age`, 공통 secrets 파일, 런타임 주입 확인까지 진행합니다.
3. 시크릿이 비어 있으면 값을 채팅에 붙여 넣지 말고, [공통 설정 가이드](docs/setup.md)와 [보안/시크릿 정책](docs/security-and-secrets.md)에 따라 로컬에 안전하게 등록합니다.
2. 설치가 끝나면 `k-skill-setup` 스킬을 사용해 credential 확보와 환경변수 확인을 진행합니다.
3. 시크릿이 비어 있으면 [공통 설정 가이드](docs/setup.md)와 [보안/시크릿 정책](docs/security-and-secrets.md)에 따라 credential resolution order로 확보합니다.
4. Node/Python 패키지가 없으면 먼저 전역 설치를 기본으로 진행합니다.
5. 각 기능 문서를 열어 입력값, 예시, 제한사항을 확인합니다.
@ -48,7 +48,7 @@ Claude code, codex, opencode 등 각종 코딩 에이전트 지원합니다.
| 문서 | 설명 |
| --- | --- |
| [설치 방법](docs/install.md) | 패키지 설치, 선택 설치, 로컬 테스트 방법 |
| [공통 설정 가이드](docs/setup.md) | `sops + age` 설치, age key 생성, 공통 secrets 파일 준비 |
| [공통 설정 가이드](docs/setup.md) | credential resolution order, 기본 secrets 파일 준비 |
| [보안/시크릿 정책](docs/security-and-secrets.md) | 인증 정보 저장 원칙, 금지 패턴, 표준 환경변수 이름 |
| [k-skill 프록시 서버 가이드](docs/features/k-skill-proxy.md) | 무료 API를 프록시 서버로 바로 호출하는 방법 |
| [릴리스/배포 가이드](docs/releasing.md) | npm Changesets, Python release-please, trusted publishing 운영 규칙 |

View file

@ -13,7 +13,7 @@
- [보안/시크릿 정책](../security-and-secrets.md) 확인
- `k-skill-proxy` 또는 에어코리아 OpenAPI key
## 필요한 시크릿
## 필요한 환경변수
클라이언트 기본값:
@ -24,6 +24,13 @@
- `AIR_KOREA_OPEN_API_KEY`
### Credential resolution order
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
## 입력값
- 기본: 지역명/행정구역 힌트(`regionHint`)
@ -41,9 +48,7 @@
프록시 예시:
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'python3 scripts/fine_dust.py report --region-hint "서울 강남구" --json'
python3 scripts/fine_dust.py report --region-hint "서울 강남구" --json
```
후보 반환 예시:
@ -77,29 +82,25 @@ curl -fsS --get 'https://k-skill-proxy.nomadamas.org/B552584/ArpltnInforInqireSv
지역 기반 direct fallback:
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'curl -sG "http://apis.data.go.kr/B552584/MsrstnInfoInqireSvc/getMsrstnList" \
--data-urlencode "serviceKey=${AIR_KOREA_OPEN_API_KEY}" \
--data-urlencode "returnType=json" \
--data-urlencode "numOfRows=50" \
--data-urlencode "pageNo=1" \
--data-urlencode "addr=서울 강남구"'
curl -sG "http://apis.data.go.kr/B552584/MsrstnInfoInqireSvc/getMsrstnList" \
--data-urlencode "serviceKey=${AIR_KOREA_OPEN_API_KEY}" \
--data-urlencode "returnType=json" \
--data-urlencode "numOfRows=50" \
--data-urlencode "pageNo=1" \
--data-urlencode "addr=서울 강남구"
```
실시간 측정값:
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'curl -sG "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty" \
--data-urlencode "serviceKey=${AIR_KOREA_OPEN_API_KEY}" \
--data-urlencode "returnType=json" \
--data-urlencode "numOfRows=100" \
--data-urlencode "pageNo=1" \
--data-urlencode "stationName=중구" \
--data-urlencode "dataTerm=DAILY" \
--data-urlencode "ver=1.4"'
curl -sG "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty" \
--data-urlencode "serviceKey=${AIR_KOREA_OPEN_API_KEY}" \
--data-urlencode "returnType=json" \
--data-urlencode "numOfRows=100" \
--data-urlencode "pageNo=1" \
--data-urlencode "stationName=중구" \
--data-urlencode "dataTerm=DAILY" \
--data-urlencode "ver=1.4"
```
helper script 반복 검증:

View file

@ -15,11 +15,18 @@
- [공통 설정 가이드](../setup.md) 완료
- [보안/시크릿 정책](../security-and-secrets.md) 확인
## 필요한 시크릿
## 필요한 환경변수
- `KSKILL_KTX_ID`
- `KSKILL_KTX_PASSWORD`
### Credential resolution order
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
## 입력값
- 출발역
@ -44,7 +51,7 @@
## 기본 흐름
1. `korail2` 또는 `pycryptodome` 패키지가 없으면 다른 방법으로 우회하지 말고 먼저 전역 설치한다.
2. `KSKILL_KTX_ID`, `KSKILL_KTX_PASSWORD` 가 없으면 채팅에 붙여 넣게 하지 말고 로컬 secrets 등록 절차를 안내한다.
2. `KSKILL_KTX_ID`, `KSKILL_KTX_PASSWORD` 가 없으면 credential resolution order에 따라 확보한다.
3. helper 로 먼저 열차를 조회한다.
4. 후보 열차의 `index`, `train_id`, 출발/도착 시각, KTX 여부, 좌석 여부를 보여준다.
5. 대상 열차가 명확할 때만 예약한다.
@ -55,9 +62,7 @@
조회:
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'python3 scripts/ktx_booking.py search 서울 부산 20260328 090000 --limit 5'
python3 scripts/ktx_booking.py search 서울 부산 20260328 090000 --limit 5
```
좌석이 없는 열차까지 같이 보고 싶으면 `--include-no-seats`, 예약 대기 가능 열차도 같이 보고 싶으면 `--include-waiting-list` 를 붙인다.
@ -67,9 +72,7 @@ sops exec-env "$HOME/.config/k-skill/secrets.env" \
예약:
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'python3 scripts/ktx_booking.py reserve 서울 부산 20260328 090000 --train-id <train_id> --seat-option general-first'
python3 scripts/ktx_booking.py reserve 서울 부산 20260328 090000 --train-id <train_id> --seat-option general-first
```
좌석이 없을 때 예약 대기까지 시도하려면 조회 단계에서도 `--include-waiting-list` 를 켜고, 예약 단계에서 `--try-waiting` 을 추가한다.
@ -77,17 +80,13 @@ sops exec-env "$HOME/.config/k-skill/secrets.env" \
예약 확인:
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'python3 scripts/ktx_booking.py reservations'
python3 scripts/ktx_booking.py reservations
```
취소:
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'python3 scripts/ktx_booking.py cancel <reservation_id>'
python3 scripts/ktx_booking.py cancel <reservation_id>
```
응답은 JSON 으로 나오며 예약번호, 구입기한, 운임 확인에 바로 쓸 수 있다. **결제는 제외** 하고 예약까지만 자동화한다.
@ -95,6 +94,6 @@ sops exec-env "$HOME/.config/k-skill/secrets.env" \
## 주의할 점
- SRT 예매와는 별도 표면이므로 혼용하지 않는다.
- 평문 비밀번호 전달은 금지한다.
- credential은 환경변수로 주입한다.
- 결제 완료까지 자동화하는 범위는 아니다.
- Korail anti-bot 규칙이 다시 바뀌면 helper 도 함께 점검해야 한다.

View file

@ -12,10 +12,17 @@
- [보안/시크릿 정책](../security-and-secrets.md) 확인
- 서울 열린데이터 광장 API key
## 필요한 시크릿
## 필요한 환경변수
- `SEOUL_OPEN_API_KEY`
### Credential resolution order
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
## 입력값
- 역명
@ -23,17 +30,14 @@
## 기본 흐름
1. `SEOUL_OPEN_API_KEY` 가 없으면 채팅에 붙여 넣게 하지 말고 로컬 secrets 등록 절차를 안내합니다.
2. API key가 안전하게 주입되는지 확인합니다.
1. `SEOUL_OPEN_API_KEY` 가 없으면 credential resolution order에 따라 확보합니다.
3. 역명 기준으로 실시간 도착정보를 조회합니다.
4. 호선, 진행 방향, 도착 메시지, 조회 시점을 함께 요약합니다.
## 예시
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'curl -s "http://swopenAPI.seoul.go.kr/api/subway/${SEOUL_OPEN_API_KEY}/json/realtimeStationArrival/0/8/강남"'
curl -s "http://swopenAPI.seoul.go.kr/api/subway/${SEOUL_OPEN_API_KEY}/json/realtimeStationArrival/0/8/강남"
```
## 주의할 점

View file

@ -15,11 +15,18 @@
- [공통 설정 가이드](../setup.md) 완료
- [보안/시크릿 정책](../security-and-secrets.md) 확인
## 필요한 시크릿
## 필요한 환경변수
- `KSKILL_SRT_ID`
- `KSKILL_SRT_PASSWORD`
### Credential resolution order
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
## 입력값
- 출발역
@ -32,7 +39,7 @@
## 기본 흐름
1. `SRTrain` 패키지가 없으면 다른 방법으로 우회하지 말고 먼저 전역 설치합니다.
2. `KSKILL_SRT_ID`, `KSKILL_SRT_PASSWORD` 가 없으면 채팅에 붙여 넣게 하지 말고 로컬 secrets 등록 절차를 안내합니다.
2. `KSKILL_SRT_ID`, `KSKILL_SRT_PASSWORD` 가 없으면 credential resolution order에 따라 확보합니다.
3. 먼저 열차를 조회합니다.
4. 후보 열차의 출발/도착 시각, 좌석 여부, 운임을 보여줍니다.
5. 대상 열차가 명확할 때만 예약합니다.
@ -41,8 +48,7 @@
## 예시
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" 'python3 - <<'"'"'PY'"'"'
python3 - <<'PY'
import os
from SRT import SRT
@ -52,11 +58,10 @@ trains = srt.search_train("수서", "부산", "20260328", "080000", time_limit="
for idx, train in enumerate(trains[:5], start=1):
print(idx, train)
PY
'
```
## 주의할 점
- 평문 비밀번호를 채팅이나 `.env` 평문 파일에 두지 않습니다.
- credential은 환경변수로 주입합니다.
- 결제 완료까지 자동화하는 문서는 아닙니다.
- 매진 시 공격적인 재시도 루프는 피합니다.

View file

@ -15,7 +15,7 @@
Codex나 Claude Code에 아래 문장을 그대로 붙여 넣으면 된다.
```text
이 레포의 설치 문서를 읽고 k-skill 전체 스킬을 먼저 설치해줘. 설치가 끝나면 k-skill-setup 스킬을 사용해서 sops + age, 공통 secrets 파일, 런타임 주입 확인까지 이어서 진행해줘. 끝나면 설치된 스킬과 다음 단계만 짧게 정리해.
이 레포의 설치 문서를 읽고 k-skill 전체 스킬을 먼저 설치해줘. 설치가 끝나면 k-skill-setup 스킬을 사용해서 credential 확보와 환경변수 확인까지 이어서 진행해줘. 끝나면 설치된 스킬과 다음 단계만 짧게 정리해.
```
## 직접 설치

View file

@ -1,38 +1,23 @@
# Security And Secrets
`k-skill`인증이 필요한 스킬에서 비밀번호나 토큰을 채팅창에 직접 붙여 넣는 방식을 허용하지 않는다. 기본 원칙은 "비밀값은 암호화된 파일로 보관하고, 런타임에만 주입"이다.
`k-skill`**필요한 환경변수 이름만 선언**하고, 그 값을 어떻게 공급하느냐는 에이전트의 자유에 맡긴다.
## Missing secret handling policy
## Credential resolution order
인증이 필요한 스킬에서 필요한 값이 없으면 우회하지 않는다.
모든 credential-bearing 스킬은 아래 우선순위를 따른다.
- 어떤 값이 비어 있는지 정확한 환경변수 이름으로 사용자에게 알려준다
- 그 값을 채팅창에 붙여 넣으라고 하지 않는다
- 대체 사이트, 대체 API, 하드코딩, 임시 평문 `.env` 파일 같은 우회 경로를 찾지 않는다
- 사용자가 직접 로컬에 안전하게 등록하도록 안내한 뒤 다시 진행한다
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
안내 기본형:
기본 경로에 저장하는 것은 fallback일 뿐, 강제가 아니다.
1. 필요한 값 이름을 짚는다. 예: `KSKILL_SRT_ID`, `KSKILL_SRT_PASSWORD`
2. `~/.config/k-skill/secrets.env.plain` 에 값을 적고
3. `sops``~/.config/k-skill/secrets.env` 로 암호화한 뒤
4. plaintext 파일을 지우고
5. `bash scripts/check-setup.sh` 로 다시 확인하게 한다
## Default secrets file
즉, "시크릿이 없으면 사용자에게 필요한 정보를 요청하고, 안전한 등록 절차를 안내한 뒤 멈춘다"가 기본 동작이다.
## Required
- `sops`
- `age`
- local age private key
- encrypted dotenv file for `k-skill`
## Allowed patterns
### 1. `sops exec-env` with an encrypted dotenv file
평문 예시는 한 번만 작성하고, 바로 암호화해서 지운다.
- 경로: `~/.config/k-skill/secrets.env`
- 형식: plain dotenv (`KEY=value`, 한 줄에 하나)
- 퍼미션: `0600` (owner-only read/write)
```dotenv
KSKILL_SRT_ID=replace-me
@ -44,52 +29,31 @@ AIR_KOREA_OPEN_API_KEY=replace-me
KSKILL_PROXY_BASE_URL=https://k-skill-proxy.nomadamas.org
```
실행은 항상 다음 패턴으로 한다.
## Missing secret handling policy
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" '<command>'
```
인증이 필요한 스킬에서 필요한 값이 없으면 우회하지 않는다.
### 2. `sops` encrypted file with repo-external storage
권장 기본 위치:
- encrypted secrets file: `~/.config/k-skill/secrets.env`
- age private key: `~/.config/k-skill/age/keys.txt`
이렇게 하면 저장소 안에 평문 비밀 파일을 둘 필요가 없다.
### 3. Wrapper commands that consume secrets internally
가장 강한 모델은 에이전트에게 secret 값을 직접 주지 않고, 내부에서만 secret을 소비하는 래퍼 명령을 두는 것이다.
예:
- `kskill-run srt-search ...`
- `kskill-run ktx-book ...`
이 경우 비밀값은 helper process 내부에서만 쓰인다.
- 어떤 값이 비어 있는지 정확한 환경변수 이름으로 사용자에게 알려준다
- credential resolution order에 따라 확보한다
- 대체 사이트, 대체 API, 하드코딩 같은 우회 경로를 찾지 않는다
- 시크릿이 없다는 이유로 다른 서비스나 비공식 우회 수단을 자동 채택하지 않는다
## Forbidden patterns
- 채팅 메시지에 비밀번호/토큰을 직접 붙여 넣기
- 실제 비밀값이 들어있는 plaintext `.env` 파일을 git에 두기
- 셸 히스토리에 남는 `export PASSWORD=...`
- 실제 비밀값이 들어있는 파일을 git에 두기
- 스킬 문서 안에 예시용 실비밀번호를 쓰기
- 시크릿이 없다는 이유로 다른 서비스나 비공식 우회 수단을 자동 채택하기
## Threat model notes
## Threat model
- `sops + age`는 저장 시점과 git 저장소에서의 노출을 줄여준다
- 하지만 `sops exec-env`로 실행된 프로세스는 복호화된 env var를 사용할 수 있다
- 즉 "에이전트가 쓸 수는 있지만 절대로 읽을 수는 없는" 구조는 아니다
- 그 수준이 필요하면 secret을 직접 주입하지 말고 capability wrapper를 둬야 한다
- `~/.config/k-skill/secrets.env`는 plain dotenv 파일이다
- 파일 퍼미션 `0600`으로 같은 머신의 다른 유저로부터 보호한다
- `.gitignore`로 git 노출을 방지한다
- 에이전트는 이 파일을 읽고 쓸 수 있다 — 이것은 의도된 동작이다
- OpenClaw/에이전트 환경에서 유저는 에이전트에게 credential을 위임하는 것을 전제로 사용한다
## Standard variable names
실제 환경변수 이름은 현재 다음을 사용한다.
- `KSKILL_SRT_ID`
- `KSKILL_SRT_PASSWORD`
- `KSKILL_KTX_ID`
@ -98,11 +62,4 @@ sops exec-env "$HOME/.config/k-skill/secrets.env" '<command>'
- `AIR_KOREA_OPEN_API_KEY`
- `KSKILL_PROXY_BASE_URL`
## Why sops plus age
- 가입과 클라우드 로그인이 필요 없다
- macOS, Linux, Windows 모두 가능하다
- dotenv 파일을 그대로 암호화할 수 있다
- `sops exec-env`로 런타임 주입 패턴이 단순하다
이 레포의 credential-bearing skill은 전부 이 정책을 전제로 작성한다. 자세한 공통 설치 절차는 [공통 설정 가이드](setup.md)를 본다.

View file

@ -1,73 +1,25 @@
# 공통 설정 가이드
`k-skill` 전체 스킬을 설치한 뒤, `k-skill-setup` 스킬이 실제로 수행해야 하는 공통 설정 절차를 이 문서에 정리한다.
`k-skill` 전체 스킬을 설치한 뒤, 인증 정보가 필요한 기능(SRT 예매, KTX 예매, 서울 지하철 도착정보 조회, 미세먼지 조회)을 사용하려면 이 절차를 진행하면 된다.
SRT 예매, KTX 예매, 서울 지하철 도착정보 조회, 사용자 위치 미세먼지 조회처럼 인증 정보가 필요한 기능은 설치 직후 이 절차를 진행하면 된다.
## Credential resolution order
## 이 설정으로 해결하는 것
모든 credential-bearing 스킬은 아래 우선순위를 따른다.
- `sops + age` 설치
- age key 생성
- 공통 secrets 파일 생성
- 암호화 확인
- 런타임 주입 확인
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
## 기본 경로
에이전트가 자체 vault를 사용 중이라면 기본 경로 설정을 건너뛰어도 된다.
- age key: `~/.config/k-skill/age/keys.txt`
- encrypted secrets file: `~/.config/k-skill/secrets.env`
## 기본 경로로 설정하기
## 1) 필요한 도구 설치
### macOS
```bash
brew install sops age
```
### Ubuntu / Debian
```bash
sudo apt-get update
sudo apt-get install -y sops age
```
### Arch Linux
```bash
sudo pacman -S sops age
```
### Windows
```powershell
winget install Mozilla.SOPS FiloSottile.age
```
도구가 없으면 다른 비밀 관리 방식으로 우회하지 말고, 이 도구들을 먼저 설치하는 것을 기본으로 합니다.
## 2) age key 만들기
```bash
mkdir -p ~/.config/k-skill/age
age-keygen -o ~/.config/k-skill/age/keys.txt
```
출력된 public key를 복사해 둡니다.
## 3) `.sops.yaml` 만들기
```yaml
creation_rules:
- path_regex: .*secrets\.env(\.plain)?$
age: age1replace-with-your-public-key
```
## 4) 공통 secrets 파일 만들기
에이전트가 별도 vault를 쓰지 않는 경우, 기본 fallback 파일을 만든다.
```bash
mkdir -p ~/.config/k-skill
cat > ~/.config/k-skill/secrets.env.plain <<'EOF'
cat > ~/.config/k-skill/secrets.env <<'EOF'
KSKILL_SRT_ID=replace-me
KSKILL_SRT_PASSWORD=replace-me
KSKILL_KTX_ID=replace-me
@ -76,56 +28,23 @@ SEOUL_OPEN_API_KEY=replace-me
AIR_KOREA_OPEN_API_KEY=replace-me
KSKILL_PROXY_BASE_URL=https://k-skill-proxy.nomadamas.org
EOF
chmod 0600 ~/.config/k-skill/secrets.env
```
실제 값을 채운 뒤 바로 암호화합니다.
실제 값을 채운다.
```bash
cd ~/.config/k-skill
sops --encrypt --input-type dotenv --output-type dotenv \
secrets.env.plain > secrets.env
rm secrets.env.plain
```
## 시크릿이 없을 때의 기본 응답
인증이 필요한 스킬에서 값이 비어 있으면 다음 식으로 안내하는 것을 기본으로 합니다.
- 어떤 값이 필요한지 정확한 변수 이름으로 알려주기
- 그 값을 채팅에 보내지 말라고 안내하기
- 아래 절차로 로컬에 직접 등록하게 하기
예:
```text
이 작업에는 KSKILL_SRT_ID, KSKILL_SRT_PASSWORD 가 필요합니다.
값을 채팅창에 붙여 넣지 말고, ~/.config/k-skill/secrets.env.plain 에 채운 뒤
sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요.
끝나면 plaintext 파일은 지우고 bash scripts/check-setup.sh 로 다시 확인해 주세요.
```
## 5) 런타임 주입 확인
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'test -n "$KSKILL_SRT_ID" || test -n "$KSKILL_KTX_ID" || test -n "$SEOUL_OPEN_API_KEY" || test -n "$AIR_KOREA_OPEN_API_KEY"'
```
또는:
## 확인
```bash
bash scripts/check-setup.sh
```
## 6) 실행 래퍼 두기
## 시크릿이 없을 때의 기본 응답
```bash
kskill-run() {
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" "$@"
}
```
인증이 필요한 스킬에서 값이 비어 있으면 credential resolution order에 따라 확보한다.
- 어떤 값이 필요한지 정확한 변수 이름으로 알려주기
- resolution order에 따라 유저에게 확보 방법 안내하기
## 기능별로 필요한 값
@ -144,4 +63,4 @@ kskill-run() {
- [사용자 위치 미세먼지 조회 가이드](features/fine-dust-location.md)
- [보안/시크릿 정책](security-and-secrets.md)
설치 기본 흐름은 "전체 스킬 설치 → `k-skill-setup` 실행 → 개별 기능 사용" 입니다.
설치 기본 흐름은 "전체 스킬 설치 → 개별 기능 사용" 이다.

View file

@ -37,5 +37,3 @@
- CJ대한통운 배송상세 JSON: https://www.cjlogistics.com/ko/tool/parcel/tracking-detail
- 우체국 배송조회: https://service.epost.go.kr/trace.RetrieveRegiPrclDeliv.postal?sid1=
- 우체국 배송상세 HTML: https://service.epost.go.kr/trace.RetrieveDomRigiTraceList.comm
- SOPS docs: https://getsops.io/docs/
- age: https://github.com/FiloSottile/age

View file

@ -1,3 +0,0 @@
creation_rules:
- path_regex: .*secrets\.env(\.plain)?$
age: age1replace-with-your-public-key

View file

@ -1,6 +1,6 @@
---
name: k-skill-setup
description: After installing the full k-skill bundle, configure and verify the shared cross-platform setup with sops plus age, then optionally wire update checks and GitHub starring with explicit user consent.
description: After installing the full k-skill bundle, configure and verify the shared cross-platform setup, then optionally wire update checks and GitHub starring with explicit user consent.
license: MIT
metadata:
category: setup
@ -14,47 +14,33 @@ metadata:
전체 `k-skill` 설치가 끝난 뒤, 공통 후속 작업을 처리한다.
- `sops + age` 설치
- age key 생성
- 공통 secrets 파일 생성
- 암호화 확인
- 런타임 주입 확인
- credential 확보 (에이전트 vault 또는 기본 secrets.env)
- 런타임 환경변수 확인
- 선택 사항: 주기적인 업데이트 확인 자동화
- 선택 사항: GitHub star 여부 확인 및 동의 시 실행
이 스킬의 기본 정책:
- 시크릿이 없으면 필요한 값 이름을 사용자에게 정확히 알려준다
- 값을 채팅창에 붙여 넣으라고 하지 않는다
- 로컬에 안전하게 등록하는 절차를 안내한 뒤 다시 진행한다
- credential resolution order에 따라 확보한다
- 필요한 패키지가 없으면 대체 구현을 찾기보다 전역 설치를 먼저 시도한다
- `cron`, `launchd`, `schtasks`, `gh` 같은 지속성/외부 상태 변경은 자동으로 하지 말고 먼저 사용자 동의를 받는다
- GitHub star는 사용자가 명시적으로 동의했을 때만 실행한다
## Why this is the default setup path
## Credential resolution order
- 계정 가입이 필요 없다
- macOS, Linux, Windows 모두 가능하다
- 스킬은 비밀값 위치를 몰라도 되고, 표준 환경변수 이름만 보면 된다
- 비밀값은 저장소에 평문으로 두지 않아도 된다
- 설치 단계와 시크릿/운영 자동화 단계를 분리할 수 있다
모든 credential-bearing 스킬은 아래 우선순위를 따른다.
## Security model
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
중요한 한계:
기본 경로에 저장하는 것은 fallback일 뿐, 강제가 아니다.
- 암호화된 파일은 안전하게 저장할 수 있다
- 하지만 `sops exec-env ...` 로 실행된 프로세스는 복호화된 환경변수를 사용할 수 있다
- 즉, 에이전트가 "쓸 수는 있지만 절대로 읽을 수는 없는" 구조는 아니다
## Standard file location
더 강한 모델이 필요하면 비밀값 자체를 넘기지 말고, 비밀값을 내부에서 소비하는 래퍼 명령만 노출해야 한다.
## Standard file locations
- age key: `~/.config/k-skill/age/keys.txt`
- encrypted secrets file: `~/.config/k-skill/secrets.env`
원하면 다른 위치를 써도 되지만, 기본 문서는 이 경로를 기준으로 한다.
- secrets file (기본 fallback): `~/.config/k-skill/secrets.env`
## Install
@ -68,59 +54,15 @@ npx --yes skills add <owner/repo> --all -g
설치가 끝나면 이 스킬을 호출해 아래 setup 단계를 이어간다.
### macOS
```bash
brew install sops age
```
### Ubuntu / Debian
```bash
sudo apt-get update
sudo apt-get install -y sops age
```
### Arch Linux
```bash
sudo pacman -S sops age
```
### Windows
```powershell
winget install Mozilla.SOPS FiloSottile.age
```
패키지 이름은 배포 채널에 따라 바뀔 수 있으니, 실패하면 공식 releases 페이지를 확인한다.
## Setup steps
### 1. Create an age key
### 1. Create the default secrets file (if no vault is in use)
```bash
mkdir -p ~/.config/k-skill/age
age-keygen -o ~/.config/k-skill/age/keys.txt
```
출력에 보이는 public key를 복사한다.
### 2. Create `.sops.yaml`
작업 디렉터리나 secrets 파일이 있는 디렉터리에 생성한다.
```yaml
creation_rules:
- path_regex: .*secrets\.env(\.plain)?$
age: age1replace-with-your-public-key
```
### 3. Create the plaintext env file once
에이전트가 자체 vault를 쓰지 않는 경우, 기본 fallback 파일을 만든다.
```bash
mkdir -p ~/.config/k-skill
cat > ~/.config/k-skill/secrets.env.plain <<'EOF'
cat > ~/.config/k-skill/secrets.env <<'EOF'
KSKILL_SRT_ID=replace-me
KSKILL_SRT_PASSWORD=replace-me
KSKILL_KTX_ID=replace-me
@ -128,31 +70,16 @@ KSKILL_KTX_PASSWORD=replace-me
SEOUL_OPEN_API_KEY=replace-me
AIR_KOREA_OPEN_API_KEY=replace-me
EOF
chmod 0600 ~/.config/k-skill/secrets.env
```
실제 값을 채운다.
### 4. Encrypt it
```bash
cd ~/.config/k-skill
sops --encrypt --input-type dotenv --output-type dotenv \
secrets.env.plain > secrets.env
rm secrets.env.plain
```
유저에게 물어서 실제 값을 채운다.
### Missing secret response template
인증 스킬에서 값이 빠졌을 때는 다음 식으로 안내한다.
인증 스킬에서 값이 빠졌을 때는 credential resolution order에 따라 확보한다.
```text
이 작업에는 <REQUIRED_SECRET_NAMES> 이 필요합니다.
값을 채팅창에 보내지 말고 ~/.config/k-skill/secrets.env.plain 에 직접 채운 뒤
sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요.
암호화가 끝나면 plaintext 파일은 지우고 bash scripts/check-setup.sh 로 다시 확인해 주세요.
```
예를 들면:
필요한 값 예:
- SRT: `KSKILL_SRT_ID`, `KSKILL_SRT_PASSWORD`
- KTX: `KSKILL_KTX_ID`, `KSKILL_KTX_PASSWORD`
@ -161,28 +88,13 @@ sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요.
시크릿이 비어 있다는 이유로 다른 서비스나 비공식 우회 경로를 자동 선택하지 않는다.
### 5. Verify runtime injection
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'test -n "$KSKILL_SRT_ID" || test -n "$KSKILL_KTX_ID" || test -n "$SEOUL_OPEN_API_KEY" || test -n "$AIR_KOREA_OPEN_API_KEY"'
```
또는 저장소에 들어있는 점검 스크립트를 쓴다.
### 2. Verify runtime environment
```bash
bash scripts/check-setup.sh
```
### 6. Run tools with the encrypted file
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" '<your command>'
```
### 7. Offer scheduled update checks
### 3. Offer scheduled update checks
setup이 끝나면 사용자에게 주기적인 업데이트 확인 자동화를 원하는지 먼저 묻는다. 원하지 않으면 건너뛴다.
@ -226,7 +138,7 @@ schtasks /Create /SC DAILY /TN "k-skill-update-check" /TR "\"$HOME/.config/k-ski
- `~/.config/k-skill/logs/skills-check.log`
### 8. Offer GitHub starring with explicit consent
### 4. Offer GitHub starring with explicit consent
setup 마지막에는 다음처럼 짧게 묻는다.
@ -249,31 +161,13 @@ gh repo star NomaDamas/k-skill
성공하면 짧게 완료만 알린다.
## Recommended shell helper
```bash
kskill-run() {
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" "$@"
}
```
예시:
```bash
kskill-run python your-script.py
```
## Completion checklist
- `sops --version` works
- `age-keygen --version` or `age --version` works
- `~/.config/k-skill/age/keys.txt` exists
- `~/.config/k-skill/secrets.env` exists and is encrypted
- `sops exec-env ...` can inject expected env vars
- `~/.config/k-skill/secrets.env` exists with permission `0600` (또는 에이전트가 자체 vault로 credential을 관리 중)
- 필요한 환경변수가 설정되어 있다
- 사용자가 원한 경우에만 업데이트 확인 자동화 또는 GitHub star가 설정되었다
## Notes
- 기본 흐름은 "전체 스킬 설치 → 이 setup skill 실행 → 개별 기능 사용" 이다
- 저장소 안에는 plaintext secret file을 두지 않는다
- 저장소 안에는 secret file을 두지 않는다

View file

@ -33,15 +33,21 @@ metadata:
- Python 3.10+
- `python3 -m pip install korail2 pycryptodome`
- `sops` and `age` installed
- common setup reviewed in `../k-skill-setup/SKILL.md`
- secret policy reviewed in `../docs/security-and-secrets.md`
## Required secrets
## Required environment variables
- `KSKILL_KTX_ID`
- `KSKILL_KTX_PASSWORD`
### Credential resolution order
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
기본 경로에 저장하는 것은 fallback일 뿐, 강제가 아니다.
## Inputs
- 출발역
@ -62,16 +68,9 @@ metadata:
python3 -m pip install korail2 pycryptodome
```
### 1. Stop for secure registration when secrets are missing
### 1. Ensure credentials are available
`KSKILL_KTX_ID`, `KSKILL_KTX_PASSWORD`, `~/.config/k-skill/secrets.env`, `~/.config/k-skill/age/keys.txt` 중 하나라도 없으면 다음 식으로 안내하고 멈춘다.
```text
이 작업에는 KSKILL_KTX_ID, KSKILL_KTX_PASSWORD 가 필요합니다.
값을 채팅창에 붙여 넣지 말고 ~/.config/k-skill/secrets.env.plain 에 직접 채운 뒤
sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요.
암호화가 끝나면 plaintext 파일은 지우고 bash scripts/check-setup.sh 로 다시 확인해 주세요.
```
`KSKILL_KTX_ID`, `KSKILL_KTX_PASSWORD` 환경변수가 설정되어 있는지 확인한다. 없으면 위 credential resolution order에 따라 확보한다.
시크릿이 없다는 이유로 웹사이트를 직접 긁거나 다른 비공식 경로를 찾지 않는다.
@ -80,9 +79,7 @@ sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요.
항상 helper 를 통해 조회한다.
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'python3 scripts/ktx_booking.py search 서울 부산 20260328 090000 --limit 5'
python3 scripts/ktx_booking.py search 서울 부산 20260328 090000 --limit 5
```
좌석이 없는 열차도 후보에 포함하려면 `--include-no-seats`, 예약 대기 가능한 열차도 같이 보고 싶으면 `--include-waiting-list` 를 붙인다.
@ -103,9 +100,7 @@ sops exec-env "$HOME/.config/k-skill/secrets.env" \
조회 결과의 `train_id` 를 고른 뒤에만 예약한다. 이 값은 helper 가 열차 번호/운행일/시각/역 코드를 묶어 만든 stable selector 이므로, 재조회 시 같은 열차가 아직 있으면 그대로 잡고 없으면 실패한다.
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'python3 scripts/ktx_booking.py reserve 서울 부산 20260328 090000 --train-id <train_id> --seat-option general-first'
python3 scripts/ktx_booking.py reserve 서울 부산 20260328 090000 --train-id <train_id> --seat-option general-first
```
응답에는 예약번호, 운임, 구입기한이 포함된다. **결제는 자동화하지 않는다.**
@ -116,15 +111,11 @@ sops exec-env "$HOME/.config/k-skill/secrets.env" \
취소는 대상 예약을 다시 조회해 식별한 뒤에만 진행한다.
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'python3 scripts/ktx_booking.py reservations'
python3 scripts/ktx_booking.py reservations
```
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'python3 scripts/ktx_booking.py cancel <reservation_id>'
python3 scripts/ktx_booking.py cancel <reservation_id>
```
## Done when

View file

@ -21,11 +21,11 @@
## 로컬 실행
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'node packages/k-skill-proxy/src/server.js'
node packages/k-skill-proxy/src/server.js
```
환경변수(`AIR_KOREA_OPEN_API_KEY` 등)가 이미 설정되어 있거나 `~/.config/k-skill/secrets.env`를 source한 상태에서 실행한다.
## PM2 실행
루트의 `ecosystem.config.cjs` + `scripts/run-k-skill-proxy.sh` 조합을 사용하면 재부팅 이후에도 같은 encrypted secrets 경로로 다시 올라옵니다.
루트의 `ecosystem.config.cjs` + `scripts/run-k-skill-proxy.sh` 조합을 사용하면 재부팅 이후에도 같은 환경변수로 다시 올라옵니다.

View file

@ -2,44 +2,28 @@
set -euo pipefail
secrets_file="${1:-$HOME/.config/k-skill/secrets.env}"
age_key_file="${SOPS_AGE_KEY_FILE:-$HOME/.config/k-skill/age/keys.txt}"
missing=0
check_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "missing command: $1"
if [[ ! -f "$secrets_file" ]]; then
echo "missing secrets file: $secrets_file"
missing=1
else
perms=$(stat -f '%Lp' "$secrets_file" 2>/dev/null || stat -c '%a' "$secrets_file" 2>/dev/null)
if [[ "$perms" != "600" ]]; then
echo "insecure permissions on $secrets_file: $perms (expected 600)"
missing=1
fi
}
check_cmd sops
check_cmd age
check_cmd age-keygen
if [[ ! -f "$age_key_file" ]]; then
echo "missing age key file: $age_key_file"
missing=1
fi
if [[ ! -f "$secrets_file" ]]; then
echo "missing encrypted secrets file: $secrets_file"
missing=1
fi
if [[ "$missing" -ne 0 ]]; then
cat <<EOF
next steps:
1. follow k-skill-setup / docs/setup.md
2. register required secrets in ~/.config/k-skill/secrets.env.plain
3. encrypt it to ~/.config/k-skill/secrets.env with sops
4. delete the plaintext file
5. run this check again
1. create ~/.config/k-skill/secrets.env with your credentials
2. chmod 0600 ~/.config/k-skill/secrets.env
3. run this check again
EOF
exit 1
fi
SOPS_AGE_KEY_FILE="$age_key_file" \
sops exec-env "$secrets_file" 'true'
echo "k-skill setup looks usable"

View file

@ -337,10 +337,9 @@ def build_report(
def build_missing_secret_message() -> str:
return (
f"이 작업에는 {SECRET_NAME} 가 필요합니다.\n"
"값을 채팅창에 붙여 넣지 말고 ~/.config/k-skill/secrets.env.plain 에 직접 채운 뒤\n"
"sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요.\n"
"암호화가 끝나면 plaintext 파일은 지우고 bash scripts/check-setup.sh 로 다시 확인해 주세요."
f"이 작업에는 {SECRET_NAME} 환경변수가 필요합니다.\n"
"환경변수가 설정되어 있지 않으면 ~/.config/k-skill/secrets.env 에 추가하거나\n"
"에이전트의 secret vault에서 주입해 주세요."
)

View file

@ -625,9 +625,9 @@ def build_client() -> PatchedKorail:
korail_pw = os.environ.get("KSKILL_KTX_PASSWORD")
if not korail_id or not korail_pw:
raise SystemExit(
"이 작업에는 KSKILL_KTX_ID, KSKILL_KTX_PASSWORD 가 필요합니다. "
"값을 채팅창에 붙여 넣지 말고 ~/.config/k-skill/secrets.env.plain 에 직접 채운 뒤 "
"sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요."
"이 작업에는 KSKILL_KTX_ID, KSKILL_KTX_PASSWORD 환경변수가 필요합니다. "
"환경변수가 설정되어 있지 않으면 ~/.config/k-skill/secrets.env 에 추가하거나 "
"에이전트의 secret vault에서 주입해 주세요."
)
client = PatchedKorail(korail_id, korail_pw)
if not client.logined:

View file

@ -3,23 +3,13 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SECRETS_FILE="${KSKILL_SECRETS_FILE:-$HOME/.config/k-skill/secrets.env}"
AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-$HOME/.config/k-skill/age/keys.txt}"
if ! command -v sops >/dev/null 2>&1; then
echo "missing command: sops" >&2
exit 1
fi
if [[ ! -f "$SECRETS_FILE" ]]; then
echo "missing encrypted secrets file: $SECRETS_FILE" >&2
exit 1
fi
if [[ ! -f "$AGE_KEY_FILE" ]]; then
echo "missing age key file: $AGE_KEY_FILE" >&2
exit 1
if [[ -f "$SECRETS_FILE" ]]; then
set -a
# shellcheck disable=SC1090
source "$SECRETS_FILE"
set +a
fi
cd "$ROOT_DIR"
exec env SOPS_AGE_KEY_FILE="$AGE_KEY_FILE" \
sops exec-env "$SECRETS_FILE" 'node packages/k-skill-proxy/src/server.js'
exec node packages/k-skill-proxy/src/server.js

View file

@ -210,7 +210,7 @@ test("ktx-booking docs document the helper-based live Korail workflow", () => {
assert.match(doc, /--include-no-seats/);
assert.match(doc, /--include-waiting-list/);
assert.match(doc, /--try-waiting/);
assert.match(doc, /sops exec-env/);
assert.match(doc, /credential resolution order|KSKILL_KTX_ID/);
assert.match(doc, /anti-bot|Dynapath|x-dynapath-m-token/i);
assert.match(doc, /결제(까지)?는 자동화하지 않는다|결제는 제외/);
assert.doesNotMatch(doc, /예약 시 선택할 `--train-index`/);

View file

@ -23,15 +23,21 @@ metadata:
## Prerequisites
- 서울 열린데이터 광장 API key
- `sops` and `age` installed
- common setup reviewed in `../k-skill-setup/SKILL.md`
- secret policy reviewed in `../docs/security-and-secrets.md`
- optional: `jq`
## Required secrets
## Required environment variables
- `SEOUL_OPEN_API_KEY`
### Credential resolution order
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
기본 경로에 저장하는 것은 fallback일 뿐, 강제가 아니다.
## Inputs
- 역명
@ -39,34 +45,18 @@ metadata:
## Workflow
### 1. Stop for secure registration when the API key is missing
### 1. Ensure credentials are available
평문 key를 붙여 넣지 않는다.
`SEOUL_OPEN_API_KEY`, `~/.config/k-skill/secrets.env`, `~/.config/k-skill/age/keys.txt` 중 하나라도 없으면 다음 식으로 안내하고 멈춘다.
```text
이 작업에는 SEOUL_OPEN_API_KEY 가 필요합니다.
값을 채팅창에 붙여 넣지 말고 ~/.config/k-skill/secrets.env.plain 에 직접 채운 뒤
sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요.
암호화가 끝나면 plaintext 파일은 지우고 bash scripts/check-setup.sh 로 다시 확인해 주세요.
```
`SEOUL_OPEN_API_KEY` 환경변수가 설정되어 있는지 확인한다. 없으면 위 credential resolution order에 따라 확보한다.
시크릿이 없다는 이유로 비공식 미러 API나 다른 출처로 자동 우회하지 않는다.
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" 'test -n "$SEOUL_OPEN_API_KEY"'
```
### 2. Query the official station arrival endpoint
서울 실시간 지하철 API는 역명 기준 실시간 도착 정보를 JSON/XML로 제공한다. 기본 질의 예시는 다음 패턴을 쓴다.
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" \
'curl -s "http://swopenAPI.seoul.go.kr/api/subway/${SEOUL_OPEN_API_KEY}/json/realtimeStationArrival/0/8/강남"'
curl -s "http://swopenAPI.seoul.go.kr/api/subway/${SEOUL_OPEN_API_KEY}/json/realtimeStationArrival/0/8/강남"
```
### 3. Summarize the response

View file

@ -31,16 +31,20 @@ metadata:
- Python 3.10+
- `python3 -m pip install SRTrain`
- `sops` and `age` installed
- common setup reviewed in `../k-skill-setup/SKILL.md`
- secret policy reviewed in `../docs/security-and-secrets.md`
## Required secrets
## Required environment variables
- `KSKILL_SRT_ID`
- `KSKILL_SRT_PASSWORD`
평문 비밀번호는 금지한다. 항상 `sops exec-env ...` 패턴을 사용한다.
### Credential resolution order
1. **이미 환경변수에 있으면** 그대로 사용한다.
2. **에이전트가 자체 secret vault(1Password CLI, Bitwarden CLI, macOS Keychain 등)를 사용 중이면** 거기서 꺼내 환경변수로 주입해도 된다.
3. **`~/.config/k-skill/secrets.env`** (기본 fallback) — plain dotenv 파일, 퍼미션 `0600`.
4. **아무것도 없으면** 유저에게 물어서 2 또는 3에 저장한다.
기본 경로에 저장하는 것은 fallback일 뿐, 강제가 아니다.
## Inputs
@ -61,18 +65,9 @@ metadata:
python3 -m pip install SRTrain
```
### 1. Validate secrets path and stop for secure registration when missing
### 1. Ensure credentials are available
비밀번호를 직접 받지 않는다. 필요한 경우 encrypted secrets file 경로와 변수 이름만 확인한다.
`KSKILL_SRT_ID`, `KSKILL_SRT_PASSWORD`, `~/.config/k-skill/secrets.env`, `~/.config/k-skill/age/keys.txt` 중 하나라도 없으면 다음 식으로 안내하고 멈춘다.
```text
이 작업에는 KSKILL_SRT_ID, KSKILL_SRT_PASSWORD 가 필요합니다.
값을 채팅창에 붙여 넣지 말고 ~/.config/k-skill/secrets.env.plain 에 직접 채운 뒤
sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요.
암호화가 끝나면 plaintext 파일은 지우고 bash scripts/check-setup.sh 로 다시 확인해 주세요.
```
`KSKILL_SRT_ID`, `KSKILL_SRT_PASSWORD` 환경변수가 설정되어 있는지 확인한다. 없으면 위 credential resolution order에 따라 확보한다.
시크릿이 없다는 이유로 웹사이트를 직접 긁거나 다른 비공식 경로를 찾지 않는다.
@ -81,8 +76,7 @@ sops 로 ~/.config/k-skill/secrets.env 로 암호화해 주세요.
먼저 조회해서 후보를 요약한다.
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" 'python3 - <<'"'"'PY'"'"'
python3 - <<'PY'
import os
from SRT import SRT
@ -92,7 +86,6 @@ trains = srt.search_train("수서", "부산", "20260328", "080000", time_limit="
for idx, train in enumerate(trains[:5], start=1):
print(idx, train)
PY
'
```
### 3. Summarize options before side effects
@ -108,8 +101,7 @@ PY
예약은 부작용이 있으므로 정확한 열차를 고른 뒤에만 진행한다.
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" 'python3 - <<'"'"'PY'"'"'
python3 - <<'PY'
import os
from SRT import Adult, SRT, SeatType
@ -122,16 +114,14 @@ reservation = srt.reserve(
)
print(reservation)
PY
'
```
### 5. Inspect or cancel
예약 확인이나 취소도 같은 credential path를 유지한다. 취소 전에는 대상 예약을 다시 식별한다.
취소 전에는 대상 예약을 다시 식별한다.
```bash
SOPS_AGE_KEY_FILE="$HOME/.config/k-skill/age/keys.txt" \
sops exec-env "$HOME/.config/k-skill/secrets.env" 'python3 - <<'"'"'PY'"'"'
python3 - <<'PY'
import os
from SRT import SRT
@ -139,7 +129,6 @@ srt = SRT(os.environ["KSKILL_SRT_ID"], os.environ["KSKILL_SRT_PASSWORD"])
reservations = srt.get_reservations()
print(reservations)
PY
'
```
## Done when