From 45e9aa1e0361c178371539ef78803779f4806544 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 9 Apr 2026 23:00:05 +0000 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20=EC=8A=A4=EB=AA=A8=ED=81=AC=C2=B7?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=AC=B8=EC=84=9C,=20README=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98=20=ED=9D=90=EB=A6=84,=20SPEC=C2=B7PLANS=C2=B7STATUS,?= =?UTF-8?q?=20macOS=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/smoke-test.md, docs/uninstall.md; README Raycast·릴리스 분기 - SPEC 전원 프리셋·LaunchDaemon; PLANS/STATUS 동기화(AppIcon 보류) - build-macos.yml: 서명 없이 xcodebuild + zip 아티팩트 project: kkamppak agent: cursor-background role: worker artifacts: LOG-20260409-001 Co-authored-by: devuterian --- .github/workflows/build-macos.yml | 32 ++++++++++++++++++ PLANS.md | 27 +++++++++++---- README.en.md | 17 +++++++--- README.md | 17 +++++++--- SPEC.md | 11 ++++++- STATUS.md | 18 +++++----- docs/build.md | 4 ++- docs/smoke-test.md | 33 +++++++++++++++++++ docs/uninstall.md | 33 +++++++++++++++++++ .../LOG-20260409-001-repo-remodel.md | 7 ++++ 10 files changed, 174 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/build-macos.yml create mode 100644 docs/smoke-test.md create mode 100644 docs/uninstall.md diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml new file mode 100644 index 0000000..d8fc3a0 --- /dev/null +++ b/.github/workflows/build-macos.yml @@ -0,0 +1,32 @@ +# Compiles the macOS app on push/PR (no signing). Artifact: kkamppak.app.zip + +name: Build (macOS) + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: macos-14 + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Build kkamppak.app (unsigned) + run: | + set -euo pipefail + PROJ="apps/kkamppak/kkamppak.xcodeproj" + xcodebuild -project "$PROJ" -scheme kkamppak -configuration Release -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO build + APP=$(find ~/Library/Developer/Xcode/DerivedData -path '*/Build/Products/Release/kkamppak.app' -print -quit) + test -n "$APP" -a -d "$APP" + echo "BUILT_APP=$APP" >> "$GITHUB_ENV" + (cd "$(dirname "$APP")" && zip -r "$GITHUB_WORKSPACE/kkamppak.app.zip" kkamppak.app) + + - name: Upload kkamppak.app.zip + uses: actions/upload-artifact@v4 + with: + name: kkamppak-app-release-unsigned + path: kkamppak.app.zip diff --git a/PLANS.md b/PLANS.md index 9d429cb..79b6dbb 100644 --- a/PLANS.md +++ b/PLANS.md @@ -8,19 +8,32 @@ ### Release artifacts -- **Outcome:** GitHub Releases에 `.app` zip 또는 빌드 산출물 업로드 흐름 문서화 -- **Why this is accepted:** README의 “Releases에서 받기”와 실제 배포를 맞추기 위함 -- **Preconditions:** 서명·아카이브 방침 결정 +- **Outcome:** GitHub Releases에 `.app` zip 업로드 (또는 CI 아티팩트를 릴리스에 붙이기). README **방법 B**와 맞출 것. +- **Why this is accepted:** 사용자가 소스 빌드 없이 받을 수 있게. +- **Preconditions:** 태그·릴리스 노트; (선택) Developer ID 서명 시 GitHub secret 연동. - **Related ids:** — +### 문서·검증 + +- **Outcome:** 스모크 테스트·제거 절차·Raycast 예시가 README·`docs/`에 정리됨. +- **Status:** 진행 중 (`docs/smoke-test.md`, `docs/uninstall.md`). + +### CI 빌드 + +- **Outcome:** PR·`main` 푸시 시 macOS runner에서 **서명 없이** `xcodebuild`로 컴파일 검증, `kkamppak.app.zip` 아티팩트. +- **Workflow:** `.github/workflows/build-macos.yml` + ## Sequencing ### Near term -- **Initiative:** AppIcon 에셋 채우기 -- **Why now:** Dock·응용 프로그램 폴더에서 식별성 향상 +- **Initiative:** 첫 GitHub Release 게시 (unsigned zip이라도) + README와 동기화 +- **Initiative:** (선택) Release 워크플로로 태그 시 자동 zip 첨부 ### Mid term -- **Initiative:** 선택적 GitHub Actions로 Xcode 빌드 (macOS runner) -- **Why later:** 서명·시크릿 정리 후 +- **Initiative:** Developer ID 서명 + (필요 시) 공증은 제품 정책 바뀔 때만 검토 — 현재 SPEC은 Notarization 비목표 + +### 보류 (운영자 전용) + +- **Initiative:** AppIcon 에셋 채우기 — Dock 식별용; 별도 요청 시 진행 diff --git a/README.en.md b/README.en.md index 3937b93..3cbcd9a 100644 --- a/README.en.md +++ b/README.en.md @@ -12,23 +12,29 @@ Repo operating model: [REPO.md](REPO.md). Product summary: [SPEC.md](SPEC.md). ## Install -1. Grab the latest build from **[Releases](https://github.com/devuterian/kkamppak/releases)** (or **build from source** below). -2. Copy **kkamppak.app** into **Applications**. +**Option A — build from source (always available)** + +1. Build `kkamppak.app` in Xcode as described in [`docs/build.md`](docs/build.md). +2. Copy it into **Applications** and run it. 3. On **first launch**, enter your admin password once (installs helper script, LaunchDaemon, and a narrow `sudoers` rule). 4. After that, pick a mode without typing your password again. +**Option B — release binary (when published)** + +If **[Releases](https://github.com/devuterian/kkamppak/releases)** has a zip or other artifact, download it and install the same way. If nothing is there yet, use **Option A**. + **If macOS blocks the app:** unsigned or ad-hoc builds may need **Right-click → Open** once, or approval under **System Settings → Privacy & Security**. This project assumes **no Notarization** is required by design. --- ## Usage -1. Run the app from Dock, Raycast, or a script. +1. Run the app from Dock, Raycast, or a script. (Raycast **script command** example: `open -a kkamppak`) 2. The alert shows the **current mode** (from `pmset -g custom`). 3. Choose **기본 설정으로** (default) or **Remote Ready로**. The button matching the current mode is disabled. 4. A **result** alert appears; **OK** quits the app. -**Remote Ready** sets `sleep` / `displaysleep` / `disksleep` to **0**. **Default** uses a **1 / 10 / 10** (minutes) preset. If AC and Battery blocks differ, the UI shows **custom**. +**Remote Ready** sets `sleep` / `displaysleep` / `disksleep` to **0**. **Default** uses the app’s **1 / 10 / 10** (minutes) preset (this may not match every Mac’s factory defaults). If AC and Battery blocks differ, the UI shows **custom**. --- @@ -36,12 +42,15 @@ Repo operating model: [REPO.md](REPO.md). Product summary: [SPEC.md](SPEC.md). - Does not **guarantee** battery life, thermals, or security posture. Remote Ready trades power savings for staying awake. - The passwordless path is limited to the **installed apply script**; if that path changes, you may need to re-run the installer flow. +- The installed **LaunchDaemon** (`com.kkamppak.helper`) is a placeholder (`RunAtLoad` false, no real job). Removal steps: [`docs/uninstall.md`](docs/uninstall.md). --- ## Source & contributing - Build: [`docs/build.md`](docs/build.md) +- Smoke test: [`docs/smoke-test.md`](docs/smoke-test.md) +- Uninstall (helper & sudoers): [`docs/uninstall.md`](docs/uninstall.md) - Commit hooks & trailers: [`CONTRIBUTING.md`](CONTRIBUTING.md) - Behavior summary: [`SPEC.md`](SPEC.md) diff --git a/README.md b/README.md index b4c012c..802c3e2 100644 --- a/README.md +++ b/README.md @@ -12,23 +12,29 @@ ## 설치하기 -1. **[Releases에서 최신 아티팩트](https://github.com/devuterian/kkamppak/releases)** 를 받아요. (또는 아래 **소스에서 빌드**) -2. **kkamppak.app**을 **응용 프로그램** 폴더에 넣어요. +**방법 A — 소스에서 빌드 (항상 가능)** + +1. [`docs/build.md`](docs/build.md)대로 Xcode로 `kkamppak.app`을 만듭니다. +2. **응용 프로그램** 폴더에 넣고 실행합니다. 3. **첫 실행**에서 관리자 암호를 한 번 입력해요. (도구 스크립트·LaunchDaemon·`sudoers` 한 줄이 깔려요.) 4. 그다음부터는 암호 없이 모드만 고르면 돼요. +**방법 B — 릴리즈 바이너리 (있을 때)** + +[Releases](https://github.com/devuterian/kkamppak/releases)에 올라온 **zip 등**이 있으면 받아서 같은 방식으로 Applications에 넣고 실행하면 됩니다. 아직 없으면 **방법 A**를 쓰면 돼요. + **보안 경고가 뜨면:** 직접 빌드하거나 서명이 없으면 **우클릭 → 열기**로 한 번 실행하거나, **시스템 설정 → 개인 정보 보호 및 보안**에서 허용해 주면 돼요. 이 프로젝트는 기획상 **공증(Notarization) 없이** 배포해도 된다고 가정해요. --- ## 쓰는 법 -1. Dock·Raycast 등에서 앱을 실행해요. +1. Dock·Raycast 등에서 앱을 실행해요. (Raycast **스크립트 커맨드** 예: `open -a kkamppak`) 2. **현재 모드**가 안내문에 나와요. (`pmset -g custom` 기준) 3. **기본 설정으로** / **Remote Ready로** 중 하나를 골라요. 지금 모드와 같은 버튼은 비활성화돼요. 4. 적용이 끝나면 **결과 알림**이 뜨고, **OK** 누르면 앱이 꺼져요. -**Remote Ready**는 `sleep` / `displaysleep` / `disksleep`을 0으로 맞춰요. **기본**은 1 / 10 / 10(분) 프리셋이에요. AC와 배터리 설정이 서로 다르면 **사용자 지정**으로 보여요. +**Remote Ready**는 `sleep` / `displaysleep` / `disksleep`을 0으로 맞춰요. **기본**은 앱이 정한 **1 / 10 / 10(분)** 프리셋이에요(맥 기본값과 항상 같지는 않을 수 있어요). AC와 배터리 설정이 서로 다르면 **사용자 지정**으로 보여요. --- @@ -36,12 +42,15 @@ - **배터리 수명·발열·보안**까지 대신 판단해 주진 않아요. Remote Ready는 전원을 덜 아끼는 쪽이에요. - `sudoers`에 **적용 스크립트 한 줄**만 열어 두므로, 그 파일이 바뀌면 다시 설치 흐름이 필요할 수 있어요. +- 설치 시 등록되는 **LaunchDaemon**(`com.kkamppak.helper`)은 기획상 자리만 잡아 두는 형태예요(`RunAtLoad` false, 실제 작업 없음). 지우는 법은 [`docs/uninstall.md`](docs/uninstall.md). --- ## 소스 코드·기여 - 빌드: [`docs/build.md`](docs/build.md) +- 스모크 테스트: [`docs/smoke-test.md`](docs/smoke-test.md) +- 제거(헬퍼·sudoers): [`docs/uninstall.md`](docs/uninstall.md) - 커밋 메시지·훅: [`CONTRIBUTING.md`](CONTRIBUTING.md) - 동작 요약: [`SPEC.md`](SPEC.md) diff --git a/SPEC.md b/SPEC.md index 63870cc..805bb67 100644 --- a/SPEC.md +++ b/SPEC.md @@ -5,7 +5,7 @@ - **Name:** kkamppak - **Canonical repo:** https://github.com/devuterian/kkamppak - **Project id:** `kkamppak` -- **Last updated:** 2026-04-09 +- **Last updated:** 2026-04-10 ## Thesis @@ -17,6 +17,15 @@ macOS에서 전원 모드를 **대화상자(NSAlert)만**으로 바꾸는 작은 - `pmset -g custom` 파싱으로 현재 모드 표시 (AC·배터리 블록이 같을 때만 프리셋 라벨) - 최초 실행 시 관리자 권한으로 헬퍼 설치: 적용 스크립트, LaunchDaemon 등록, 해당 스크립트만 NOPASSWD `sudoers` +## Power presets + +- **Remote Ready:** `pmset -a`로 `sleep`, `displaysleep`, `disksleep`을 **0**(분)으로 설정. +- **기본(앱 프리셋):** 위 세 값을 **1 / 10 / 10**(분)으로 설정. Apple 기본 전원 프로파일과 동일하다고 가정하지 않음; UI·문서에서 “앱이 정한 기본”임을 구분. + +## LaunchDaemon (설치 시) + +- `com.kkamppak.helper`: 기획 요건(등록) 충족용. 현재 plist는 `RunAtLoad` false이며 실제 주기 작업 없음. 제거 절차는 `docs/uninstall.md`. + ## Invariants - UI는 AppKit `NSAlert` 중심 (별도 설정 창 없음) diff --git a/STATUS.md b/STATUS.md index 222e6f5..6e90c3b 100644 --- a/STATUS.md +++ b/STATUS.md @@ -2,29 +2,31 @@ ## Snapshot -- **Last updated:** 2026-04-09 +- **Last updated:** 2026-04-10 - **Overall posture:** `active` -- **Current focus:** macOS AppKit 앱 + repo-template 레이아웃 정리 -- **Highest-priority blocker:** 없음 (CI·로컬 훅은 커밋 trailer 정책 준수 필요) +- **Current focus:** 문서·CI 빌드로 설치 경로 정리; 릴리스는 운영자가 GitHub에서 게시하면 README 방법 B와 연결 +- **Highest-priority blocker:** 없음 (커밋은 provenance trailer 또는 명시적 migration/bootstrap 예외) - **Related decisions:** — ## Current state summary -Xcode 프로젝트는 `apps/kkamppak/kkamppak.xcodeproj`에 있으며, Swift 앱이 `pmset` 기반 스크립트로 전원 프리셋을 적용한다. 저장소 루트는 [repo-template](https://github.com/LPFchan/repo-template) 스타일의 SPEC/STATUS/PLANS/INBOX·skills·records·커밋 검사를 포함한다. +Xcode 프로젝트는 `apps/kkamppak/kkamppak.xcodeproj`에 있으며, Swift 앱이 `pmset` 기반 스크립트로 전원 프리셋을 적용한다. 저장소 루트는 SPEC/STATUS/PLANS/INBOX·skills·records·커밋 검사를 포함한다. PR·`main`에 대해 **서명 없는** macOS 빌드 워크플로가 `.github/workflows/build-macos.yml`에 있다. ## Active phases or tracks ### App delivery -- **Goal:** 직접 빌드·배포 가능한 `.app` 유지 +- **Goal:** 빌드 검증(CI) + 문서로 설치·제거·스모크 경로 명확화 - **Status:** `in progress` -- **Exit criteria:** README의 설치·사용 흐름과 코드 동작 일치 +- **Exit criteria:** README와 `docs/`가 “소스 빌드 / 릴리스(있을 때)”를 일관되게 설명 - **Related ids:** — ## Recent changes to project reality -- **2026-04-09:** 앱 소스를 `apps/kkamppak/` 아래로 이동, repo-template 스캐폴드 및 커밋 표준 도입 +- **2026-04-09:** 앱 소스 `apps/kkamppak/`, repo 운영 문서·커밋 표준 도입 +- **2026-04-10:** `docs/smoke-test.md`, `docs/uninstall.md`, README 정리, SPEC에 프리셋·LaunchDaemon 설명, macOS CI 빌드 워크플로 ## Immediate next steps -- Releases 아티팩트 또는 빌드 파이프라인은 운영자가 필요 시 추가 +- 운영자: [Releases](https://github.com/devuterian/kkamppak/releases)에 zip 첫 업로드 시 README **방법 B** 문구와 실제 링크 맞추기 +- 로컬 개발: `./scripts/install-hooks.sh`로 커밋 훅 켜 두기 (`CONTRIBUTING.md`) diff --git a/docs/build.md b/docs/build.md index 1442bd3..b1c723e 100644 --- a/docs/build.md +++ b/docs/build.md @@ -11,6 +11,8 @@ 2. 스킴 **kkamppak**을 선택합니다. 3. **Product → Build** (⌘B) 또는 **Archive**로 `.app`을 만듭니다. +로컬에서 zip으로 묶을 때는 Release 산출물 폴더에서 예를 들어 `zip -r kkamppak.app.zip kkamppak.app` 을 실행하면 됩니다. + ## 산출물 - 빌드 결과: Xcode가 지정한 DerivedData 경로의 `kkamppak.app` @@ -18,5 +20,5 @@ ## 참고 -- 아이콘: `Assets.xcassets`의 AppIcon에 실제 PNG를 넣으면 Dock에 표시됩니다. - 공증(Notarization)은 이 프로젝트 범위 밖으로 두었습니다. 배포 시 Gatekeeper 안내는 루트 [README.md](../README.md)를 참고하세요. +- 동작 확인: [`smoke-test.md`](smoke-test.md) diff --git a/docs/smoke-test.md b/docs/smoke-test.md new file mode 100644 index 0000000..b1b26fa --- /dev/null +++ b/docs/smoke-test.md @@ -0,0 +1,33 @@ +# 스모크 테스트 (kkamppak) + +실제 Mac에서 **한 번씩** 확인할 때 쓰는 짧은 체크리스트예요. + +## 준비 + +- [ ] Xcode로 `apps/kkamppak/kkamppak.xcodeproj` 빌드하거나, 배포용 `.app` 준비 +- [ ] 테스트 맥에서 로그인 세션(GUI) 사용 + +## 첫 실행·설치 + +- [ ] 앱 실행 → **설치** 선택 → 관리자 암호 입력 → 오류 없이 끝남 +- [ ] `/usr/local/lib/kkamppak/kkamppak-apply.sh` 존재 +- [ ] `/Library/LaunchDaemons/com.kkamppak.helper.plist` 존재 +- [ ] `/private/etc/sudoers.d/kkamppak` 존재 (해당 사용자·스크립트 경로만 NOPASSWD) + +## 모드 전환 + +- [ ] 알림에 **현재 모드** 표시됨 (`pmset -g custom`과 대략 일치) +- [ ] 현재 모드와 같은 버튼 비활성화 +- [ ] 다른 모드 선택 → **결과 알림** → OK 시 앱 종료 +- [ ] 터미널에서 `pmset -g custom`으로 `sleep` / `displaysleep` / `disksleep` 값이 기대와 맞는지 확인 + - Remote Ready: AC·배터리 블록 모두 해당 키가 `0` 근처 + - 기본 프리셋: 앱이 쓰는 **1 / 10 / 10(분)** ([SPEC.md](../SPEC.md) 참고) + +## 재실행 + +- [ ] 두 번째 실행부터는 설치 단계 없이 바로 모드 선택 알림만 뜸 +- [ ] `sudo` 암호 없이 적용되는지 확인 (설치된 스크립트 경로가 그대로일 때) + +## 제거 + +- [ ] [`uninstall.md`](uninstall.md) 순서대로 지운 뒤 헬퍼·sudoers가 사라졌는지 확인 diff --git a/docs/uninstall.md b/docs/uninstall.md new file mode 100644 index 0000000..ccd5efa --- /dev/null +++ b/docs/uninstall.md @@ -0,0 +1,33 @@ +# 제거하기 (kkamppak 헬퍼) + +앱을 **응용 프로그램에서 지우는 것만**으로는 시스템에 깔린 헬퍼가 남을 수 있어요. 완전히 지울 때는 아래를 **터미널**에서 실행하세요. (관리자 암호 필요) + +## 1. LaunchDaemon 내리기·plist 삭제 + +```bash +sudo launchctl bootout system /Library/LaunchDaemons/com.kkamppak.helper.plist 2>/dev/null || true +sudo rm -f /Library/LaunchDaemons/com.kkamppak.helper.plist +``` + +## 2. 설치 스크립트 디렉터리 삭제 + +```bash +sudo rm -rf /usr/local/lib/kkamppak +``` + +## 3. sudoers 항목 삭제 + +```bash +sudo rm -f /private/etc/sudoers.d/kkamppak +``` + +## 4. 앱 번들 + +**응용 프로그램**에서 `kkamppak.app`을 휴지통으로 내면 됩니다. + +## 확인 + +```bash +test ! -f /usr/local/lib/kkamppak/kkamppak-apply.sh && echo "apply script gone" +test ! -f /private/etc/sudoers.d/kkamppak && echo "sudoers drop-in gone" +``` diff --git a/records/agent-worklogs/LOG-20260409-001-repo-remodel.md b/records/agent-worklogs/LOG-20260409-001-repo-remodel.md index 6ca00df..659a098 100644 --- a/records/agent-worklogs/LOG-20260409-001-repo-remodel.md +++ b/records/agent-worklogs/LOG-20260409-001-repo-remodel.md @@ -20,3 +20,10 @@ Recorded by agent: cursor-background - 운영자 지시문 원문 보관: `docs/operator-prompt-repo-template-adoption.md` - `recreate-prompt.md` 추가, 루트 `skills/`를 업스트림 스캐폴드와 동기화, `REPO.md`·`AGENTS.md`·`CONTRIBUTING.md`에 안내 링크. - 중복이던 로컬 `scaffold/` 제거 — 복사용 원본은 [LPFchan/repo-template](https://github.com/LPFchan/repo-template/tree/main/scaffold)만 유지. + +## Follow-up 2026-04-10 + +- `docs/smoke-test.md`, `docs/uninstall.md` 추가; README·README.en 설치 흐름(소스 우선 / 릴리스 선택)·Raycast 예시·LaunchDaemon 설명. +- `SPEC.md`에 전원 프리셋·LaunchDaemon 역할 명시. +- `PLANS.md`·`STATUS.md` 갱신(AppIcon은 보류 섹션). +- `.github/workflows/build-macos.yml` — PR·main용 서명 없는 Xcode 빌드 + zip 아티팩트. From 71abdf5145e29662d42a8ca439971533810ea7c9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 9 Apr 2026 23:02:31 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20classifyPmsetBlock=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=ED=83=80=EC=9E=85=EC=9D=84=20PowerMode=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BlockMode와 PowerMode 혼용으로 CI Swift 컴파일이 실패함. project: kkamppak agent: cursor-background role: worker artifacts: LOG-20260409-001 Co-authored-by: devuterian --- apps/kkamppak/kkamppak/AppDelegate.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/kkamppak/kkamppak/AppDelegate.swift b/apps/kkamppak/kkamppak/AppDelegate.swift index 8896aaf..483d106 100644 --- a/apps/kkamppak/kkamppak/AppDelegate.swift +++ b/apps/kkamppak/kkamppak/AppDelegate.swift @@ -137,9 +137,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { return .custom } - private enum BlockMode { case `default`, remoteReady, custom } - - private func classifyPmsetBlock(_ block: String) -> BlockMode { + private func classifyPmsetBlock(_ block: String) -> PowerMode { let sleepM = parsePmsetInt(key: "sleep", in: block) let displayM = parsePmsetInt(key: "displaysleep", in: block) let diskM = parsePmsetInt(key: "disksleep", in: block)