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.json

stdout 경로는 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 인증 복구
  • 화면 없는 서버에서 localhost redirect가 안 열릴 때 → 연결 거부 페이지라도 주소창에 code=가 이미 들어 있습니다. 그 코드를 직접 토큰 엔드포인트와 교환하면 됩니다(5분 유효·1회용). → GWS CLI 수동 OAuth

8. 함께 읽기