5 Commits
Author SHA1 Message Date
adrianbonpin bd9b636791 Add MIT LICENSE file (#1) 2026-09-21 06:27:48 +00:00
adrianbonpin 14624d78fe Prevent display sleep by default
`caffeinate -i` blocks system idle sleep but lets the display blank, so the
screen still slept and the extension looked broken. The "Also Prevent Display
Sleep" preference that fixes this defaulted to off.

Flip it to on, and guard it with a manifest test that reads package.json and
asserts the shipped defaults map to `-i -d`. Verified on macOS: `caffeinate -i`
yields PreventUserIdleDisplaySleep 0, `caffeinate -i -d` yields 1.
2026-09-20 21:17:14 +08:00
adrianbonpin 5ca37fbc79 Make orphaning structurally impossible: the process is the state
The extension used to record the spawned pid in LocalStorage, which meant
two pieces of state had to stay in sync: the record and the process. When
the record was lost (extension reinstalled, storage cleared, a crash
between spawn and save) the process kept holding a sleep assertion that
nothing could ever release — a machine that silently never sleeps. That
happened for real during development.

Fix it by removing the thing that can be lost rather than guarding it:
the process now carries its own identity, so the OS is the single source
of truth.

Each session is tagged in its own command line:

  vicinae-caffeinate:v1:<startedAt>:<flags>   e.g. ...:1789895271765:id

- macOS: caffeinate has no free-form field, so the marker is installed as
  argv[0] via the shell's `exec -a`. exec replaces the shell in place, so
  the pid Node receives is the caffeinate pid. (Verified that both `ps -o
  command=` and `ps -o comm=` surface the custom argv[0].)
- Linux: the marker rides in systemd-inhibit's --why next to a
  human-readable reason.

status is now a `ps` scan for the marker; stop signals every match. This
deletes lib/state.ts and every LocalStorage call — the extension is
stateless, which also retires the Vicinae null-vs-undefined storage quirk
as a hazard for this codebase.

Also:
- Signal the process group (-pid) rather than a single pid. Sessions are
  spawned detached so the child is a group leader, and on Linux
  systemd-inhibit forks a `sleep` that a parent-only signal can leave
  behind.
- start() clears existing sessions first, so assertions cannot stack.
- If the spawn never registers a marker, roll back instead of leaving a
  half-started session.
- Drop the now-meaningless "stale" concept; add `extra` so an unexpected
  duplicate surfaces in the status view instead of being invisible.
- Add describeFlags() to render flag letters for humans.

Verified by deleting the installed extension while a session was active,
restarting Vicinae, then reinstalling: the fresh install saw the leftover
as ACTIVE and stopped it rather than starting a second and orphaning the
first. A user-owned `caffeinate -u -dmi` was untouched throughout.

vici lint, tsc, eslint, 17/17 vitest, vici build all green; live toggle
on/off round-trips cleanly.
2026-09-20 17:08:19 +08:00
adrianbonpin a303e161d3 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.
2026-09-20 17:01:30 +08:00
adrianbonpin 65005b22a6 Add native Vicinae caffeinate extension
Keeps the computer awake from Vicinae. Written against @vicinae/api
directly because the Raycast `coffee` extension cannot work here:

- Its `index` command is `menu-bar` mode, which Vicinae does not support.
- Its `status` command crashes on `Cannot read properties of null
  (reading 'IsManuallyDecafed')` because it gates on
  `LocalStorage.getItem(key) === undefined`. Vicinae resolves a missing
  key to `null`, not `undefined` (verified with a runtime probe).

Sleep prevention is an OS-level assertion, so the extension delegates and
gets out of the way:

- macOS:  caffeinate -i [-d] [-s] [-t <seconds>]
- Linux:  systemd-inhibit --what=idle:sleep --why=<reason> sleep <n|infinity>

The process is spawned detached + unref'd, because Vicinae unloads a
no-view command as soon as its function returns. Timed sessions need no
babysitter: `caffeinate -t` and `sleep <n>` self-terminate.

Commands: toggle (no-view), status (view), caffeinate-for (view).

Two deliberate safety choices:
- Stopping signals the recorded PID rather than running `killall
  caffeinate`, so a caffeinate the user started themselves survives.
  Verified against a real concurrent `caffeinate -u -dmi` process.
- The PID's name is re-checked with `ps -o comm=` before signalling, since
  PIDs get recycled and a stale entry could otherwise kill something
  unrelated.

Also: display-sleep prevention is macOS-only, and the UI says so on Linux
rather than pretending systemd-inhibit can hold the display awake.

Verified: vici lint, tsc --noEmit, eslint, 23/23 vitest, vici build, and
live in Vicinae — toggle starts `caffeinate -i` which survives the
command unloading, toggle again stops only ours, both view commands load
with clean stdout/stderr.
2026-09-20 16:16:57 +08:00