GWS CLI — 구글 워크스페이스를 명령 한 줄로
구글 드라이브·지메일·캘린더·시트·독스를 브라우저 없이 터미널에서 다루는 공식 CLI입니다. 사람이 클릭으로 하던 일을 AI가 끝까지 대신할 수 있게 만드는 통로이기도 합니다.
1. 무엇인가
gws는 구글이 내놓은 워크스페이스 명령줄 도구입니다(@googleworkspace/cli). 구글 REST API를 그대로 감싸면서, 인증 토큰은 키링에 넣어두고 자동으로 갱신합니다. 한 번 로그인해두면 그 뒤로는 토큰 걱정 없이 명령만 쓰면 됩니다.
브라우저 자동화와 결정적으로 다릅니다. 화면을 따라 하지 않고 API를 직접 부르니 화면 바뀜에 안 깨지고, 결과가 JSON으로 나와 다음 명령에 그대로 물릴 수 있습니다.
2. 명령 구조
gws <서비스> <리소스> [하위리소스] <메서드> [플래그]
gws drive files list --params '{"pageSize":10}'
gws gmail users messages list --params '{"userId":"me","q":"is:unread"}'
gws calendar events list --params '{"calendarId":"primary"}'파라미터를 모를 때는 스키마를 물으면 됩니다. 추측하지 말고 이걸 쓰십시오.
gws schema drive.files.list
gws schema calendar.freebusy.query --resolve-refs
gws drive # 리소스 목록
gws drive files --help # 메서드 목록3. 두 층이 있다 — 원시 API와 +헬퍼
처음 쓸 때 가장 많이 놓치는 대목입니다. 서비스마다 +로 시작하는 헬퍼 명령이 따로 있고, 이쪽이 훨씬 짧습니다.
| 서비스 | 헬퍼 |
|---|---|
gmail | +send +triage +read +reply +reply-all +forward +watch |
calendar | +agenda +insert |
drive | +upload |
sheets | +read +append |
docs | +write |
workflow | +standup-report +meeting-prep +email-to-task +weekly-digest +file-announce |
메일 한 통 보내는 일을 비교하면 차이가 분명합니다. 원시 API는 RFC 5322 본문을 조립해 base64url로 인코딩해서 raw에 넣어야 하는데, 헬퍼는 이렇게 끝납니다.
gws gmail +send --to [email protected] --subject '제목' --body '본문'헬퍼가 MIME 인코딩·base64·한글 제목 처리를 다 합니다. 반대로 세밀한 제어(라벨 조작, 배치 업데이트, 서식 있는 문서 편집)는 원시 API 쪽에만 있습니다. 쉬운 일은 헬퍼로, 세밀한 일은 원시 API로 갈라 쓰면 됩니다.
4. 서비스 19종
| 쓰는 것 | 서비스 이름 |
|---|---|
| 자주 | drive gmail calendar sheets docs slides tasks |
| 때때로 | forms people chat keep meet script |
| 조직 관리 | admin-reports(감사 로그·사용량) classroom events |
| 묶음 | workflow(여러 서비스를 엮은 업무 단위) |
| 안전 | modelarmor(응답 내용 필터) |
5. 알아야 할 플래그
| 플래그 | 쓰임 |
|---|---|
--params '<JSON>' | URL·쿼리 파라미터. --q 같은 개별 옵션은 없습니다 |
--json '<JSON>' | 요청 본문(POST·PATCH·PUT) |
--upload <파일> / --upload-content-type <MIME> | 파일 올리기 |
--output <경로> | 바이너리 응답 저장 |
--format json|table|yaml|csv | 기본은 json. 사람이 볼 땐 table |
--page-all / --page-limit N | 자동 페이지 넘김(페이지당 JSON 한 줄, NDJSON) |
--dry-run | 보내지 않고 조립된 요청만 보여줍니다 |
--sanitize <템플릿> | Model Armor로 응답을 걸러 받습니다(cloud-platform 스코프 필요) |
출력에 대해 하나. Using keyring backend: keyring 안내는 stderr로 나갑니다. stdout은 순수 JSON이라 | python3 -c 나 | jq에 그대로 물려도 됩니다.
6. 함정 — 실측된 것만
① --output과 --upload는 현재 디렉토리 밖을 거부한다
error[validation]: --upload '/tmp/x.txt' resolves to '/tmp/x.txt'
which is outside the current directory
보안 정책입니다. 파일이 있는 디렉토리에서 실행하고 파일명만 넘기십시오. 코드에서는 cwd=파일의 부모 + 파일명만. 손으로 테스트할 때는 이미 cd를 한 상태라 통과하고, 코드로 옮기면 터지는 전형적인 함정입니다.
예외가 하나 있습니다. gws drive +upload 헬퍼는 절대경로를 받습니다(2026-10-09 실측 — 같은 파일이 files create --upload로는 거부되고 +upload로는 업로드까지 성공). 올리기만 할 거면 헬퍼 쪽이 편합니다.
② --output은 내용이 JSON인 파일을 0바이트로 떨군다
--output 경로는 응답 본문을 “API 응답”으로 먹습니다. 받는 내용이 JSON이면 CLI가 그걸 자기 응답으로 파싱해버리고 파일에는 아무것도 안 씁니다. 종료 코드는 0입니다. 재시도해도 계속 0바이트입니다.
# 바이너리(pdf·xlsx·이미지·safetensors) → --output
gws drive files get --params '{"fileId":"<ID>","alt":"media"}' --output ./file.pdf
# JSON·텍스트 → stdout 리다이렉트
gws drive files get --params '{"fileId":"<ID>","alt":"media"}' 2>/dev/null > out.jsonstdout 경로는 JSON을 재포맷합니다(비ASCII를 \uXXXX로 escape). 값은 같지만 md5가 원본과 안 맞습니다. 그러니 받은 뒤 판정은 ①비JSON은 md5 ②JSON은 파싱·로드 성공으로 가르십시오.
③ files download 서브커맨드는 500으로 죽는다
gws drive files download ... → {"error":{"code":500,"reason":"backendError"}}
일시적 장애가 아니라 이 서브커맨드 자체가 실패합니다. 다운로드 표준은 files get + alt:media 입니다.
④ 엉뚱한 download.html 0바이트 파일이 생긴다
삭제 같은 일부 응답을 처리하면서 현재 디렉토리에 download.html(0바이트)을 떨구는 자리가 있습니다(2026-10-09 실측). 작업 뒤 쓰레기 파일이 생겼는지 한 번 보십시오.
⑤ 받은 뒤 크기를 대조하라
종료 코드 0이 받았다는 증거가 아닙니다. 드라이브 메타데이터의 size와 로컬 파일 크기를 맞춰보는 걸 마지막 단계로 넣으십시오.
7. 인증
| 자리 | 파일 |
|---|---|
| 자격증명 | ~/.config/gws/credentials.json |
| OAuth 클라이언트 | ~/.config/gws/client_secret.json |
| 토큰 저장 | 시스템 키링(기본). GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file로 파일 저장도 가능 |
gws auth status # 계정·클라이언트·활성 API 확인
gws auth login --services drive,gmail,calendar🔴 OAuth 동의화면을 “프로덕션”으로 게시해야 한다
이걸 안 하면 refresh token이 발급 7일 만에 강제 만료됩니다. 구글 정책상 External + Testing 상태 앱의 토큰 수명이 7일입니다. 일주일마다 재로그인하는 삽질의 원인이 거의 이것입니다.
진단: 자격증명 백업 파일 날짜가 정확히 7일 간격으로 늘어서 있으면 확정입니다. invalid_grant: Token has been expired or revoked가 그 신호입니다(access token만 죽었으면 invalid_token).
해결: 클라우드 콘솔 OAuth “대상” → 게시 상태 → 앱 게시. 개인 지메일 계정이면 “내부”는 선택할 수 없고 프로덕션 게시가 유일한 길입니다. 민감 스코프라도 본인만 쓰면 구글 검증(verification)은 필요 없습니다. 게시 뒤에도 로그인할 때 “확인되지 않은 앱” 경고는 계속 뜨지만 토큰은 안 죽습니다.
인증이 깨졌을 때
Bad authorized user secret: missing field 'type'→credentials.json에"type": "authorized_user"한 필드만 넣으면 복구됩니다. 재인증 불필요. → GWS CLI 인증 복구- 화면 없는 서버에서
localhostredirect가 안 열릴 때 → 연결 거부 페이지라도 주소창에code=가 이미 들어 있습니다. 그 코드를 직접 토큰 엔드포인트와 교환하면 됩니다(5분 유효·1회용). → GWS CLI 수동 OAuth
8. 함께 읽기
- 구글 업무를 AI와 함께 하는 법 — 무엇을 AI에게 맡기고 무엇을 사람이 쥐는가
- GWS CLI 구글드라이브 · GWS CLI 지메일 · GWS CLI 캘린더 — 서비스별 레시피
- GWS CLI 인증 복구 · GWS CLI 수동 OAuth