Reduce to a pure on/off extension

Durations were the only thing keeping a lot of machinery alive, so drop
them and let the dead code go with them.

Removed:
- The "Caffeinate for a Duration" command.
- lib/duration.ts entirely (parseDuration / formatDuration /
  formatRemaining / normalizeDuration / DURATION_PRESETS) plus its test
  file. Every export was reachable only from duration paths.
- The 30s setInterval re-render in the status view. It existed only to
  keep a "27m left" countdown honest, and with no timed sessions there is
  nothing to count down.
- The duration-preset actions in the status view.
- describeOptions(), which only the duration form used to explain itself.
- durationSeconds / expiresAt from the options, state and command
  builder, along with the `-t <seconds>` and `sleep <n>` branches. Both
  platforms now hold the assertion until signalled, which is what "on"
  means here.
- The unused "restarted" member of ToggleResult.

The status view now reports "since <time>" instead of a countdown, and
keeps a Refresh action.

Also fixes a type error the narrower helper surfaced: the status view's
run() helper was typed () => Promise<void> while startCaffeination
returns Promise<CaffeinateState> and stopCaffeination returns
Promise<boolean>.

407 deletions against 57 insertions.

Verified: vici lint, tsc --noEmit, eslint, 8/8 vitest, vici build, and
live in Vicinae — toggle on spawns `caffeinate -i`, toggle off stops it,
and a pre-existing user-owned `caffeinate -u -dmi` is left untouched
throughout.
This commit is contained in:
2026-09-20 17:01:30 +08:00
parent 65005b22a6
commit a303e161d3
10 changed files with 57 additions and 407 deletions
+18 -18
View File
@@ -1,6 +1,6 @@
# Caffeinate
> Keep your computer awake from [Vicinae](https://vicinae.com). Start, stop, and time-limit sleep prevention without touching a terminal.
> Keep your computer awake from [Vicinae](https://vicinae.com). One toggle, no terminal.
![Caffeinate icon](./assets/caffeinate.png)
@@ -14,9 +14,8 @@ This is a native `@vicinae/api` extension, so none of that applies.
| Command | Mode | What it does |
| --- | --- | --- |
| **Toggle Caffeination** | no-view | Starts or stops sleep prevention, whichever is appropriate. |
| **Caffeination Status** | view | Live readout of whether sleep is being blocked, for how much longer, and the exact command holding it. Start/stop from the actions panel. |
| **Caffeinate for a Duration** | view | Keeps the computer awake for a set time, then lets it sleep again automatically. |
| **Toggle Caffeination** | no-view | Flips sleep prevention on or off, whichever is appropriate. The fast path. |
| **Caffeination Status** | view | Whether sleep is being blocked, since when, and the exact command holding it. Start/stop from here. |
## How it works
@@ -24,16 +23,18 @@ Sleep prevention is an OS-level assertion, so the extension delegates to the pla
| Platform | Mechanism |
| --- | --- |
| macOS | `/usr/bin/caffeinate -i [-d] [-s] [-t <seconds>]` |
| Linux | `systemd-inhibit --what=idle:sleep --why=<reason> sleep <seconds\|infinity>` |
| macOS | `/usr/bin/caffeinate -i [-d] [-s]` |
| Linux | `systemd-inhibit --what=idle:sleep --why=<reason> sleep infinity` |
The process is spawned **detached and unref'd**. That is required: Vicinae unloads a `no-view` command as soon as its function returns, so a child process would otherwise be torn down with it. Timed sessions need no babysitter — `caffeinate -t` and `sleep <seconds>` self-terminate when the window elapses.
Both run until signalled — there is no timer, so nothing needs to be refreshed or babysat.
State is recorded in `LocalStorage` as `{ pid, startedAt, expiresAt, command, args, processName, … }`.
The process is spawned **detached and unref'd**. That is required: Vicinae unloads a `no-view` command as soon as its function returns, so a child process would otherwise be torn down with it.
State is recorded in `LocalStorage` as `{ pid, startedAt, command, args, processName, … }`.
Stopping signals the **recorded PID**, not `killall caffeinate`, so a `caffeinate` you started yourself in a terminal is left alone. Before signalling, the PID's process name is re-checked via `ps -o comm=` — PIDs get recycled, and without that check a stale entry could kill an unrelated process.
Status is self-healing: if the recorded process is gone (timed out, or killed elsewhere), the stale state is cleared on the next read.
Status is self-healing: if the recorded process is gone (killed elsewhere, or a reboot), the stale state is cleared on the next read.
## Preferences
@@ -42,12 +43,6 @@ Status is self-healing: if the recorded process is gone (timed out, or killed el
| Also Prevent Display Sleep | off | macOS only. `systemd-inhibit` cannot hold the display awake — that is the compositor's job, and the UI says so rather than pretending. |
| Also Prevent System Sleep | off | On macOS this assertion only takes effect while on AC power. |
## Duration formats
A bare number means **minutes**. Otherwise: `45m`, `2h`, `90s`, `1d`, `1h30m`, or friendly words like `1 hour` / `30 minutes`.
The whole string must parse — `1h30` is rejected rather than silently read as `1h`.
## Development
```sh
@@ -63,11 +58,16 @@ To install a production build into Vicinae:
npm run build # writes to ~/.local/share/vicinae/extensions/caffeinate
```
## No top bar indicator
Vicinae has no menu-bar command support. This is not a Raycast-compat gap — a native `mode: "menu-bar"` command is accepted by the manifest and builds cleanly, then is **silently dropped** at registration, with no warning. `@vicinae/api` exports a `MenuBarExtra` component, but there is no host to render it into.
For a real top bar indicator you need a standalone macOS menu-bar app; it cannot be part of a Vicinae extension.
## Not implemented
- **Windows.** `detectPlatform` throws with a clear message instead of failing obscurely. `powercfg` support would be the path.
- **While-an-app-is-running / scheduled windows.** `caffeinate -w <pid>` covers the former on macOS; neither is wired up.
- **A persistent menu bar indicator** — Vicinae has no menu bar support. Use **Caffeination Status** instead.
- **Windows.** `detectPlatform` throws with a clear message instead of failing obscurely. `powercfg` would be the path.
- **Timed windows** (`caffeinate -t`), **while-an-app-is-running** (`caffeinate -w <pid>`), and **scheduled** windows. Deliberately omitted — on/off covers the common case.
## License