Documentation
¶
Overview ¶
Package devserver — Stripe mock backend.
This file implements just enough of Stripe's HTTP API to round-trip cleanly through the official stripe-go SDK. Apps point stripe-go at the dev proxy via stripe.SetBackend(...) with BackendConfig.URL = "<HAMR_DEV_URL>/__hamr/stripe", and stripe-go appends paths like /v1/checkout/sessions to that base.
The mock is dev-only and has no production safeguards beyond the gating done by hamr.toml [dev.stripe].
Index ¶
- Constants
- Variables
- func CheckLatestVersion(ctx context.Context, currentVersion string, onResult func(latest string))
- func ComposeArgs(dc *DockerCompose) []string
- func HamrDevTag() string
- func ListenAndServeProxy(addr string, handler http.Handler) (*http.Server, net.Listener, error)
- func MCPAreaNames() []string
- func MakefileTargetsFromPath(path string) ([]string, error)
- func NewProxyHandler(target string, broker *SSEBroker, errorState *ErrorState, logBuf *LogBuffer, ...) http.Handler
- func PrefsPathFor(configPath string) string
- func ReadDotenvKey(path, key string) (string, bool)
- func ResolveEnvRewrites(dir string) ([]string, error)
- func RewriteValueForWalks(dir, value string) string
- func RunMockServe(ctx context.Context, logger *slog.Logger) error
- func WaitForConfigChangeOrQuit(ctx context.Context, path string, hotkeys <-chan HotkeyAction) error
- type Config
- type ConsoleFrame
- type ConsoleSink
- type Daemon
- type DevActions
- func (a *DevActions) Broker() *SSEBroker
- func (a *DevActions) CheckRestart() error
- func (a *DevActions) DockerComposes() []DockerCompose
- func (a *DevActions) DockerWipe(dc *DockerCompose, service string)
- func (a *DevActions) ErrorState() *ErrorState
- func (a *DevActions) RebuildAll()
- func (a *DevActions) RegisterRoutes(mux *http.ServeMux)
- func (a *DevActions) RestartServer() bool
- func (a *DevActions) RunMake(target string) (<-chan MakeResult, func())
- type DevConfig
- type DockerCompose
- type Duration
- type EmailConfig
- type ErrorState
- type FileEvent
- type Graph
- type HotkeyAction
- type HotkeySource
- type LogBuffer
- type LogLine
- type MCPConfig
- type MCPHandshake
- type MailMock
- func (m *MailMock) Clear()
- func (m *MailMock) Delete(id string) bool
- func (m *MailMock) Get(id string) *mailMessage
- func (m *MailMock) List() []*mailMessage
- func (m *MailMock) RegisterIngestRoutes(mux *http.ServeMux)
- func (m *MailMock) RegisterRoutes(mux *http.ServeMux)
- func (m *MailMock) RegisterUIRoutes(mux *http.ServeMux)
- func (m *MailMock) SetStatus(id, status, note string) bool
- type MailMockOptions
- type MakeResult
- type MockProvider
- type MountedMock
- type Option
- func WithActionsHook(fn func(*DevActions)) Option
- func WithConfigPath(path string) Option
- func WithDockerLogSinks(sinks map[string]io.Writer) Option
- func WithHotkeys(h HotkeySource) Option
- func WithLogWriter(w io.Writer) Option
- func WithLogger(l *slog.Logger) Option
- func WithMCPLogHook(fn func(line string)) Option
- func WithMCPStatusHook(fn func(enabled bool, tools int)) Option
- func WithNoProxy(v bool) Option
- func WithProcessOutput(stdout, stderr io.Writer) Option
- func WithProxyURLHook(fn func(string)) Option
- func WithVerbose(v bool) Option
- type ProcessManager
- func (pm *ProcessManager) ClearCallbacks()
- func (pm *ProcessManager) RunCommand(ctx context.Context, rule *WatchRule) (string, error)
- func (pm *ProcessManager) SetFileLog(w io.Writer)
- func (pm *ProcessManager) SetInjectedEnv(env []string)
- func (pm *ProcessManager) SetLogOutput(buf *LogBuffer, broker *SSEBroker)
- func (pm *ProcessManager) SetOutputSinks(stdout, stderr io.Writer)
- func (pm *ProcessManager) StartProcess(ctx context.Context, rule *WatchRule) error
- func (pm *ProcessManager) StopAll()
- type ProxyConfig
- type ReloadScope
- type RequestLog
- type RequestLogEntry
- type Runner
- type SMSConfig
- type SMSMock
- func (m *SMSMock) Clear()
- func (m *SMSMock) Delete(id string) bool
- func (m *SMSMock) Get(id string) *smsMessage
- func (m *SMSMock) List() []*smsMessage
- func (m *SMSMock) RegisterIngestRoutes(mux *http.ServeMux)
- func (m *SMSMock) RegisterRoutes(mux *http.ServeMux)
- func (m *SMSMock) RegisterUIRoutes(mux *http.ServeMux)
- func (m *SMSMock) SetStatus(id, status, note string) bool
- type SMSMockOptions
- type SSEBroker
- type SSEEvent
- type StringOrSlice
- type StripeAccountSummary
- type StripeConfig
- type StripeLineItemSummary
- type StripeMock
- func (m *StripeMock) FireEvent(ctx context.Context, eventType string, dataObject map[string]any) error
- func (m *StripeMock) RegisterAPIRoutes(mux *http.ServeMux)
- func (m *StripeMock) RegisterRoutes(mux *http.ServeMux)
- func (m *StripeMock) RegisterUIRoutes(mux *http.ServeMux)
- func (m *StripeMock) SetWebhookEndpoint(ep WebhookEndpoint)
- type StripeMockOptions
- type StripeObjectSummary
- type StripeSessionSummary
- type StripeStateSummary
- type TunnelConfig
- type VersionStatus
- type WatchRule
- type Watcher
- type WebhookEndpoint
Constants ¶
const ( StripeModeOff = "off" StripeModeMock = "mock" StripeModeListen = "listen" )
const ( EvBuilding = "building" // Data: rule name EvBuildOK = "build_ok" // Data: rule name EvBuildError = "build_error" // Data: JSON {rule, output} EvOutput = "output" // Data: JSON {rule, text, color} EvReload = "reload" // Data: reload mode EvShutdown = "shutdown" // Data: "" EvDarkFilter = "dark_filter" // Data: "on" / "off" EvRestarting = "restarting" // Data: "" EvMakeStart = "make_start" // Data: target EvMakeDone = "make_done" // Data: "<target> <exit code>" EvComposeUp = "compose_up" // Data: compose entry name EvComposeOK = "compose_ok" // Data: compose entry name EvTunnelStart = "tunnel_start" // Data: "starting" / "stopping" EvTunnelUp = "tunnel_up" // Data: public URL EvTunnelDown = "tunnel_down" // Data: "" (off, or a start that failed) EvStripeMode = "stripe_mode" // Data: "mock" / "listen" / "switching" )
Event types broadcast on the broker. The broker is the dev server's one event bus: every consumer (browser dev panel, TUI, MCP) subscribes to it rather than being wired its own callback. See docs/adr/004-dev-event-bus.md.
Always broadcast with one of these constants — a free string here is a typo no compiler catches, and the mismatch only shows up as a consumer that silently never updates.
const ( TunnelCloudflared = "cloudflared" TunnelNgrok = "ngrok" )
const MCPHandshakeFile = ".hamr/dev.json"
MCPHandshakeFile is the per-project runtime descriptor the `hamr mcp` bridge reads to find and authenticate to this dev server. Mode 0600, gitignored. Path is relative to the project root.
const PrefsFileName = ".pref.hamr.toml"
PrefsFileName is the per-developer override read from the same directory as the main config. Gitignored by the scaffold: it holds preferences a developer wants locally without imposing them on the team.
Variables ¶
var ErrConfigReload = errors.New("config changed, reloading")
ErrConfigReload is returned by Run when the config file changes. The caller should reload the config and call Run again.
var ErrRestart = errors.New("restart requested")
ErrRestart is returned by Run when a restart is requested explicitly (the TUI's R hotkey or the dev.restart MCP tool). Identical handling to ErrConfigReload — the caller reloads the config and calls Run again — but a distinct sentinel so the caller can say why it is restarting.
Functions ¶
func CheckLatestVersion ¶
CheckLatestVersion checks GitHub for the latest hamr release and calls onResult with the latest version string if it is newer than current. The check runs in a goroutine and is best-effort — network errors are silently ignored.
func ComposeArgs ¶
func ComposeArgs(dc *DockerCompose) []string
ComposeArgs returns the `docker` arguments hamr itself uses for a compose entry — project directory, base file, and the generated port-walk override when one exists. Exported so `hamr compose` can hand external callers the same merged config the dev server is running, instead of them merging the base file alone and reconciling the stack back onto un-walked ports.
Paths are relative to the project root, so the caller must run docker from there.
func HamrDevTag ¶
func HamrDevTag() string
HamrDevTag returns the colored [hamr dev] tag string for use outside the logger.
func ListenAndServeProxy ¶
ListenAndServeProxy starts the proxy server. It blocks until the context is cancelled or an error occurs. Kept for tests and external callers that don't need the +1-on-busy port walking — the dev runner uses listenWalk + serveProxy directly so it can react to EADDRINUSE before constructing the handler.
func MCPAreaNames ¶
func MCPAreaNames() []string
MCPAreaNames returns every configurable [dev.mcp.access] area in a stable order. Exported for the `hamr setup` picker, which needs the canonical list without duplicating it.
func MakefileTargetsFromPath ¶
MakefileTargetsFromPath reads the Makefile at path and returns its declared target names in the order they appear. Pattern rules (containing '%'), variables (lines with ':=' / '+=' / '?='), comments, and recipe lines (starting with a tab) are ignored. The function does not parse includes or expand variables — the dev TUI's run overlay just needs the human-visible target list, not full make semantics.
Returns ([]string{}, nil) when the file does not exist so callers can gate UI on a non-error empty list.
func NewProxyHandler ¶
func NewProxyHandler(target string, broker *SSEBroker, errorState *ErrorState, logBuf *LogBuffer, actions *DevActions, mailMock *MailMock, smsMock *SMSMock, stripeMock *StripeMock, console *ConsoleSink, gateway *mcpGateway, requestLog *RequestLog, injectReload bool) http.Handler
NewProxyHandler creates an HTTP handler that reverse-proxies to the target address, optionally injecting the live reload script into HTML responses. The SSE broker handler is mounted at /__hamr/reload. If errorState is non-nil, HTML requests are intercepted with an error page when there are active build errors. If mailMock is non-nil, the mail inbox UI and ingest endpoint are mounted under /__hamr/mail. If smsMock is non-nil, the SMS equivalents are mounted under /__hamr/sms. If console is non-nil, the browser console transport is mounted as a WebSocket at /__hamr/console; the injected reload script connects to it and pipes window.console.* + uncaught errors back into the dev TUI/log.
func PrefsPathFor ¶
PrefsPathFor returns the override path that pairs with the given config file — a sibling of it, so `hamr dev --config /elsewhere/hamr.toml` reads /elsewhere/.pref.hamr.toml.
func ReadDotenvKey ¶ added in v0.37.0
ReadDotenvKey returns the first value for key in the .env file at path. ok is false on a miss or an unreadable file. It never touches the process env, so callers read credentials only where they need them.
func ResolveEnvRewrites ¶
ResolveEnvRewrites loads .hamr/walks.json and .env from dir, applies the active port-walk rewrite rules, and returns the rewritten KEY=VALUE pairs the spawned-children injection would emit. Empty result (nil, nil) when nothing walked or no .env present — consumers can use the result unconditionally; an empty slice makes their downstream a no-op.
This is the canonical entry point for callers outside the package (cmd/env, etc.). Match rules and limitations are documented on the per-rule helpers.
func RewriteValueForWalks ¶
RewriteValueForWalks applies the active port-walk rewrites to a single value (typically one read out of .env by hamr sync). Returns the value unchanged when no walks file is present or when nothing in the value matches a walked port. Errors loading walks.json are swallowed: a malformed file shouldn't break a CLI invocation that has a perfectly good fallback in the literal value the caller already has.
func RunMockServe ¶
RunMockServe stands up the selected mocks and serves until ctx is cancelled. Selection and all configuration come from environment variables.
func WaitForConfigChangeOrQuit ¶
func WaitForConfigChangeOrQuit(ctx context.Context, path string, hotkeys <-chan HotkeyAction) error
WaitForConfigChangeOrQuit blocks until the file at path is written or created (returns nil), ctx is cancelled, a HotkeyQuit comes in (returns context.Canceled), or a HotkeyRestart comes in (returns ErrRestart — retry now). Restart is honored here because a parse error is not the only reason the last attempt failed: the config can be valid and startup still fail on a clashing port or a bad .env, and neither of those touches hamr.toml, so waiting on a file write would hang forever. Other hotkeys are silently consumed. A nil hotkeys channel is safe (blocks forever).
Types ¶
type Config ¶
type Config struct {
Dev DevConfig `toml:"dev"`
Proxy ProxyConfig `toml:"proxy"`
// ProxyConfigured is true when [proxy] was explicitly present in the TOML.
// When false, the proxy is not started and no defaults are applied.
ProxyConfigured bool `toml:"-"`
}
Config is the top-level hamr.toml configuration.
func LoadConfig ¶
LoadConfig reads and parses a hamr.toml file, merges the per-developer .pref.hamr.toml override over it if present, applies defaults, and validates.
func LoadConfigNoPrefs ¶
LoadConfigNoPrefs is LoadConfig without the .pref.hamr.toml merge. Use it anywhere the loaded values are written back to hamr.toml — merging first would promote a developer's gitignored local preference into the team's committed config.
type ConsoleFrame ¶
type ConsoleFrame struct {
// Level is one of: "log", "info", "debug", "warn", "error".
// Internal categories the JS may send for non-console events:
// "rejection" (unhandled promise), "resource" (load failure),
// "csp" (CSP violation). Only "warn" and "error" get a colored
// uppercase level label in the rendered line; every other value
// (including the internal categories) renders without a label so
// the line stays scannable. This matches the backend slog handler's
// convention.
Level string `json:"level"`
// Msg is the rendered text (args joined client-side, objects already
// JSON-stringified, capped per-arg by the JS serializer).
Msg string `json:"msg"`
// Src is an optional source location, used only for uncaught errors
// where the file:line:col is load-bearing. Plain console.* calls
// leave it empty.
Src string `json:"src,omitempty"`
}
ConsoleFrame is one log entry sent up by the browser. The field set is kept narrow on purpose: anything more (timestamps, URLs, multi-tab IDs) goes through a different conversation.
type ConsoleSink ¶
type ConsoleSink struct {
// contains filtered or unexported fields
}
ConsoleSink is the dev-server side of the browser-console transport. It owns the WS endpoint and a single io.Writer (the same multiwriter the dev slog handler uses) so frames land in TUI tab 0 and the rolling dev_logs.txt file alongside backend events, in arrival order.
func NewConsoleSink ¶
func NewConsoleSink(w io.Writer, filterHamr bool) *ConsoleSink
NewConsoleSink wires the sink to the same writer as the dev logger. Pass filterHamr=true to drop frames whose msg contains "[hamr]" (i.e. hamr's own reload-script chatter); default is to show everything.
func (*ConsoleSink) Handler ¶
func (c *ConsoleSink) Handler() http.Handler
Handler returns the WS upgrade handler. Mount at /__hamr/console. Each frame on the wire is JSON; the wire format accepts either a single ConsoleFrame object or an array (the JS client batches small bursts to keep frame count down). Unparseable payloads are dropped silently — dev-only, not worth surfacing to the user.
Origin gating is the coder/websocket default: Origin must equal Host. In dev that's always true (the JS is injected by the same proxy that serves the WS). External callers hitting the endpoint cross-origin will be rejected at upgrade.
func (*ConsoleSink) Snapshot ¶
func (c *ConsoleSink) Snapshot(level, contains string, tail int) []consoleLine
Snapshot returns up to tail recent frames matching the level (exact, case- insensitive) and contains (substring on msg) filters, oldest first.
func (*ConsoleSink) Write ¶
func (c *ConsoleSink) Write(f ConsoleFrame)
Write renders a single frame and emits it through the dev writer. Empty messages and (when filtering) hamr-prefixed messages are dropped. Exported so tests can drive formatting without standing up a WS server.
type Daemon ¶
type Daemon struct {
Name string `toml:"name"`
Cmd string `toml:"cmd"`
Dir string `toml:"dir"`
Env []string `toml:"env"`
}
Daemon defines a long-running background process started once at launch.
type DevActions ¶
type DevActions struct {
// contains filtered or unexported fields
}
DevActions encapsulates API action handlers for the dev panel.
func (*DevActions) Broker ¶ added in v0.37.0
func (a *DevActions) Broker() *SSEBroker
Broker returns the dev server's event bus so in-process consumers (the TUI runtime) can Subscribe to the same stream the browser gets.
func (*DevActions) CheckRestart ¶ added in v0.36.0
func (a *DevActions) CheckRestart() error
CheckRestart reports whether a restart would be accepted right now, returning nil when it would and an explanation when it would not — no live runner, or the current run is still inside the restart cooldown. It changes nothing, so a caller can answer first and restart afterwards.
func (*DevActions) DockerComposes ¶
func (a *DevActions) DockerComposes() []DockerCompose
DockerComposes returns the configured docker compose entries the runner is managing.
func (*DevActions) DockerWipe ¶
func (a *DevActions) DockerWipe(dc *DockerCompose, service string)
DockerWipe triggers a "down -v + up -d" cycle for the given compose entry, removing volumes. When service is "" the whole entry is wiped; otherwise only that service. Runs synchronously on the calling goroutine — TUI callers should dispatch in a goroutine to keep the UI responsive.
func (*DevActions) ErrorState ¶
func (a *DevActions) ErrorState() *ErrorState
ErrorState returns the underlying error state so non-HTTP consumers (the TUI runtime) can subscribe to error changes.
func (*DevActions) RebuildAll ¶
func (a *DevActions) RebuildAll()
RebuildAll enqueues every watch rule onto the scheduler, which resolves topological order and dependency gating itself. Used by the hotkey system to trigger a full rebuild.
func (*DevActions) RegisterRoutes ¶
func (a *DevActions) RegisterRoutes(mux *http.ServeMux)
RegisterRoutes registers the action API endpoints on the given mux.
func (*DevActions) RestartServer ¶ added in v0.36.0
func (a *DevActions) RestartServer() bool
RestartServer asks the runner to tear down and re-run its entire startup lifecycle — docker compose, port resolution, .env injection, builds, daemons, watcher — without exiting the TUI. Use it for state the runner only reads at startup: a clashing port, an edited .env, a container that came up wrong. Returns false when no live runner is attached.
Returns as soon as the request is queued, not when the restart completes: the proxy that carried an MCP call is one of the things torn down, so the caller has to get its response out first.
ponytail: between teardown and the next Run's writeWalks(".", nil), walks.json still holds the old run's port rewrites — so `hamr env --export` (and the scaffold Makefile targets that shell it) can report pre-restart ports for a second or two. Same window config reload has always had. Clear walks at teardown instead if that ever bites.
func (*DevActions) RunMake ¶ added in v0.37.0
func (a *DevActions) RunMake(target string) (<-chan MakeResult, func())
RunMake runs `make <target>` through the ProcessManager, so its output reaches every consumer at once — the TUI viewport, the browser log overlay, the shared LogBuffer behind MCP logs.read — tagged "make:<target>". Callers must not spawn make themselves: output from a locally-spawned process is visible only to whoever spawned it.
Broadcasts EvMakeStart immediately and EvMakeDone with the exit code when the process exits, and appends a "[make:<target>] exited <n>" marker to the log buffer so an agent polling logs.read can tell a finished run from a hung one.
Returns straight away: read done for the result (it receives exactly once, then closes), or call cancel to SIGKILL the process group. Cancel is safe after the run has already finished.
ponytail: concurrent runs of the same or different targets are allowed — they already were, since MCP and the TUI could overlap. Serialise here if two makes stepping on each other ever proves to be a real problem.
type DevConfig ¶
type DevConfig struct {
Watch []WatchRule `toml:"watch"`
Daemons []Daemon `toml:"daemon"`
DockerCompose []DockerCompose `toml:"docker_compose"`
LogFile string `toml:"log_file"`
LogFileMaxLines int `toml:"log_file_max_lines"`
ProxyListen string `toml:"proxy_listen"`
ProxyTarget string `toml:"proxy_target"`
InjectReload *bool `toml:"inject_reload"`
Email EmailConfig `toml:"email"`
SMS SMSConfig `toml:"sms"`
Stripe StripeConfig `toml:"stripe"`
MCP MCPConfig `toml:"mcp"`
Tunnel TunnelConfig `toml:"tunnel"`
// DarkFilter sets the initial state of the dev-panel "Dark filter"
// toggle: an invert(1) hue-rotate(180deg) CSS filter over the proxied
// site so a light-mode app is bearable to work on. Default false. The
// panel toggle flips it at runtime in the hamr dev process only — the
// value is never written back to hamr.toml.
DarkFilter bool `toml:"dark_filter"`
// HamrConsoleCapture toggles the entire browser-console transport
// (window.console.* + uncaught errors + unhandled rejections +
// resource-load failures + CSP violations → /__hamr/console WS →
// `[site:console]` lines in the dev TUI / log file). Default true
// (nil pointer): on by default in fresh scaffolds. Set false to skip
// mounting the WS endpoint and to tell the injected reload script
// not to patch console or open a connection — zero overhead.
HamrConsoleCapture *bool `toml:"hamr_console_capture"`
// HamrConsoleFilter, when true, drops browser console frames whose
// message contains "[hamr]" — i.e. logs emitted by hamr's own injected
// reload script. Default false: show everything the browser sees,
// including hamr's own chatter. Flip true if the per-save chatter
// (`[hamr] live reload connected`, `[hamr] page swapped`, etc.) is
// noisy enough to drown out app-side logs. No effect when
// HamrConsoleCapture is false.
HamrConsoleFilter bool `toml:"hamr_console_filter"`
// PortWalk toggles the +1-on-busy walk for hamr-managed ports
// (proxy.listen, proxy.target / spawned-app PORT, and docker-compose
// host-port publishes). Default true: when a port is busy hamr walks +1
// up to a small cap and logs a WARN per shift, so two `hamr dev`
// instances on the same machine don't collide. Set false to disable
// walking and fail fast on EADDRINUSE — useful when CI or external
// tooling pins a specific port and would mis-target if hamr silently
// shifted.
PortWalk *bool `toml:"port_walk"`
}
DevConfig holds the [dev] table with watch rules and daemons.
func (DevConfig) HamrConsoleCaptureEnabled ¶
HamrConsoleCaptureEnabled returns whether the browser-console transport is on. Defaults to true when the field is unset (nil) — opt-out, not opt-in.
func (DevConfig) PortWalkEnabled ¶
PortWalkEnabled returns whether the +1-on-busy port walk is enabled. Defaults to true when the field is unset (nil) — opt-out, not opt-in.
type DockerCompose ¶
type DockerCompose struct {
Name string `toml:"name"`
File string `toml:"file"`
Services []string `toml:"services"`
KeepRunning bool `toml:"keep_running"`
WaitReady bool `toml:"wait_ready"`
Env []string `toml:"env"`
}
DockerCompose declares a docker compose file that hamr ensures is running.
type Duration ¶
Duration wraps time.Duration with TOML unmarshaling that accepts an integer (milliseconds) or a Go duration string like "200ms".
func (*Duration) UnmarshalTOML ¶
UnmarshalTOML implements the toml.Unmarshaler interface.
type EmailConfig ¶
type EmailConfig struct {
Enabled bool `toml:"enabled"`
MaxMessages int `toml:"max_messages"` // default 500
MaxMessageBytes int64 `toml:"max_message_bytes"` // default 10MiB
Persist *bool `toml:"persist"` // default true
PersistPath string `toml:"persist_path"` // default ".hamr/mail/inbox.mbox"
}
EmailConfig holds the [dev.email] table for the mail mock. When Enabled is true, hamr dev runs an email inbox at /__hamr/mail on the reverse proxy. Requires [proxy] to be configured.
Persistence defaults to on: the inbox is mirrored to an mbox file at PersistPath so it survives hamr dev restart. Set Persist=false for an ephemeral in-memory-only inbox.
func (EmailConfig) PersistEnabled ¶
func (c EmailConfig) PersistEnabled() bool
PersistEnabled returns whether persistence is on. Defaults to true when the field is unset (nil).
func (EmailConfig) ResolvedPersistPath ¶
func (c EmailConfig) ResolvedPersistPath() string
ResolvedPersistPath returns PersistPath with the default applied.
type ErrorState ¶
type ErrorState struct {
// contains filtered or unexported fields
}
ErrorState tracks active build/process errors in a thread-safe manner. The proxy reads it to decide whether to serve the error page; devserver writes it on build failure / success.
func (*ErrorState) Clear ¶
func (e *ErrorState) Clear(rule string)
Clear removes the error for the given rule.
func (*ErrorState) HasErrors ¶
func (e *ErrorState) HasErrors() bool
HasErrors returns true if any rule has an active error.
func (*ErrorState) OnChange ¶
func (e *ErrorState) OnChange(fn func())
OnChange registers a callback that fires after Set or Clear modifies state.
func (*ErrorState) RuleNames ¶
func (e *ErrorState) RuleNames() []string
RuleNames returns the sorted names of rules with active errors.
func (*ErrorState) Set ¶
func (e *ErrorState) Set(rule, output string)
Set records a build/process error for the given rule.
func (*ErrorState) Snapshot ¶
func (e *ErrorState) Snapshot() map[string]string
Snapshot returns a copy of the current errors map.
type Graph ¶
type Graph struct {
// contains filtered or unexported fields
}
Graph tracks dependency relationships and coordinates execution order between watch rules using channels for signaling.
func NewGraph ¶
NewGraph builds a dependency graph from the given watch rules. It assumes rules have already been validated (no cycles, no unknown deps).
func (*Graph) MarkRunning ¶
MarkRunning resets the named rule's done channel so dependees will block.
func (*Graph) TopologicalOrder ¶
TopologicalOrder returns rule names in an order that respects dependencies (dependencies come before dependees).
type HotkeyAction ¶
type HotkeyAction int
HotkeyAction represents a user-triggered hotkey action.
const ( HotkeyRebuild HotkeyAction = iota HotkeyOpenBrowser HotkeyQuit HotkeyMCPToggle // HotkeyRestart tears the dev server down and re-runs its whole startup // lifecycle (docker compose, port resolution, .env injection, builds, // daemons, watcher) in-place, leaving the TUI running. The escape hatch // for state the runner only reads at startup: a clashing port, an edited // .env, a container that came up wrong. HotkeyRestart // HotkeyTunnelToggle starts or stops the public tunnel ([dev.tunnel]). HotkeyTunnelToggle // HotkeyStripeMode flips [dev.stripe] between the mock and `stripe listen`. HotkeyStripeMode )
type HotkeySource ¶
type HotkeySource interface {
Actions() <-chan HotkeyAction
}
HotkeySource emits hotkey actions for the dev runner to consume. The TUI implements this with a bubbletea-backed adapter; the runner reads from Actions() in its event loop. A nil channel means no source is attached and the loop should never fire on it.
type LogBuffer ¶
type LogBuffer struct {
// contains filtered or unexported fields
}
LogBuffer is a thread-safe ring buffer that keeps the last N log lines.
func NewLogBuffer ¶
NewLogBuffer creates a new LogBuffer capped at max lines.
type LogLine ¶
type LogLine struct {
Rule string `json:"rule"`
Text string `json:"text"`
Color string `json:"color,omitempty"`
Time time.Time `json:"time"`
}
LogLine is a single line of process output tagged with its rule name.
type MCPConfig ¶
type MCPConfig struct {
// Enabled is the initial runtime state at launch. The TUI kill-switch can
// flip the live gateway without rewriting this. Default false.
Enabled bool `toml:"enabled"`
// Access maps a functional area (dev, logs, docker, mail, sms, build, stripe) to
// a level ("read", "write", or "deny"). "write" implies "read". Areas absent
// from the map are denied. With no table at all, zero tools are exposed.
Access map[string]string `toml:"access"`
// MakeTargets constrains make.run to a named subset. Empty = every Makefile
// target is allowed (the permissive default).
MakeTargets []string `toml:"make_targets"`
// MakeWait bounds how long make.run blocks before returning a "still
// running, poll logs" result. Default 20s when unset.
MakeWait Duration `toml:"make_wait"`
// LogFile is the MCP audit log path. Default ".hamr/mcp_logs.txt"; set to
// "none" to disable the audit log.
LogFile string `toml:"log_file"`
}
MCPConfig holds the [dev.mcp] table — the gateway that lets an AI agent drive hamr dev through the `hamr mcp` stdio bridge. Off by default; opt-in.
The bridge connects over the existing localhost /__hamr/* HTTP API, authenticated by a per-run token (see the gateway). Permissions are granted per functional area at read/write/deny granularity via Access; an area not granted exposes none of its tools.
func (MCPConfig) EnabledTools ¶
EnabledTools returns the set of tool names the Access map exposes. Unknown areas/levels are ignored here (validate() rejects them at load time).
func (MCPConfig) MakeTargetAllowed ¶
MakeTargetAllowed reports whether make.run may run the given target. Empty MakeTargets means every target is allowed.
func (MCPConfig) ResolvedLogFile ¶
ResolvedLogFile returns the audit-log path with the default applied, or "" when the audit log is disabled ("none").
func (MCPConfig) ResolvedMakeWait ¶
ResolvedMakeWait returns the make.run bounded-wait duration, defaulting to 20s when unset or non-positive.
func (MCPConfig) ToolAllowed ¶
ToolAllowed reports whether the named tool is exposed by the current Access map. The gateway uses this to enforce permissions per call.
type MCPHandshake ¶
MCPHandshake is the JSON written to .hamr/dev.json by an enabled gateway and read by the `hamr mcp` bridge — the single source of truth for the wire format the two share.
func ReadMCPHandshake ¶
func ReadMCPHandshake(projectRoot string) (MCPHandshake, error)
ReadMCPHandshake loads the handshake descriptor from projectRoot. Returns a clear error when the file is absent (dev server not running / MCP disabled).
type MailMock ¶
type MailMock struct {
// contains filtered or unexported fields
}
MailMock is a dev-only email inbox. Messages arrive via /__hamr/mail/ingest (POST JSON), are stored in a ring buffer, and are viewable at /__hamr/mail on the reverse proxy. If persistPath is set, the inbox is mirrored to an mbox file on disk so it survives hamr dev restart.
func NewMailMock ¶
func NewMailMock(opts MailMockOptions) *MailMock
NewMailMock returns a MailMock with the given options. If PersistPath is non-empty, any existing inbox at that path is loaded and subsequent changes are mirrored there. Load failures are reported via OnPersistError (if set) but never fatal — the in-memory inbox starts empty.
func (*MailMock) Get ¶
Get returns a deep copy of the message with the given id, or nil if not found. The copy decouples callers from concurrent mutation.
func (*MailMock) List ¶
func (m *MailMock) List() []*mailMessage
List returns a newest-first snapshot of the inbox. Messages are deep-copied so callers can read fields without racing concurrent mutators (SetStatus).
func (*MailMock) RegisterIngestRoutes ¶
RegisterIngestRoutes mounts the SMTP capture sink. handleIngest is server-to-server (no browser Origin) — intentionally NOT origin-guarded; see guardUnsafe.
func (*MailMock) RegisterRoutes ¶
RegisterRoutes mounts both the UI and the ingest endpoint on mux. Do not register twice on the same mux — http.ServeMux panics on duplicate patterns.
func (*MailMock) RegisterUIRoutes ¶
RegisterUIRoutes mounts the human-facing inbox UI. Split from the ingest sink so the two can live on separate listeners (see `hamr mock-serve`).
func (*MailMock) SetStatus ¶
SetStatus marks a stored message as having a particular outcome. Used by the UI to simulate post-hoc bounce/delay on an already-captured message. Allowed values: "failed", "delayed" ("delivered" is the implicit default set at ingest and cannot be re-applied here). Unknown values are rejected.
Persistence: rewrites the whole mbox file (status is in the headers).
type MailMockOptions ¶
type MailMockOptions struct {
MaxMessages int // default 500
MaxMessageBytes int64 // default 10 MiB
PersistPath string // "" disables persistence
OnPersistError func(error) // invoked on disk write/read errors; nil is silent
}
MailMockOptions configures a MailMock at construction.
type MakeResult ¶ added in v0.37.0
MakeResult is the outcome of a RunMake call.
type MockProvider ¶
type MockProvider struct {
Name string
Build func(logger *slog.Logger) (*MountedMock, error)
}
MockProvider registers one mock. Each provider reads its own HAMR_* env vars in Build. Adding a new mock is one entry in mockProviders.
type MountedMock ¶
type MountedMock struct {
RegisterAPI func(*http.ServeMux) // app-facing (stripe /v1 + /v2, mail/sms ingest)
RegisterUI func(*http.ServeMux) // human-facing dashboards
}
MountedMock is what a provider returns: the route registrations for each surface. Either may be nil if a mock has no routes on that surface.
type Option ¶
type Option func(*Runner)
Option configures a Runner.
func WithActionsHook ¶
func WithActionsHook(fn func(*DevActions)) Option
WithActionsHook registers a callback that fires once Run has constructed its DevActions object. The TUI uses this to capture a reference for dispatching actions (e.g. docker wipe) that aren't expressible through the scalar HotkeyAction enum. The hook fires on the runner goroutine; copy the pointer and return — do not block.
func WithConfigPath ¶
WithConfigPath sets the config file path so the runner can watch it for changes.
func WithDockerLogSinks ¶
WithDockerLogSinks subscribes one writer per `[[dev.docker_compose]]` entry to that stack's `docker compose logs -f` output. Keys in the map are the same `name` field hamr.toml uses; entries without a writer are skipped (no follower spawned).
The runner manages follower lifetime: started once an entry has been brought up, restarted automatically if the follower exits early (typically because `docker compose down -v` from a wipe killed it), stopped on shutdown via the runner ctx.
func WithHotkeys ¶
func WithHotkeys(h HotkeySource) Option
WithHotkeys wires the bubbletea-backed HotkeySource that feeds q / r / o into Run's event loop. The TUI runtime owns the source's lifecycle.
func WithLogWriter ¶
WithLogWriter overrides the base writer used by the runner's default slog handler (defaults to os.Stderr). The file logger fan-out, when enabled, remains on top. TUI mode wires this to a viewport-backed sink so the runner's own log lines render inside the TUI instead of corrupting the frame.
func WithMCPLogHook ¶
WithMCPLogHook registers a callback that receives a one-line summary of every MCP request the gateway handles, so the TUI can render a dedicated MCP tab. Fires on the gateway's request goroutine; do not block.
func WithMCPStatusHook ¶
WithMCPStatusHook registers a callback that receives the MCP gateway's state (enabled, exposed-tool count) at startup and on every M-toggle, so the TUI can render its indicator. Fires on the runner goroutine; do not block.
func WithProcessOutput ¶
WithProcessOutput redirects child-process stdout/stderr away from the terminal into the given writers. Internally calls SetOutputSinks on the ProcessManager once it's constructed inside Run. Used by the TUI runtime.
func WithProxyURLHook ¶
WithProxyURLHook registers a callback that fires once the reverse proxy has bound (after any +1-on-busy port walking) so the caller can publish the actual reachable URL to its UI surface. The TUI runtime uses this to push the URL into a bubbletea message. The hook fires on the runner goroutine; copy the string and return — do not block.
type ProcessManager ¶
type ProcessManager struct {
OnProcessExit func(rule string, err error, output string)
// contains filtered or unexported fields
}
ProcessManager handles running one-shot commands and long-running processes.
func NewProcessManager ¶
func NewProcessManager(logger *slog.Logger) *ProcessManager
NewProcessManager creates a new process manager.
func (*ProcessManager) ClearCallbacks ¶
func (pm *ProcessManager) ClearCallbacks()
ClearCallbacks disables all process exit callbacks. Used during shutdown to prevent spurious build_error events.
func (*ProcessManager) RunCommand ¶
RunCommand runs a one-shot command to completion. Stdout and stderr are streamed through the logger, and the captured tail output is returned regardless of exit status (alongside the error on failure) — callers that only care about output on error can ignore it on success, while one-shot tools (e.g. the MCP make.run) can surface it.
func (*ProcessManager) SetFileLog ¶
func (pm *ProcessManager) SetFileLog(w io.Writer)
SetFileLog enables writing prefixed process output to a rolling file logger.
func (*ProcessManager) SetInjectedEnv ¶
func (pm *ProcessManager) SetInjectedEnv(env []string)
SetInjectedEnv configures vars hamr will inject into every spawned rule process. Rule-level Env still overrides on key conflict (last-wins via buildEnv). Used to feed scaffolded apps the mock URLs hamr is hosting (e.g. HAMR_STRIPE_MOCK_URL=http://localhost:3000) so the scaffold's main.go doesn't need to hardcode those URLs and they automatically track hamr.toml's [proxy].listen.
Safe to call while processes are running (the tunnel toggle swaps env at runtime); only processes started afterwards see the new values.
func (*ProcessManager) SetLogOutput ¶
func (pm *ProcessManager) SetLogOutput(buf *LogBuffer, broker *SSEBroker)
SetLogOutput enables streaming process output to a LogBuffer and SSE broker.
func (*ProcessManager) SetOutputSinks ¶
func (pm *ProcessManager) SetOutputSinks(stdout, stderr io.Writer)
SetOutputSinks redirects subprocess stdout/stderr away from the terminal (os.Stdout / os.Stderr) into the given writers. The file logger fan-out configured via SetFileLog is preserved on top. Pass nil writers to clear.
Used by the TUI runtime so child output flows into a bubbletea-managed viewport instead of corrupting the rendered frame.
func (*ProcessManager) StartProcess ¶
func (pm *ProcessManager) StartProcess(ctx context.Context, rule *WatchRule) error
StartProcess starts a long-running process, killing any previous instance. The process is tracked and can be stopped via StopAll.
func (*ProcessManager) StopAll ¶
func (pm *ProcessManager) StopAll()
StopAll gracefully stops all tracked processes.
type ProxyConfig ¶
type ProxyConfig struct {
Listen string `toml:"listen"`
Target string `toml:"target"`
InjectReload *bool `toml:"inject_reload"`
}
ProxyConfig holds the [proxy] table.
type ReloadScope ¶
type ReloadScope string
ReloadScope controls what kind of browser reload a rule triggers. Values: "full", "css", "none", or a boolean (true="full", false="none").
const ( ReloadFull ReloadScope = "full" ReloadCSS ReloadScope = "css" ReloadNone ReloadScope = "none" )
func (*ReloadScope) UnmarshalTOML ¶
func (r *ReloadScope) UnmarshalTOML(data any) error
UnmarshalTOML implements the toml.Unmarshaler interface.
type RequestLog ¶
type RequestLog struct {
// contains filtered or unexported fields
}
RequestLog is a thread-safe ring buffer of recent proxy requests, feeding the MCP http.read tool. It captures every request the proxy serves — proxied app traffic, static assets, the /__hamr/* endpoints, and the SSE/WS streams — which is the view the app's own access log can't give (the app never sees proxy-handled routes, and skips /static).
func NewRequestLog ¶
func NewRequestLog(max int) *RequestLog
NewRequestLog creates a request log capped at max entries.
func (*RequestLog) Record ¶
func (rl *RequestLog) Record(e RequestLogEntry)
Record appends a completed entry, trimming the oldest once over capacity.
func (*RequestLog) Snapshot ¶
func (rl *RequestLog) Snapshot() []RequestLogEntry
Snapshot returns a copy of all buffered entries (oldest first).
type RequestLogEntry ¶
type RequestLogEntry struct {
Time time.Time `json:"time"`
Method string `json:"method"`
Path string `json:"path"`
Status int `json:"status"`
DurationMs int64 `json:"durationMs"`
}
RequestLogEntry is one observed HTTP request through the dev proxy.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner is the top-level dev server orchestrator.
type SMSConfig ¶
type SMSConfig struct {
Enabled bool `toml:"enabled"`
MaxMessages int `toml:"max_messages"` // default 500
Persist *bool `toml:"persist"` // default true
PersistPath string `toml:"persist_path"` // default ".hamr/sms/inbox.jsonl"
}
SMSConfig holds the [dev.sms] table for the SMS mock. When Enabled is true, hamr dev runs an SMS inbox at /__hamr/sms on the reverse proxy. Requires [proxy] to be configured.
Persistence defaults to on: the inbox is mirrored to a JSONL file at PersistPath so it survives hamr dev restart. Set Persist=false for an ephemeral in-memory-only inbox.
func (SMSConfig) PersistEnabled ¶
PersistEnabled returns whether persistence is on. Defaults to true when the field is unset (nil) — matches the email mock's behaviour.
func (SMSConfig) ResolvedPersistPath ¶
ResolvedPersistPath returns PersistPath with the default applied.
type SMSMock ¶
type SMSMock struct {
// contains filtered or unexported fields
}
SMSMock is a dev-only SMS inbox. Messages arrive via /__hamr/sms/ingest (POST JSON), are stored in a ring buffer, and are viewable at /__hamr/sms on the reverse proxy. If persistPath is set, the inbox is mirrored to a JSONL file on disk so it survives hamr dev restart.
func NewSMSMock ¶
func NewSMSMock(opts SMSMockOptions) *SMSMock
NewSMSMock returns an SMSMock with the given options. If PersistPath is non-empty, any existing inbox at that path is loaded and subsequent changes are mirrored there. Load failures are reported via OnPersistError (if set) but never fatal — the in-memory inbox starts empty.
func (*SMSMock) List ¶
func (m *SMSMock) List() []*smsMessage
List returns a newest-first snapshot of the inbox. Messages are copied so callers can read fields without racing concurrent mutators (SetStatus).
func (*SMSMock) RegisterIngestRoutes ¶
RegisterIngestRoutes mounts the capture sink. handleIngest is server-to-server (no browser Origin) — intentionally NOT origin-guarded; see guardUnsafe.
func (*SMSMock) RegisterRoutes ¶
RegisterRoutes mounts both the UI and the ingest endpoint on mux. Do not register twice on the same mux — http.ServeMux panics on duplicate patterns.
func (*SMSMock) RegisterUIRoutes ¶
RegisterUIRoutes mounts the human-facing inbox UI. Split from the ingest sink so the two can live on separate listeners (see `hamr mock-serve`).
func (*SMSMock) SetStatus ¶
SetStatus marks a stored message as having a particular outcome. Used by the UI to simulate post-hoc failure/delay on an already-captured message. Allowed values: "failed", "delayed" ("delivered" is the implicit default set at ingest and cannot be re-applied here). Unknown values are rejected.
Persistence: rewrites the whole JSONL file.
type SMSMockOptions ¶
type SMSMockOptions struct {
MaxMessages int // default 500
PersistPath string // "" disables persistence
OnPersistError func(error) // invoked on disk write/read errors; nil is silent
}
SMSMockOptions configures an SMSMock at construction.
type SSEBroker ¶
type SSEBroker struct {
// contains filtered or unexported fields
}
func NewSSEBroker ¶
func NewSSEBroker(rules []WatchRule, daemons []Daemon, dockerCompose []DockerCompose, mailMockEnabled, smsMockEnabled, stripeMockEnabled, consoleCaptureEnabled, darkFilter bool) *SSEBroker
NewSSEBroker creates a new SSE broker. The provided watch rules, daemons, and docker compose entries are serialized once and sent to each client on connect as a "config" event. The mock flags decide which mock-shortcut buttons the dev panel renders. consoleCaptureEnabled toggles the browser-console transport client-side: true tells the injected reload script to patch console + open /__hamr/console; false tells it to do nothing. darkFilter seeds the dark comfort filter state (see SSEBroker.darkFilter).
func (*SSEBroker) Broadcast ¶
Broadcast sends an event to all connected clients. Non-blocking: a slow consumer never wedges the caller, which is usually a build goroutine.
On a full buffer the event is dropped, except for the state-change events evictable reports: those evict the oldest queued event to make room. A consumer that misses "output" loses one log line it can read from the log buffer; one that misses "build_ok" shows that build as running until something else resets it, so the two cannot share a drop policy.
func (*SSEBroker) ClientCount ¶
ClientCount returns the number of connected SSE clients.
func (*SSEBroker) Handler ¶
func (b *SSEBroker) Handler() http.HandlerFunc
Handler returns an http.HandlerFunc that serves SSE connections.
func (*SSEBroker) Subscribe ¶ added in v0.37.0
Subscribe registers a consumer and returns its event channel plus a cancel func that removes and closes it. The channel is buffered (16) and a full buffer never blocks the sender, exactly as for HTTP clients — a stalled TUI must not wedge a build. See Broadcast for what a full buffer costs.
exclude names event types this consumer never wants, skipped before they can take a buffer slot. Since the buffer is a shared budget between rare state events and per-line EvOutput, a consumer that reads output elsewhere should exclude EvOutput so its buffer holds only what it renders. Broadcast's eviction already keeps state events from being lost, so this is a way to stop carrying traffic the consumer discards, not a correctness requirement.
In-process consumers (the TUI runtime) use this directly; Handler uses it for each browser connection. Cancel is idempotent and must be called, or the consumer's slot leaks for the life of the broker.
Closing under the write lock is safe: Broadcast sends while holding the read lock, so no send can be in flight when the close happens.
type SSEEvent ¶
type SSEEvent struct {
Type string // event type (e.g., "reload", "css")
Data string // event data
}
SSEEvent is a server-sent event.
type StringOrSlice ¶
type StringOrSlice []string
StringOrSlice accepts either a single string or a list of strings in TOML.
func (*StringOrSlice) UnmarshalTOML ¶
func (s *StringOrSlice) UnmarshalTOML(data any) error
UnmarshalTOML implements the toml.Unmarshaler interface.
type StripeAccountSummary ¶
type StripeConfig ¶
type StripeConfig struct {
Mode string `toml:"mode"` // "off" | "mock" | "listen"; default "off"
WebhookURL string `toml:"webhook_url"` // required unless off
ThinWebhookURL string `toml:"thin_webhook_url"` // optional; empty = thin events are not delivered
ConnectWebhookURL string `toml:"connect_webhook_url"` // optional; connected-account events. Empty = WebhookURL
ThinConnectWebhookURL string `toml:"thin_connect_webhook_url"` // optional; connected-account thin events. Empty = ThinWebhookURL
ThinEvents []string `toml:"thin_events"` // thin events `stripe listen` forwards; default ResolvedThinEvents
WebhookSecret string `toml:"webhook_secret"` // mock mode signing secret; required unless off
Persist *bool `toml:"persist"` // mock only; default true
PersistPath string `toml:"persist_path"` // mock only; default ".hamr/stripe/state.json"
}
StripeConfig holds the [dev.stripe] table. Mode picks how hamr dev handles Stripe; the S hotkey flips mock ⇄ listen at runtime.
- "off" (default): nothing mounted, nothing injected.
- "mock": hamr mounts a Stripe-compatible HTTP backend on the proxy mux (/v1/*, /v2/*) that real stripe-go clients reach via stripe.SetBackend(...) pointed at HAMR_STRIPE_MOCK_URL, and fires signed webhooks at the URLs below with WebhookSecret, exactly as Stripe would.
- "listen": hamr runs `stripe listen` against a real sandbox (key from STRIPE_KEY in .env), forwarding to the same URLs, and injects the secret it prints. The app calls api.stripe.com.
Both modes need [proxy]: the mock surfaces stay mounted in listen mode. The mock is dev-only: no production safeguards. Apps gate by leaving STRIPE_MOCK unset in production so stripe-go reaches api.stripe.com.
func (StripeConfig) Active ¶ added in v0.37.0
func (c StripeConfig) Active() bool
Active reports whether [dev.stripe] is on in either mode.
func (StripeConfig) PersistEnabled ¶
func (c StripeConfig) PersistEnabled() bool
PersistEnabled returns whether persistence is on. Defaults to true when the field is unset (nil) — matches the email mock's behaviour.
func (StripeConfig) ResolvedMode ¶ added in v0.37.0
func (c StripeConfig) ResolvedMode() string
ResolvedMode returns Mode with the default applied.
func (StripeConfig) ResolvedPersistPath ¶
func (c StripeConfig) ResolvedPersistPath() string
ResolvedPersistPath returns PersistPath with the default applied.
func (StripeConfig) ResolvedThinEvents ¶ added in v0.37.0
func (c StripeConfig) ResolvedThinEvents() []string
ResolvedThinEvents returns ThinEvents with the default applied: the Accounts v2 events the scaffold's thin handler reacts to. `stripe listen` has no default for --thin-events, so an unset list would forward none.
type StripeLineItemSummary ¶
type StripeMock ¶
type StripeMock struct {
// contains filtered or unexported fields
}
StripeMock is a dev-only in-memory Stripe backend. Routes implement enough of /v1/* for stripe-go to round-trip CheckoutSession create/retrieve.
func NewStripeMock ¶
func NewStripeMock(opts StripeMockOptions) *StripeMock
NewStripeMock returns a mock backend, loading any persisted state if PersistPath is set. Load failures are reported via OnPersistError but never fatal — the in-memory state simply starts empty.
All log lines from the mock are prefixed with [hamr:stripe] (via the "component" slog attr that the dev handler interprets as a tag override) so they're distinguishable from the rest of `hamr dev`'s output.
func (*StripeMock) FireEvent ¶
func (m *StripeMock) FireEvent(ctx context.Context, eventType string, dataObject map[string]any) error
FireEvent records and delivers a signed v1 snapshot event. The dataObject is the Stripe resource that triggered the event (e.g. a serialized CheckoutSession for checkout.session.completed); it is embedded under data.object exactly as Stripe would do.
Returns nil when no endpoint is configured (silent drop). On delivery failure or non-2xx response, returns an error so callers can log/surface. This call is synchronous — for fire-and-forget semantics, wrap in a goroutine.
func (*StripeMock) RegisterAPIRoutes ¶
func (m *StripeMock) RegisterAPIRoutes(mux *http.ServeMux)
RegisterAPIRoutes mounts the Stripe API endpoints on mux. The mux MUST be served at the root of its listener because stripe-go validates that req.URL.Path starts with /v1 or /v2 and rejects anything under a sub-path. Every route replays repeated Idempotency-Key POSTs (see idempotent).
POST /v1/checkout/sessions — create session
GET /v1/checkout/sessions/{id} — retrieve session
POST /v1/checkout/sessions/{id}/expire — expire an open session
func (*StripeMock) RegisterRoutes ¶
func (m *StripeMock) RegisterRoutes(mux *http.ServeMux)
RegisterRoutes mounts all Stripe mock endpoints (API + UI) on mux.
func (*StripeMock) RegisterUIRoutes ¶
func (m *StripeMock) RegisterUIRoutes(mux *http.ServeMux)
RegisterUIRoutes mounts the dev-facing checkout page + outcome handler on mux. These routes live on the proxy mux (/__hamr/* namespace), separate from the Stripe-API routes which require a path-free root and run on the dedicated stripe listener.
GET /__hamr/stripe/checkout?session=<id> — pick-an-outcome page POST /__hamr/stripe/complete — record outcome, fire webhook, redirect
func (*StripeMock) SetWebhookEndpoint ¶
func (m *StripeMock) SetWebhookEndpoint(ep WebhookEndpoint)
SetWebhookEndpoint configures the destinations + signing secret for events. Replaces any previously configured endpoint. An endpoint with an empty URL or Secret still records events in the event log but delivers nothing — useful so callers don't have to gate every fire.
type StripeMockOptions ¶
type StripeMockOptions struct {
// BaseURL is the proxy origin (scheme + host + port) used to build the
// hosted-checkout URL returned in CheckoutSession.URL. Required.
BaseURL string
// Logger receives errors from async webhook fanout. Defaults to slog.Default().
Logger *slog.Logger
// PersistPath enables JSON-file persistence of all in-memory state.
// When set, state is loaded on construction (corrupt/missing files are
// silently tolerated) and the entire state is atomically rewritten on
// every mutation. Empty = in-memory only.
PersistPath string
// OnPersistError is invoked whenever a load or write fails. Typically
// wired to a slog.Warn so dev failures surface in `hamr dev` output.
// Nil = silent.
OnPersistError func(error)
}
StripeMockOptions configures a StripeMock at construction.
type StripeObjectSummary ¶
type StripeSessionSummary ¶
type StripeSessionSummary struct {
ID string `json:"id"`
Status string `json:"status"`
Amount int64 `json:"amount"`
Currency string `json:"currency"`
URL string `json:"url"`
LineItems []StripeLineItemSummary `json:"lineItems,omitempty"`
}
StripeSessionSummary adds the hosted-checkout URL and line items so an agent can verify it's acting on the right session before completing/expiring it.
type StripeStateSummary ¶
type StripeStateSummary struct {
Sessions []StripeSessionSummary `json:"sessions"`
PaymentIntents []StripeObjectSummary `json:"paymentIntents"`
Payouts []StripeObjectSummary `json:"payouts"`
Refunds []StripeObjectSummary `json:"refunds"`
Accounts []StripeAccountSummary `json:"accounts"`
}
StripeStateSummary is the read-only snapshot returned by stripe.list.
type TunnelConfig ¶ added in v0.37.0
type TunnelConfig struct {
// Provider picks the built-in command: "cloudflared" (default, no account,
// random *.trycloudflare.com URL per start) or "ngrok".
Provider string `toml:"provider"`
// Args are extra flags appended to the built-in provider command.
Args []string `toml:"args"`
// Cmd replaces Provider+Args with a custom shell command. {port} is
// replaced with the tunnel listener's port; the first https:// URL the
// command prints is taken as the public URL.
Cmd string `toml:"cmd"`
// Env lists the vars set to the public URL. Default ["BASE_URL"].
Env []string `toml:"env"`
}
TunnelConfig holds the [dev.tunnel] table. The tunnel is never started at boot: the TUI's T hotkey toggles it at runtime. While on, hamr dev runs a locally installed tunnel binary pointed at a dedicated proxy listener and sets each var in Env to the public URL for the processes it spawns.
func (TunnelConfig) ResolvedEnv ¶ added in v0.37.0
func (c TunnelConfig) ResolvedEnv() []string
ResolvedEnv returns Env with the default applied. An explicit empty list means "set nothing".
func (TunnelConfig) ResolvedProvider ¶ added in v0.37.0
func (c TunnelConfig) ResolvedProvider() string
ResolvedProvider returns Provider with the default applied.
type VersionStatus ¶
type VersionStatus int
VersionStatus indicates the CLI-vs-project version state.
const ( VersionOK VersionStatus = iota // versions match or no project version VersionDev // CLI is a dev build VersionMismatch // CLI major.minor differs from project VersionUpdate // newer version available on GitHub )
type WatchRule ¶
type WatchRule struct {
Name string `toml:"name"`
Watch StringOrSlice `toml:"watch"`
Ignore StringOrSlice `toml:"ignore"`
Cmd string `toml:"cmd"`
Run string `toml:"run"`
Dir string `toml:"dir"`
Depends StringOrSlice `toml:"depends"`
Debounce Duration `toml:"debounce"`
Reload ReloadScope `toml:"reload"`
Env []string `toml:"env"`
}
WatchRule defines a single watch/build/run rule.
Dir sets the working directory for cmd and run, relative to the directory hamr dev runs in. It does NOT affect watch/ignore globs — those stay root-relative regardless, so a rule can watch the whole repo while building inside a subdirectory.
type Watcher ¶
type Watcher struct {
// contains filtered or unexported fields
}
Watcher watches the filesystem for changes and emits FileEvents.
func NewWatcher ¶
NewWatcher creates a file watcher for the given rules. root is the base directory to watch (typically ".").
func (*Watcher) Done ¶
func (w *Watcher) Done() <-chan struct{}
Done returns a channel that is closed when the watcher loop exits.
type WebhookEndpoint ¶
type WebhookEndpoint struct {
URL string
ThinURL string
ConnectURL string
ThinConnectURL string
Secret string
}
WebhookEndpoint is where signed events are delivered. URL is the absolute HTTP(S) URL of the app's webhook handler for v1 snapshot events; ThinURL is the handler for v2 thin event notifications (empty = thin events are logged but not delivered). Events carrying a connected account go to ConnectURL / ThinConnectURL when set, the plain URLs otherwise. Secret signs all of them, the same way `stripe listen` uses one secret for every --forward-*-to.
Source Files
¶
- actions.go
- composeinspect.go
- composeports.go
- config.go
- configwatch.go
- console.go
- devserver.go
- envinject.go
- envlayers.go
- errorpage.go
- errorstate.go
- filelog.go
- graph.go
- hotkeys.go
- log.go
- logbuffer.go
- logwriter.go
- mailmock.go
- mailmock_mbox.go
- mailmock_page.go
- makefile.go
- mcpaudit.go
- mcpconfig.go
- mcpgateway.go
- mcpgateway_tools.go
- mcptypes.go
- mockserve.go
- portwalk.go
- process.go
- proxy.go
- requestlog.go
- smsmock.go
- smsmock_page.go
- sse.go
- stripelisten.go
- stripemock.go
- stripemock_account.go
- stripemock_account_page.go
- stripemock_balance.go
- stripemock_charge.go
- stripemock_clone.go
- stripemock_dashboard.go
- stripemock_dispute.go
- stripemock_events.go
- stripemock_form.go
- stripemock_ledger.go
- stripemock_mcp.go
- stripemock_page.go
- stripemock_paymentintent.go
- stripemock_paymentintent_page.go
- stripemock_payout.go
- stripemock_payout_page.go
- stripemock_persist.go
- stripemock_refund.go
- stripemock_transfer.go
- stripemock_v2account.go
- stripemock_views.go
- stripemock_webhook.go
- tunnel.go
- version_status.go
- versioncheck.go
- walks.go
- watcher.go