[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-105377":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":13,"stars7d":13,"stars30d":13,"stars90d":15,"forks30d":15,"starsTrendScore":16,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":19,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":36,"readmeContent":37,"aiSummary":9,"trendingCount":15,"starSnapshotCount":15,"syncStatus":38,"lastSyncTime":39,"discoverSource":40},105377,"bulchimbeon","s3onghyun\u002Fbulchimbeon","s3onghyun","🪖 불침번 — Your Mac's night-watch soldier. Keeps your Mac awake only while AI agents work. No hooks, no daemon, macOS 13+.",null,"Go",53,7,1,15,0,3,41.31,"MIT License",false,"main",[22,23,24,25,26,27,28,29,30,31,32,33,34,35],"ai-agents","amphetamine","caffeinate","clamshell","claude-code","codex","coding-agents","keep-awake","macos","menu-bar-app","menubar","sleep-prevention","swift","swiftui","2026-11-01 00:15:24","# 🪖 Bulchimbeon (불침번)\n\n> **Your Mac's night-watch soldier.**\n> A menu bar app that keeps your Mac awake **only while your AI coding agents are actually working**,\n> then salutes *\"Shift over!\"* and lets it sleep.\n>\n> **No hooks, no daemon, no installed root helper. One app. macOS 13 and later.**\n> On Linux, a small daemon with a command line and a tray icon does the same job: see [Linux](#linux).\n\n*Bulchimbeon* (불침번) is the night-watch duty in the Korean army: someone stays up all night so everyone else can sleep.\nA pixel-art private takes that shift for your Mac.\n\n> 🌐 [한국어](README.ko.md) · [日本語](README.ja.md) · [简体中文](README.zh-CN.md)\n> 💡 [Ideas \u002F 문의 \u002F 建议](https:\u002F\u002Fgithub.com\u002Fs3onghyun\u002Fbulchimbeon\u002Fdiscussions)\n> 🗳️ **Which agent do you use it with? Where did you get stuck?** [Vote and tell us](https:\u002F\u002Fgithub.com\u002Fs3onghyun\u002Fbulchimbeon\u002Fdiscussions\u002F64)\n\n![Bulchimbeon demo](docs\u002Fmedia\u002Fdemo.gif)\n\n## Start here\n\nInstall: `brew install --cask s3onghyun\u002Ftap\u002Fbulchimbeon` — or [download the latest macOS zip](https:\u002F\u002Fgithub.com\u002Fs3onghyun\u002Fbulchimbeon\u002Freleases) and move it to `\u002FApplications`.\n\n**First launch:** the app is ad-hoc signed and not notarized by Apple yet, so macOS blocks it once:\n\n1. Open Bulchimbeon. macOS says it can't be opened; click **Done**.\n2. Go to **System Settings → Privacy & Security** and scroll down to **Security**.\n3. Next to *\"Bulchimbeon\" was blocked*, click **Open Anyway**, then confirm.\n   (Since macOS 15, right-click → Open no longer bypasses this.)\n\n![System Settings, Privacy & Security: \"Bulchimbeon\" was blocked to protect your Mac, with the Open Anyway button](docs\u002Fmedia\u002Fgatekeeper-open-anyway.png)\n\nNotifications show up as **\"Script Editor\"**. That is normal: until the app is notarized, macOS won't grant it\nnotification permission of its own.\n\n**Supported agents:** Claude Code · Codex · Gemini CLI · Cursor Agent · Copilot CLI · Aider · OpenCode (and your own custom agents).\n\n### 1-minute check after install\n\n1. Give an agent a task, for example ask Claude Code to run your tests.\n2. Within a few seconds the private in the menu bar turns his flashlight on.\n3. Open the menu. Below the welcome note (shown until you click **Got it**), the status line reads\n   **Bulchimbeon — On duty · 0:12 · …**, and the line under it says why, such as **Claude Code working**.\n4. Nothing happens? Run `\u002FApplications\u002FBulchimbeon.app\u002FContents\u002FMacOS\u002FBulchimbeon --diagnose` to see what it\n   detects, and [tell us where you got stuck](https:\u002F\u002Fgithub.com\u002Fs3onghyun\u002Fbulchimbeon\u002Fdiscussions\u002F64).\n\nTo see the sleep block macOS itself is holding, run `pmset -g assertions`. While on watch:\n\n```text\n2026-10-31 13:17:17 +0900\nAssertion status system-wide:\n   BackgroundTask                 0\n   ApplePushServiceTask           0\n   UserIsActive                   0\n   PreventUserIdleDisplaySleep    0\n   SoftwareUpdateTask             0\n   PreventSystemSleep             0\n   ExternalMedia                  0\n   PreventUserIdleSystemSleep     1\n   NetworkClientActive            0\nListed by owning process:\n   pid 2102(Bulchimbeon): [0x002847ba00019690] 13:31:53 PreventUserIdleSystemSleep named: \"Bulchimbeon on duty: keeping AI agents awake\"\nKernel Assertions: 0x100=MAGICWAKE\n   id=592  level=255 0x100=MAGICWAKE creat=  mod= description=en0 owner=IOSkywalkNetworkBSDClient\n```\n\n- `2026-10-31 13:17:17 +0900`: when the command ran.\n- `Assertion status system-wide`: every kind of sleep block, with how many are held right now.\n  `PreventUserIdleSystemSleep 1` means something is keeping the Mac from idle sleep. `PreventUserIdleDisplaySleep 0`\n  means the display may still sleep, the default watch mode. With **Keep display on**, Bulchimbeon takes a display\n  block instead, so `PreventUserIdleDisplaySleep` reads 1 and Bulchimbeon's own line below shows that kind.\n- `Listed by owning process`: who holds each block. `pid 2102(Bulchimbeon)` is the app (your pid will differ),\n  `[0x002847ba00019690]` is the block's ID, and `13:31:53` is when the watch took it. That time has no date, so it\n  can be from an earlier day and read later than the header time. `PreventUserIdleSystemSleep` is its kind\n  (`PreventUserIdleDisplaySleep` with **Keep display on**), and `named: \"Bulchimbeon on duty: keeping AI agents awake\"`\n  is the fixed name to look for.\n  Other apps holding the Mac awake would be listed here too.\n- `Kernel Assertions`: macOS's own entries (here a network wake for `en0`), not from Bulchimbeon; yours may differ.\n\nCaptured on macOS 26.6.2, Apple Silicon, Bulchimbeon 0.11.0.\n\nWhile idle (no agent working), Bulchimbeon releases its block, so `pmset -g assertions | grep -i bulchimbeon` prints no Bulchimbeon line.\n\n### FAQ\n\n**Why not just use `caffeinate` or Amphetamine?** Those are great when you want a manual, fixed wake lock. Bulchimbeon starts and ends the lock around active AI-agent work, so it does not get left on overnight.\n\n**What does it inspect or send?** It uses no hooks. Its only network request is the once-a-day [update check](#updates), which sends nothing but its version and can be turned off. It observes only your own local process metadata to decide whether a supported agent is working; it never stores or prints process command lines or environment variables.\n\n**Can it keep a closed laptop awake?** Yes, but turning on the lid-closed mode requires an administrator approval. It restores normal lid sleep when the watch ends or the app quits.\n\n| State | The private | When |\n|-------|-------------|------|\n| 💤 Off duty | Dozing in his helmet, zzz | Nothing to guard |\n| 👀 Standing by | Looking around | Waiting for an agent to clock in |\n| 🪖 On duty | Flashlight on, on patrol | The Mac won't sleep |\n| 🫡 Shift over | `^ ^` salute | Agents went quiet \u002F watch time is up |\n| 🥵 Combat rest | Sweating, eyes closed | The Mac is overheating |\n\n### 🚶 Patrol mode\n\n\u003Cimg src=\"docs\u002Fsprites-preview.png\" width=\"360\" align=\"right\" alt=\"Sprites\">\n\nThe private patrols just under your menu bar with a flashlight. While standing by he looks around in the corner;\nat shift change he salutes with a speech bubble. Clicks pass straight through, he fades when your pointer comes close,\nand he stays out of full-screen apps (presentations, screen sharing, video). Size 2× \u002F 3×, or turn him off.\n\n### 🗣️ The private's lines\n\n| When | He says |\n|------|---------|\n| On watch | \"Won't doze off! …probably.\" · \"Sleep tight. I've got this!\" |\n| Standing by | \"Polishing my rifle… standing by.\" |\n| Mac heating up | \"Heat advisory! Hydrating 🥵\" → \"Combat rest! Cooling down 🧊\" |\n| Low battery | \"Reporting to the duty officer 🔋 Battery at 15%… out of strength, standing down.\" |\n| Sleep block lost | \"Not AWOL! Back on post! 🫡\" |\n| You come back | \"Handover report! 📋\" |\n\n## Why\n\nTo leave an agent running overnight I used to do this:\n\n```bash\ncaffeinate -d                 # ...and forget to turn it off\nsudo pmset disablesleep 1     # ...and forget to turn it back on → battery at 0%\n```\n\nBulchimbeon stays awake **only while an agent is working** and hands the Mac back to normal sleep when it's done.\n\n## Features\n\n| Feature | What it does |\n|---------|--------------|\n| 🤖 Agent watch | Detects Claude Code, Codex, Gemini CLI, Cursor Agent, Copilot CLI, Aider, OpenCode (plus your own). **Only counts agents that are actually working**, not sessions that are merely open. Clocks in when one starts working and hands over 5 minutes (adjustable) after everything goes quiet |\n| 🪖 Timed watch | 30 min \u002F 1 h \u002F 2 h \u002F 4 h \u002F indefinitely |\n| Watch modes | Keep display on (`caffeinate -d`) · Display may sleep, Mac stays awake (`caffeinate -i`) · **Stay awake with the lid closed** (admin) |\n| 🔋 Battery stand-down | Stands down when running on battery below your threshold (default 20%) |\n| ⚠️ Lid safety net | Restores normal lid sleep (`disablesleep` back to off) when the watch ends or the app quits; after a crash, the next launch offers **Restore lid sleep** in the menu |\n| 🌡️ Heat advisory → 🔥 combat rest | Warns when the Mac heats up; when it gets critical, pauses the watch to cool down and **resumes automatically** once it cools (no more cooking a closed Mac in a bag) |\n| ⚠️ Abandoned-post check | If the sleep block disappears for any reason, it is re-engaged immediately |\n| 🕵️ Watchdog | In lid-closed mode, restores normal sleep within 5 s when the app quits or the watch ends (including a timed watch running out) — no second password prompt needed; re-enabling replaces rather than stacks it |\n| 📋 Night-watch handover | Come back after 10+ minutes away and get a report of what happened while you were gone |\n| 🔁 Survives restarts | Remaining watch time carries over app restarts, updates and reboots (for security, a lid-closed watch comes back as a system watch for that watch only; lid mode stays selected in the menu and asks for approval again on the next watch) |\n| 🖥️ Who else is holding the Mac awake | The menu shows other apps blocking sleep, e.g. \"Vivaldi (video)\" |\n| 🌐 Korean \u002F English \u002F Simplified Chinese | Follows the system language by default; pick one from **언어 \u002F Language \u002F 语言** in the menu |\n| 📒 Duty log | `~\u002FLibrary\u002FApplication Support\u002FBulchimbeon\u002Fduty-log.txt` |\n\n## Compared with similar apps\n\n[Adrafinil](https:\u002F\u002Fgithub.com\u002Fkageroumado\u002Fadrafinil) solves the same problem and came first. The two take opposite\napproaches, so pick the one that fits. (As of October 2026.)\n\n| | Bulchimbeon | Adrafinil |\n|---|---|---|\n| How it knows an agent is working | **Watches from outside**: the agent's `caffeinate` child and CPU use | **The agent tells it**: hooks (acquire on prompt submit, release when the reply ends) plus observation |\n| What installing changes | **Nothing.** One app; agent config files are never touched | Hooks added to agent configs (one-click install\u002Funinstall), a user daemon, a root helper |\n| macOS | **13 and later** | 26.4 and later |\n| Awake with the lid closed | Admin password each time you turn it on; a watchdog guarantees the setting is restored | Approved once at install, no password afterwards |\n| Agent asks to stay awake (long builds) | — | CLI and MCP tool |\n| Heat | Warns even with the lid open; when it gets too hot, rests and **resumes automatically once cool** | Force-releases on overheating while the lid is closed |\n| Distribution | Ad-hoc signed (notarization planned), personal Homebrew tap | Notarized, official Homebrew cask |\n\n**Choose Adrafinil** if you're on macOS 26.4+ and want hooks to track turns precisely, or want agents to request\nwake time themselves.\n**Choose Bulchimbeon** if you're on macOS 13–15, don't want anything installed into your agent configs or system,\nor would rather have a private on watch.\n\n## Install\n\n```bash\nbrew install --cask s3onghyun\u002Ftap\u002Fbulchimbeon\n\n# Update — refresh the tap first, Homebrew's auto-update interval is long\nbrew update && brew upgrade --cask bulchimbeon\n```\n\nOr download the zip from [Releases](https:\u002F\u002Fgithub.com\u002Fs3onghyun\u002Fbulchimbeon\u002Freleases) and move it to `\u002FApplications`.\nmacOS 13+, universal (Apple Silicon and Intel).\n\nFirst launch: macOS blocks the app once; the steps are in [Start here](#start-here).\n\n### Verify the download (optional)\n\nRelease zips are built only in GitHub Actions and carry a build provenance attestation.\n\n```bash\ngh attestation verify Bulchimbeon-\u003Cversion>.zip --repo s3onghyun\u002Fbulchimbeon\nshasum -a 256 -c Bulchimbeon-\u003Cversion>.zip.sha256\n```\n\n## Updates\n\nOnce a day the app (and the Linux daemon) asks GitHub whether a newer release is out. If one is, the menu shows\n**New version X.Y.Z — Update** and **Release notes**; a release that fixes a serious bug is marked\n**(recommended: important fix)**. Nothing is downloaded or installed until you click Update and confirm.\n\n- **Installed with Homebrew:** Terminal opens and runs `brew update && brew upgrade --cask bulchimbeon` in plain\n  sight, then reopens the app.\n- **Installed from the zip:** the app downloads `Bulchimbeon-X.Y.Z.zip` and its `.sha256` from that release, checks\n  the checksum, and checks that the new app's code signature is valid, has the same signing identifier and team as\n  the running app, uses the hardened runtime and is exactly the offered version. Only then does it replace itself and\n  restart; the current watch carries on. If any step fails, the installed app is left as it was and the release page\n  is offered instead. The app must sit in a folder you can write to, such as `\u002FApplications`.\n- **Linux:** notify only. `bulchimbeon status`, the tray menu and `bulchimbeon doctor` show the update with the\n  command for how you installed it (`brew upgrade bulchimbeon-linux`, `sudo apt install .\u002F….deb`,\n  `sudo dnf install .\u002F….rpm`, `yay -Syu bulchimbeon-bin`, or the tarball), and the daemon notes it in the journal.\n\n**Privacy.** The check is a single HTTPS `GET https:\u002F\u002Fapi.github.com\u002Frepos\u002Fs3onghyun\u002Fbulchimbeon\u002Freleases\u002Flatest`,\nat most once a day, with only `User-Agent: Bulchimbeon\u002F\u003Cversion>`, `Accept` and a fixed `Accept-Language: en`.\nNo settings, duty log, agent names, identifiers or cookies are sent, and there is no telemetry. As with any web\nrequest, GitHub sees your IP address. This is the only network access Bulchimbeon makes.\n\nTo turn it off, untick **Check for updates (once a day)** in the menu, or:\n\n```bash\ndefaults write io.github.s3onghyun.bulchimbeon checkForUpdates -bool false   # macOS\nbulchimbeon config set checkForUpdates false                                  # Linux\n```\n\n## How it works\n\n| Watch mode | Under the hood |\n|------------|----------------|\n| Keep display on | `IOPMAssertion` `PreventUserIdleDisplaySleep`, same as `caffeinate -d` |\n| Display may sleep (default) | `IOPMAssertion` `PreventUserIdleSystemSleep`, same as `caffeinate -i` |\n| Lid closed | The above plus `pmset -a disablesleep 1`. Closing the lid is a hardware trigger that assertions can't block |\n\n### Agent detection\n\nBulchimbeon scans **your own user's** processes with libproc and matches agents by executable name, `argv[0]`,\ninstall path or script path. Never by substring (Claude Code hooks run `node -e \"...'.claude'...\"`, which would match).\n\n| Install method | What the process looks like | Matched by |\n|----------------|-----------------------------|------------|\n| Claude Code native installer | executable `~\u002F.local\u002Fshare\u002Fclaude\u002Fversions\u002F2.1.296` (named after the version!) | `argv[0]` = `claude`, or the install path |\n| Homebrew \u002F IDE-bundled binary | `...\u002Fclaude`, `\u002Fopt\u002Fhomebrew\u002Fbin\u002Fcodex` | executable name |\n| npm global install | `node \u002Fopt\u002Fhomebrew\u002Fbin\u002Fclaude` | script name |\n| npx \u002F package path | `node ...\u002F@anthropic-ai\u002Fclaude-code\u002Fcli.js` | package path |\n| bun \u002F deno | `bun ...\u002F@google\u002Fgemini-cli\u002F...` | package path |\n\nSupported: Claude Code · Codex · Gemini CLI · Cursor Agent · Copilot CLI · Aider · OpenCode\n\n**Only while working.** People keep several Claude Code sessions open, so counting open sessions would never let the\nMac sleep. An agent counts as working when (you can switch back to counting open sessions in the menu):\n\n| Signal | Why (measured on a real machine) |\n|--------|----------------------------------|\n| It has a `caffeinate` child process | Claude Code spawns `caffeinate -i -t 300` itself, only while it's working |\n| Its process tree uses ≥ 2% CPU averaged over 60 s | Covers tool runs like builds and tests. Idle sessions sit at 0–0.1%. A 5-second sample made periodic background jobs look busy forever |\n| Headless sessions with no tools (`--tools=`) are ignored | Plugins such as claude-mem keep helper sessions running |\n\nCheck what gets detected on your machine, or add an agent that isn't on the list:\n\n```bash\n\u002FApplications\u002FBulchimbeon.app\u002FContents\u002FMacOS\u002FBulchimbeon --diagnose\ndefaults write io.github.s3onghyun.bulchimbeon customAgents -array goose amp\n```\n\nAgents running inside Docker or on a remote host over SSH are not processes on this Mac, so they aren't detected.\n\n```bash\npmset -g assertions | grep Bulchimbeon   # is it on watch right now?\n```\n\nThe duty logic lives in a pure state machine (`DutyEngine`) with no system calls, tested scenario by scenario with a\nfake clock. Design notes, including signals we tried and rejected: [docs\u002Fdesign.md](docs\u002Fdesign.md).\n\n## Linux\n\nBulchimbeon also runs on Linux as a small daemon with a command line and a [tray icon](#tray-icon).\nIt uses the same engine as the macOS app: a Go port that must pass the same golden test cases\n([`testdata\u002Fengine`](testdata\u002Fengine)) as the Swift core, in CI on every pull request.\nTested on Ubuntu 24.04 (systemd 255). It needs systemd and logind. While on duty it blocks automatic suspend\nwith your desktop's own inhibitor, or with logind's when there is no desktop ([below](#the-sleep-lock-on-each-desktop)).\n\n### Install\n\nWith [Homebrew on Linux](https:\u002F\u002Fdocs.brew.sh\u002FHomebrew-on-Linux) (no root; `brew upgrade` brings new versions):\n\n```bash\nbrew install s3onghyun\u002Ftap\u002Fbulchimbeon-linux\nbrew services start bulchimbeon-linux    # systemd user unit, started at login\n```\n\nPick one install method. If you use Homebrew, do not also install the tarball or enable the `.deb` or `.rpm` unit\n(`systemctl --user enable bulchimbeon`), or two daemons will run.\n\nOr download from [Releases](https:\u002F\u002Fgithub.com\u002Fs3onghyun\u002Fbulchimbeon\u002Freleases): a tarball or a `.deb` for amd64 or arm64,\nor, from 0.9.0, an `.rpm` for x86_64 or aarch64.\n\n```bash\n# Tarball: no root needed. Installs ~\u002F.local\u002Fbin\u002Fbulchimbeon and a systemd user unit, then starts it\ntar -xzf bulchimbeon_\u003Cversion>_linux_amd64.tar.gz\ncd bulchimbeon_\u003Cversion>_linux_amd64 && .\u002Finstall-linux.sh\n\n# or the .deb: installs \u002Fusr\u002Fbin\u002Fbulchimbeon and the user unit; each user then enables it\nsudo apt install .\u002Fbulchimbeon_\u003Cversion>_amd64.deb\nsystemctl --user enable --now bulchimbeon\n\n# or the .rpm (Fedora and others, from 0.9.0): the same files; each user then enables it\nsudo dnf install .\u002Fbulchimbeon-\u003Cversion>-1.x86_64.rpm\nsystemctl --user enable --now bulchimbeon\n```\n\nComing soon, not published yet: a Fedora COPR repository (`dnf copr enable`) and the AUR package `bulchimbeon-bin`.\nUntil then, use the `.rpm` on Fedora, and on Arch the tarball, or `makepkg -si` in\n[`packaging\u002Faur\u002Fbulchimbeon-bin`](packaging\u002Faur\u002Fbulchimbeon-bin) of a checkout.\n\nVerify the download (optional): `sha256sum -c SHA256SUMS --ignore-missing`, and\n`gh attestation verify \u003Cfile> --repo s3onghyun\u002Fbulchimbeon`.\n\nUninstall with `brew services stop bulchimbeon-linux && brew uninstall bulchimbeon-linux`,\n`.\u002Finstall-linux.sh --uninstall`, or `systemctl --user disable --now bulchimbeon` followed by\n`sudo apt remove bulchimbeon` or `sudo dnf remove bulchimbeon`. Your settings (`~\u002F.config\u002Fbulchimbeon`) and duty log\n(`~\u002F.local\u002Fstate\u002Fbulchimbeon`) are kept.\n\nTo keep the service running while you are logged out, run `loginctl enable-linger` once.\n\n### Use\n\n```bash\nbulchimbeon status                       # on duty or not, and why\nbulchimbeon doctor                       # check the desktop session, systemd, logind and polkit\nbulchimbeon config set agentGraceMinutes 10\nbulchimbeon config set customAgents goose amp\nbulchimbeon config set language ko       # ko | en | zh | system (default: follows LANG)\njournalctl --user -u bulchimbeon -f      # the daemon's log\n```\n\n```text\n$ bulchimbeon status\n🪖 Bulchimbeon — on duty for 0:35 (agent watch)\nLock:      blocking auto-suspend via org.gnome.SessionManager · confirmed by the session\nWhy:       Claude Code pid 1174857 11.7% (process-tree CPU ≥ 2.0%, 60 s average)\nAgents:    Claude Code ×7 open, 1 working\nHandover:  5 min after the last agent goes quiet (agentGraceMinutes)\nPower:     battery: none · heat: normal (coretemp 49 °C, critical 105 °C) · lid: not handled (closing it still suspends)\n```\n\n`bulchimbeon run` is the daemon itself; the user unit runs it. It always does agent watch: the\nsame detection rules, a 5-second check, engage when an agent's process tree uses at least 2% CPU\n(60-second average; Linux has no `caffeinate` signal), hand over after the grace time.\n\n### Tray icon\n\n`bulchimbeon tray` puts the private in your desktop panel, in the same poses as on the Mac menu bar and animated the\nsame way: asleep when the daemon is off, looking around while it waits for an agent, flashlight on while on duty,\nsweating through a combat rest. Hover for the full status. The menu shows every line of `bulchimbeon status` in the\nsame order, then a **🧥 Uniform** picker that sets `uniformTheme`, and **Quit tray**, which closes the icon only:\nthe daemon keeps watch without it.\n\nThe `.deb` and `.rpm` start it with every desktop session through an XDG autostart entry,\n`\u002Fetc\u002Fxdg\u002Fautostart\u002Fbulchimbeon-tray.desktop`; `install-linux.sh` puts the same entry in `~\u002F.config\u002Fautostart\u002F`.\nWith Homebrew, add `bulchimbeon tray` to your desktop's startup applications. Where there is nothing to show it in\n(SSH, a headless machine, a desktop without a tray), or for a user who has never started the daemon, it logs one line\nand exits.\n\nIt is a StatusNotifierItem, the tray protocol KDE, XFCE and the GNOME AppIndicator extension speak:\n\n| Desktop | What it needs |\n|---|---|\n| XFCE 4.16 or later | the **Status Tray** panel item (`systray`), which the default panel has |\n| XFCE 4.14 or older | the `xfce4-statusnotifier-plugin` panel item |\n| GNOME | the AppIndicator extension (`gnome-shell-extension-appindicator`, on by default in Ubuntu) |\n| KDE Plasma | nothing. Off duty or standing by, the item is Passive, so Plasma keeps it in the tray's hidden icons until the private goes on duty |\n\n```bash\nbulchimbeon tray &                          # show it now, in this desktop session\nbulchimbeon tray --lang ko                  # menu in Korean (ko | en | zh), whatever the language setting says\nbulchimbeon config set uniformTheme navy    # default | navy | arctic | desert: same as the Uniform menu\n```\n\nTo stop it starting at login, turn it off in your desktop's session and startup settings, or remove the autostart file.\n\n### How it works on Linux\n\n| | Linux |\n|---|---|\n| Agents | Your own processes from `\u002Fproc`, with the same matching rules and the same privacy limits as on macOS |\n| Sleep lock | The desktop session's suspend inhibitor, or a logind `sleep` inhibitor without one ([per desktop](#the-sleep-lock-on-each-desktop)). Never an idle lock. If the daemon dies, even with `kill -9`, the lock goes with it |\n| Battery stand-down | `\u002Fsys\u002Fclass\u002Fpower_supply` |\n| Heat | hwmon (`coretemp`, `k10temp`, …) or thermal zones: heat advisory 15 °C below the critical temperature, combat rest 5 °C below |\n| Duty log | `~\u002F.local\u002Fstate\u002Fbulchimbeon\u002Fduty-log.txt`, same format as on macOS |\n\n### The sleep lock on each desktop\n\n| Session | Lock while on duty | Still works |\n|---|---|---|\n| GNOME (Ubuntu, Fedora), Cinnamon, MATE, Budgie | `org.gnome.SessionManager.Inhibit` with the suspend flag only | the screen dims and locks; your own suspend from the menu. GNOME 46 (Ubuntu 24.04) backs any suspend inhibitor with a logind block lock of its own, so `systemctl suspend` in a terminal asks for `-i` |\n| KDE Plasma | `org.freedesktop.PowerManagement.Inhibit`, which PowerDevil applies to automatic suspend only | the screen dims, turns off and locks; manual suspend |\n| XFCE | by default a logind `sleep` inhibitor, as without a desktop, which does **not** stop xfce4-power-manager's automatic suspend on any systemd version (`block` and `block-weak` both let your own user's suspend through). `bulchimbeon config set xfceKeepAwake true` switches to `org.freedesktop.PowerManagement.Inhibit` | the screen blanks and locks; manual suspend. With `xfceKeepAwake`, automatic suspend is blocked too, but XFCE's power manager treats any inhibitor like presentation mode, so the screen then stays on and unlocked while on duty |\n| No desktop: headless, SSH only, sway, Hyprland, other compositors | a logind `sleep` inhibitor, `block-weak` on systemd 257 and later, `block` on 256 and older, held by a `systemd-inhibit` child tied to a pipe | your own suspend. It stops logind's `IdleAction` and other users' suspend requests. On systemd 256 and older, `systemctl suspend` in a terminal asks for `-i` |\n\nWhy not logind everywhere: on systemd 256 and older (Ubuntu 24.04, Debian 12) logind ignores a block inhibitor\nfor requests from the user who holds it, and GNOME's, XFCE's and Cinnamon's power managers ask logind to suspend as\nyou. Measured on Ubuntu 24.04 GNOME: gsd-power's automatic suspend request went through logind while Bulchimbeon held\nits lock. On systemd\n257 and later a logind `block` lock does stop them, but it also refuses your own manual suspend.\n\nThe daemon looks for the desktop on your session bus, the user bus that `systemd --user` gives its services. Log in\nto a desktop while it is on duty and the lock moves to the desktop within one check (5 seconds); log out and it moves\nback to logind. The desktop drops its inhibitor when the daemon's bus connection closes, so a crash releases it.\n`bulchimbeon status` shows which lock is held and why; `bulchimbeon doctor` tries each one.\n\n### Limits\n\n- **Closing the lid still suspends.** By default logind ignores inhibitors for the lid switch\n  (`LidSwitchIgnoreInhibited=yes`), and Bulchimbeon never changes logind's settings.\n- **Over SSH or on a headless machine, the sleep block needs a polkit rule.** By default polkit\n  allows a `sleep` block inhibitor only from a local login (someone at the machine's screen). From an\n  SSH session, or as a user service on a machine nobody logs into locally, logind refuses it,\n  and `bulchimbeon status` says plainly that sleep is not blocked.\n  To allow it for one user, save this as `\u002Fetc\u002Fpolkit-1\u002Frules.d\u002F50-bulchimbeon.rules` (owner root, mode `0644`),\n  with your user name; `bulchimbeon doctor` prints it with your name filled in:\n\n  ```js\n  \u002F\u002F Bulchimbeon: let YOUR-USER-NAME hold a logind \"sleep\" block inhibitor from an SSH or\n  \u002F\u002F systemd --user session (polkit's default only allows it from a local session).\n  \u002F\u002F Grants nothing else. Remove this file to undo.\n  polkit.addRule(function (action, subject) {\n    if (action.id == \"org.freedesktop.login1.inhibit-block-sleep\" && subject.user == \"YOUR-USER-NAME\") return polkit.Result.YES;\n  });\n  ```\n\n  It grants only `org.freedesktop.login1.inhibit-block-sleep` to that user; delete the file to undo.\n  Bulchimbeon never runs this for you. A desktop session does not need it: its own inhibitor needs no polkit\n  permission. Only the logind lock, used without a desktop, does.\n- **sway, Hyprland and other compositors without a desktop power manager get the logind lock only.** It does not stop\n  an idle daemon that suspends as you (`swayidle` or `hypridle` running `systemctl suspend`), because logind lets your\n  own suspend through. Bulchimbeon holds no idle lock there either: on those compositors one would also stop the\n  screen from locking.\n- **On XFCE, automatic suspend is not blocked by default.** xfce4-power-manager treats any suspend inhibitor like\n  presentation mode and keeps the screen on and unlocked, so Bulchimbeon leaves the screen lock alone and holds only\n  the logind lock, which xfce4-power-manager's own suspend gets past on any systemd version. `bulchimbeon status`\n  and `doctor` say so. To block it anyway, accepting that the screen stays on and unlocked while on duty:\n  `bulchimbeon config set xfceKeepAwake true`.\n- **The desktop inhibitor covers your own session.** Another user's session on the same machine (fast user switching)\n  can still suspend it. A desktop whose session bus is private (started with `dbus-launch`, without\n  `dbus-user-session`) is invisible to the service, which then keeps the logind lock.\n- Only the \"Display may sleep\" watch mode exists on Linux (`dutyMode` is ignored), there is no patrol mode, and\n  notifications go to the journal.\n- Windows is planned: the platform layer is in [`internal\u002Fplatform`](internal\u002Fplatform).\n\n## Security\n\nThe app runs `pmset` with admin rights and reads other processes' metadata, so it has its own threat model:\n[SECURITY.md](SECURITY.md).\n\n- No network access except the once-a-day [update check](#updates), which sends only the app version and can be turned off; no third-party dependencies\n- Updates install only after you click: Homebrew installs go through `brew` in a visible Terminal, zip installs are replaced only after the checksum and code-signature checks pass\n- No shell-injection surface: absolute paths, minimal environment, and only an app PID and a safely quoted app-owned stop-flag path enter the admin command\n- The root lid watchdog has no deadline to extend; the app owns all watch timing. A user-owned, non-world-writable empty stop flag can only make it run `pmset -a disablesleep 0`; it never reads the flag, follows it elsewhere, or writes it\n- Never stores or prints other processes' arguments or environment; never reads AI transcripts\n- Hardened runtime, SHA-256 checksums and build provenance for releases, CI actions pinned by commit SHA\n\n## Development\n\nOnly the Command Line Tools are needed, no Xcode. The Linux daemon is a Go module (Go 1.27 or later):\n`go test .\u002F...` and `go build .\u002Fcmd\u002Fbulchimbeon`.\n\n```bash\nmake test       # unit + scenario tests\nmake app        # dist\u002FBulchimbeon.app (ad-hoc signed)\nmake run        # build and launch\nmake icons      # regenerate the app icon and preview after editing sprites\n\n# launch and start agent watch right away\nopen dist\u002FBulchimbeon.app --args --agent-watch\n\n# print the engine's effects to stderr (for tracing)\nBULCHIMBEON_DEBUG=1 dist\u002FBulchimbeon.app\u002FContents\u002FMacOS\u002FBulchimbeon\n```\n\nWant to help? Start with [CONTRIBUTING.md](CONTRIBUTING.md).\n\n### Release (maintainer)\n\n```bash\ngit tag vX.Y.Z && git push origin vX.Y.Z   # → the Release workflow builds, attests and uploads\n.\u002Fscripts\u002Fbump-cask.sh X.Y.Z               # → verifies checksums and provenance, then updates the tap's cask and Linux formula\n.\u002Fscripts\u002Fbump-aur.sh X.Y.Z                # → same checks, then updates packaging\u002Faur for the AUR (packaging\u002Faur\u002FREADME.md)\n# Fedora COPR, once set up: copr-cli buildscm ... --commit vX.Y.Z (packaging\u002Frpm\u002FREADME.md)\n```\n\nSprites are ASCII grids in `Sources\u002FBulchimbeonCore\u002FSprites.swift`: one character per pixel, drawn as vector\nrectangles so both the 16 pt menu bar icon and the 1024 px app icon stay crisp.\n\n## Roadmap\n\nTracked as [milestones](https:\u002F\u002Fgithub.com\u002Fs3onghyun\u002Fbulchimbeon\u002Fmilestones). 1.0 means settings keys, command-line\nflags and the duty log format stop changing: see the [compatibility promise](docs\u002Fcompatibility.md) (in Korean; the\ntables are self-explanatory) and the [CHANGELOG](CHANGELOG.md).\n\n- [x] Pixel-art private, patrol mode, Korean\u002FEnglish\u002FSimplified Chinese UI\n- [x] Detect working vs. idle agents\n- [x] Homebrew cask (personal tap)\n- [ ] Apple notarization and the official Homebrew cask\n- [ ] Privileged helper to remove the admin prompt in lid-closed mode\n- [ ] Unlockable skins: Joseon night watchman, scholar\n\n## Contributing\n\nBug reports (\"🚨 soldier misbehaving\"), agent requests, translation fixes, and ideas for lines or sprites are\nall welcome. Issues and PRs in English, Korean, Japanese or Chinese are fine. Look for\n[`good first issue`](https:\u002F\u002Fgithub.com\u002Fs3onghyun\u002Fbulchimbeon\u002Flabels\u002Fgood%20first%20issue) to get started, and read\n[CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Caution\n\nHeavy, long-running work with the lid closed makes the Mac warm. Plug in power when you can.\n\n## License\n\nMIT\n",2,"2026-10-31 04:26:35","CREATED_QUERY"]