Documentation
¶
Overview ¶
Hook config installer — patches ~/.claude/settings.json (and codex/grok equivalents) with the hook entries that route agent events into the daemon via the `rmote-agent hook-forward` subcommand.
Pairs with the curl installer and user-service installer. Once `install --hooks` runs, agent CLIs on the host will fire hook events at the daemon → bus → iOS long-poll → Live Activity / notification.
Pattern matches scripts/install-cli-aliases.sh:
- marker-delimited managed block (so re-runs replace cleanly)
- backup file before write
- --uninstall restores by stripping the managed block
Hook config shape per agent (Claude example):
{
"hooks": {
"UserPromptSubmit": [{"type":"command","command":"rmote-agent hook-forward --agent claude"}],
"Stop": [{"type":"command","command":"rmote-agent hook-forward --agent claude"}],
"PostToolUse": [{"type":"command","command":"rmote-agent hook-forward --agent claude"}]
}
}
Package installer writes launchd/systemd unit files for the rmoted daemon. `rmoted install --user` detects OS, writes the right unit to the user-level location, force-reloads the supervisor so the new unit takes effect immediately, and prints next-step instructions.
User-level (not system) on purpose:
- The daemon reads ~/.rmote/agent/secret (mode 0600) — system-level would need a separate user, breaking the "same user owns CLI + daemon + agent transcripts" invariant that lets the daemon read ~/.claude/projects/.
- launchd's LaunchAgents and systemd's --user units both run as the current user without sudo, matching the install-script's no-sudo flow.
Both service templates keep the daemon on loopback by default. Direct LAN exposure is an explicit runtime choice because the protocol does not provide transport encryption; remote clients should normally use SSH or a trusted private-network tunnel.
Non-destructive on re-runs: a prior plist's EnvironmentVariables block is extracted and merged into the new template, so user customizations survive `rmoted install --user` on upgrade.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DetectBinaryInPath ¶
func DetectBinaryInPath() string
DetectBinaryInPath reports whether `rmoted` (or the pre-rename legacy `rmote-agent`) resolves via $PATH. Used by main.go's `install` flow to warn before writing a unit that references an absolute path the user might move later. Returns the first hit, preferring the current name.
func HasOwnedHook ¶
HasOwnedHook verifies an exact Rmote-owned invocation for one agent. It is intentionally stricter than a substring search so foreign commands cannot make support state appear configured.
func UninstallUser ¶
UninstallUser unloads/disables the supervisor job before removing its unit file. It leaves daemon state under ~/.rmote/agent untouched.
Types ¶
type Adapter ¶
type Adapter interface {
Configure([]HookTarget) *HookInstallResult
Remove([]HookTarget) *HookUninstallResult
}
Adapter isolates configuration ownership for one agent. The compatibility hook facade remains shared, while each agent registers its target adapter here so support-state orchestration stays generic.
func AdapterFor ¶
type HookInstallResult ¶
type HookInstallResult struct {
Patched []string // settings files modified
Skipped []string // agents present but settings file missing (init the agent first)
}
HookInstallResult is what `install --hooks` returns for printing.
func PatchAll ¶
func PatchAll(targets []HookTarget) *HookInstallResult
PatchAll patches every enabled target. Skips disabled ones (returns them in Skipped). Errors per-target are accumulated — one missing settings file shouldn't abort the rest.
The absolute binary path is resolved once here (via os.Executable) and threaded through to every patched hook command. Hook subprocesses don't source ~/.zshrc, so PATH-based lookups fail; absolute path is the only reliable cross-shell solution.
type HookTarget ¶
type HookTarget struct {
Name string // "claude" | "codex" | "grok"
Path string // absolute; check existence before patching
Enabled bool // skip if agent not installed on host
}
HookTarget describes one agent's settings file: path + how to merge. Claude/Codex/Grok each have different file locations and JSON shapes; this struct isolates those differences.
func DiscoverHookTargets ¶
func DiscoverHookTargets() []HookTarget
DiscoverHookTargets returns the installable hook targets on this host. Targets whose ~/.{agent} dir is absent are returned with Enabled=false so the caller can surface "skipped: claude not installed" rather than silently writing into a stale layout.
type HookUninstallResult ¶
HookUninstallResult reports every target that was cleaned and every target that could not be verified. Callers must treat Errors as incomplete cleanup; silently removing the binary after an unreadable settings file would leave a dead hook behind.
func UninstallAll ¶
func UninstallAll(targets []HookTarget) *HookUninstallResult
UninstallAll strips daemon-owned hook wrappers from every target. It recognizes both rmoted and the legacy rmote-agent name, preserves foreign wrappers, and records malformed or unwritable settings files instead of swallowing them.
type LegacyMacHookFound ¶
LegacyMacHookFound is one Mac-written hook command located by FindLegacyMacHooks, with the event that would lose it.
type LegacyMacHookReport ¶
type LegacyMacHookReport struct {
Target string
Path string
Commands []LegacyMacHookFound
}
LegacyMacHookReport lists the Mac-written hooks in one settings file.
func FindLegacyMacHooks ¶
func FindLegacyMacHooks(targets []HookTarget) []LegacyMacHookReport
FindLegacyMacHooks reports the retired app's hook entries without writing anything. Backs the dry-run preview so the exact set can be reviewed before a config the agents read on every event gets rewritten.
Shares entryIsLegacyMacHook with UninstallLegacyMacHooks, so the preview can't claim a different set than the removal touches.
type LegacyMacRemoval ¶
type LegacyMacRemoval struct {
Target string
Path string
// Entries is the number of individual Mac hook commands removed.
Entries int
// Wrappers is the number of wrapper objects dropped because removing the
// Mac entry left them with no commands at all. Wrappers that also held a
// user hook are kept and only shrunk, so Wrappers <= Entries.
Wrappers int
}
LegacyMacRemoval reports what was stripped from one settings file.
func UninstallLegacyMacHooks ¶
func UninstallLegacyMacHooks(targets []HookTarget) []LegacyMacRemoval
UninstallLegacyMacHooks strips the retired app's hook entries from every enabled target, leaving the daemon's own hooks and the user's hooks intact.
Removal is entry-granular, not wrapper-granular: the retired app sometimes appended its command into a wrapper that already held a user hook (e.g. claude's SessionStart carries ddev-context.cjs and the Mac cjs in one wrapper). Dropping whole wrappers would take those user hooks with it.
Returns one entry per file actually modified. Files with nothing to strip are left untouched — no rewrite, no backup churn.
type UserInstallResult ¶
type UserInstallResult struct {
UnitPath string
StartCmd string // what the user runs to enable + start
StopCmd string
StatusCmd string
LogCmd string // tail the daemon's structured log
}
UserInstallResult is what `install --user` returns to main.go for printing next steps. Each target is a unit file path that was written.
func InstallUser ¶
func InstallUser() (*UserInstallResult, error)
InstallUser writes the appropriate unit file for the current OS and returns next-step instructions. Returns an error if the OS is unsupported or the unit file can't be written (perms, missing parent).