Skip to content

Latest commit

 

History

History
118 lines (82 loc) · 7.05 KB

File metadata and controls

118 lines (82 loc) · 7.05 KB

Client external hooks (experimental)

Status: experimental. The hook protocol, environment variables, and payload fields may change or be removed in a future release without a major version bump. If you build integrations on this, pin a marchat version and watch release notes.

Client hooks run local executables you choose, fed one JSON object per event on stdin (newline-terminated). They are not server plugins: they only see what your client process sees, after decrypt on receive and before encrypt on send.

Why this exists

  • Server plugins automate hub-side behavior and see server-wide trust boundaries.
  • Built-in notifications cover in-TUI alerts with a fixed feature set.
  • Client hooks fill the gap: pipe chat events to your scripts, loggers, bridges, or external notification systems without new client dependencies or server changes.

Hooks are side-effect only: they cannot modify, block, or transform messages (runs are asynchronous).

Security and trust

  1. Plaintext: A receive hook runs after the client decrypts; a send hook runs with the plaintext you typed. Anyone who can read hook logs or the hook binary's behavior gets the same material as the TUI. This does not weaken wire encryption (the server still sees ciphertext for E2E traffic); it widens local exposure to whatever you execute.
  2. Absolute paths only: MARCHAT_CLIENT_HOOK_RECEIVE and MARCHAT_CLIENT_HOOK_SEND must be absolute paths to a regular file. Relative paths are rejected to avoid surprising PATH / working-directory behavior.
  3. Trust the binary: Hooks run as your user. Only point at programs you trust, same as running them manually.
  4. Timeouts: Each invocation is killed if it exceeds the configured timeout (default 5s, max 120s) so a stuck script does not pile up forever.
  5. File messages: Payloads include file metadata (filename, size) only; raw file bytes are never sent to hooks.

Environment variables

Variable Meaning
MARCHAT_CLIENT_HOOK_RECEIVE Absolute path to executable for inbound events (message_received).
MARCHAT_CLIENT_HOOK_SEND Absolute path to executable for outbound composer sends (message_send).
MARCHAT_CLIENT_HOOK_TIMEOUT_SEC Optional. Per-hook timeout in seconds (default 5, max 120).
MARCHAT_CLIENT_HOOK_RECEIVE_TYPING Set to 1, true, or yes to deliver typing indicators to the receive hook. Default: off (reduces log noise).
MARCHAT_CLIENT_HOOK_DEBUG Set to 1, true, or yes to log successful hook completion (duration) and label stdout as a debug preview.
MARCHAT_HOOK_LOG Optional log file path for the bundled example_hook binary only. The marchat client does not read this variable.

Unset hook paths mean that hook is disabled.

Diagnostics (-doctor)

Run marchat-client -doctor (or -doctor-json) to inspect your client environment:

  • The Environment section lists hook-related variables, including MARCHAT_HOOK_LOG for visibility, even though only example_hook uses it (with the same masking and truncation rules as other doctor output).
  • If MARCHAT_CLIENT_HOOK_RECEIVE or MARCHAT_CLIENT_HOOK_SEND is set, doctor runs a path check: the value must be an absolute path to an existing regular file, matching what the client enforces at runtime.

Server doctor does not list client-only hook variables, even if they are set in the environment (the server never reads them; hiding them avoids noise when client and server are run from the same shell).

Protocol

Transport

  • One UTF-8 JSON object per invocation, written to the process stdin, terminated with a single newline (\n).
  • The client waits for the process to exit, up to the timeout. Stdout and stderr are captured; non-empty stdout is logged at INFO (see debug flag above). A non-zero exit or timeout is logged as a failure.

Envelope

{
  "event": "message_received | message_send",
  "version": 1,
  "message": { }
}
  • version: Integer. Incremented when incompatible payload changes are introduced; until then, treat as 1.
  • event:
    • message_received: Fired for each inbound shared.Message on the chat WebSocket path after the client has applied decrypt when E2E is on. Typing is excluded unless MARCHAT_CLIENT_HOOK_RECEIVE_TYPING is enabled.
    • message_send: Fired for outbound text from the main composer path: global plaintext, global E2E (plaintext before encrypt), DMs, and : server/admin lines sent as AdminCommandType. Other UI actions (e.g. code snippet, file picker) may not invoke the send hook.

message object

Mirrors shared.Message fields where applicable, as a JSON object:

Field Notes
type e.g. text, dm, typing, reaction, admin_command, … May be empty if the server omitted it on older history.
sender, content, encrypted, message_id, recipient, edited, channel Same meaning as wire types.
created_at RFC3339 nano UTC. Omitted when the client has no real timestamp (zero time), so you will not see 0001-01-01 sentinels.
reaction Present for reaction events (emoji, target_id, is_removal).
file For file messages: { "filename", "size" } only; no data.

Filter scripts on message.type / event as needed.

Example: append-only logger

Build the bundled sample (from repo root). The directory is named _example_hook on purpose: Go omits paths whose first path element begins with _ (or .) or is named testdata from ./..., so this sample package main tree is not matched by go test ./... at the repository root. You still build or run it by passing that path explicitly, as below.

go build -o /tmp/marchat-hook-log ./client/exthook/_example_hook

Run the client with hooks (paths must be absolute on your OS):

export MARCHAT_CLIENT_HOOK_RECEIVE=/tmp/marchat-hook-log
export MARCHAT_CLIENT_HOOK_SEND=/tmp/marchat-hook-log
# Optional: override log path (otherwise uses $TMPDIR/marchat-client-hook.log)
export MARCHAT_HOOK_LOG=$HOME/marchat-hook.log
go run ./client

On Windows (PowerShell), use Resolve-Path for absolute paths and run go run ./client from the repository root (not go run .).

Use cases (illustrative)

  • Custom logging or archival to your own storage
  • Webhooks or bridges to other chat systems
  • Keyword-triggered local alerts beyond built-in notifications
  • Development and integration testing

Relationship to other systems

Mechanism Where it runs Typical use
Server plugins Server Commands, hub automation, shared bots
Client hooks Your machine Personal automation, local pipelines
Second WebSocket client Your machine / elsewhere Full bots, independent sessions

Stability

Treat this document and the version field as the reference for experiments. For production-like integrations, prefer discussing on GitHub issues so breaking changes can be coordinated.