relay

package
v0.0.0-...-af439f5 Latest Latest
Warning

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

Go to latest
Published: Aug 1, 2026 License: AGPL-3.0 Imports: 39 Imported by: 0

Documentation

Overview

Package relay wires the agenthooks runtime to the Gram hooks backend. It builds an agenthooks.Runner whose handlers translate every coding-agent hook event into the canonical Gram ingest contract, POST it to the authenticated /rpc/hooks.ingest endpoint, and honor the server's allow/deny verdict. The server remains the sole authority on blocking; this package only relays events and enforces the returned decision.

Index

Constants

View Source
const DefaultServerURL = "https://app.getgram.ai"

DefaultServerURL is the production Gram endpoint used when no override is configured in the environment.

Variables

View Source
var BinaryVersion = "dev"

BinaryVersion is stamped by the main package with the release version so device telemetry can attribute behavior to the exact binary build.

Functions

func NewRunner

func NewRunner(cfg Config) *agenthooks.Runner

NewRunner constructs the agenthooks Runner: gating events (prompt.submitted, tool.requested) POST synchronously and honor deny; every other event is relayed as fire-and-forget telemetry. Handler failures fail open — a broken hook must never wedge the agent — and the credential ratchet governs the unauthenticated case.

func RunDrain

func RunDrain(ctx context.Context, out io.Writer) int

RunDrain is the `speakeasy-hooks drain` entrypoint. Exit 1 only when the control plane proved unreachable mid-run — the one outcome a retry fixes — so a supervising caller (the device agent's recovery trigger) logs it as incomplete. Skipped-only leftovers (no credential, newer entry schema) exit 0: retrying can't deliver them until the machine's state changes, and a permanent exit-1 would train operators to ignore the signal. The counts on stdout carry the detail either way.

func RunSkillFeedbackMCP

func RunSkillFeedbackMCP(ctx context.Context, cfg Config) error

RunSkillFeedbackMCP serves the speakeasy-skill-feedback MCP server over stdio. Generated plugin packages point their .mcp.json at this subcommand through the bootstrap script, so the agent-facing feedback tool authenticates with the same credential chain as hook ingestion instead of a key baked into the MCP config.

func RunSkillUpload

func RunSkillUpload(ctx context.Context, args []string, credential io.Reader) int

RunSkillUpload reads one bounded credential, reopens one manifest from a trusted parent-supplied root, and uploads it only if its full raw hash still matches the accepted activation.

func WritePlugin

func WritePlugin(ctx context.Context, provider, dir string, cfg PluginConfig) error

WritePlugin renders a provider hook package under dir that drives the speakeasy-hooks binary. provider is the agenthooks slug (claude-code, cursor, codex, opencode). For claude-code and cursor, dir is a plugin directory; for codex, which has no plugin layout for hooks, dir is the Codex home the config installs into; for opencode, dir receives an .opencode/plugin shim usable either as a project directory or referenced from an OpenCode config's plugin list.

Types

type Config

type Config struct {
	// ServerURL is the Gram API base, e.g. https://app.getgram.ai.
	ServerURL string
	// SiteURL is the dashboard origin the browser sign-in opens. Production
	// serves the API and dashboard from one domain, so this stays empty and
	// ServerURL is used; local development splits them across ports and needs
	// the override.
	SiteURL string
	// ProjectSlug routes events to a project via the Gram-Project header.
	ProjectSlug string
	// OrgID scopes the cached credential so a key minted for another org can
	// never authenticate this plugin (shared "default" slugs would otherwise
	// cross project boundaries).
	OrgID string
	// HooksAPIKey is the shared org-wide hooks key baked into a published
	// plugin. It is a fallback behind explicit and cached per-user keys and is
	// never persisted locally.
	HooksAPIKey string
	// BrowserLogin controls whether fresh machines may mint a per-user hooks
	// key through the dashboard. Published plugins default this off and use the
	// shared org key unless the organization opts in.
	BrowserLogin bool
	// Nonblocking is the legacy observability-mode flag still baked into
	// plugins published before the feature was removed. It is honored only as
	// the fail-open posture (unreachable/5xx allow); explicit deny decisions
	// and credential failures enforce regardless. New plugins never set it.
	Nonblocking bool
	// DebugLog, when set, appends one diagnostic line per event. It travels as a
	// command flag so it survives providers that scrub the hook environment.
	DebugLog string
	// ConfigPath records the speakeasy.json the config was loaded from, so the
	// login nudge can point the sign-in command at the same deployment identity
	// instead of the production defaults.
	ConfigPath string
	// ConfigError records a failure to read an explicitly referenced
	// speakeasy.json. The deployment identity is unknown, so events must not
	// fall back to the default server where a cached key could route them to
	// another workspace.
	ConfigError string
}

Config holds the deployment identity baked into a generated plugin. Every field has an environment override so a single binary can serve any project/org without recompilation, mirroring the env knobs the legacy bash senders read.

func LoadConfig

func LoadConfig(defaults Config) Config

LoadConfig resolves the effective config from the environment, layering the GRAM_HOOKS_* overrides over the provided defaults.

func SplitInlineFlags

func SplitInlineFlags(defaults Config, args []string) (Config, []string)

SplitInlineFlags extracts the deployment flags from a hook command's argv and returns the config they carry plus the remaining args to hand to agenthooks. The install packaging bakes only --config=<path>: deployment values must never ride argv because providers parse and filter hook commands (Cursor drops the whole hooks.json over a scheme:// URL, agenthooks quirk #29) and Codex trust-hashes the command definition. The remaining flags are manual overrides layered on top of the file.

type DrainSummary

type DrainSummary struct {
	// Replayed entries were accepted by the server and deleted.
	Replayed int
	// Dropped entries were deleted without acceptance: the server rejected
	// them definitively (4xx — a replay would fail identically), or the
	// file was corrupt and could never replay.
	Dropped int
	// Expired entries aged past spoolMaxAge and were deleted unsent.
	Expired int
	// Skipped entries were left in place for a later run: a newer schema
	// version this binary doesn't understand, or a deployment we currently
	// hold no credential for.
	Skipped int
	// Remaining is the entry count left on disk when the run ended.
	Remaining int
	// Aborted is true when the control plane proved unreachable mid-drain;
	// the rest of the backlog is kept rather than hammered.
	Aborted bool
}

DrainSummary reports one drain run's outcome.

func Drain

func Drain(ctx context.Context) DrainSummary

Drain replays the spool under the drain lock. Concurrent drains serialize; on Windows, where the lock is a no-op, a racing double-send is harmless because both carry the same Idempotency-Key.

type FileConfig

type FileConfig struct {
	ServerURL    string `json:"server_url"`
	SiteURL      string `json:"site_url,omitempty"`
	Project      string `json:"project"`
	Org          string `json:"org,omitempty"`
	HooksAPIKey  string `json:"hooks_api_key,omitempty"`
	BrowserLogin bool   `json:"browser_login,omitempty"`
	Nonblocking  bool   `json:"nonblocking,omitempty"`
}

FileConfig is the on-disk schema of a plugin's speakeasy.json. Evolution is strictly additive: readers ignore unknown keys and default missing ones, so a binary and a config file can skew in either direction. Never rename or repurpose a key.

type PluginConfig

type PluginConfig struct {
	ServerURL    string
	SiteURL      string
	ProjectSlug  string
	OrgID        string
	HooksAPIKey  string
	BrowserLogin bool
	BinaryPath   string
}

PluginConfig carries the deployment identity written into a provider package.

type Relay

type Relay struct {
	// contains filtered or unexported fields
}

Relay translates coding-agent hook events into Gram ingest requests and enforces the server's verdict.

func NewRelay

func NewRelay(cfg Config) *Relay

NewRelay builds a Relay from the resolved config.

func (*Relay) Login

func (r *Relay) Login(ctx context.Context, force bool) error

Login runs an interactive browser sign-in. force re-mints even when a credential is already cached. It backs the standalone login subcommand and the mid-session auth fallback.

Jump to

Keyboard shortcuts

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