pi-keepawake

Keep the host from idle sleeping while a pi process is running. Blocks system sleep only — display sleep and the lock screen still work.

Packages

Package details

extension

Install pi-keepawake from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-keepawake
Package
pi-keepawake
Version
1.0.2
Published
Oct 23, 2026
Downloads
704/mo · 704/wk
Author
zxh126
License
MIT
Types
extension
Size
25.3 KB
Dependencies
1 dependency · 0 peers
Pi manifest JSON
{
  "extensions": [
    "./index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-keepawake

npm license: MIT

English | 简体中文

A pi extension that keeps the host from idle sleeping or hibernating as long as any pi process is running. Display sleep and the lock screen keep working — nothing here interferes with them.

Zero configuration: install it and it works. MIT licensed.


Features

  • Process-scoped: held from session_start to session_shutdown, i.e. "pi is open, the machine stays awake". Every pi process holds its own OS assertion and the OS aggregates them — open three pi processes, close two, the host still does not sleep.
  • System sleep only: no ES_DISPLAY_REQUIRED (Windows), no -d (macOS), no idle / handle-lid-switch (Linux). The screen may turn off, the session may lock.
  • Zero configuration: no flags, no environment variables, no commands.
  • Fail-safe: inside containers, without PowerShell, or on an unsupported platform, pi keeps running and you get one warning in the TUI.
  • Cross-platform: every platform uses its own built-in mechanism. No admin rights, no native addons, no node-gyp.
  • Observable: the TUI footer shows ☕ keep-awake, and the underlying helper process (plus its command line) can be inspected directly — see below.
  • No leftovers: released on normal exit, Ctrl-C and process.exit(); automatically re-engaged if the OS primitive dies. A session_start that follows a reload or session replacement first sweeps helpers left by the previous runtime of the same pi process, so a reload cannot leave a second assertion engaged.

Install

Requires Node.js ≥ 20 (bundled with pi).

1. From npm (recommended)

pi install npm:pi-keepawake

2. From Gitee

pi install git:gitee.com/zxh126/pi-keepaware

Both routes make pi install the dependency (pervigil) for you. Afterwards pi list shows the package and /reload activates it. To pin a version, spell it out: pi install npm:pi-keepawake@1.0.0.

3. Manually into the user extensions directory

git clone https://gitee.com/zxh126/pi-keepaware "$HOME/.pi/agent/extensions/keep-awake"
cd "$HOME/.pi/agent/extensions/keep-awake" && npm install

On Windows the path is %USERPROFILE%\.pi\agent\extensions\keep-awake. Pi discovers every subdirectory of extensions/ that contains an index.ts.

Pi does not install dependencies for local directory packages, so the npm install step is mandatory here.

4. Try it once

pi -e npm:pi-keepawake
pi -e git:gitee.com/zxh126/pi-keepaware
pi -e ./pi-keepawake            # local path, run npm install first

Uninstall

pi remove npm:pi-keepawake
pi remove git:gitee.com/zxh126/pi-keepaware

Or simply delete ~/.pi/agent/extensions/keep-awake.

What it actually does

The extension only decides when to hold and release; the OS interaction is done by pervigil (zero dependencies, no native addons, MIT).

Platform Mechanism Flags
Windows a PowerShell child calls SetThreadExecutionState and blocks to keep that thread alive ES_CONTINUOUS | ES_SYSTEM_REQUIRED (no ES_DISPLAY_REQUIRED)
macOS caffeinate(1) -i (no -d)
Linux systemd-inhibit(1), falling back to /sys/power/wake_lock --what=sleep --mode=block (no idle, no handle-lid-switch)

Lifecycle:

session_start ──▶ acquire ──▶ [ held for the whole session ] ──▶ session_shutdown ──▶ release
                                      │
                                      └─ helper dies ⇒ primitiveDied ⇒ re-engage (max 5 times)

More notes:

  • No power-plan changes (powercfg), no presentation mode, no input simulation — so neither the lock screen nor the screen saver is suppressed.
  • A user-initiated sleep/hibernate is not blocked. That is the semantics of an OS assertion, and it is deliberately preserved.
  • Inside containers (/.dockerenv, the container env var, ...) pervigil becomes a silent no-op instead of throwing.

Verify it is working

The obvious one: the pi footer shows ☕ keep-awake.

From the OS point of view:

Windows (PowerShell, no admin required)

Get-CimInstance Win32_Process -Filter "Name='powershell.exe' OR Name='pwsh.exe'" |
  Where-Object { $_.CommandLine -like '*Pervigil*' } |
  Select-Object ProcessId, ParentProcessId,
    @{ n = 'flags'; e = {
        @(
          if ($_.CommandLine -match 'ES_SYSTEM_REQUIRED')  { 'ES_SYSTEM_REQUIRED' }
          if ($_.CommandLine -match 'ES_DISPLAY_REQUIRED') { 'ES_DISPLAY_REQUIRED' }
        ) -join ','
      } }

While pi is running there should be exactly one helper whose flags read ES_SYSTEM_REQUIRED (not ES_DISPLAY_REQUIRED); it should disappear once pi exits.

powercfg /requests shows the request as well, but it requires administrator rights and fails in a normal terminal.

macOS

pmset -g assertions | grep -i caffeinate

Linux

systemd-inhibit --list | grep -i pi

Known limitations

  • One long-lived PowerShell process on Windows (measured: WorkingSet ≈ 74 MB / Private ≈ 59 MB). That is the direct cost of the "held for the whole session" semantics; it is reclaimed when pi exits.
  • On Linux you need a systemd user session (systemd-inhibit) or a writable /sys/power/wake_lock; otherwise it degrades to a no-op with one warning.
  • Lid-close suspend is not blocked (the Linux handle-lid-switch inhibitor is intentionally not requested), and neither is a user-initiated sleep.
  • Verified on Windows 11 only (helper appears/releases, multiple processes aggregate, re-engage after the helper is killed, no leftovers after a hard kill). The macOS / Linux code paths are reviewed against pervigil's semantics but have no on-device test record.
  • If pi is killed in a way no hook can observe (TerminateProcess / OOM), the extension's own cleanup does not run. On the author's machine the helper dies with its parent anyway; as a safety net every session_start reaps wake-lock helpers whose parent is this same pi process (a leftover of a reload that skipped session_shutdown) or whose parent process is gone. Helpers of other live processes are never touched.

Development

pi-keepawake/
├── index.ts          # the extension (single source file)
├── package.json      # pi manifest: pi.extensions -> ./index.ts, dependency pervigil
├── LICENSE           # MIT
├── README.md         # this file
└── README.zh-CN.md   # Chinese translation
  • The extension runs inside the pi process through pi's jiti loader — no build step.
  • The only runtime dependency is pervigil; the @earendil-works/* packages are imported with import type only and stay out of dependencies.
  • After editing, /reload inside pi is enough. To try a checkout without installing: pi -e <local path>.

Relevant pi events: session_start (create the wake-lock controller and acquire), session_shutdown (release and clear the footer status). At runtime the pervigil primitiveDied / degraded events drive re-engagement and the one-time warning.

Release flow (maintainer): bump version in package.json → commit → push to Gitee → npm publish --registry=https://registry.npmjs.org (the local .npmrc points its default registry at the npmmirror mirror, which only affects installs — publishing must target the official registry explicitly).

Dependencies and credits

  • pervigil — cross-platform sleep inhibitor (MIT, by Oleksandr Zhuravlov).
  • pi — the extension host.

License

MIT © 2026 zxh126