installer

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

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

func HasOwnedHook(body []byte, agent string) bool

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

func UninstallUser() (string, error)

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

func AdapterFor(id string) (Adapter, bool)

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

type HookUninstallResult struct {
	Removed []string
	Errors  []string
}

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

type LegacyMacHookFound struct {
	Event   string
	Command string
}

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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL