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.
75 lines
3.4 KiB
Markdown
75 lines
3.4 KiB
Markdown
# Caffeinate
|
|
|
|
> Keep your computer awake from [Vicinae](https://vicinae.com). One toggle, no terminal.
|
|
|
|

|
|
|
|
## Why this exists
|
|
|
|
The Raycast [`coffee`](https://www.raycast.com/mooxl/coffee) extension does not work on Vicinae. Its `index` command is a `menu-bar` command, which Vicinae cannot run at all, and its `status` command crashes because it gates on `LocalStorage.getItem(key) === undefined` — Vicinae resolves a missing key to `null` instead of `undefined`.
|
|
|
|
This is a native `@vicinae/api` extension, so none of that applies.
|
|
|
|
## Commands
|
|
|
|
| Command | Mode | What it does |
|
|
| --- | --- | --- |
|
|
| **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
|
|
|
|
Sleep prevention is an OS-level assertion, so the extension delegates to the platform tool and then gets out of the way:
|
|
|
|
| Platform | Mechanism |
|
|
| --- | --- |
|
|
| macOS | `/usr/bin/caffeinate -i [-d] [-s]` |
|
|
| Linux | `systemd-inhibit --what=idle:sleep --why=<reason> sleep infinity` |
|
|
|
|
Both run until signalled — there is no timer, so nothing needs to be refreshed or babysat.
|
|
|
|
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 (killed elsewhere, or a reboot), the stale state is cleared on the next read.
|
|
|
|
## Preferences
|
|
|
|
| Preference | Default | Notes |
|
|
| --- | --- | --- |
|
|
| 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. |
|
|
|
|
## Development
|
|
|
|
```sh
|
|
npm install
|
|
npm run dev # vici develop — Vicinae must be running
|
|
```
|
|
|
|
Other scripts: `npm run lint` (manifest), `npm run lint:code` (ESLint), `npm run typecheck`, `npm test`.
|
|
|
|
To install a production build into Vicinae:
|
|
|
|
```sh
|
|
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` 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
|
|
|
|
MIT
|