The Host is a Linux process inside WSL Debian and the Tray is a Windows process, so keeping it running takes one thing on each side (ADR-0007):
- a systemd user service inside Debian, which runs the Host;
- a Task Scheduler entry at logon on Windows, which starts WSL and holds the distribution up, because WSL2 stops a distribution when its last process exits.
Install the Debian half first. Without it the Windows half starts a distribution that runs nothing.
Install:
firstmate service on # from an npm install
node src/cli.ts service on # from a cloneIt writes one file, ~/.config/systemd/user/firstmate.service (under
$XDG_CONFIG_HOME when that is set). The unit starts the program that ran the
command, with the absolute path of the node that ran it: the service's PATH
has no nvm, so a bare node there can be an older one. From an npm install it
starts the package's dist/main.js; from a clone, the clone's src/main.ts.
It then enables the service, starts it, and enables lingering so that the Host
runs without an interactive login. It refuses a Node older than 24.
Run it again after a new Node or a new install: it writes the unit again.
When loginctl refuses to enable lingering, it says so and gives the command
to run by hand: sudo loginctl enable-linger <user>.
Remove:
firstmate service offIt stops and disables the service and removes exactly the unit file. It leaves lingering on, because other services can depend on it.
Where there is no systemd, both say so and do nothing. Run the Host with
firstmate start instead.
Read what it is doing:
systemctl --user status firstmate
journalctl --user -u firstmate -fThe journal holds the Host's output and every Plugin Server's output with it. The Host keeps no logs of its own (ADR-0005).
Stop it, start it, restart it after editing a Plugin:
systemctl --user stop firstmate
systemctl --user start firstmate
systemctl --user restart firstmateA Plugin is read at start, so restarting the Host is how a new Plugin or a changed one is picked up.
Inside WSL, firstmate service on ends by printing the one command that
installs the logon task from where FirstMate really is, and firstmate service off prints the one that removes it. They only print: a command inside WSL does
not write Windows state. Run the printed command once, from Windows PowerShell.
From an npm install, the scripts are in the package's own windows/ directory,
so the command is of this form:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File `
\\wsl.localhost\Debian\<npm prefix>\lib\node_modules\@luan-afonso\firstmate\windows\install-logon-task.ps1From a clone, it names the clone's windows/ directory:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File `
\\wsl.localhost\Debian\home\<user>\.first-mate\windows\install-logon-task.ps1It works out the distribution from the path it is run from. It writes two
places: %LOCALAPPDATA%\FirstMate, holding a copy of the holder and the launch
shim, and one Task Scheduler entry named FirstMate Host.
The logon task is not the Tray's "Start at logon". The task holds WSL up so that the Host runs; the Tray's entry starts the window.
Start it now, without logging out:
Start-ScheduledTask -TaskName 'FirstMate Host'Remove it with the command firstmate service off prints, which runs
uninstall-logon-task.ps1 from the same windows/ directory:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File `
\\wsl.localhost\Debian\<path to FirstMate>\windows\uninstall-logon-task.ps1See it:
Get-ScheduledTask -TaskName 'FirstMate Host'
Get-ScheduledTaskInfo -TaskName 'FirstMate Host'The Tray is FirstMate's Windows program: a notification-area icon and a window of FirstMate's own. It needs Node 24 on Windows.
Install, from Windows PowerShell:
npm install -g @luan-afonso/firstmate
firstmate desktopfirstmate desktop works out the distribution and the Host's home directory
itself, so there is nothing to type beyond that. It asks the distribution once,
the first time it needs to, and every later run finds the same runtime file on
its own (see "Open it from Windows" in README.md).
Turn on start at logon from the icon's own menu, "Start at logon".
Update it the same way:
npm install -g @luan-afonso/firstmateQuit the Tray first, from the icon's menu. npm install -g replaces the whole
package directory the program runs out of, and a running program holds those
files open.
Run the code in this repository instead of the published package:
# in WSL, inside the repository
npm pack --pack-destination /tmp# on Windows, over \\wsl.localhost\
npm install -g \\wsl.localhost\Debian\tmp\luan-afonso-firstmate-<version>.tgzRemove it:
-
Turn "Start at logon" off from the icon's menu.
-
Quit the Tray, from the icon's menu.
-
npm uninstall -g @luan-afonso/firstmate -
Remove the registry key the Tray writes so its Windows pop-ups say "FirstMate" and wear its mark (
src/notice-helper.ts). Nothing removes it on its own:Remove-Item -LiteralPath 'HKCU:\Software\Classes\AppUserModelId\LuanAfonso.FirstMate' -Recurse -Force
What the icon does:
| Action | What happens |
|---|---|
| Click | Opens the window, on the Index Page or where it last was. |
| Open FirstMate | The same. |
| Plugins | Every Plugin and its state. Opens any one in the window. |
| Start, Restart | Asks systemd inside the distribution. The label says which. |
| Start at logon | Writes or removes one file in the Startup folder. |
| Quit | Removes the icon. The Host keeps running. |
The anchor is green when the Host answered and admitted the program's token, and grey when it did not. The tooltip names the state and, when running, the port.
The program asks the Host itself rather than only asking whether the port is open, because the port is fixed and the token is not: the Host mints a new one at every start, so a run can end and be replaced without the port ever stopping answering. A refused token sends the program back to the runtime file at once, and the window returns to the page it was on at the address the new run answers.
Reading that file crosses into the distribution, so a Host that does not answer
at all is read again no more than every thirty seconds, and wsl.exe is asked
whether the distribution is running before any path into it is touched. Looking
at FirstMate does not wake WSL.
Start at logon is one file, FirstMate.vbs in the Startup folder. The menu
writes it, and turning the toggle off deletes exactly that file. Nothing is
scheduled, and the toggle writes nothing to the registry. The one key the Tray
writes is the one its pop-ups need, in step 4 above.
On Windows 11 a new notification-area icon starts hidden: click the chevron
(^) beside the clock and drag the FirstMate icon out to keep it on the
taskbar.
After logging out of Windows and back in, with nothing started by hand:
systemctl --user is-active firstmate # active
cat ~/.firstmate/runtime.json # the port and the token
curl "http://127.0.0.1:4747/?token=$(python3 -c 'import json;print(json.load(open("'"$HOME"'/.firstmate/runtime.json"))["token"])')"The Tray opens the same address without any of that typing (issue #9).