{"id":"paseo-remote-power","repo":"georgepullen/paseo","url":"https://github.com/georgepullen/paseo/tree/main/plugins/paseo-remote-power","package":"paseo-remote-power","npm":{"package":"paseo-remote-power","version":"0.1.0","integrity":"sha512-ysCs9q7W+U4RSM+AISLb7tY24ob7IL/Wu/ivGsxPoxcT0k6hfnzLVJI/Knc299pKWoFfIiOzEXaTZf/seLaHhg==","publishedAt":"2026-10-08T09:37:29.376Z","downloadsLast30Days":377},"name":"paseo-remote-power","description":"Wake and status control for remote machines — homelab boxes, GPU workstations — from the Paseo daemon: roster UI plus embedded MCP tools (power_status, power_wake, power_job_status) so agents can wake a host before running work on it.","categories":["automation","monitoring","orchestration"],"platforms":["macos","linux"],"caveats":["SSH status probes need key-based ssh from the daemon host to each managed host","Wake-on-LAN needs an L2 path to the target network; use the http/udp/microlink transports for routed targets","The command transport runs arbitrary shell from host config — host config is trusted","Wake secrets (bearer tokens, microlink HMAC keys) live in the plugin state dir, never in host records","Embedded MCP power tools are injected into agents by default; disable in settings if unwanted"],"images":[],"themes":[],"health":{"manifestValid":true,"hasReadme":true,"hasLicense":true,"hasTests":true,"hasTypecheckScript":true,"updatedRecently":true},"scannedAt":"2026-10-12T11:28:41.840Z","addedAt":"2026-10-12T16:24:58+05:00","npmSecurity":{"status":"passed","blockingFindings":0,"advisoryFindings":0,"scannedAt":"2026-10-12T11:28:14.457Z","version":"0.1.0","integrity":"sha512-ysCs9q7W+U4RSM+AISLb7tY24ob7IL/Wu/ivGsxPoxcT0k6hfnzLVJI/Knc299pKWoFfIiOzEXaTZf/seLaHhg=="},"path":"plugins/paseo-remote-power","version":"0.1.0","security":{"status":"passed","blockingFindings":0,"advisoryFindings":0,"scannedAt":"2026-10-12T11:28:14.457Z","commit":"fde9912cf5636761ae580eb1d47d33ab70d3de93"},"license":"MIT","paseoVersionRequirement":">=0.10.0","descriptionNodes":[{"type":"text","text":"Wake and status control for remote machines — homelab boxes, GPU workstations — from the Paseo daemon: roster UI plus embedded MCP tools (power_status, power_wake, power_job_status) so agents can wake a host before running work on it."}],"caveatNodes":[[{"type":"text","text":"SSH status probes need key-based ssh from the daemon host to each managed host"}],[{"type":"text","text":"Wake-on-LAN needs an L2 path to the target network; use the http/udp/microlink transports for routed targets"}],[{"type":"text","text":"The command transport runs arbitrary shell from host config — host config is trusted"}],[{"type":"text","text":"Wake secrets (bearer tokens, microlink HMAC keys) live in the plugin state dir, never in host records"}],[{"type":"text","text":"Embedded MCP power tools are injected into agents by default; disable in settings if unwanted"}]],"manifest":{"id":"paseo-remote-power","requirements":{"paseo":">=0.10.0"},"build":[["npm","install","--omit=dev","--ignore-scripts","--no-audit","--no-fund","--no-workspaces"]]},"repoMeta":{"stars":0,"defaultBranch":"main","pushedAt":"2026-10-08T09:33:44Z"},"owner":{"login":"georgepullen","avatarUrl":"https://avatars.githubusercontent.com/u/90179633?v=4"},"installNotesHtml":"<p>Install it like any Paseo plugin (from the plugin directory or via the paseo\nCLI), then add your first host on the <strong>Remote power</strong> surface. Requirements:\n<code>paseo >= 0.10.0</code>, node >= 18.</p>","readmeText":"# paseo-remote-power\n\nWake and status control for remote machines — homelab boxes, GPU workstations,\nlab servers — from the Paseo daemon.\n\nThe plugin keeps a small roster of hosts. Each host has an ordered list of wake\ntransports (generic: an HTTP POST endpoint, a Wake-on-LAN magic packet, or a\nlocal command), a reachability probe (ssh `BatchMode`/`ConnectTimeout`, or a\ncustom status command that answers by exit code), and a bounded wake window\n(default 110s). Waking is idempotent: if the probe already says up, nothing is\nsent. You get:\n\n- a **roster surface** in the Paseo client — status dots (up / down / waking /\n  unknown, polled), a Wake button per host with live job progress, add/edit/\n  remove forms, and the recent wake-jobs list;\n- an **embedded MCP server** injected into every new agent (interactive or\n  scheduled) with three tools: `power_status`, `power_wake`, `power_job_status`\n  — so an agent can wake its machine before running work on it;\n- **settings**: default wake window, probe timeout, max concurrent wakes, and a\n  toggle for the agent injection.\n\n## Install\n\nInstall it like any Paseo plugin (from the plugin directory or via the paseo\nCLI), then add your first host on the **Remote power** surface. Requirements:\n`paseo >= 0.10.0`, node >= 18.\n\n## The host model\n\n| Field | Type | Meaning |\n| --- | --- | --- |\n| `id` | string | Stable id (`host-xxxxxxxx`, generated). |\n| `name` | string | Display name; unique per roster. |\n| `sshTarget` | string? | ssh alias or `user@host` used by the reachability probe. |\n| `statusCommand` | `{command, args?}?` | Optional probe run on the daemon host; exit 0 = up. Wins over ssh. |\n| `wakeWindowSeconds` | number? | Per-host wake window override (10–3600, default from settings). |\n| `wakeTransports` | array | Ordered wake transports; first success wins, then verification starts. |\n\nTransports:\n\n| Type | Fields | Behavior |\n| --- | --- | --- |\n| `http` | `url`, `tokenRef?` | `POST` to the endpoint; when `tokenRef` is set, the matching bearer token from the plugin state dir is sent as `Authorization: Bearer …`. |\n| `wol` | `mac`, `host?` | Standard 102-byte magic packet over UDP (port 9). `host` targets a subnet-directed or relay address; default is broadcast. |\n| `command` | `command`, `args?` | Runs a local command on the daemon host; exit 0 = accepted. |\n| `microlink` | `host`, `mode?`, `secretRef?`, `port?`, `scheme?`, `headerPrefix?` | HMAC-signed wake to a wake authority on an ESP32-S3: a signed `POST /v1/wake` over HTTP, or one signed UDP datagram on the tailnet (`mode: \"udp\"`). Defaults: `mode` `http`, `port` 48320. `secretRef` names the hex secret in the plugin state dir; the secret never appears in a host record. The authorization scheme and header names are configurable per host (defaults `Microlink-HMAC` / `X-Wake-`; older authorities may override scheme and header names per host). |\n\n### Example configuration\n\nEverything lives in the Paseo plugin state dir\n(`~/.paseo/plugin-data/xpufx/paseo-remote-power/`) at runtime — never in this\nrepository. Placeholder shapes:\n\n```json\n{\n  \"hosts\": [\n    {\n      \"id\": \"host-1a2b3c4d\",\n      \"name\": \"lab-gpu-box\",\n      \"sshTarget\": \"lab-gpu-box\",\n      \"wakeWindowSeconds\": 110,\n      \"wakeTransports\": [\n        { \"type\": \"http\", \"url\": \"http://192.0.2.10:8080/wake\", \"tokenRef\": \"lab-gpu-wake\" },\n        { \"type\": \"microlink\", \"host\": \"wake.tailnet.net\", \"mode\": \"udp\", \"secretRef\": \"wake-secret\" },\n        { \"type\": \"wol\", \"mac\": \"00:11:22:33:44:55\", \"host\": \"192.0.2.255\" },\n        { \"type\": \"command\", \"command\": \"/usr/local/bin/wake-lab-gpu\", \"args\": [\"--port\", \"9\"] }\n      ],\n      \"createdAt\": 1767000000000,\n      \"updatedAt\": 1767000000000\n    }\n  ]\n}\n```\n\nBearer tokens are stored **only** in `tokens.json` in the same state dir,\nkeyed by `tokenRef`. The roster form's write-only \"token\" field stores one for\nthe host and wires it to every `http` transport without an explicit ref;\n`hosts.json` itself never contains secrets. Microlink hex wake secrets work\nthe same way: `secretRef` names a key in `tokens.json`, resolved only when a\nwake runs, and never logged or echoed.\n\n### Reachability probe\n\n`statusCommand` wins over the microlink observer, which wins over ssh: a host\nwith a `microlink` transport is asked first via its unsigned\n`GET /v1/observer` (`target_awake: true` = up; no answer falls through to\nssh), so the device's own wake authority settles the question without an ssh\nround-trip.\n\n## Wake semantics (ported from a battle-tested wake script)\n\n1. **Idempotence.** Before waking, the host is probed. Already up → nothing is\n   sent; the API answers `up` / `alreadyUp`.\n2. **Ordered transports with fallback.** Transports run top to bottom; the\n   first success starts verification.\n3. **Bounded verification.** The engine polls the probe every 3s until the\n   host is reachable or the wake window (default 110s) expires, with one final\n   probe past the deadline.\n4. **Two distinct failure modes.** `no-transport` — nothing accepted the wake\n   request; `never-reachable` — a transport accepted the burst but the host\n   never came up inside the window. These are reported per job (`error.mode`)\n   so an agent can tell \"the wake path is broken\" from \"the machine is sick\".\n5. **Bounded concurrency.** At most `maxConcurrentWakes` jobs run at once;\n   a host with a wake in flight returns the existing job instead of stacking\n   another.\n\n## Agents: power_status / power_wake / power_job_status\n\nThe plugin registers an MCP server (stdio) at the `agent.create` gate, so\nevery agent the daemon spawns — including **scheduled agents** — gets the\ntools automatically. A scheduled agent that needs a machine awake before it\nworks looks like:\n\n1. `power_status({})` → is `lab-gpu-box` already up?\n2. If down: `power_wake({ hostId: \"host-1a2b3c4d\" })` → `{ jobId, status }`\n   immediately.\n3. Poll `power_job_status({ jobId })` until the job reports `up` or `failed`.\n4. On `failed`, read `error.mode`: `never-reachable` means the wake path\n   worked but the machine did not come up (worth retrying once or paging a\n   human); `no-transport` means the wake path itself is down.\n\nJobs are shared state (the plugin server and the MCP server coordinate through\nthe same jobs file in the plugin state dir), so a wake started from the roster\nUI is visible to agents and vice versa.\n\n## Settings\n\n| Setting | Default | Meaning |\n| --- | --- | --- |\n| `defaultWakeWindowSeconds` | 110 | Verification window for wake jobs. |\n| `probeTimeoutSeconds` | 8 | Connect timeout for the ssh probe. |\n| `maxConcurrentWakes` | 2 | Global cap on simultaneous wake jobs. |\n| `agentInjectionEnabled` | true | Inject the power tools into new agents (reload to apply). |\n\n## Caveats worth knowing before you rely on it\n\n- **ssh probing needs key auth from the daemon host.** The probe runs\n  `ssh -o BatchMode=yes -o ConnectTimeout=<n> <target> true` — set up the\n  key/alias on the machine running the Paseo daemon, or use `statusCommand`\n  instead.\n- **WoL needs an L2 path** to the target's broadcast domain, or a configured\n  target `host` (a subnet-directed address or a WoL relay). Waking across the\n  internet with plain WoL does not work; put an `http` transport or a relay in\n  front of it.\n- **The `command` transport runs arbitrary shell on the daemon host.** Host\n  configuration is trusted operator input — anyone who can edit the roster can\n  run commands where the daemon runs.\n- **Tokens are bearer secrets.** They live in the state dir with 0600 files;\n  treat that directory accordingly. HTTP wake endpoints should be on a\n  trusted network or behind their own auth.\n- **Probes are best-effort.** \"unknown\" (no probe configured, or the probe\n  itself errored) is reported honestly rather than guessed.\n\n## Development\n\n```sh\nnpm run typecheck   # tsc --noEmit\nnpm test            # typecheck + node --test suites (unit + stdio protocol)\n```\n\nTests are hermetic: fixture state dirs and fake spawners/sockets, never a real\nnetwork or the developer's home.\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n"}