로컬 개발 서버의 포트와 RAM을 차지하는 개발용 브라우저를 한 화면에서 찾아 정리하는 CLI/TUI입니다.
portwarden을 터미널에서 실행하면 인터랙티브 화면이 열립니다. 파이프나 CI에서는 자동으로 일반 텍스트를 출력하고, JSON·watch·안전한 종료 명령도 제공합니다.
- Node.js 20.5 이상
- macOS 또는 Linux
lsof명령
Windows는 현재 지원하지 않습니다.
npm install --global portwarden설치 없이 한 번 실행할 수도 있습니다.
npx portwarden# 터미널에서는 TUI, 파이프에서는 표 출력
portwarden
# 모든 LISTEN 포트
portwarden --all --plain
# 자동화에 쓰기 좋은 JSON
portwarden --all --zombies --json
# 5173 다음의 실제 빈 포트
portwarden --next-port 5173
# 2초마다 갱신
portwarden --all --watch 2--json --watch 조합은 각 스냅샷을 한 줄에 하나씩 쓰는 NDJSON 스트림입니다.
기본 화면에는 핀한 포트, 개발 서버, 개발용 브라우저가 표시됩니다. 개발 서버의 확인된 실행 프로그램(npm/pnpm/yarn의 dev/start/watch 등)과 하위 서버·워커는 하나의 TASK 항목으로 묶습니다. 함께 종료되는 모든 PID·포트·합산 메모리를 표시하고, 검색에서 숨겨진 포트도 범위에 포함합니다. 단독 프로세스의 여러 포트도 한 항목으로 묶습니다. p로 그룹에 현재 포함된 모든 포트를 함께 핀/해제할 수 있고, enter로 펼치면 개별 포트를 열거나 핀할 수 있습니다. a는 시스템·앱 포트도 표시하며, 여러 PID를 가진 같은 앱의 그룹은 펼쳐서 종료 대상을 선택합니다.
| 키 | 동작 |
|---|---|
↑ / ↓ |
선택 이동 |
← / → |
같은 구역 안에서 순서 변경, 작업·PID·앱 그룹 접기/펼치기 |
enter |
작업·PID·앱 그룹 접기/펼치기, 확인 창 승인 |
a |
개발 포트 / 전체 LISTEN 포트 전환 |
z |
자동화 좀비를 기존 목록 아래에 표시/숨김 |
p |
선택한 포트 또는 그룹 전체 핀/해제 |
o |
브라우저로 열기 |
m |
검증된 다음 빈 포트로 이동 |
x / f |
SIGTERM / SIGKILL로 종료 |
g |
무덤(Graveyard) 열기 |
s |
브라우저·확인 모드·갱신 주기 설정 |
/ |
필터 |
r |
즉시 새로고침 |
? |
도움말 |
q |
종료 |
한글 두벌식 입력 상태에서는 같은 물리 키 위치의 단축키도 인식합니다. 메인 목록에는 j/k 별칭을 두지 않아 검색 입력과 키 동작이 섞이지 않습니다.
PortWarden은 PID만 믿고 프로세스를 종료하지 않습니다.
- 종료 직전에 PID, 소유 사용자, 실행 파일, 시작 시각, 포트, cwd, 명령을 다시 확인합니다. macOS에서 축약된 프로세스명만 보이면
lsof의 실행 파일 정보를 보강해 다시 검증합니다. - 단독 리스너의 실행 파일 경로만 끝내 확인되지 않는 경우
x는 중단하지만, 명시적인 강제 종료f는 나머지 신원이 직전 스냅샷과 모두 일치하면 진행합니다. 작업·브라우저 묶음은 모든 구성원의 신원을 확인해야 종료합니다. TASK의x/f는 표시된 실행 프로그램부터 서버·워커까지 함께 종료합니다. 부모 관계, 실행 명령, 같은 작업 폴더를 확인해 범위를 정하며, 같은 PGID나 같은 에이전트 아래라는 이유만으로 다른 작업을 합치지 않습니다. 알 수 없는 부모 프로그램으로 범위를 확장하지 않습니다.- 작업의 PID·포트·구성원 신원이 화면 스냅샷과 달라졌다면 신호를 보내기 전에 중단하고 새로고침을 요구합니다. 종료 후 프로세스와 포트가 모두 사라져야 성공으로 표시합니다.
x또는f를 누르면 종료 대상 전체를 목록에서 즉시 숨기고 다음 항목을 선택합니다. 확인 모드에서는 승인 후 숨깁니다. 종료 확인을 기다리지 않고 다음 항목도 계속 종료 요청할 수 있으며, 실제 안전 검사와 종료는 대기열 순서대로 수행합니다. 같은 대상에 대한 중복 요청은 무시합니다.- 종료에 실패하면 현재 스캔에 남아 있는 대상이 목록 아래에 빨간
FAILED표시로 돌아옵니다. 선택은 다음 항목에 유지되며, 실패 항목을 다시 선택하면 오류 이유를 확인하고x/f로 재시도할 수 있습니다. 펼친 그룹은 하위 항목과 함께 이동합니다. 종료 대기열이 처리 중일 때q와Ctrl+C는 앱을 종료하지 않습니다. - 핀한 리스너는
x,f,--kill-*로 종료할 수 없습니다. 작업 전체나 브라우저 하위 프로세스에 핀된 포트가 하나라도 있으면 묶음 전체의 종료를 막습니다. m은 새 프로세스를 셸 없이 실행하고, 새 포트·cwd·명령이 모두 일치한 뒤에만 기존 프로세스를 내립니다. 검증 실패 시 새 프로세스를 롤백합니다.- 종료된 개발 서버는 실제로 포트가 닫힌 뒤에만 무덤에 저장됩니다.
- 토큰·비밀번호 같은 민감한 인수가 포함된 명령은 무덤에 저장하거나 재실행하지 않습니다.
- 잘못된 설정 파일은 조용히 덮어쓰지 않고 오류를 냅니다. 설정은 원자적으로 저장되며 파일 권한은
0600입니다.
기본 설정은 확인 창 없이 바로 동작합니다. 실수를 줄이고 싶다면 s 화면에서 Confirm actions를 켜세요.
m은 확인된 개발 서버 명령의 포트 표현만 안전하게 바꿉니다. 예: --port 3000, --port=3000, PORT=3000, python -m http.server 3000, Next.js의 -p 3000. 다른 프로그램에서 의미가 달라질 수 있는 -p나 단순히 인수에 vite라는 단어가 들어간 명령은 추측해서 바꾸지 않습니다. 명령을 안전하게 해석하거나 포트를 바꿀 수 없으면 원본 프로세스를 그대로 두고 중단합니다.
여러 프로세스로 묶인 작업의 m 이동은 지원하지 않습니다. 작업을 새 포트로 다시 시작해야 합니다.
x 또는 f로 종료한 재실행 가능한 개발 서버는 무덤에 저장됩니다. 작업 묶음은 전체 종료가 검증된 뒤 원래 실행 프로그램의 명령을 저장합니다.
g→r: 원래 cwd와 인수로 detached 재실행g→d: 저장된 재실행 정보만 삭제- 로그:
~/.portwarden/logs/<project>-<port>.log
재실행한 서버가 예상 포트와 명령으로 실제 LISTEN 상태가 되어야 무덤 기록이 제거됩니다.
Playwright·Puppeteer의 임시 프로필이나 브라우저 자동화 플래그를 가진 Chrome/Chromium 세션은 기본 표시됩니다. 창이 있는 Chrome, --remote-debugging-pipe로 연결되어 포트가 없는 Chrome, 제어 프로세스가 살아 있는 세션도 포함합니다. 일반 개인용 Chrome과 디버깅 포트만 연 개인 프로필은 제외합니다.
브라우저마다 렌더러·GPU 등 같은 사용자 소유의 하위 프로세스를 한 묶음으로 표시하며, 합산 RSS 메모리 내림차순으로 정렬합니다. 실행 시간, 프로세스 수, 프로필 경로, 부모 프로세스 상태도 볼 수 있습니다. 부모가 살아 있다는 사실만으로 실제 사용 중인지 또는 방치되었는지 판정하지 않습니다.
x: 선택한 브라우저와 검증된 하위 프로세스에 SIGTERMf: 같은 범위에 SIGKILL- CLI:
portwarden --kill-pid <브라우저 PID>또는--force추가 --plain,--json,--watch에도 개발용 브라우저가 기본 포함됩니다. JSON의type: "browser"항목에는memoryBytes,processCount,memberPids가 있습니다. 리스너의stopScope는 실제 종료 범위를 보여줍니다. 작업은type: "task",rootPid,memberPids,ports,processCount를 포함합니다.
종료 직전에 소유자·실행 파일·명령·시작 시각·프로세스 트리를 재검증하고, 부모 제어 프로그램에는 신호를 보내지 않습니다. macOS에서 보조 프로세스의 실행 파일 경로가 누락되면 lsof의 프로그램 정보를 통해 보강합니다. RSS 합산은 공유 메모리를 중복 계산할 수 있으므로 실제 반환될 RAM과 같지는 않습니다. 수집할 수 없는 값은 ? RAM으로 표시합니다.
z 또는 --zombies는 기본 브라우저 목록에 더해 현재 사용자 소유의 Playwright, Puppeteer, headless Chrome 계열 고아 프로세스를 찾습니다. 일반 Chrome, 살아 있는 컨트롤러의 하위 프로세스, 리스너, 서버 모드, 포트 인수를 가진 프로세스는 제외합니다.
# 후보 확인
portwarden --zombies --plain
# 60초 이상 된 안전한 후보만 미리보기
portwarden --reap --dry-run
# 종료 직전에 실행 파일·명령·시작 시각을 다시 검증하고 SIGTERM
portwarden --reap
# 같은 검증 후 SIGKILL
portwarden --reap --force프로세스나 lsof 정보를 확실히 읽지 못하면 자동 정리는 실패-닫힘(fail closed)으로 중단됩니다.
-a, --all 모든 TCP LISTEN 포트
-j, --json JSON 출력 (`--watch`와 함께 쓰면 NDJSON)
--plain 일반 표 출력
-t, --tui TUI 강제 실행
-z, --zombies 자동화 좀비 포함
--reap 안전 판정된 좀비 종료
--dry-run --reap 미리보기
-f, --force 종료 명령에서 SIGKILL 사용
-w, --watch [seconds] 일반/JSON 출력을 반복 갱신 (기본 2초)
-b, --browser <name> 이번 TUI에서 사용할 브라우저
--next-port <port> 다음 빈 포트 출력
--kill-port <port> 해당 포트의 작업 묶음 종료(실행 프로그램·워커 포함)
--kill-pid <pid> 해당 PID가 속한 개발 작업·브라우저 묶음 또는 단독 PID 종료
숫자 옵션은 전체 문자열을 엄격하게 검사하므로 3000abc 같은 값은 거부합니다. 여러 종료 작업을 한 번에 섞는 것도 허용하지 않습니다.
- macOS:
~/Library/Application Support/portwarden/config.json - Linux:
${XDG_CONFIG_HOME:-~/.config}/portwarden/config.json
이전 dev-port-watch 설정과 revivablePins 무덤 기록은 처음 실행할 때 가능한 항목만 새 형식으로 옮깁니다. 설정 파일에는 비밀 값이 포함된 재실행 명령을 저장하지 않습니다.
TUI는 ~/.portwarden/logs/runtime.jsonl에 실행 시각, 60초 간격의 메모리 사용량,
종료 키 입력, SIGTERM/SIGINT/SIGHUP, 오류 종류·스택 위치, 종료 코드를 기록합니다.
로그는 약 1MiB마다 회전하며 이전 파일 하나(runtime.jsonl.1)만 유지합니다.
파일 권한은 0600이며 스캔한 명령·환경 변수·오류 메시지 본문은 저장하지 않습니다.
로그 쓰기 실패는 앱 실행을 중단하지 않습니다.
SIGKILL, 런타임의 치명적 메모리 부족, 시스템 종료 등은 마지막 이벤트를 남기지 못할 수
있습니다. exit 없이 기록이 끊긴 사실만으로 원인을 확정할 수는 없습니다.
이미 실행 중인 프로세스에는 새 코드가 적용되지 않으므로 업데이트 후 다시 실행해야 합니다.
git clone https://github.com/chenjingdev/portwarden.git
cd portwarden
corepack enable
pnpm install
pnpm check
pnpm devPortWarden 1.0은 TypeScript, Ink/React, Commander, Execa, Conf, Zod를 사용합니다. 직접 만든 ANSI 화면·인자 파서·설정 저장 구현은 제거했고, PortWarden 고유의 프로세스 판별과 안전 검증에 코드를 집중했습니다.
상시 실행에는 빌드된 portwarden 또는 pnpm start를 사용합니다. 빌드는 React의 production 렌더러와 캐시 상한을 적용한 Ink를 함께 포함합니다. 이로써 화면 갱신마다 개발용 성능 기록과 과거 표시 문자열이 계속 쌓이는 것을 막으며, 실행한 셸이나 개발 서버의 NODE_ENV는 변경하지 않습니다. Ink 패치는 patches/ink@6.8.0.patch에 있으며, pnpm check에서 반복 렌더링과 캐시 메모리 증가를 검사합니다.