← 一覧に戻る

awakebook

GitHub ↗ Swift 最終push: 2026/7/2 16:23

WIP(現在進行中)

Work In Progress

このプロジェクトで現在進行中の作業と、過去のスナップショットを記録する。

現在の状況

フェーズ: MVP+永続化/Customタイマー+多言語(9言語) 完成 / Codex QA ラウンド1 対応済み・再検証待ち

  • M0〜M7(MVP)+ M8 一部(設定永続化・Custom タイマー)+ 多言語対応9言語 まで実装、実アプリ手動確認済み。
  • Codex QA ループ導入済み.codex-review/AGENTS.md)。ラウンド1で F-001/F-002(両方低)を対応・push 済み → Codex の再検証待ち
  • コア機構(pmset disablesleep)はライブ検証済み。受け入れ基準 §11 を満たす。
  • 多言語: Localization.swift の手書きテーブル方式(システム言語追従、英語フォールバック)。英/日/繁/簡/韓/西/葡/仏/独。--dump-localization で網羅性確認可。
  • 全ソース: Package.swift / Sources/awakebook/{main,AppDelegate,MenuBarController,SleepController,AutoOffTimer,BatteryMonitor,PowerStateObserver,Settings,Localization}.swift / Scripts/{install,uninstall}-sudoers.sh
  • sudoers ルール /etc/sudoers.d/awakebook 配置済み(このマシン)。release バイナリ .build/release/awakebook(約196K)。

次にやること

  • Codex に再検証を依頼(F-001 訳語のネイティブ視点での自然さ、F-002 アイコンの見た目)。新 findings が来たら codex-qa-loop(「findings 処理して」)で回す。
  • M8 残(任意): .app バンドル化+Info.plist(LSUIElement=YES)+署名/notarization、ログイン項目登録(SMAppService.mainApp)、(opt) 蓋閉じ輝度0。
  • バンドルアイコン設定(.app 化とセット。F-002 の根本対応)。

既知の小課題

  • カウントダウン表記が1時間境界で "1:00"→"59:59" と意味が切り替わる(後で精緻化候補)。
  • 低電力モードのライブ発火確認は未実施(ロジック検証のみ)。
  • 用語ゆれ: メニューは「スリープさせない」、README/エラー文は「スリープ抑止」。統一は将来検討。

メモ / 注意(ハマりどころ)

  • pmset -g の値区切りはタブ。SleepDisabled は 0 のとき行が出ないこともあれば、一度設定後は \t\t0 で残ることもある → 値で判定(パーサ対応済み)。
  • macOS の /bin/bash は 3.2。シェルスクリプトで全角文字直前の $VAR${VAR} と波括弧で囲う(誤解釈する)。
  • 実機トグル検証は disablesleep を一時的に 1 にするので、必ず 0 に戻すこと。

確定事項

  • アプリ正式名称は awakebook(仕様書の lid-awake は仮称。リポジトリ名に合わせて確定)。バンドル ID / 表示名 / sudoers ファイル名はこれに揃える。

過去のWIPアーカイブ

(新しい「現在の状況」を書く前に、古いものをここに追記でアーカイブする。新しいものが上)

2026-06-30 21:50 時点のスナップショット

  • フェーズ: MVP+永続化/Customタイマー+多言語(9言語) 完成・実機確認済み / 残りは M8 配布系(任意)。
  • Codex QA はまだ未導入だった(→ 本セッションで導入し、ラウンド1対応まで完了)。

2026-06-30 20:52 時点のスナップショット

  • フェーズ: MVP(M0〜M7)完成 / 残りは M8(オプション・配布)。M8 一部(設定永続化・Custom タイマー・フロア5–50%拡張)完了。
  • 全機能実装済みだが実アプリでの手動 GUI 動作確認は未実施の段階だった(→ 本セッション末で確認完了)。

ROADMAP(計画)

ロードマップ

lid-awake ビルド仕様(~/Downloads/引き続き.md)の M0〜M8 をマイルストーンとして配置。

今週

  • M0 環境: メニューバーアプリ雛形(SwiftPM)、.accessory で Dock 非表示、空の NSStatusItem 表示 — 2026-06-30 完了
  • M1 コア: SleepController の有効化/解除/読み戻し、Keep Awake トグル + 状態表示 — 2026-06-30 完了(ライブ動作確認は M2 のルール配置後)
  • M2 権限: install-sudoers.shvisudo -cf 検証、NOPASSWD トグルをライブ確認 — 2026-06-30 完了

今月

  • M3 タイマー: オートオフ(Off/30分/1時間/2時間)+カウントダウン表示、満了で自動解除 — 2026-06-30 完了(ライブ満了確認は sudoers 配置後)。Custom… は未実装
  • M4 バッテリー: BatteryMonitor(IOKit.ps)、フロア割れで自動解除、Battery floor ▸+残量表示 — 2026-06-30 完了(フロアは10/15/20/25/30%プリセット)
  • M5 低電力モード: PowerStateObserver で連動自動解除、状態行に注記 — 2026-06-30 完了
  • M6 安全網: 終了時/SIGTERM時に必ず 0 復帰、起動時の状態同期 — 2026-06-30 完了(SIGTERM 経路をE2E検証)
  • M7 仕上げ: README(セットアップ/使い方)、受け入れ基準 §11 チェック完了 — 2026-06-30 完了(MVP 完成)

今四半期

  • M8 設定永続化: バッテリーフロア・Custom タイマー分数を UserDefaults suite に保存 — 2026-06-30 完了
  • M8 Custom タイマー: 任意分数入力、フロアを 5–50% に拡張 — 2026-06-30 完了
  • M8 (opt): 蓋閉じで内蔵ディスプレイ輝度0(Wedge 相当)、ログイン項目登録、動作ログ
  • M8 配布: .app バンドル化+Info.plist(LSUIElement)+Developer ID 署名/notarization

いつか

  • 権限を SMAppService.daemon(特権ヘルパー)方式へ移行

DECISIONS(意思決定)

意思決定記録

このプロジェクトで下した重要な意思決定を記録する。 最新が上に来る。


2026-06-30 — ビルドは SwiftPM、Dock 非表示は .accessory

  • 決定: ビルドは Xcode プロジェクトではなく SwiftPM 実行可能パッケージPackage.swift)で行う。仕様書の「Xcode が楽」案からの変更。
  • 理由: swift build で CLI から再現ビルド・検証でき、AI 駆動の開発フローに乗せやすい。.pbxproj の手書き/差分管理は壊れやすい。Package.swift は Xcode でもそのまま開ける。
  • Dock 非表示: Info.plistLSUIElement=YES の代わりに、起動時 NSApp.setActivationPolicy(.accessory) で実行時に等価の挙動を得る。
  • 言語モード: 当面 Swift 5 モード(swiftLanguageModes: [.v5])。Swift 6 の strict concurrency は後で対応。
  • 将来: 配布時は executable を .app バンドル化し、Info.plist に LSUIElement=YES を入れた上で Developer ID 署名+notarization する。

2026-06-30 — アプリ正式名称は awakebook

  • 決定: アプリ名は awakebook に確定。仕様書(引き続き.md)の lid-awake は仮称で、リポジトリ名に合わせた。
  • 影響: バンドル ID / 表示名 / sudoers ファイル名(/etc/sudoers.d/awakebook)/ Xcode プロジェクト名をこれに揃える。

2026-06-30 — コア機構は pmset disablesleep、caffeinate は不採用

  • 決定: スリープ抑止のコアは sudo pmset -a disablesleep 1/0(カーネルの SleepDisabled フラグ)に一本化する。
  • 理由: caffeinate および caffeinate 系アプリ(KeepingYouAwake 等)は macOS の power assertion を使うため、アイドルタイマーは止められても 蓋を閉じたときのハードウェア的スリープトリガーを上書きできないSleepDisabled フラグは IOPMrootDomain がスリープ拒否権として認識し lid-close を跨いで持続する(Apple Silicon でも有効)。
  • 補足: SleepDisabled は解除するか再起動するまで残る(永続化はされない=再起動でクリアされるので安全側)。

2026-06-30 — UI の唯一の真実は pmset -g の読み戻し

  • 決定: メニューバーのアイコン/状態表示は、内部フラグではなく毎回 pmset -g | grep SleepDisabled の実値を読み戻して反映する。
  • 理由: 「メニューバーが嘘をつかない」を最優先。タイマー満了・バッテリーフロア割れ・低電力モード・手動 OFF・終了 のすべての復帰経路を SleepController.disable() に集約し、状態の単一情報源をカーネルの実フラグにするため。

2026-06-30 — 権限は sudoers NOPASSWD(MVP)、将来 SMAppService へ

  • 決定: MVP では /etc/sudoers.d/lid-awake に指定2コマンド(pmset -a disablesleep 0 / 1)だけを NOPASSWD で許可する。install-sudoers.sh で配置し、必ず visudo -cf で構文検証する。
  • 理由: GUI アプリはパスワードを入力できない。sudoers はコマンド+引数を完全一致で照合するため、アプリが叩く引数と一字一句揃える。壊れた sudoers は sudo 全体を壊すので検証必須。
  • 将来: 配布を考えるなら SMAppService.daemon(macOS 13+)の特権ヘルパーへ移行。SMJobBless は非推奨なので使わない。
  • 配布制約: sudoers / pmset がサンドボックス外のため App Store 配布は不可。配るなら Developer ID 署名+notarization で直配。

DEVLOG(作業ログ)

開発日誌

このプロジェクトでの作業を時系列で記録する。 最新のエントリが上に来る。


2026-07-01 12:12 — Codex QA ループ導入 + findings 対応(F-001/F-002)

  • 多言語対応を含むレビューを Codex に依頼するため、codex-qa-loop スキルで足場を生成: .codex-review/{findings,response}.mdAGENTS.md(Codex 契約セクション+「今回の重点: 多言語対応のレビュー」=各言語で起動して翻訳・繁簡取り違え・レイアウト崩れ・言語判定・書式崩れを見る観点、-AppleLanguages / --dump-localization の使い方)。
  • Codex が2件起票(両方 低)、両方対応:
    • F-001: Custom ダイアログのタイトルが es/pt/fr/de で「Mac 本体の自動シャットダウン設定」に読める("apagado automático" 等)→ 全9言語で「スリープ抑止を続ける時間」と分かる表現に統一(Keep awake for (分) 系、keepAwake の訳語と整合)。
    • F-002: Custom ダイアログに無関係な青いフォルダ風アイコン(.app バンドル未作成でアプリアイコンが無いため)→ アラートに moon.zzz(48pt) を設定。エラーアラート(.warning)は caution アイコンが妥当なので対象外。バンドルアイコンは M8 で対応予定。
    • 検証: release ビルド・--dump-localization(新タイトル+欠落0)・常駐起動 OK。アイコンの見た目はヘッドレスで未確認(実ダイアログで要確認)。
  • 文言判断: メニューの Keep Awake 日本語は候補(スリープ防止/抑止/起こしておく)を提示の上、「スリープさせない」を維持(現状維持)。
  • 気づき: メニューは「スリープさせない」だが README/エラー文は「スリープ抑止」を使用。用語の統一は将来検討の余地あり(今回は未着手)。

2026-06-30 21:50 — 多言語対応(9言語)

  • メニュー UI 全文字列を9言語対応: English / 日本語 / 繁體中文(zh-Hant) / 简体中文(zh-Hans) / 한국어 / Español / Português / Français / Deutsch。
  • 方式: .app バンドル外でも確実に動くよう、リソースバンドルに依存しない手書きローカライズテーブル Localization.swiftenum L + LocKey + [LocKey: [Lang: String]])。Locale.preferredLanguages から解決し未対応は英語フォールバック。言語変更は再起動で反映。
  • 対象: メニュー項目 / タイマープリセット・Custom / ステータス行 / バッテリー行 / Custom ダイアログ / エラーアラート / SleepController.Failure のメッセージ。SleepDisabled(カーネルフラグ名)と数値%は非翻訳。
  • ついでの改善: tick 時のステータス行更新が hasPrefix("Status:") 前提で壊れていたため、updateStatusLine(on: true) 呼び出しに変更(pmset は叩かない)。
  • 検証: --dump-localization 診断フラグを追加。欠落 0(23キー×9言語)、全言語で書式文字列(%d/%@)の描画が正常。言語判定ロジックを18ケース(zh-Hant/Hans 各種、順序付き優先、フォールバック)でテスト全 PASS。通常起動・release ビルド確認。
  • 補足: システム設定を変えずに awakebook -AppleLanguages '(fr)' 等で任意言語をプレビュー可能(README に追記)。

2026-06-30 20:52 — セッション総括(仕様受領 → MVP+α 完成・実機動作確認)

  • このセッションで ~/Downloads/引き続き.mdlid-awake 仮称仕様)を起点に、awakebook を ゼロから M0〜M7(MVP)+ M8 一部 まで一気に実装。
  • 流れ: 仕様を .devnotes/ へ流し込み → 名称確定 → M0 雛形(SwiftPM) → M1 コア → M2 sudoers(ライブ検証) → M3 タイマー → M4 バッテリー → M5 低電力 → M6 安全網(SIGTERM E2E) → M7 README/受け入れ基準 → M8 設定永続化+Custom タイマー。
  • 途中で実バグ2件を発見・修正: ①pmset -g タブ区切りでパーサが値1を読めず → 空白/タブ両対応に修正、②install-sudoers.sh の bash3.2 全角変数バグ → ${VAR}化。
  • ユーザーが実アプリを起動し手動で全機能の動作を確認 → 正常動作(「動いたーーー」)
  • 現状: 実用上「蓋を閉じて Claude Code を走らせ続ける」が使える状態。残りは M8 の配布系(.app バンドル化+署名/notarization、ログイン項目登録)と任意の輝度0。
  • コミット履歴: c84dd5e → e331348(計10コミット、すべて push 済み)。

2026-06-30 — M8(一部): 設定の永続化 + Custom タイマー

  • 設定の永続化: SettingsUserDefaults(suiteName: "com.awakebook"))を追加。.app バンドル外の bare executable でも確実に保存されるよう専用 suite を使用。
    • バッテリーフロアを保存・起動時復元(BatteryMonitor(floorPercent: Settings.batteryFloor)selectFloor で保存)。
    • Custom タイマーの最終入力分数を保存(次回入力ダイアログの初期値)。
  • Custom タイマー: Timer submenu に Custom… を追加。NSAlertNSTextField で 1〜1440 分を入力 → keepAwake ON+カウントダウン開始。プリセットに無い値のときは Custom にチェック+残り時間をラベル表示。
  • フロア範囲拡張: 仕様の 5–50% に合わせ、プリセットを [5,10,15,20,25,30,40,50] に拡張。
  • 検証: swift build 成功・常駐起動。UserDefaults suite のラウンドトリップ単体テスト PASS。README のメニュー表を更新(Custom、フロア5–50%、再起動後保持)。
  • 残 M8: .app バンドル化+署名/notarization、ログイン項目登録、(opt) 蓋閉じ輝度0。

2026-06-30 — M7 仕上げ: README + 受け入れ基準チェック(MVP 完成)

  • README に「必要環境 / セットアップ(swift build -c releaseinstall-sudoers.sh)/ 使い方(メニュー各項目)/ 注意」を記入。README 内の旧称 lid-awake の名残も整理(正式名 awakebook)。
  • release ビルドを README 手順どおり確認(.build/release/awakebook、約140K)。起動・常駐・終了・SIGTERM 復帰を確認。
  • 受け入れ基準(仕様 §11)チェック:
    1. ✅ コア機構(pmset disablesleep)をライブ検証済み。
    2. menuWillOpen で毎回 pmset -g を読み戻し、表示が実値と一致。
    3. ✅ タイマー満了/フロア割れ/低電力/手動OFF/終了 が全て disableKeepAwake()・終了クリーンアップに集約。SIGTERM 経路は E2E 検証済み。
    4. ✅ install/uninstall スクリプトとも visudo -cf 検証付き。配置は parsed OK 確認済み。
  • これで MVP(M0〜M7)完成。残りは M8(.app バンドル化/署名・notarization、ログイン項目、Custom タイマー、設定永続化、蓋閉じ輝度0 など)。

2026-06-30 — M5/M6 実装: 低電力モード連動 + 安全網

  • M5(低電力モード連動): PowerStateObserver を実装。.NSProcessInfoPowerStateDidChange を監視し、off→on のエッジで onEnteredLowPowerMode を発火。MenuBarController.handleLowPowerMode() で keepAwake が ON のときだけ disableKeepAwake()。状態行に [低電力モード] を併記。
  • M6(安全網):
    • 終了時: AppDelegate.applicationWillTerminateMenuBarController.cleanupOnTerminate()setDisableSleep(false)(メニューの Quit 経路)。
    • 異常終了: main.swift に SIGINT/SIGTERM の DispatchSource ハンドラを追加し、kill されても setDisableSleep(false) してから exit(0)
    • 起動時同期: syncOnLaunch() で実フラグと UI を同期。起動直後すでに低電力モードかつ ON なら念のため解除。前回 ON のまま落ちた値(同一ブート内のみ残存)は UI に反映(再起動で自動クリア)。
  • 検証: swift build 成功。M6 をエンドツーエンドで確認 — disablesleep=1 でアプリ起動(低電力 off なので 1 維持=正しい)→ SIGTERM 終了 → pmset -g が 0 に復帰。
  • 補足: 低電力モードのライブ発火は実機で再現しづらいためロジック検証に留めた(解除経路は M2 で実証済みの共通経路)。

2026-06-30 — M4 実装: バッテリー監視

  • BatteryMonitor を実装。IOKit.ps(IOPSCopyPowerSourcesInfo / IOPSCopyPowerSourcesList / IOPSGetPowerSourceDescription)で残量%を取得。
  • 変化検知は IOPSNotificationCreateRunLoopSource を main RunLoop(.commonModes)に登録。取りこぼし対策に 60 秒ポーリングを併用。
  • フロア割れは復帰するまで一度だけ通知する hasBreached ラッチ付き。MenuBarController.handleFloorBreached() で keepAwake が ON のときだけ disableKeepAwake()(M2 で実証済みの共通解除経路)。
  • メニューに Battery floor ▸(10/15/20/25/30%、既定20%)と Battery: NN%(フロア NN%) 表示を追加。
  • 検証: swift build 成功・常駐起動。readBatteryPercent() 単体テストで実機 100%(pmset と一致)を確認。フロア割れ→解除の経路は M2 の共通経路を流用。
  • 補足: 仕様のフロア設定範囲は 5–50% だが、まずは 10–30% のプリセットを提供(後で拡張可)。

2026-06-30 — M2 ライブ検証完了+パーサのバグ修正

  • ユーザーが sudo bash Scripts/install-sudoers.sh を実行。/etc/sudoers.d/awakebook(root:wheel / 0440)配置成功、visudo -cf も通過。
  • ライブ検証成功: sudo -n /usr/bin/pmset -a disablesleep 1/0 がパスワードなしで通り、pmset -gSleepDisabled が 1/0 に追従。NOPASSWD・コア機構がエンドツーエンドで動作確認。
  • バグ発見&修正: 実機の pmset -g は値区切りがタブ SleepDisabled\t\t1)。isSleepDisabled() は半角スペースで split していたため 値1を true 判定できていなかった。値1を実機で読めなかったため未検出だった。
    • 修正: split(whereSeparator: { $0.isWhitespace }) で空白・タブ両対応。パース部を parseSleepDisabled(from:) に純粋関数として分離。
    • 検証: タブ/スペース×0/1・行なし・空文字・他行混在の7ケース全 PASS。さらに実機 flag=1 の生出力を捕獲してパーサに通し true 判定を確認(テスト後 0 に復帰)。
  • スクリプト修正: macOS の /bin/bash 3.2 は全角文字直前の $VAR を誤解釈する。install-sudoers.sh の最終 echo を ${VAR} 波括弧に修正(配置自体は元から成功、cosmetic な行のみ)。

2026-06-30 — M3 実装: オートオフタイマー

  • AutoOffTimer を実装。start(duration:) で 1秒刻みの Timer.common モード登録でメニュー操作中も止まらない)を回し、onTick で残り秒数、onExpire で満了を通知。
  • MenuBarControllerTimer submenu(Off(無期限) / 30分 / 1時間 / 2時間)を追加。時間を選ぶと keepAwake を ON にしてカウントダウン開始、満了で disableKeepAwake()(手動 OFF・自動解除と同じ共通経路)。
  • メニューバーのタイトルに残り時間を併記(updateCountdownTitle / tick で更新)。状態行にも「(残り H:MM)」を表示。
  • 表示形式 formatCountdown: 1時間以上 "H:MM"、未満 "M:SS"。既知の小課題: 1時間境界で "1:00"(1h)→"59:59"(59m59s) と意味が切り替わる。後で精緻化候補。
  • 検証: swift build 成功、常駐起動・正常終了。タイマーのライブ満了確認は keepAwake の ON が必要=sudoers 配置後にまとめて確認。
  • 未実装(M3 スコープ外): Custom…(任意時間入力)。後続で追加候補。

2026-06-30 — M1/M2 実装: SleepController + sudoers スクリプト

  • M1(コア): SleepController を実装。
    • setDisableSleep(_:)sudo -n /usr/bin/pmset -a disablesleep 1/0-n(非対話)で sudoers 未設定時に GUI がパスワード待ちでフリーズせず即失敗する。
    • isSleepDisabled()pmset -gSleepDisabled 行を読み戻し。
    • MenuBarControllerKeep Awake トグル+Status: SleepDisabled=x 表示を追加。メニューを開くたび(menuWillOpen)実値を読み戻すので表示が嘘をつかない。切り替え失敗時は NSAlert で install-sudoers.sh の案内を出す。
  • M2(権限): Scripts/install-sudoers.sh / uninstall-sudoers.sh を作成。一時ファイルに書いて visudo -cf で検証 → install -m 0440 -o root -g wheel/etc/sudoers.d/awakebook に配置 → 再検証。対象ユーザーは SUDO_USER から特定。
  • 発見(非自明): pmset -gSleepDisabled0 のとき行ごと出力されない(1 のときだけ SleepDisabled 1 が出る)。よって「行が無ければ false」で判定するのが正しい。isSleepDisabled() はこの挙動に対応済み。
  • 検証: swift build 成功、新メニューでの常駐起動・正常終了を確認。pmset -g 読み戻し(sudo 不要)も動作確認。
  • 未検証(要ユーザー操作): ライブのトグル成功確認は sudoers 配置が前提で、配置にはパスワードが要る。sudo bash Scripts/install-sudoers.sh 実行後に NOPASSWD トグルを検証する。

2026-06-30 — M0 完了: メニューバーアプリ雛形

  • ビルド方式を SwiftPM 実行可能パッケージに決定(仕様の「Xcode プロジェクト」案から変更)。swift build で CLI 再現ビルド・検証でき、git にクリーンに乗るため。詳細は DECISIONS.md。
  • Dock アイコン非表示は LSUIElement の代わりに NSApp.setActivationPolicy(.accessory)(実行時の等価設定)で実現。.app バンドル化は後続で対応。
  • ファイル構成: Package.swift / Sources/awakebook/{main,AppDelegate,MenuBarController}.swift.gitignore に Swift/Xcode 系を追加。
  • MenuBarControllermoon.zzz シンボルの NSStatusItemQuit のみ(M0 雛形)。
  • 検証: swift build 成功(Swift 6.3.2 / 言語モード 5 / macOS .v13)。バイナリ起動 → メニューバー常駐を確認 → 正常終了。
  • 次は M1SleepController の有効化/解除/読み戻し)。

2026-06-30 — ビルド仕様を受領、開発の足場を確定

  • ~/Downloads/引き続き.mdlid-awake 仮称のビルド仕様書)を受領。本プロジェクト awakebook の実装対象として採用。
  • アプリ概要: 蓋を閉じた状態・バッテリー駆動・外部ディスプレイなしでも Mac を起こし続ける macOS メニューバーアプリ(Sleepless / Amphetamine 相当の自作版)。狙いは Claude Code のリモートセッションや長時間ジョブを蓋閉じで走らせ続けること。
  • コア機構を sudo pmset -a disablesleep 1/0(カーネルの SleepDisabled フラグ)に確定。caffeinate 系は power assertion で蓋閉じスリープを止められないため不採用。詳細は DECISIONS.md。
  • 仕様を .devnotes/(DEVLOG / WIP / DECISIONS / ROADMAP)と CLAUDE.md / README に流し込み、M0〜M8 のマイルストーンを ROADMAP に配置。
  • 次の一手は M0(Xcode メニューバーアプリ雛形 + LSUIElement + 空の NSStatusItem 表示)。

最近のコミット

README

awakebook

概要

蓋を閉じた状態・バッテリー駆動・外部ディスプレイなしでも Mac を起こし続ける macOS メニューバーアプリ。 Claude Code のリモートセッションや長時間ジョブを、MacBook の蓋を閉じたまま・電源につながず走らせ続けるためのツール。

「一晩中スリープしない」「バッテリーを使い切る」事故を防ぐため、安全装置(オートオフタイマー・バッテリー下限・低電力モード連動)付きで実装している。Sleepless / Amphetamine 相当を自作するプロジェクト。

しくみ

  • コアは sudo pmset -a disablesleep 1/0(カーネルの SleepDisabled フラグ)。蓋を閉じてもスリープしなくなる。
  • caffeinate 系(power assertion)は蓋閉じスリープを止められないため使わない。
  • メニューバーの表示は常に pmset -g の実値を読み戻して反映する(表示が嘘をつかない)。
  • SleepDisabled は再起動でクリアされる(永続化されない)ため、戻し忘れても再起動すれば安全側。

必要環境

  • macOS 13 以降(Apple Silicon / Intel)
  • Xcode コマンドラインツールまたは Xcode(swift が使えること)

セットアップ

1. ビルド

git clone https://github.com/ichirokisanuki/awakebook.git
cd awakebook
swift build -c release

ビルド成果物: .build/release/awakebook

2. sudoers ルールの配置(必須)

GUI アプリはパスワードを入力できないため、pmset の切り替え 2 コマンドだけをパスワードなしで許可する sudoers ルールを置く。visudo -cf 検証付きで安全に配置される。

sudo bash Scripts/install-sudoers.sh

実行すると /etc/sudoers.d/awakebookroot:wheel / 0440)が配置され、最後に確認用コマンドが表示される。許可されるのは次の 2 つだけ:

/usr/bin/pmset -a disablesleep 0
/usr/bin/pmset -a disablesleep 1

ルールを外すとき:

sudo bash Scripts/uninstall-sudoers.sh

3. 起動

.build/release/awakebook

メニューバーに月アイコンが出れば常駐成功(Dock アイコンは出ない)。ログイン項目への登録は今後対応予定。

使い方

メニューバーの月アイコンをクリックすると以下のメニューが出る。

項目 説明
Keep Awake スリープ抑止の ON/OFF トグル。ON 中はアイコンが塗りつぶしになる
Timer ▸ オートオフ(Off=無期限 / 30分 / 1時間 / 2時間 / Custom…=任意の分数)。時間を選ぶと自動的に Keep Awake が ON になり、満了で自動解除。ON 中はアイコン横に残り時間を併記
Battery floor ▸ バッテリー下限(5〜50%、既定20%)。残量がこれを下回ると自動解除。設定は再起動後も保持される
Status: SleepDisabled = x pmset -g の実値(タイマー作動中は残り時間、低電力モード中は [低電力モード] も表示)
Battery: NN% 現在の残量とフロア設定
Quit 終了(終了時に必ずスリープ抑止を解除する)

次のいずれでも確実に解除される: タイマー満了 / バッテリーフロア割れ / 低電力モード突入 / 手動 OFF / アプリ終了(Quit・kill 含む)。

多言語対応

メニュー UI は macOS のシステム言語に追従して以下9言語で表示される(未対応言語は英語にフォールバック)。

English / 日本語 / 繁體中文 / 简体中文 / 한국어 / Español / Português / Français / Deutsch

システム設定を変えずに特定言語を試したいときは、起動時に言語を指定できる:

.build/release/awakebook -AppleLanguages '(fr)'      # フランス語で起動
.build/release/awakebook -AppleLanguages '(zh-Hant)' # 繁体字で起動

翻訳は Sources/awakebook/Localization.swift の1テーブルに集約している。網羅性と書式の確認は awakebook --dump-localization で行える。

注意

  • 蓋を閉じると排気が落ちる。重い持続負荷を蓋閉じで回すのは要注意(熱)。
  • 充電が一定以下になると pmset の設定に関係なく macOS がハードウェアレベルで強制スリープする(回避不可)。バッテリーフロアはその手前で安全に止めるためのもの。
  • App Store 配布は不可(sudoers / pmset がサンドボックス外)。配るなら Developer ID 署名+notarization で直配する。