원문 캡처 · github.com
원문 캡처 · github.com

pdt는 PowerShell·Python 스크립트를 예약 작업으로 만들어 AWS, Azure, Google Cloud 또는 Windows 작업 스케줄러에 배포하는 오픈소스 도구다. 작업 파일과 실행 환경은 사용자의 저장소와 클라우드 계정에 두며, 별도의 pdt 서버는 필요하지 않다. 제작사 AutoIDM은 배포 계획과 월간 비용 추정치를 변경 전에 보여주고, 로그·상태 점검·대시보드·알림 기능도 제공한다고 설명한다.

저장소와 설치 안내는 pdt GitHub 저장소, 설치 문서에서 확인할 수 있다. Windows에서는 설치 파일을 내려받아 실행한다. macOS와 Linux에서는 uv를 설치한 뒤 pdt를 설치한다.

curl -LsSf https://astral.sh/uv/install.sh | sh source $HOME/.local/bin/env uv tool install pdt-cli uv tool update-shell

프로젝트와 첫 작업 만들기

pdt init my-jobs는 pdt.yml이 들어갈 프로젝트 폴더를 만들고 사용할 클라우드와 리전을 묻는다. 폴더 이름을 생략하면 현재 폴더를 사용하고, pdt init DIR은 지정한 폴더를 만든다. --yes를 붙이면 기본값으로 진행한다. 리전은 컴퓨터의 시간대를 바탕으로 제안하며, 첫 배포 때 설정되지 않았다면 다시 제안한 뒤 확인하거나 다른 리전을 입력하게 한다. --yes를 쓰면 제안된 리전을 선택하고 그 이름을 알린다. 프로젝트의 계정·구독·Google Cloud 프로젝트는 첫 배포 때 인증 정보에서 가져오며, 선택한 설정은 pdt.yml에 기록된다.

빈 폴더에서 초기화하면 hello-world 앱도 만들어진다. cd my-jobs 뒤 pdt run hello-world로 실행할 수 있으며, 이 앱은 로그 한 줄만 출력한다. hello-world/run.py를 고쳐 실제 작업을 넣거나 폴더를 지우고 포함된 예제에서 시작할 수 있다. 기존 파일이 있는 폴더에는 시작 앱을 추가하지 않는다.

pdt examples로 예제 목록을 보고, pdt new my-report --from impossible-travel-report처럼 예제를 프로젝트에 복사한다. my-report/env.template에서 필요한 환경 변수 이름을 .env에 옮긴 다음 pdt validate와 pdt run my-report로 설정을 검사하고 로컬 실행을 시험한다. 프로젝트에는 공통 설정 파일 pdt.yml, 커밋하지 않을 비밀값을 담는 .env, 그리고 작업별 폴더가 들어간다. Python 앱은 run.py, PowerShell 앱은 .ps1 스크립트로 구성한다. 각 명령은 현재 폴더에서 상위 폴더 방향으로 프로젝트를 찾는다.

배포와 실행 관리

pdt deploy my-report는 설정된 플랫폼에 앱을 배포한다. 배포 전에 만들 리소스 계획과 월간 비용 추정치를 보여주며, --yes를 붙이면 확인 질문을 건너뛴다. 비용은 컴퓨터의 지역 설정 통화로 표시하고 이를 읽을 수 없으면 미국 달러를 쓴다. 클라우드가 해당 통화 가격을 제공하면 그 값을 사용한다. 그렇지 않으면 클라우드 자체 환율 또는 유럽중앙은행의 일일 기준 환율로 환산하고 적용한 환율을 표시한다.

배포가 끝나면 pdt는 지금 작업을 실행하고 로그를 보여줄지 묻는다. 실행에는 실제 사람에게 이메일을 보내는 것 같은 부작용이 있을 수 있어 기본 응답은 아니오다. 동의하면 배포된 환경에서 한 번 실행하고 로그를 따라가며, 성공하면 종료 코드 0, 실패하면 1로 끝난다. --yes를 쓰거나 터미널이 없는 빌드 서버에서는 묻지 않고 실행도 하지 않는다. --run은 질문 없이 실행하고 로그를 따라간다. pdt deploy --all은 앱별 실행 여부를 묻지 않으며, pdt deploy --all --run은 배포를 마친 뒤 배포에 성공한 앱을 하나씩 실행하고 로그를 표시한다.

배포된 앱을 바로 실행하려면 pdt run my-report --deployed, 로그를 읽으려면 pdt logs my-report를 쓴다. pdt destroy my-report는 배포로 만든 리소스를 제거하고, pdt login my-report는 다시 로그인하거나 다른 클라우드 계정으로 전환한다. 앱을 배포된 상태로 두되 예약 실행만 멈추려면 pdt pause APP, 다시 허용하려면 pdt unpause APP를 쓴다. 두 명령은 앱의 config.yml에 pause: true 또는 pause: false를 기록하고 배포된 일정에도 적용한다. 일시 정지한 앱도 pdt run APP --deployed로 요청하면 실행할 수 있다. 준비되지 않은 앱은 config.yml에서 enabled: false로 설정한다. 목록에는 계속 나타나지만 다른 pdt 명령은 앱이 없는 것처럼 취급한다. 이 설정만으로 이미 배포된 일정은 멈추지 않으므로 먼저 일시 정지하거나 제거해야 한다.

pdt deploy는 AWS·Azure·Google Cloud 배포에서 Docker가 실행 중이면 컴퓨터에서 이미지를 만들고 클라우드 레지스트리에 올린다. Docker가 설치되지 않았거나 실행되지 않으면 Azure의 ACR Tasks, Google Cloud의 Cloud Build, AWS의 CodeBuild에서 이미지를 만든다. 클라우드 빌드에는 앱 폴더와 pdt.yml만 올리며 .env와 다른 비밀 파일은 포함하지 않는다. 어느 쪽이 빌드하는지와 이유를 배포 과정에서 알린다. 빌드 실패 시 오류를 표시하고 배포를 중단한다. docker-build: auto가 기본값이며, local은 로컬 Docker가 없을 때 중단하고 cloud는 클라우드에서만 빌드한다. 이 설정은 프로젝트의 pdt.yml 또는 앱별 config.yml에 둘 수 있으며 앱별 값이 우선한다.

모든 작업은 각 클라우드에서 컨테이너로 실행된다. 기본 Dockerfile은 run.py의 스크립트 헤더에 선언된 의존성을 uv sync --script로 설치하고 앱 폴더와 pdt.yml을 /workspace에 복사한 뒤 run.py를 실행한다. 앱 폴더에 자체 Dockerfile을 두면 시스템 패키지를 추가하거나 무거운 도구를 실행 때마다 설치하지 않도록 빌드할 수 있다. .dockerignore의 패턴은 앱 폴더 기준으로 작성하며 pdt가 빌드 컨텍스트 루트로 옮긴다. 비밀 파일은 .dockerignore와 관계없이 빌드 컨텍스트에 들어가지 않는다. 사용자 Dockerfile은 실행 종료 때 pdt: exit N 형식의 줄을 출력해야 하며, pdt는 이 줄에서 종료 코드를 읽는다. RUN --mount=type=ssh로 비공개 Git 의존성을 설치할 수 있지만 SSH 에이전트는 로컬 Docker 빌드에서만 사용할 수 있다. 이런 앱을 클라우드에서 빌드해야 하는 상황이면 pdt는 해당 단계를 알리고 중단한다. Windows 제공자는 이미지를 빌드하지 않으므로 해당 앱의 Dockerfile은 pdt validate에서 오류로 보고된다.

클라우드와 Windows 설정

플랫폼은 프로젝트의 pdt.yml에서 모든 앱에 지정하거나 앱별 config.yml에서 지정한다. 앱별 설정이 우선한다. 기본 시간대는 플랫폼 설정에 둘 수 있고 앱별 시간대가 이를 덮어쓴다. pdt는 각 클라우드에서 컴퓨팅·일정·비밀값·저장소·로그·알림에 해당하는 서비스를 선택한다. AWS는 AWS Batch(Fargate), EventBridge Scheduler, Secrets Manager, S3, CloudWatch Logs를 사용한다. Azure는 Container Apps Jobs, Key Vault, Blob Storage, Log Analytics를 사용하고, Google Cloud는 Cloud Run Jobs, Cloud Scheduler, Secret Manager, Cloud Storage, Cloud Logging을 사용한다. Windows에서는 작업 스케줄러, .env, %ProgramData%\pdt, 로컬 로그 파일을 사용한다. 각 플랫폼에서 Slack, Teams, 이메일 알림을 지원한다.

Azure 설정은 provider: azure, 리전, 선택 사항인 구독으로 구성한다. 구독이 없거나 올바르지 않으면 배포가 선택을 요청한다. 구독 안의 모든 pdt 프로젝트는 리전마다 pdt-shared 리소스 그룹에 있는 pdt-<region> 공유 Container Apps 환경을 사용한다. 환경을 사용하는 마지막 앱을 제거하면 환경도 제거하며, 환경이 더 이상 없으면 pdt-shared도 제거한다. 기존 환경을 resource-group/name 형식으로 지정하면 pdt는 그 환경을 만들거나 변경하거나 삭제하지 않는다.

AWS 설정은 리전과 계정으로 구성하며 계정은 첫 배포 때 인증 정보에서 기록한다. profile은 선택 사항이다. 설정하지 않으면 AWS_PROFILE 또는 컴퓨터의 유일한 프로필을 사용한다. 여러 프로필이 있거나 설정된 프로필을 찾지 못하면 선택을 요청하고 결과를 파일에 기록한다. Google Cloud의 project도 선택 사항이며, 값이 없거나 올바르지 않으면 배포가 프로젝트 목록을 보여주고 선택 결과를 저장한다.

Windows 제공자는 PC의 현지 시간으로 작업을 실행하며 timezone은 local만 허용한다. 작업은 SYSTEM 계정으로 실행되고, 성공은 Windows Application 로그의 pdt 원본 이벤트 1000, 실패는 이벤트 1001로 기록된다. 허용 일정은 hourly, daily, weekly, monthly, yearly, 60을 나누는 분 간격의 */N * * * *, 매일 정해진 시각, 선택한 요일의 정해진 시각, 선택한 달의 날짜별 정해진 시각이다. Windows 작업 스케줄러로 변환할 수 없는 다른 cron 형식은 거부한다. 실행은 30분 뒤 중단되며 다음 일정 시각에도 실행 중이면 새 실행을 건너뛴다.

파일, 상태, 로그와 대시보드

클라우드에서 작업이 끝나면 실행 컴퓨터와 그 컴퓨터의 파일은 사라진다. pdt는 클라우드 계정마다 pdt-data-와 짧은 코드를 조합한 버킷을 만들고, 앱마다 그 안에 별도 폴더를 둔다. PDT_STORAGE_URL은 클라우드 실행 때 해당 폴더를 가리킨다. Windows 작업은 %ProgramData%\pdt\APP\storage\를, 로컬 pdt run APP은 프로젝트 안의 .pdt/storage/APP/를 사용한다. 앱은 pdt.utils.storage로 파일을 읽고 쓸 수 있다.

store.fs()는 앱 폴더를 기준으로 한 fsspec 파일시스템을 제공하며 store.run_folder()는 실행별 runs/<time>-<id>/ 폴더를 가리킨다. 이전 실행에서 필요한 데이터베이스나 Meltano 북마크는 state에, 이후 사람이 확인할 파일은 output에 둔다. storage.sync()는 상태를 내려받아 잠근 뒤 실행 결과를 업로드한다. 블록이 정상 종료하면 상태 파일이 다른 실행에 의해 바뀌지 않았는지 확인하고 저장한 뒤 잠금을 해제한다. 오류나 실행 중단이 발생해도 output은 올리지만 state는 마지막 정상 상태 그대로 둔다. 오류가 나도 저장한 상태를 유지해도 안전한 앱은 storage.sync(push_state_on_error=True)를 사용할 수 있다. 로컬 폴더는 앱 폴더의 .pdt/runs/ 아래에 남는다. 수동으로는 store.pull("state/", folder)가 임대 정보를 돌려주며 성공 때 store.push(folder, "state/", lease), 실패 때 store.abort(lease)를 호출한다.

실행이 두 호출에 도달하지 못하면 잠금이 남을 수 있다. 같은 컴퓨터에서 실행한 프로세스가 종료됐거나 PDT_RUN_ID가 같다고 확인할 수 있으면 다음 실행이 잠금을 즉시 넘겨받는다. 그렇지 않으면 30분 기다리거나, 확인 절차가 있는 pdt storage APP unlock으로 직접 해제한다. 로컬에서 pdt storage APP ls, get, query를 쓰면 자신의 클라우드 로그인으로 파일을 읽는다. 파일 삭제는 pdt storage APP destroy만 수행하며 먼저 확인을 요청한다. 저장 기능이 필요 없는 앱은 config.yml에서 storage: false로 설정한다.

pdt gui는 로컬 컴퓨터에서 웹 서버를 시작해 브라우저로 대시보드를 연다. 첫 화면에는 앱 상태와 시간·일·주·월별 실행 이력이 나오며, 앱을 선택하면 실행 이력, 실행을 선택하면 로그와 저장 파일을 볼 수 있다. 실행에서 보관한 CSV는 브라우저의 SQL 작업 공간에서 정렬·필터·페이지 이동을 하거나 DuckDB 기반 SQL로 조회·조인할 수 있다. 각 파일에는 다운로드 링크가 있다. DuckDB는 첫 사용 때 ~/.local/share/pdt의 pdt 데이터 폴더로 내려받는다. 화면의 수치는 pdt 명령에서 가져오고 버튼도 일시 정지·재개·실행 명령을 수행한다. 조회 결과는 프로젝트의 .pdt/gui.sqlite3에 저장되며 5분보다 오래됐거나 Refresh를 누르면 다시 가져온다. 서버 안의 작업자는 2분마다 앱의 실행 목록을 갱신하고, 남아 있는 각 실행의 로그와 파일을 최신순으로 가져온다. 페이지는 이미 가져온 데이터를 보여주며 작업자가 아직 처리하지 않은 데이터를 요청할 때만 기다린다. Stats 링크에서 각 페이지와 pdt 명령의 처리 시간을 볼 수 있다. 서버는 localhost에서만 수신하고 로그인 기능은 없다. pdt gui --background는 명령이 끝난 뒤에도 서버를 유지하며 pdt gui --stop으로 종료한다.

pdt runs APP는 최근 실행 10개의 종료 코드를 보여준다. --since 3d처럼 기간을 지정할 수 있고 12h, 2w, 2026-09-20, 2026-09-20T14:00도 지원한다. --span 1d는 시작 시점 뒤 해당 기간의 실행만 남기고 --count N은 최근 N개만 남긴다. 실행 번호는 pdt가 찾을 수 있는 전체 실행 가운데 위치를 유지한다. --open은 Windows 작업 스케줄러나 클라우드 콘솔을 연다. pdt logs APP [N]은 해당 번호의 로그를 읽으며 번호가 없으면 최신 로그, --failed는 최신 실패 로그를 고른다. --id ID로 실행 ID를 지정할 수 있고 여러 ID를 반복해 지정하면 JSON 결과는 ID를 키로 한다. --since, --span, --count로 대상 실행을 제한하고 --errors는 DEBUG와 INFO 줄을 제외한다. 기본값은 마지막 20줄이며 --lines N, --head, --full로 출력 범위를 바꿀 수 있다. --follow는 실행이 끝나고 종료 줄이 기록될 때까지 시간순으로 새 줄을 표시한다. 실행 중 로그는 Azure 컨테이너 로그 스트림, Google Cloud Logging tail, AWS CloudWatch Logs Live Tail에서 실시간으로 가져온다. 로그 저장소에는 줄이 나타나기까지 수 초에서 수 분 걸릴 수 있으며, 실행 중이거나 로그가 아직 도착하지 않았으면 이를 알린다. Windows에서는 읽는 로그 파일 경로를 표시한다. pdt logs APP --list는 같은 실행 필터로 로그 목록을 보여준다.

명령과 자동화

주요 명령은 다음과 같다.

  • pdt init [DIR]: 현재 위치 또는 지정 경로에 프로젝트를 만든다.
  • pdt examples: 포함된 예제 앱을 나열한다. pdt new APP는 빈 앱을 추가하고 pdt new <app> --from <example>는 예제를 복사한다.
  • pdt list: 앱의 일정과 플랫폼을 표시한다. --names는 활성 앱 이름만, --json은 이름·일정·플랫폼·활성·일시 정지·오류를 앱별 JSON 객체로 출력한다.
  • pdt validate: 설정 파일과 필요한 환경 변수를 검사하고 문제가 있으면 종료 코드 1을 반환한다. --json은 {"ok": ..., "problems": [{"app": ..., "message": ...}]}를 출력한다. 프로젝트 전체 또는 여러 앱에 걸친 문제에서 app은 null이다.
  • pdt run APP: 로컬에서 실행한다. --deployed는 배포된 플랫폼에서 한 번 실행하고, --deployed --follow는 로그를 따라가며 성공 0·실패 1로 종료한다.
  • pdt deploy APP: 배포 후 실행 여부를 묻는다. --run은 실행과 로그 추적을 바로 시작하고 --rebuild는 앱이 바뀌지 않아도 이미지를 다시 빌드해 올린다. pdt deploy --all은 활성 앱을 순서대로 배포하며 계획을 거부한 앱은 건너뛴다. 실패한 앱을 비활성화하고 건너뛸지 묻는다. 무인 실행에는 --yes --skip-failures를 쓴다.
  • pdt destroy APP는 배포 리소스를 제거한다. pdt destroy --all은 앱별 계획과 확인을 거쳐 순서대로 제거하며, 무인 실행은 --yes --skip-failures를 사용한다.
  • pdt pause APP, pdt unpause APP는 예약 실행을 멈추거나 다시 허용한다. pdt secrets APP는 로컬 .env와 배포된 값의 차이를 보여준다. pdt secrets APP save는 로컬 값을 배포 앱으로 보내 다음 실행부터 쓰게 하고, get은 배포 값을 .env.<provider> 파일로 복사한다. set NAME은 표준 입력에서 읽은 값을 비밀값에 저장한다.
  • pdt storage APP ls|get|query|unlock|destroy는 저장 파일을 조회·가져오기·질의·잠금 해제·삭제한다. ls는 폴더별 파일 수와 크기를 표시하고 --recursive는 하위 파일을 모두 나열한다. --json은 JSON 목록, --open은 저장 폴더나 클라우드 콘솔을 연다. Windows에서는 ls와 get 경로에 Tab 완성을 지원하며 get PATH --open은 파일을 연다.
  • pdt health [APP] [--all]은 마지막 실행 성공 여부를 표시하고 실패가 있으면 종료 코드 1을 반환한다. 앱 폴더 안에서는 그 앱만 확인하며 --all은 전체 앱을 확인한다.
  • pdt gui는 대시보드를 연다. --background는 서버를 유지하고 --stop은 종료한다. --port N, --no-browser 옵션도 있다.
  • pdt aws ..., pdt az ..., pdt gcloud ...는 pdt가 설치하는 각 클라우드 CLI에 인수를 전달하고, 없으면 먼저 설치한다. 예를 들어 pdt az account list를 실행할 수 있다. pdt completion [SHELL]은 셸의 Tab 완성을 설정한다.

앱 폴더나 그 아래에서 앱 이름을 받는 명령의 APP를 생략하면 해당 앱을 사용한다. 다른 위치에서 이름을 생략하거나 잘못 입력하면 프로젝트의 앱 목록을 보여준다. 잘못 입력한 앱 이름이나 명령·옵션은 가장 가까운 항목을 제안한다. 예를 들어 pdt lgos는 pdt logs를 제안한다. pdt list, validate, runs, logs, health, storage APP ls의 --json은 표준 출력에 JSON만 기록하고 오류나 로그인 질문은 표준 오류로 보낸다. 활성 앱이 없는 프로젝트에서 runs, logs, health, storage APP ls는 []를 출력한다. 출력할 JSON이 없는 명령은 0이 아닌 종료 코드로 끝나며 표준 출력에는 아무것도 쓰지 않는다.

빌드 서버에서도 명령은 실행 환경의 변수를 읽으므로 .env 없이 CI 변수로 설정할 수 있다. 환경 변수는 같은 이름의 .env 값보다 우선한다. CI가 비밀 하나만 보관한다면 PDT_ENV_JSON에 모든 값을 JSON 객체로 넣는다. 예: PDT_ENV_JSON={"PDT_TOKEN": "abc", "PDT_SMTP_USER": "reports@example.com"}. pdt는 CI 변수와 터미널 부재를 감지해 빌드 서버에 .env를 만들지 않는다. 이메일 인증은 미리 해야 한다. 브라우저가 있는 컴퓨터에서 pdt run APP을 한 번 실행한 뒤 .env의 PDT_SMTP_OAUTH_CACHE_B64 또는 Microsoft Graph용 PDT_GRAPH_MAIL_CACHE_B64를 CI 변수로 복사한다. CI에서 deploy와 destroy는 변경 전에 확인하므로 --yes를 추가한다. 빌드 서버의 deploy는 자동 실행하지 않으며 실행하려면 --run을 쓴다.

예제 앱과 앱 작성

예제는 pdt examples로 확인하고 pdt new <name> --from <example>으로 복사한다. hello-world는 로그에 “Hello world.”를 기록하는 작은 앱이다. azure-license-waste는 사용하지 않는 Microsoft 365 라이선스, 즉 비활성 계정이나 90일 동안 또는 한 번도 로그인하지 않은 계정의 유료 라이선스, 미할당 좌석을 찾는다. LicenseWaste-<date>.xlsx를 저장하고 월간·연간 비용 요약과 항목별 시트를 만든다. 가격은 앱의 license-prices.csv에서 가져오며, 테넌트 가격에 맞게 수정할 수 있다. 가격표에 없는 라이선스는 Unpriced Licenses 시트에 나온다. Graph 권한과 환경 변수는 config.yml, env.template에 명시된다.

impossible-travel-report는 최근 Entra ID 로그인을 살펴보고 짧은 시간에 멀리 떨어진 장소에서 로그인한 사람이 있으면 이메일을 보낸다. 조회 기간, 최소 거리, 수신 이메일은 config.yml에서 설정한다. intune-export는 IntuneManagement 버전 4의 일괄 내보내기로 테넌트 설정을 매주 내보내고 IntuneExport-<date>.zip으로 보관한다. 가져오려면 압축을 풀고 회사 폴더를 IntuneManagement의 가져오기에 지정한다. 고정된 IntuneManagement 커밋을 한 번 내려받아 SHA-256을 검사한다. 내보낼 객체와 도구 설정은 앱 설정에 있으며, Microsoft Graph가 객체 목록 조회를 거부하면 권한 누락을 조용히 숨기지 않고 실행을 실패시킨다. 압축 파일에 스크립트와 민감한 설정이 들어갈 수 있으므로 데이터 저장소를 민감 정보로 취급해야 한다. monday-orphaned-account-report는 활성 Monday 사용자의 이메일이 활성 Entra ID 사용자의 userPrincipalName과 일치하지 않는 경우를 보고한다. powershell-hello-world는 hello.ps1로 인사말을 기록하며 Quick Start에서 사용한다. powershell-report는 Export-Csv로 작은 CSV 보고서를 만들고 저장소에 보관한다.

Python 앱은 run.py에 main() 함수를 두고 스크립트 헤더에서 의존성을 선언한다. 앱은 pdt.config의 check_env, load_env, merged_app, pdt.utils.log 등을 사용할 수 있다. 앱이 고정하는 pdt-cli[apps] 버전은 로컬과 배포 환경에서 실행할 버전이며 컴퓨터의 pdt 업그레이드만으로 바뀌지 않는다. 앱의 버전을 올린 뒤에는 다시 배포해야 한다.

PowerShell 앱은 run.py가 없는 .ps1 파일 폴더다. pdt는 스크립트에서 필요한 모듈을 찾아 pwsh로 파일 이름 순서대로 실행한다. 로컬 실행 전 필요한 모듈을 pdt 전용 폴더에 설치하므로 다른 PowerShell 세션은 해당 폴더를 사용하지 않는다. run.ps1이 있으면 그것만 진입 스크립트로 쓰며, 없으면 config.yml의 run_scripts로 파일과 순서를 지정한다. 둘을 함께 쓰지 않는다. 기본 동작은 첫 실패에서 멈추는 것이며 continue_on_error: true를 설정하면 모든 진입 스크립트를 실행하되 앱 실행은 첫 스크립트의 종료 코드로 실패 처리한다. pdt new <app> --from-scripts는 고정 버전 모듈 목록인 requirements.psd1과 스크립트를 실행하는 run.py를 만든다. 파일이 생긴 뒤에는 목록에 명시된 모듈만 설치하며 빠진 모듈은 pdt validate가 경고한다.

PowerShell 스크립트는 $env:PDT_CONFIG_JSON | ConvertFrom-Json에서 앱 설정을 읽는다. $env:PDT_OUTPUT_DIR에 쓴 파일은 데이터 저장소에 보관한다. 다음 실행에 필요한 파일은 $env:PDT_STATE_DIR에 둔다. 성공하면 상태를 저장하고 실패하면 마지막 정상 실행의 상태를 유지한다. 같은 앱이 상태를 잠근 동안 다른 실행은 오류로 멈추며, 종료된 실행이 남긴 잠금은 pdt storage <app> unlock으로 해제할 수 있다.

이메일·로그 유틸리티

pdt.utils.send_email은 일반 텍스트 이메일을 전송한다. SMTP 비밀번호 인증과 Google·Microsoft SMTP OAuth, Microsoft Graph 위임형 Mail.Send, Amazon SES, Resend를 지원한다. SMTP에는 PDT_SMTP_HOST, PDT_SMTP_USER가 필요하다. 비밀번호 인증에는 PDT_SMTP_PASSWORD, OAuth에는 PDT_SMTP_OAUTH_CLIENT_ID가 필요하다. PDT_SMTP_AUTH=oauth2는 선택 사항이며 기본 포트는 587(STARTTLS), 465는 암시적 TLS다. Google OAuth에는 PDT_SMTP_OAUTH_CLIENT_SECRET도 필요하다. Microsoft의 PDT_SMTP_OAUTH_TENANT_ID는 선택 사항이며 기본값은 common이다. 비밀번호가 없고 호스트가 smtp.gmail.com, smtp.office365.com, smtp-mail.outlook.com이면 pdt가 OAuth로 판단한다. 인증이 취소되면 터미널에서 앱을 실행하거나 배포해 다시 인증한다.

Google SMTP OAuth는 Google Auth Platform에서 Desktop app OAuth 클라이언트를 만들고 ID와 비밀값을 설정한다. Google이 SMTP OAuth에 https://mail.google.com/ 범위를 요구하므로 동의 화면에 추가해야 한다. Microsoft SMTP OAuth는 Microsoft Entra 관리 센터에서 앱 등록을 만들고 http://localhost 리디렉션 URI를 갖는 Mobile and desktop application 플랫폼, 위임형 Office 365 Exchange Online 권한 https://outlook.office.com/SMTP.Send, 공개 클라이언트 흐름을 설정한다. 발신 사서함에서 Authenticated SMTP도 켜야 한다.

Microsoft Graph 메일에는 PDT_GRAPH_MAIL_USER, PDT_GRAPH_MAIL_CLIENT_ID가 필요하며 PDT_GRAPH_MAIL_TENANT_ID는 선택 사항이고 기본값은 common이다. 발신자는 PDT_GRAPH_MAIL_USER이므로 config.yml의 email_from과 일치해야 한다. Graph는 HTML 부분만 보내고 다른 전송 방식은 HTML과 텍스트 부분을 모두 보낸다. Entra 앱 등록에는 http://localhost 리디렉션 URI, 위임형 Microsoft Graph Mail.Send, 공개 클라이언트 흐름을 설정하되 클라이언트 비밀값은 추가하지 않는다. SES는 PDT_SES_REGION이 필요하다. PDT_SES_ACCESS_KEY_ID, PDT_SES_SECRET_ACCESS_KEY는 선택 사항이며 비워 두면 AWS_PROFILE, 환경 변수, 워크로드 역할 등의 표준 AWS 인증 체인을 사용한다. Resend에는 PDT_RESEND_API_KEY가 필요하다.

pdt.utils.log는 이벤트마다 한 줄을 기록한다. 수준은 debug, info, warning, error다. 로컬에서는 사람이 읽을 수 있는 텍스트로 표준 출력에 기록하고, LOG_FORMAT=json을 설정하면 줄마다 JSON 객체를 출력한다. Cloud Run에서는 JSON 형식이 기본값이다.

프로젝트에서 pdt 명령과 코드는 uv tool install이 만든 도구 환경에, 예제 앱은 pdt 패키지 안에 있으며 pdt new가 복사한다. 앱·pdt.yml·.env는 버전 관리하는 프로젝트 폴더에 둔다. Google Cloud CLI는 ~/.local/share/pdt/gcloud 또는 %LOCALAPPDATA%\pdt\gcloud, PowerShell 7은 ~/.local/share/pdt/pwsh 또는 %ProgramData%\pdt\pwsh, 대시보드 DuckDB 파일은 ~/.local/share/pdt/duckdb-wasm 또는 %LOCALAPPDATA%\pdt\duckdb-wasm에 저장된다. 대시보드가 가져온 데이터는 프로젝트의 .pdt/gui.sqlite3에, Windows 작업의 로그와 파일은 %ProgramData%\pdt\<app>\logs와 %ProgramData%\pdt\<app>\storage에 둔다. 클라우드 로그인 정보는 통상적인 ~/.aws, ~/.azure, ~/.config/gcloud 경로를 사용한다. PDT_PROJECT를 설정하면 상위 폴더를 검색하는 대신 프로젝트 경로를 직접 지정할 수 있다.

문제·질문·아이디어는 GitHub 이슈로 제보할 수 있다.