Help us shape the future of Go! Take the 2026 Go Developer Survey

webcap

package module
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 42 Imported by: 0

README

webcap

webcap is a Go package and CLI for browser screenshots, manifest-driven capture batches, image diffs, workflow captures, HTML review reports, and a stdio MCP server.

Install

Install the released binary with Homebrew:

brew tap goliatone/homebrew-tap
brew install webcap

Or install from source with Go:

go install github.com/goliatone/webcap/cmd/webcap@latest

For local development from this checkout:

go run ./cmd/webcap shot --output shots/home.png http://localhost:3000

CLI

webcap help
webcap shot --output shots/home.png http://localhost:3000
webcap shot --visible --output shots/home-visible.png http://localhost:3000
webcap shot --json --output shots/home.png http://localhost:3000
webcap multi --output-dir ./shots ./shots.yaml
webcap diff --output ./diff.png ./baseline.png ./current.png
OPENAI_API_KEY=... webcap semantic-diff --provider openai --model gpt-5.1 --focus "primary CTA,nav labels" --metadata ./semantic.json ./current.png ./baseline.png
webcap semantic-diff --provider codex-cli --model gpt-5.1 --codex-profile work --metadata ./semantic.json ./current.png ./baseline.png
webcap workflow capture-scenario --run-report ./workflow.yaml
webcap report scenario ./workflow.yaml
webcap mcp serve
webcap skill install --agent codex
webcap skill install --agent claude
webcap skill install --agent codex --force

Result commands render concise human summaries by default. Use --json or --format json for the machine-readable result structs used by automation and future API responses. Use --format human to force human output and --no-color for deterministic output in CI, redirected output, and tests. webcap shot <url> captures the full page by default. Add --visible when you only want the current viewport. Oversized full-page or selector captures fail early by default with structured limit metadata. Use --oversize tile to opt into deterministic tile artifacts, for example --tile-max-height 4096; unstitched tiled captures write <output>.tile-0000.png style files plus the metadata sidecar. Add --tile-stitch when the capture must produce a single image for pixel or semantic comparison.

Browser readiness modes such as complete and network_idle do not always mean the app UI is ready. Use selector waits for visible DOM targets, or --wait-for-function when readiness is best expressed as JavaScript:

webcap shot \
  --wait-for-function 'window.__webcapReady === true' \
  --output shots/home.png \
  http://localhost:3000

For important visual review flows, prefer stable app-owned markers such as data-webcap-ready="true". For Storybook iframe captures, wait for the preview to show the story and for the root to contain rendered content:

webcap shot \
  --wait-for-function 'document.body.classList.contains("sb-show-main") && !document.querySelector(".sb-preparing,.sb-errordisplay") && document.querySelector("#storybook-root")?.children.length > 0' \
  --output shots/story.png \
  http://localhost:6006/iframe.html?id=components-button--primary

Authenticated captures

webcap shot does not script login forms. Use webcap auth login for local form-login helpers, or acquire auth state with app tooling, curl, or Playwright, then hand that state to the capture and add guards so redirects to login fail clearly.

Cookie-authenticated local admin flow:

BASE_URL=http://localhost:9090
TARGET_URL="$BASE_URL/admin/translations/queue"
COOKIE_JAR=/tmp/admin-cookies.txt
export ADMIN_PASSWORD='local-dev-password'

webcap auth login \
  --base-url "$BASE_URL" \
  --target-url "$TARGET_URL" \
  --cookie-jar "$COOKIE_JAR" \
  --identifier admin@example.test \
  --password-env ADMIN_PASSWORD \
  --expect-cookie admin_session

webcap auth inspect \
  --cookie-jar "$COOKIE_JAR" \
  --url "$TARGET_URL" \
  --expect-cookie admin_session

webcap shot \
  --cookie-jar "$COOKIE_JAR" \
  --expect-url /admin/translations/queue \
  --fail-on-url /admin/login \
  --fail-on-selector 'form[action*="/login"]' \
  --output shots/translations-queue.png \
  "$TARGET_URL"

The embedded login helper fetches /admin/login, extracts CSRF from X-CSRF-Token or a hidden _token input, posts identifier and password, and validates the expected cookie. Use the app-specific cookie name: Garchen-style local admin apps commonly use admin_session; other go-admin examples may use admin_user. admin_debug_session is only a debug cookie and is not treated as auth.

Custom login scripts receive the same environment contract and should write the cookie jar:

webcap auth login \
  --script ./custom-login.sh \
  --base-url "$BASE_URL" \
  --target-url "$TARGET_URL" \
  --cookie-jar "$COOKIE_JAR" \
  --identifier admin@example.test \
  --password-env ADMIN_PASSWORD \
  --expect-cookie admin_session

Custom scripts run under bash with WEBCAP_BASE_URL, WEBCAP_LOGIN_PATH, WEBCAP_COOKIE_JAR, WEBCAP_IDENTIFIER, WEBCAP_PASSWORD, WEBCAP_EXPECT_COOKIE, and optional WEBCAP_TARGET_URL. Script stdout and stderr are diagnostic output and are redacted before presentation.

For local-only development auth headers:

webcap shot \
  --header "Authorization: Bearer $WEB_AUTH_TOKEN" \
  --expect-url /admin \
  --fail-on-url /login \
  http://localhost:9090/admin

For Playwright teams, pass a storage-state file:

webcap shot \
  --engine playwright \
  --storage-state .auth/admin-state.json \
  --expect-url /admin \
  http://localhost:9090/admin

Cookie jars and explicit cookies work in both engines. Playwright storage state is full-fidelity in the Playwright engine. Chromium imports cookies from Playwright storage-state files only when origins is empty; origin localStorage/sessionStorage currently fails with unsupported_error instead of being silently ignored. URL guards use substring matching, not regex or glob syntax.

Auth values are redacted from resolved_config, metadata sidecars, JSON errors, MCP responses, warnings, and human output. Shell history is outside webcap's control, so prefer files or environment expansion for secrets.

The standalone CLI does not include application-specific scenario aliases or paths. Provide scenario files explicitly. Use --openai-base-url or --anthropic-base-url with semantic-diff, workflow capture-scenario, report scenario, or mcp serve when routing built-in providers through a local fake server, proxy, or compatible gateway. Use --codex-bin, --codex-profile, --codex-oss, --codex-local-provider, and repeated --codex-extra-arg flags to configure the local Codex CLI process. CLI providers reuse local tool configuration and may still use subscriptions, API accounts, or local model resources.

webcap skill install installs the bundled webcap-agent guidance for AI coding agents. The skill tells agents to prefer webcap MCP tools when available, use the CLI as a fallback or reproducible command surface, and keep capture, pixel diff, semantic diff, and workflow report artifacts organized. Existing modified skill files are not overwritten unless --force is provided.

Go Package

engine, err := webcap.NewEngine(webcap.EngineConfig{
    EngineName: webcap.EngineChromium,
    Headless:   true,
})
if err != nil {
    return err
}

service := webcap.NewService(engine)
result, err := service.Capture(ctx, webcap.CaptureRequest{
    URL:             "http://localhost:3000",
    FullPage:        true,
    WaitForFunction: `document.querySelector("[data-webcap-ready=true]") !== null`,
    OutputPath:      "shots/home.png",
})

Semantic diff is opt-in and uses provider API keys from process configuration, not workflow YAML:

service := webcap.NewServiceWithOptions(nil, webcap.Options{
    SemanticDiff: webcap.SemanticDiffOptions{
        DefaultProvider: "openai",
        DefaultModels: map[string]string{
            "openai": "gpt-5.1",
        },
    },
})

result, err := service.SemanticDiff(ctx, webcap.SemanticDiffRequest{
    CurrentPath:   "shots/current.png",
    ReferencePath: "shots/baseline.png",
    Mode:          webcap.SemanticDiffModeFocused,
    Focus:         []string{"primary CTA", "navigation labels"},
    MetadataPath:  "shots/current.semantic.json",
})

NewService and NewServiceWithOptions include the shipped openai, anthropic, and codex-cli providers by default. Register standalone LLM providers through pkg/llms.Options.Providers; SemanticDiffOptions.Providers remains available for direct webcap.SemanticDiffProvider implementations and overrides any adapted LLM provider with the same normalized name:

service := webcap.NewServiceWithOptions(nil, webcap.Options{
    SemanticDiff: webcap.SemanticDiffOptions{
        LLMs: llms.Options{
            Providers: map[string]llms.Provider{
                "local-agent": myLLMProvider,
            },
        },
        DefaultModels: map[string]string{
            "local-agent": "gpt-5.1",
        },
    },
})

Set OPENAI_API_KEY or ANTHROPIC_API_KEY for the built-in providers. Raw provider responses are not written into metadata; use PersistRawResponse or --persist-raw-response only for local debugging because screenshots and model output can contain sensitive UI content. Hosts can set Options.SemanticDiff.RedactImage to replace or reject screenshot paths before provider payload encoding. Hosts can also configure semantic payload budgets such as MaxImageBytes, MaxProviderImageBytes, MaxImageLongEdge, MaxImagePixels, MaxEncodedImageBytes, MaxCombinedEncodedImageBytes, and MaxRequestBodyBytes. MaxImageBytes is a hard source-file guard before reads; set ResizeImages: true to shrink temporary provider-safe image copies for provider, encoded, and final request budgets while preserving the original artifacts. Provider failures use stable error codes such as provider_rate_limited, provider_auth, provider_quota, provider_invalid_request, provider_payload_too_large, provider_unavailable, provider_timeout, and provider_execution_failed; JSON output and workflow warnings include only safe metadata like status, retry-after, request IDs, provider error codes, and budget values.

Configure Codex CLI at process level, not in workflow YAML:

service := webcap.NewServiceWithOptions(nil, webcap.Options{
    SemanticDiff: webcap.SemanticDiffOptions{
        LLMs: llms.Options{
            CodexCLI: llms.CodexCLIOptions{
                CommandPath: "/usr/local/bin/codex",
                Profile:     "work",
            },
        },
    },
})

Workflow reports can run semantic diff after the normal pixel diff and reuse the existing diff image as context:

defaults:
  semantic_diff:
    enabled: true
    provider: openai
    model: gpt-5.1
    mode: general
    run: changed_only
    focus:
      - primary CTA visibility
      - navigation labels

Use provider: codex-cli in workflow YAML when the process already configured the Codex binary/profile flags or package options. Claude CLI support is deferred until local image input behavior is proven.

Per-screen overrides can change prompt mode or focus:

screens:
  - id: checkout
    route: /checkout
    reference_image: refs/checkout.png
    semantic_diff:
      mode: focused
      prompt: Compare only the checkout form and payment button state.

Semantic findings are advisory by default. To make them affect workflow report status, configure an explicit policy such as advisory_policy: enforce with failure_severity: major or failure_verdicts: [regression].

Workflow defaults are caller-owned:

opts := webcap.Options{
    Workflow: webcap.WorkflowOptions{
        DefaultSelectedScenario: "launch-success",
        DefaultPresentationMode: "review",
        HandoffQueryParam:       "simulator_handoff",
        BuildHandoff:           webcap.DefaultWorkflowHandoff,
    },
}

scenario, err := webcap.LoadWorkflowScenarioWithOptions("./workflow.yaml", opts.Workflow)
service := webcap.NewServiceWithOptions(engine, opts)

Playwright Runtime

The Playwright engine uses the Node bridge in playwright_runtime.

cd playwright_runtime
npm install
npx playwright install chromium firefox webkit

Then run with:

webcap shot --engine playwright --playwright-browser chromium http://localhost:3000

Documentation

Overview

Package webcap provides reusable browser capture services, manifest loading, image diffing, workflow reports, and engine abstractions for screenshot-oriented QA workflows.

Index

Constants

View Source
const (
	DefaultOversizePolicy      = OversizePolicyFail
	DefaultTileMaxWidth        = 8192
	DefaultTileMaxHeight       = 8192
	DefaultTileMaxPixels       = 67108864
	DefaultTileMaxTargetPixels = 536870912
	DefaultTileMaxStitchPixels = 268435456
	DefaultTileOverlap         = 0
)
View Source
const (
	WorkflowComparisonModeFull        = "full"
	WorkflowComparisonModeContentOnly = "content_only"
	WorkflowComparisonResizeCurrent   = "current"
	WorkflowComparisonResizeReference = "reference"
)
View Source
const (
	WorkflowReportFormatHTML = "html"
	WorkflowHookModeAppend   = "append"
	WorkflowHookModeReplace  = "replace"
)
View Source
const DefaultAuthLoginPath = "/admin/login"

Variables

View Source
var DefaultDebugCookieNames = []string{"admin_debug_session"}

Functions

func DefaultArtifactBaseName

func DefaultArtifactBaseName(req CaptureRequest) string

func DefaultDiffDirectoryName

func DefaultDiffDirectoryName(req DiffRequest) string

func DefaultDiffImageName

func DefaultDiffImageName(req DiffRequest) string

func DefaultPlaywrightRuntimeDir

func DefaultPlaywrightRuntimeDir() string

func DefaultWorkflowHandoff

func DefaultWorkflowHandoff(scenario WorkflowScenario) (string, error)

func LoadPlaywrightStorageState added in v0.7.0

func LoadPlaywrightStorageState(path string) (playwrightStorageState, error)

func ParseDurationOrDefault

func ParseDurationOrDefault(raw string, fallback time.Duration) (time.Duration, error)

func RedactAuthSecrets added in v0.9.0

func RedactAuthSecrets(text string, secrets ...string) string

func SemanticEnvironmentCredentialResolver

func SemanticEnvironmentCredentialResolver(_ context.Context, provider string) (string, error)

Types

type AnthropicSemanticProviderOptions

type AnthropicSemanticProviderOptions struct {
	CredentialResolver SemanticCredentialResolver
	HTTPClient         *http.Client
	BaseURL            string
}

type AuthCookieDiagnostic added in v0.9.0

type AuthCookieDiagnostic struct {
	Name            string     `json:"name"`
	Domain          string     `json:"domain,omitempty"`
	Path            string     `json:"path,omitempty"`
	Secure          bool       `json:"secure,omitempty"`
	HTTPOnly        bool       `json:"http_only,omitempty"`
	SameSite        string     `json:"same_site,omitempty"`
	Expires         int64      `json:"expires,omitempty"`
	Expired         bool       `json:"expired"`
	Skipped         bool       `json:"skipped,omitempty"`
	HostOnly        bool       `json:"host_only,omitempty"`
	Applicable      *bool      `json:"applicable,omitempty"`
	NotApplicable   []string   `json:"not_applicable,omitempty"`
	SourceLine      int        `json:"source_line,omitempty"`
	StorageState    bool       `json:"storage_state,omitempty"`
	CheckedAt       *time.Time `json:"checked_at,omitempty"`
	CaptureImportOK bool       `json:"capture_import_ok"`
}

type AuthDiagnosticWarning added in v0.9.0

type AuthDiagnosticWarning struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	Cookie  string `json:"cookie,omitempty"`
}

type AuthExpectedCookieStatus added in v0.9.0

type AuthExpectedCookieStatus struct {
	Name        string   `json:"name"`
	Status      string   `json:"status"`
	Applicable  *bool    `json:"applicable,omitempty"`
	Reasons     []string `json:"reasons,omitempty"`
	MatchedName string   `json:"matched_name,omitempty"`
}

type AuthInspectRequest added in v0.9.0

type AuthInspectRequest struct {
	CookieJar        string   `json:"cookie_jar,omitempty"`
	StorageState     string   `json:"storage_state,omitempty"`
	TargetURL        string   `json:"target_url,omitempty"`
	ExpectCookies    []string `json:"expect_cookies,omitempty"`
	WarnDebugCookies []string `json:"warn_debug_cookies,omitempty"`
}

type AuthInspectResult added in v0.9.0

type AuthInspectResult struct {
	Command          string                     `json:"command"`
	Source           AuthStateSource            `json:"source"`
	TargetURL        string                     `json:"target_url,omitempty"`
	Cookies          []AuthCookieDiagnostic     `json:"cookies"`
	ExpectedCookies  []AuthExpectedCookieStatus `json:"expected_cookies,omitempty"`
	Warnings         []AuthDiagnosticWarning    `json:"warnings,omitempty"`
	HasOriginStorage bool                       `json:"has_origin_storage,omitempty"`
	InspectedAt      time.Time                  `json:"inspected_at"`
}

func InspectAuthState added in v0.9.0

func InspectAuthState(req AuthInspectRequest, now time.Time) (AuthInspectResult, error)

type AuthLoginRequest added in v0.9.0

type AuthLoginRequest struct {
	ScriptPath    string   `json:"script,omitempty"`
	BaseURL       string   `json:"base_url"`
	LoginPath     string   `json:"login_path"`
	TargetURL     string   `json:"target_url,omitempty"`
	CookieJar     string   `json:"cookie_jar"`
	Identifier    string   `json:"identifier"`
	PasswordEnv   string   `json:"password_env"`
	ExpectCookies []string `json:"expect_cookies"`
	Timeout       string   `json:"timeout,omitempty"`
}

type AuthLoginResult added in v0.9.0

type AuthLoginResult struct {
	Command         string            `json:"command"`
	BaseURL         string            `json:"base_url"`
	LoginPath       string            `json:"login_path"`
	TargetURL       string            `json:"target_url,omitempty"`
	CookieJar       string            `json:"cookie_jar"`
	ExpectedCookies []string          `json:"expected_cookies"`
	Script          AuthScriptResult  `json:"script"`
	Inspection      AuthInspectResult `json:"inspection"`
}

func LoginAuthState added in v0.9.0

func LoginAuthState(ctx context.Context, req AuthLoginRequest, now time.Time) (AuthLoginResult, error)

type AuthScriptResult added in v0.9.0

type AuthScriptResult struct {
	Mode      string `json:"mode"`
	Path      string `json:"path,omitempty"`
	ExitCode  int    `json:"exit_code,omitempty"`
	TimedOut  bool   `json:"timed_out,omitempty"`
	Cancelled bool   `json:"cancelled,omitempty"`
	Stdout    string `json:"stdout,omitempty"`
	Stderr    string `json:"stderr,omitempty"`
}

type AuthStateSource added in v0.9.0

type AuthStateSource struct {
	Type string `json:"type"`
	Path string `json:"path"`
}

type BatchResult

type BatchResult struct {
	Results []CaptureResult `json:"results"`
}

type Bounds

type Bounds struct {
	X      float64 `json:"x"`
	Y      float64 `json:"y"`
	Width  float64 `json:"width"`
	Height float64 `json:"height"`
}

type BrowserInfo

type BrowserInfo struct {
	Engine          string `json:"engine"`
	BrowserPath     string `json:"browser_path,omitempty"`
	Product         string `json:"product,omitempty"`
	ProtocolVersion string `json:"protocol_version,omitempty"`
	Revision        string `json:"revision,omitempty"`
	UserAgent       string `json:"user_agent,omitempty"`
	JSVersion       string `json:"js_version,omitempty"`
	Headless        bool   `json:"headless"`
}

type CaptureArtifact

type CaptureArtifact struct {
	Bytes       []byte      `json:"-"`
	ImageFormat string      `json:"image_format"`
	Mode        CaptureMode `json:"mode"`
	Bounds      *Bounds     `json:"bounds,omitempty"`
	MatchCount  int         `json:"match_count,omitempty"`
	URL         string      `json:"url"`
	Viewport    Viewport    `json:"viewport"`
	Selectors   []string    `json:"selectors,omitempty"`
}

type CaptureAuth added in v0.7.0

type CaptureAuth struct {
	CookieJar    string          `json:"cookie_jar,omitempty" yaml:"cookie_jar,omitempty"`
	StorageState string          `json:"storage_state,omitempty" yaml:"storage_state,omitempty"`
	Headers      []CaptureHeader `json:"headers,omitempty" yaml:"headers,omitempty"`
	Cookies      []CaptureCookie `json:"cookies,omitempty" yaml:"cookies,omitempty"`
}

func (CaptureAuth) IsZero added in v0.7.0

func (a CaptureAuth) IsZero() bool

func (CaptureAuth) Redacted added in v0.7.0

func (a CaptureAuth) Redacted() CaptureAuth

type CaptureCookie added in v0.7.0

type CaptureCookie struct {
	Name     string `json:"name" yaml:"name"`
	Value    string `json:"value" yaml:"value"`
	Domain   string `json:"domain,omitempty" yaml:"domain,omitempty"`
	Path     string `json:"path,omitempty" yaml:"path,omitempty"`
	Secure   bool   `json:"secure,omitempty" yaml:"secure,omitempty"`
	HTTPOnly bool   `json:"httpOnly,omitempty" yaml:"httpOnly,omitempty"`
	SameSite string `json:"sameSite,omitempty" yaml:"sameSite,omitempty"`
	Expires  int64  `json:"expires,omitempty" yaml:"expires,omitempty"`
	HostOnly bool   `json:"host_only,omitempty" yaml:"host_only,omitempty"`
}

func ParseCookieJarFile added in v0.7.0

func ParseCookieJarFile(path string, now time.Time) ([]CaptureCookie, error)

type CaptureGuards added in v0.7.0

type CaptureGuards struct {
	ExpectURL      string   `json:"expect_url,omitempty" yaml:"expect_url,omitempty"`
	FailOnURL      []string `json:"fail_on_url,omitempty" yaml:"fail_on_url,omitempty"`
	FailOnSelector []string `json:"fail_on_selector,omitempty" yaml:"fail_on_selector,omitempty"`
}

func (CaptureGuards) IsZero added in v0.7.0

func (g CaptureGuards) IsZero() bool

type CaptureHeader added in v0.7.0

type CaptureHeader struct {
	Name  string `json:"name" yaml:"name"`
	Value string `json:"value" yaml:"value"`
}

type CaptureMode

type CaptureMode string
const (
	CaptureModeViewport     CaptureMode = "viewport"
	CaptureModeFullPage     CaptureMode = "full_page"
	CaptureModeSelector     CaptureMode = "selector"
	CaptureModeSelectors    CaptureMode = "selectors"
	CaptureModeSelectorAll  CaptureMode = "selector_all"
	CaptureModeSelectorsAll CaptureMode = "selectors_all"
)

type CaptureNormalization

type CaptureNormalization struct {
	OutputGenerated   bool          `json:"output_generated"`
	OutputBaseName    string        `json:"output_base_name,omitempty"`
	OutputDirectory   string        `json:"output_directory,omitempty"`
	ViewportPreset    string        `json:"viewport_preset,omitempty"`
	DevicePreset      string        `json:"device_preset,omitempty"`
	Readiness         ReadinessMode `json:"readiness"`
	ReadinessIdle     string        `json:"readiness_idle,omitempty"`
	DisableAnimations bool          `json:"disable_animations"`
	ReducedMotion     bool          `json:"reduced_motion"`
	WaitForFonts      bool          `json:"wait_for_fonts"`
}

type CaptureRequest

type CaptureRequest struct {
	URL               string             `json:"url" yaml:"url"`
	OutputPath        string             `json:"output,omitempty" yaml:"output,omitempty"`
	MetadataPath      string             `json:"metadata,omitempty" yaml:"metadata,omitempty"`
	FullPage          bool               `json:"full_page,omitempty" yaml:"full_page,omitempty"`
	Selector          string             `json:"selector,omitempty" yaml:"selector,omitempty"`
	Selectors         []string           `json:"selectors,omitempty" yaml:"selectors,omitempty"`
	SelectorAll       string             `json:"selector_all,omitempty" yaml:"selector_all,omitempty"`
	SelectorsAll      []string           `json:"selectors_all,omitempty" yaml:"selectors_all,omitempty"`
	Padding           int                `json:"padding,omitempty" yaml:"padding,omitempty"`
	Wait              string             `json:"wait,omitempty" yaml:"wait,omitempty"`
	WaitFor           string             `json:"wait_for,omitempty" yaml:"wait_for,omitempty"`
	WaitForFunction   string             `json:"wait_for_function,omitempty" yaml:"wait_for_function,omitempty"`
	JavaScript        string             `json:"javascript,omitempty" yaml:"javascript,omitempty"`
	Viewport          Viewport           `json:"viewport" yaml:"viewport,omitempty"`
	ViewportPreset    string             `json:"viewport_preset,omitempty" yaml:"viewport_preset,omitempty"`
	DevicePreset      string             `json:"device_preset,omitempty" yaml:"device_preset,omitempty"`
	UserAgent         string             `json:"user_agent,omitempty" yaml:"user_agent,omitempty"`
	Readiness         ReadinessMode      `json:"readiness,omitempty" yaml:"readiness,omitempty"`
	ReadinessIdle     string             `json:"readiness_idle,omitempty" yaml:"readiness_idle,omitempty"`
	DisableAnimations bool               `json:"disable_animations,omitempty" yaml:"disable_animations,omitempty"`
	ReducedMotion     bool               `json:"reduced_motion,omitempty" yaml:"reduced_motion,omitempty"`
	WaitForFonts      bool               `json:"wait_for_fonts,omitempty" yaml:"wait_for_fonts,omitempty"`
	BeforeNavigateJS  string             `json:"before_navigate_js,omitempty" yaml:"before_navigate_js,omitempty"`
	AfterNavigateJS   string             `json:"after_navigate_js,omitempty" yaml:"after_navigate_js,omitempty"`
	BeforeCaptureJS   string             `json:"before_capture_js,omitempty" yaml:"before_capture_js,omitempty"`
	Timeout           string             `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	Auth              CaptureAuth        `json:"auth,omitzero" yaml:"auth,omitempty"`
	Guards            CaptureGuards      `json:"guards,omitzero" yaml:"guards,omitempty"`
	OversizePolicy    OversizePolicy     `json:"oversize_policy,omitempty" yaml:"oversize_policy,omitempty"`
	Tile              CaptureTileOptions `json:"tile,omitzero" yaml:"tile,omitempty"`
}

func NormalizeCaptureRequest

func NormalizeCaptureRequest(req CaptureRequest) (CaptureRequest, error)

func ResolvePersistedPaths

func ResolvePersistedPaths(req CaptureRequest, outputDir string) (CaptureRequest, string, bool, error)

func (CaptureRequest) Mode

func (req CaptureRequest) Mode() CaptureMode

func (CaptureRequest) Normalization

func (req CaptureRequest) Normalization(outputGenerated bool, outputBaseName, outputDirectory string) CaptureNormalization

func (CaptureRequest) ReadinessIdleDuration

func (req CaptureRequest) ReadinessIdleDuration() time.Duration

func (CaptureRequest) Redacted added in v0.7.0

func (req CaptureRequest) Redacted() CaptureRequest

func (CaptureRequest) TargetSelectors

func (req CaptureRequest) TargetSelectors() ([]string, bool)

func (CaptureRequest) TimeoutDuration

func (req CaptureRequest) TimeoutDuration() time.Duration

func (CaptureRequest) WaitDuration

func (req CaptureRequest) WaitDuration() time.Duration

type CaptureResult

type CaptureResult struct {
	OutputPath     string               `json:"output_path"`
	MetadataPath   string               `json:"metadata_path,omitempty"`
	ByteSize       int                  `json:"byte_size"`
	Artifact       CaptureArtifact      `json:"artifact"`
	CapturedAt     time.Time            `json:"captured_at"`
	Engine         string               `json:"engine"`
	Browser        BrowserInfo          `json:"browser"`
	Timing         CaptureTiming        `json:"timing"`
	Warnings       []CaptureWarning     `json:"warnings,omitempty"`
	Guards         []GuardOutcome       `json:"guards,omitempty"`
	Tiling         *CaptureTiling       `json:"tiling,omitempty"`
	Normalization  CaptureNormalization `json:"normalization"`
	ResolvedConfig CaptureRequest       `json:"resolved_config"`
}

type CaptureService

type CaptureService interface {
	CaptureArtifact(ctx context.Context, req CaptureRequest) (CaptureArtifact, error)
	Capture(ctx context.Context, req CaptureRequest) (CaptureResult, error)
	CaptureBatch(ctx context.Context, manifest Manifest, outputDir string) (BatchResult, error)
}

type CaptureTile added in v0.5.0

type CaptureTile struct {
	Index             int               `json:"index"`
	Row               int               `json:"row"`
	Column            int               `json:"column"`
	SourceBounds      Bounds            `json:"source_bounds"`
	DestinationBounds *Bounds           `json:"destination_bounds,omitempty"`
	OutputPath        string            `json:"output_path,omitempty"`
	ByteSize          int               `json:"byte_size,omitempty"`
	Status            CaptureTileStatus `json:"status"`
	Error             string            `json:"error,omitempty"`
	Bytes             []byte            `json:"-"`
}

type CaptureTileLimits added in v0.5.0

type CaptureTileLimits struct {
	MaxWidth          int     `json:"max_width"`
	MaxHeight         int     `json:"max_height"`
	MaxPixels         int64   `json:"max_pixels"`
	MaxTargetPixels   int64   `json:"max_target_pixels"`
	MaxStitchedPixels int64   `json:"max_stitched_pixels"`
	Overlap           int     `json:"overlap"`
	ScaleFactor       float64 `json:"scale_factor"`
}

type CaptureTileOptions added in v0.5.0

type CaptureTileOptions struct {
	MaxWidth          int   `json:"max_width,omitempty" yaml:"max_width,omitempty"`
	MaxHeight         int   `json:"max_height,omitempty" yaml:"max_height,omitempty"`
	MaxPixels         int64 `json:"max_pixels,omitempty" yaml:"max_pixels,omitempty"`
	MaxTargetPixels   int64 `json:"max_target_pixels,omitempty" yaml:"max_target_pixels,omitempty"`
	Overlap           int   `json:"overlap,omitempty" yaml:"overlap,omitempty"`
	Stitch            bool  `json:"stitch,omitempty" yaml:"stitch,omitempty"`
	MaxStitchedPixels int64 `json:"max_stitched_pixels,omitempty" yaml:"max_stitched_pixels,omitempty"`
	CleanupTiles      bool  `json:"cleanup_tiles,omitempty" yaml:"cleanup_tiles,omitempty"`
	// contains filtered or unexported fields
}

func (CaptureTileOptions) IsZero added in v0.5.0

func (o CaptureTileOptions) IsZero() bool

func (*CaptureTileOptions) UnmarshalJSON added in v0.5.0

func (o *CaptureTileOptions) UnmarshalJSON(payload []byte) error

func (*CaptureTileOptions) UnmarshalYAML added in v0.5.0

func (o *CaptureTileOptions) UnmarshalYAML(value *yaml.Node) error

type CaptureTileStatus added in v0.5.0

type CaptureTileStatus string
const (
	CaptureTilePending   CaptureTileStatus = "pending"
	CaptureTileCompleted CaptureTileStatus = "completed"
	CaptureTileFailed    CaptureTileStatus = "failed"
)

type CaptureTiling added in v0.5.0

type CaptureTiling struct {
	Status         CaptureTilingStatus `json:"status"`
	TargetBounds   Bounds              `json:"target_bounds"`
	Limits         CaptureTileLimits   `json:"limits"`
	TileCount      int                 `json:"tile_count"`
	CompletedCount int                 `json:"completed_count"`
	FailedCount    int                 `json:"failed_count"`
	MetadataPath   string              `json:"metadata_path,omitempty"`
	StitchedPath   string              `json:"stitched_path,omitempty"`
	Tiles          []CaptureTile       `json:"tiles,omitempty"`
	Warnings       []CaptureWarning    `json:"warnings,omitempty"`
}

type CaptureTilingStatus added in v0.5.0

type CaptureTilingStatus string
const (
	CaptureTilingComplete CaptureTilingStatus = "complete"
	CaptureTilingPartial  CaptureTilingStatus = "partial"
	CaptureTilingFailed   CaptureTilingStatus = "failed"
)

type CaptureTiming

type CaptureTiming struct {
	NavigationStartedAt   time.Time `json:"navigation_started_at"`
	NavigationCompletedAt time.Time `json:"navigation_completed_at"`
	ReadyAt               time.Time `json:"ready_at"`
	CapturedAt            time.Time `json:"captured_at"`
	TotalDuration         string    `json:"total_duration"`
}

type CaptureWarning

type CaptureWarning struct {
	Code     string         `json:"code"`
	Message  string         `json:"message"`
	Metadata map[string]any `json:"metadata,omitempty"`
}

type ChromiumEngine

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

func NewChromiumEngine

func NewChromiumEngine(opts ChromiumOptions) *ChromiumEngine

func (*ChromiumEngine) Capture

func (*ChromiumEngine) Name

func (e *ChromiumEngine) Name() string

type ChromiumOptions

type ChromiumOptions struct {
	BrowserPath string
	Headless    bool
	Now         func() time.Time
}

type DeviceProfile

type DeviceProfile struct {
	Name      string   `json:"name"`
	Viewport  Viewport `json:"viewport"`
	UserAgent string   `json:"user_agent,omitempty"`
}

func LookupDevicePreset

func LookupDevicePreset(name string) (DeviceProfile, bool)

type DiffEntry

type DiffEntry struct {
	RelativePath   string           `json:"relative_path,omitempty"`
	BasePath       string           `json:"base_path"`
	ComparePath    string           `json:"compare_path"`
	OutputPath     string           `json:"output_path,omitempty"`
	MetadataPath   string           `json:"metadata_path,omitempty"`
	ByteSize       int              `json:"byte_size,omitempty"`
	Width          int              `json:"width,omitempty"`
	Height         int              `json:"height,omitempty"`
	TotalPixels    int              `json:"total_pixels,omitempty"`
	ChangedPixels  int              `json:"changed_pixels,omitempty"`
	ChangedPercent float64          `json:"changed_percent,omitempty"`
	Threshold      float64          `json:"threshold"`
	Changed        bool             `json:"changed"`
	MissingBase    bool             `json:"missing_base,omitempty"`
	MissingCompare bool             `json:"missing_compare,omitempty"`
	Warnings       []CaptureWarning `json:"warnings,omitempty"`
}

type DiffMode

type DiffMode string
const (
	DiffModeImage     DiffMode = "image"
	DiffModeDirectory DiffMode = "directory"
)

func InferDiffMode

func InferDiffMode(basePath, comparePath string) (DiffMode, error)

type DiffRequest

type DiffRequest struct {
	BasePath     string  `json:"base_path"`
	ComparePath  string  `json:"compare_path"`
	OutputPath   string  `json:"output,omitempty"`
	MetadataPath string  `json:"metadata,omitempty"`
	Threshold    float64 `json:"threshold,omitempty"`
}

func NormalizeDiffRequest

func NormalizeDiffRequest(req DiffRequest) (DiffRequest, error)

func ResolveDiffPaths

func ResolveDiffPaths(req DiffRequest, mode DiffMode) (DiffRequest, error)

type DiffResult

type DiffResult struct {
	Mode         DiffMode    `json:"mode"`
	BasePath     string      `json:"base_path"`
	ComparePath  string      `json:"compare_path"`
	OutputPath   string      `json:"output_path"`
	MetadataPath string      `json:"metadata_path,omitempty"`
	Threshold    float64     `json:"threshold"`
	Entry        *DiffEntry  `json:"entry,omitempty"`
	Entries      []DiffEntry `json:"entries,omitempty"`
	Summary      DiffSummary `json:"summary"`
	CreatedAt    time.Time   `json:"created_at"`
}

type DiffService

type DiffService interface {
	Diff(ctx context.Context, req DiffRequest) (DiffResult, error)
}

type DiffSummary

type DiffSummary struct {
	ComparedFiles       int              `json:"compared_files"`
	ChangedFiles        int              `json:"changed_files"`
	MissingBaseFiles    int              `json:"missing_base_files"`
	MissingCompareFiles int              `json:"missing_compare_files"`
	TotalChangedPixels  int              `json:"total_changed_pixels"`
	Warnings            []CaptureWarning `json:"warnings,omitempty"`
}

type Engine

type Engine interface {
	Name() string
	Capture(ctx context.Context, req CaptureRequest) (EngineResult, error)
}

func NewEngine

func NewEngine(config EngineConfig) (Engine, error)

type EngineConfig

type EngineConfig struct {
	EngineName           EngineName
	BrowserPath          string
	Headless             bool
	PlaywrightBrowser    string
	PlaywrightNodeBinary string
	PlaywrightRuntimeDir string
	PlaywrightScriptPath string
}

type EngineName

type EngineName string
const (
	EngineChromium   EngineName = "chromium"
	EnginePlaywright EngineName = "playwright"
)

func NormalizeEngineName

func NormalizeEngineName(value string) EngineName

type EngineResult

type EngineResult struct {
	Artifact CaptureArtifact  `json:"artifact"`
	Browser  BrowserInfo      `json:"browser"`
	Timing   CaptureTiming    `json:"timing"`
	Warnings []CaptureWarning `json:"warnings,omitempty"`
	Guards   []GuardOutcome   `json:"guards,omitempty"`
	Tiling   *CaptureTiling   `json:"tiling,omitempty"`
}

type Error

type Error struct {
	Code      ErrorCode      `json:"code"`
	Operation string         `json:"operation,omitempty"`
	Message   string         `json:"message"`
	Metadata  map[string]any `json:"metadata,omitempty"`
	Err       error          `json:"-"`
}

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

func (*Error) WithMetadata

func (e *Error) WithMetadata(key string, value any) *Error

type ErrorCode

type ErrorCode string
const (
	CodeValidation              ErrorCode = "validation_error"
	CodeManifest                ErrorCode = "manifest_error"
	CodeBrowserStartup          ErrorCode = "browser_startup_error"
	CodeNavigation              ErrorCode = "navigation_error"
	CodeSelectorNotFound        ErrorCode = "selector_not_found"
	CodeNoVisibleMatches        ErrorCode = "no_visible_matches"
	CodeTimeout                 ErrorCode = "timeout_error"
	CodeWrite                   ErrorCode = "write_error"
	CodeCapture                 ErrorCode = "capture_error"
	CodeOversize                ErrorCode = "oversize_error"
	CodeUnsupported             ErrorCode = "unsupported_error"
	CodePartialCapture          ErrorCode = "partial_capture_error"
	CodeProviderRateLimited     ErrorCode = "provider_rate_limited"
	CodeProviderAuth            ErrorCode = "provider_auth"
	CodeProviderQuota           ErrorCode = "provider_quota"
	CodeProviderInvalidRequest  ErrorCode = "provider_invalid_request"
	CodeProviderPayloadTooLarge ErrorCode = "provider_payload_too_large"
	CodeProviderUnavailable     ErrorCode = "provider_unavailable"
	CodeProviderTimeout         ErrorCode = "provider_timeout"
	CodeProviderExecutionFailed ErrorCode = "provider_execution_failed"
)

type GuardOutcome added in v0.7.0

type GuardOutcome struct {
	Kind     string `json:"kind"`
	Value    string `json:"value,omitempty"`
	FinalURL string `json:"final_url,omitempty"`
	Matched  bool   `json:"matched"`
	Status   string `json:"status"`
}

type Manifest

type Manifest struct {
	OutputDir         string             `json:"output_dir,omitempty" yaml:"output_dir,omitempty"`
	Viewport          *Viewport          `json:"viewport,omitempty" yaml:"viewport,omitempty"`
	ViewportPreset    string             `json:"viewport_preset,omitempty" yaml:"viewport_preset,omitempty"`
	DevicePreset      string             `json:"device_preset,omitempty" yaml:"device_preset,omitempty"`
	Wait              string             `json:"wait,omitempty" yaml:"wait,omitempty"`
	WaitFor           string             `json:"wait_for,omitempty" yaml:"wait_for,omitempty"`
	WaitForFunction   string             `json:"wait_for_function,omitempty" yaml:"wait_for_function,omitempty"`
	Readiness         ReadinessMode      `json:"readiness,omitempty" yaml:"readiness,omitempty"`
	ReadinessIdle     string             `json:"readiness_idle,omitempty" yaml:"readiness_idle,omitempty"`
	DisableAnimations bool               `json:"disable_animations,omitempty" yaml:"disable_animations,omitempty"`
	ReducedMotion     bool               `json:"reduced_motion,omitempty" yaml:"reduced_motion,omitempty"`
	WaitForFonts      bool               `json:"wait_for_fonts,omitempty" yaml:"wait_for_fonts,omitempty"`
	Timeout           string             `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	Auth              CaptureAuth        `json:"auth,omitzero" yaml:"auth,omitempty"`
	Guards            CaptureGuards      `json:"guards,omitzero" yaml:"guards,omitempty"`
	OversizePolicy    OversizePolicy     `json:"oversize_policy,omitempty" yaml:"oversize_policy,omitempty"`
	Tile              CaptureTileOptions `json:"tile,omitzero" yaml:"tile,omitempty"`
	Shots             []ManifestShot     `json:"shots" yaml:"shots"`
	SourcePath        string             `json:"-" yaml:"-"`
	SourceDir         string             `json:"-" yaml:"-"`
}

func LoadManifest

func LoadManifest(path string) (Manifest, error)

func (Manifest) Requests

func (m Manifest) Requests(outputDirOverride string) ([]CaptureRequest, error)

type ManifestShot

type ManifestShot struct {
	ID                string             `json:"id,omitempty" yaml:"id,omitempty"`
	URL               string             `json:"url" yaml:"url"`
	Output            string             `json:"output,omitempty" yaml:"output,omitempty"`
	Metadata          string             `json:"metadata,omitempty" yaml:"metadata,omitempty"`
	FullPage          bool               `json:"full_page,omitempty" yaml:"full_page,omitempty"`
	Selector          string             `json:"selector,omitempty" yaml:"selector,omitempty"`
	Selectors         []string           `json:"selectors,omitempty" yaml:"selectors,omitempty"`
	SelectorAll       string             `json:"selector_all,omitempty" yaml:"selector_all,omitempty"`
	SelectorsAll      []string           `json:"selectors_all,omitempty" yaml:"selectors_all,omitempty"`
	Padding           int                `json:"padding,omitempty" yaml:"padding,omitempty"`
	Wait              string             `json:"wait,omitempty" yaml:"wait,omitempty"`
	WaitFor           string             `json:"wait_for,omitempty" yaml:"wait_for,omitempty"`
	WaitForFunction   string             `json:"wait_for_function,omitempty" yaml:"wait_for_function,omitempty"`
	JavaScript        string             `json:"javascript,omitempty" yaml:"javascript,omitempty"`
	Viewport          *Viewport          `json:"viewport,omitempty" yaml:"viewport,omitempty"`
	ViewportPreset    string             `json:"viewport_preset,omitempty" yaml:"viewport_preset,omitempty"`
	DevicePreset      string             `json:"device_preset,omitempty" yaml:"device_preset,omitempty"`
	Readiness         ReadinessMode      `json:"readiness,omitempty" yaml:"readiness,omitempty"`
	ReadinessIdle     string             `json:"readiness_idle,omitempty" yaml:"readiness_idle,omitempty"`
	DisableAnimations bool               `json:"disable_animations,omitempty" yaml:"disable_animations,omitempty"`
	ReducedMotion     bool               `json:"reduced_motion,omitempty" yaml:"reduced_motion,omitempty"`
	WaitForFonts      bool               `json:"wait_for_fonts,omitempty" yaml:"wait_for_fonts,omitempty"`
	Timeout           string             `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	Auth              CaptureAuth        `json:"auth,omitzero" yaml:"auth,omitempty"`
	Guards            CaptureGuards      `json:"guards,omitzero" yaml:"guards,omitempty"`
	OversizePolicy    OversizePolicy     `json:"oversize_policy,omitempty" yaml:"oversize_policy,omitempty"`
	Tile              CaptureTileOptions `json:"tile,omitzero" yaml:"tile,omitempty"`
}

type OpenAISemanticProviderOptions

type OpenAISemanticProviderOptions struct {
	CredentialResolver SemanticCredentialResolver
	HTTPClient         *http.Client
	BaseURL            string
}

type Options

type Options struct {
	Workflow     WorkflowOptions
	SemanticDiff SemanticDiffOptions
	Now          func() time.Time
}

type OversizePolicy added in v0.5.0

type OversizePolicy string
const (
	OversizePolicyFail OversizePolicy = "fail"
	OversizePolicyTile OversizePolicy = "tile"
)

type PartialCaptureError added in v0.5.0

type PartialCaptureError struct {
	Operation       string         `json:"operation"`
	FailedTileIndex int            `json:"failed_tile_index,omitempty"`
	CompletedCount  int            `json:"completed_count"`
	TotalCount      int            `json:"total_count"`
	Result          *CaptureResult `json:"result,omitempty"`
	Err             error          `json:"-"`
}

func (*PartialCaptureError) Error added in v0.5.0

func (e *PartialCaptureError) Error() string

func (*PartialCaptureError) Unwrap added in v0.5.0

func (e *PartialCaptureError) Unwrap() error

func (*PartialCaptureError) WithResult added in v0.5.0

func (e *PartialCaptureError) WithResult(result CaptureResult) *PartialCaptureError

type PlaywrightEngine

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

func NewPlaywrightEngine

func NewPlaywrightEngine(opts PlaywrightOptions) (*PlaywrightEngine, error)

func (*PlaywrightEngine) Capture

func (*PlaywrightEngine) Name

func (e *PlaywrightEngine) Name() string

type PlaywrightOptions

type PlaywrightOptions struct {
	NodeBinary  string
	BrowserName string
	BrowserPath string
	Headless    bool
	RuntimeDir  string
	ScriptPath  string
}

type ReadinessMode

type ReadinessMode string
const (
	ReadinessNone        ReadinessMode = "none"
	ReadinessInteractive ReadinessMode = "interactive"
	ReadinessComplete    ReadinessMode = "complete"
	ReadinessNetworkIdle ReadinessMode = "network_idle"
)

type SemanticCredentialResolver

type SemanticCredentialResolver func(ctx context.Context, provider string) (string, error)

type SemanticDiffAdvisoryPolicy

type SemanticDiffAdvisoryPolicy string
const (
	SemanticDiffAdvisoryOnly    SemanticDiffAdvisoryPolicy = "advisory"
	SemanticDiffAdvisoryEnforce SemanticDiffAdvisoryPolicy = "enforce"
)

type SemanticDiffMode

type SemanticDiffMode string
const (
	SemanticDiffModeGeneral       SemanticDiffMode = "general"
	SemanticDiffModeFocused       SemanticDiffMode = "focused"
	SemanticDiffModeCopy          SemanticDiffMode = "copy"
	SemanticDiffModeLayout        SemanticDiffMode = "layout"
	SemanticDiffModeAccessibility SemanticDiffMode = "accessibility"
	SemanticDiffModeCustom        SemanticDiffMode = "custom"
)

type SemanticDiffOptions

type SemanticDiffOptions struct {
	Providers                    map[string]SemanticDiffProvider
	CredentialResolver           SemanticCredentialResolver
	RedactImage                  SemanticImageRedactor
	HTTPClient                   *http.Client
	DefaultProvider              string
	DefaultModels                map[string]string
	DefaultTimeout               time.Duration
	MaxImageBytes                int64
	MaxProviderImageBytes        int64
	MaxImageLongEdge             int
	MaxImagePixels               int64
	MaxEncodedImageBytes         int64
	MaxCombinedEncodedImageBytes int64
	MaxRequestBodyBytes          int64
	ResizeImages                 bool
	MaxOutputTokens              int
	PersistRawResponses          bool
	LLMs                         llms.Options
	OpenAIBaseURL                string
	AnthropicBaseURL             string
}

type SemanticDiffProvider

type SemanticDiffProvider interface {
	Name() string
	CompareImages(ctx context.Context, req SemanticProviderRequest) (SemanticProviderResponse, error)
}

func NewLLMSSemanticDiffProvider

func NewLLMSSemanticDiffProvider(provider llms.Provider) SemanticDiffProvider

func NewOpenAISemanticDiffProvider

func NewOpenAISemanticDiffProvider(options OpenAISemanticProviderOptions) SemanticDiffProvider

type SemanticDiffRequest

type SemanticDiffRequest struct {
	CurrentPath        string               `json:"current_path" yaml:"current_path"`
	ReferencePath      string               `json:"reference_path" yaml:"reference_path"`
	Provider           string               `json:"provider,omitempty" yaml:"provider,omitempty"`
	Model              string               `json:"model,omitempty" yaml:"model,omitempty"`
	Mode               SemanticDiffMode     `json:"mode,omitempty" yaml:"mode,omitempty"`
	Prompt             string               `json:"prompt,omitempty" yaml:"prompt,omitempty"`
	PromptPath         string               `json:"prompt_path,omitempty" yaml:"prompt_path,omitempty"`
	Focus              []string             `json:"focus,omitempty" yaml:"focus,omitempty"`
	PixelContext       SemanticPixelContext `json:"pixel_context" yaml:"pixel_context,omitempty"`
	MetadataPath       string               `json:"metadata_path,omitempty" yaml:"metadata_path,omitempty"`
	RawResponsePath    string               `json:"raw_response_path,omitempty" yaml:"raw_response_path,omitempty"`
	Timeout            string               `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	MaxOutputTokens    int                  `json:"max_output_tokens,omitempty" yaml:"max_output_tokens,omitempty"`
	PersistRawResponse bool                 `json:"persist_raw_response,omitempty" yaml:"persist_raw_response,omitempty"`
}

func NormalizeSemanticDiffRequest

func NormalizeSemanticDiffRequest(req SemanticDiffRequest) (SemanticDiffRequest, error)

type SemanticDiffResult

type SemanticDiffResult struct {
	CurrentPath      string                  `json:"current_path"`
	ReferencePath    string                  `json:"reference_path"`
	Provider         string                  `json:"provider"`
	Model            string                  `json:"model,omitempty"`
	Summary          string                  `json:"summary"`
	Differences      []SemanticDifference    `json:"differences,omitempty"`
	Verdict          SemanticDiffVerdict     `json:"verdict"`
	Severity         SemanticDiffSeverity    `json:"severity"`
	Prompt           SemanticPromptMetadata  `json:"prompt"`
	PixelContext     SemanticPixelContext    `json:"pixel_context"`
	MetadataPath     string                  `json:"metadata_path,omitempty"`
	RawResponsePath  string                  `json:"raw_response_path,omitempty"`
	Warnings         []CaptureWarning        `json:"warnings,omitempty"`
	ImageMetadata    []SemanticImageMetadata `json:"image_metadata,omitempty"`
	ProviderMetadata map[string]string       `json:"provider_metadata,omitempty"`
	Usage            SemanticProviderUsage   `json:"usage"`
	StartedAt        time.Time               `json:"started_at"`
	CompletedAt      time.Time               `json:"completed_at"`
	Duration         string                  `json:"duration,omitempty"`
	RawResponse      string                  `json:"-"`
}

type SemanticDiffRunPolicy

type SemanticDiffRunPolicy string
const (
	SemanticDiffRunNever       SemanticDiffRunPolicy = "never"
	SemanticDiffRunAlways      SemanticDiffRunPolicy = "always"
	SemanticDiffRunChangedOnly SemanticDiffRunPolicy = "changed_only"
)

type SemanticDiffService

type SemanticDiffService interface {
	SemanticDiff(ctx context.Context, req SemanticDiffRequest) (SemanticDiffResult, error)
}

type SemanticDiffSeverity

type SemanticDiffSeverity string
const (
	SemanticDiffSeverityInfo     SemanticDiffSeverity = "info"
	SemanticDiffSeverityMinor    SemanticDiffSeverity = "minor"
	SemanticDiffSeverityMajor    SemanticDiffSeverity = "major"
	SemanticDiffSeverityBlocking SemanticDiffSeverity = "blocking"
)

type SemanticDiffVerdict

type SemanticDiffVerdict string
const (
	SemanticDiffVerdictNoMeaningfulChange SemanticDiffVerdict = "no_meaningful_change"
	SemanticDiffVerdictNeedsReview        SemanticDiffVerdict = "needs_review"
	SemanticDiffVerdictRegression         SemanticDiffVerdict = "regression"
	SemanticDiffVerdictImprovement        SemanticDiffVerdict = "improvement"
	SemanticDiffVerdictInconclusive       SemanticDiffVerdict = "inconclusive"
)

type SemanticDifference

type SemanticDifference struct {
	Area           string               `json:"area,omitempty"`
	Description    string               `json:"description"`
	Severity       SemanticDiffSeverity `json:"severity"`
	Evidence       string               `json:"evidence,omitempty"`
	Recommendation string               `json:"recommendation,omitempty"`
}

type SemanticImageMetadata added in v0.6.1

type SemanticImageMetadata struct {
	Role             string         `json:"role"`
	OriginalPath     string         `json:"original_path"`
	MIMEType         string         `json:"mime_type"`
	OriginalByteSize int64          `json:"original_byte_size"`
	OriginalWidth    int            `json:"original_width,omitempty"`
	OriginalHeight   int            `json:"original_height,omitempty"`
	ProviderPath     string         `json:"provider_path,omitempty"`
	ProviderByteSize int64          `json:"provider_byte_size"`
	ProviderWidth    int            `json:"provider_width,omitempty"`
	ProviderHeight   int            `json:"provider_height,omitempty"`
	ResizeReason     string         `json:"resize_reason,omitempty"`
	Limits           map[string]any `json:"limits,omitempty"`
}

type SemanticImagePayload

type SemanticImagePayload struct {
	Role       string                `json:"role"`
	Path       string                `json:"path"`
	MIMEType   string                `json:"mime_type"`
	Base64Data string                `json:"-"`
	ByteSize   int64                 `json:"byte_size"`
	Width      int                   `json:"width,omitempty"`
	Height     int                   `json:"height,omitempty"`
	Metadata   SemanticImageMetadata `json:"-"`
}

type SemanticImageRedactionInput

type SemanticImageRedactionInput struct {
	Role string
	Path string
}

type SemanticImageRedactor

type SemanticImageRedactor func(ctx context.Context, input SemanticImageRedactionInput) (string, error)

type SemanticPixelContext

type SemanticPixelContext struct {
	PixelDiffImagePath string           `json:"pixel_diff_image_path,omitempty" yaml:"pixel_diff_image_path,omitempty"`
	ChangedPixels      int              `json:"changed_pixels,omitempty" yaml:"changed_pixels,omitempty"`
	TotalPixels        int              `json:"total_pixels,omitempty" yaml:"total_pixels,omitempty"`
	ChangedPercent     float64          `json:"changed_percent,omitempty" yaml:"changed_percent,omitempty"`
	Threshold          float64          `json:"threshold,omitempty" yaml:"threshold,omitempty"`
	Warnings           []CaptureWarning `json:"warnings,omitempty" yaml:"warnings,omitempty"`
}

type SemanticPromptMetadata

type SemanticPromptMetadata struct {
	Mode                 SemanticDiffMode `json:"mode"`
	Focus                []string         `json:"focus,omitempty"`
	PromptSource         string           `json:"prompt_source"`
	StructuredOutput     bool             `json:"structured_output"`
	PixelContextIncluded bool             `json:"pixel_context_included"`
}

type SemanticProviderRequest

type SemanticProviderRequest struct {
	Provider            string
	Model               string
	Prompt              string
	Images              []SemanticImagePayload
	Timeout             time.Duration
	MaxOutputTokens     int
	StructuredJSON      bool
	MaxRequestBodyBytes int64
}

type SemanticProviderResponse

type SemanticProviderResponse struct {
	Provider string
	Model    string
	RawText  string
	Warnings []CaptureWarning
	Metadata map[string]string
	Usage    SemanticProviderUsage
}

type SemanticProviderUsage

type SemanticProviderUsage struct {
	InputTokens  int `json:"input_tokens,omitempty"`
	OutputTokens int `json:"output_tokens,omitempty"`
	TotalTokens  int `json:"total_tokens,omitempty"`
}

type Service

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

func NewService

func NewService(engine Engine) *Service

func NewServiceWithOptions

func NewServiceWithOptions(engine Engine, opts Options) *Service

func (*Service) Capture

func (s *Service) Capture(ctx context.Context, req CaptureRequest) (CaptureResult, error)

func (*Service) CaptureArtifact

func (s *Service) CaptureArtifact(ctx context.Context, req CaptureRequest) (CaptureArtifact, error)

func (*Service) CaptureBatch

func (s *Service) CaptureBatch(ctx context.Context, manifest Manifest, outputDir string) (BatchResult, error)

func (*Service) CaptureScenario

func (s *Service) CaptureScenario(ctx context.Context, scenario WorkflowScenario) (WorkflowCaptureResult, error)

func (*Service) Diff

func (s *Service) Diff(ctx context.Context, req DiffRequest) (DiffResult, error)

func (*Service) GenerateWorkflowReport

func (s *Service) GenerateWorkflowReport(ctx context.Context, req WorkflowReportRequest) (WorkflowReportResult, error)

func (*Service) SemanticDiff

func (s *Service) SemanticDiff(ctx context.Context, req SemanticDiffRequest) (SemanticDiffResult, error)

type Viewport

type Viewport struct {
	Width       int     `json:"width" yaml:"width"`
	Height      int     `json:"height" yaml:"height"`
	ScaleFactor float64 `json:"scale_factor,omitempty" yaml:"scale_factor,omitempty"`
	Mobile      bool    `json:"mobile,omitempty" yaml:"mobile,omitempty"`
}

func LookupViewportPreset

func LookupViewportPreset(name string) (Viewport, bool)

type WorkflowArtifactLayout

type WorkflowArtifactLayout struct {
	Root         string `json:"root,omitempty" yaml:"root,omitempty"`
	CurrentDir   string `json:"current_dir,omitempty" yaml:"current_dir,omitempty"`
	ReferenceDir string `json:"reference_dir,omitempty" yaml:"reference_dir,omitempty"`
	DiffDir      string `json:"diff_dir,omitempty" yaml:"diff_dir,omitempty"`
	ReportDir    string `json:"report_dir,omitempty" yaml:"report_dir,omitempty"`
}

type WorkflowCaptureResult

type WorkflowCaptureResult struct {
	ScenarioID   string                        `json:"scenario_id"`
	ScenarioPath string                        `json:"scenario_path"`
	CurrentDir   string                        `json:"current_dir"`
	CapturedAt   time.Time                     `json:"captured_at"`
	Results      []WorkflowScreenCaptureResult `json:"results"`
}

type WorkflowCompareRect

type WorkflowCompareRect struct {
	X      int `json:"x" yaml:"x"`
	Y      int `json:"y" yaml:"y"`
	Width  int `json:"width" yaml:"width"`
	Height int `json:"height" yaml:"height"`
}

type WorkflowComparison

type WorkflowComparison struct {
	Mode          string               `json:"mode,omitempty" yaml:"mode,omitempty"`
	CurrentCrop   *WorkflowCompareRect `json:"current_crop,omitempty" yaml:"current_crop,omitempty"`
	ReferenceCrop *WorkflowCompareRect `json:"reference_crop,omitempty" yaml:"reference_crop,omitempty"`
	ResizeTo      string               `json:"resize_to,omitempty" yaml:"resize_to,omitempty"`
}

type WorkflowDefaults

type WorkflowDefaults struct {
	Viewport          Viewport             `json:"viewport" yaml:"viewport,omitempty"`
	ViewportPreset    string               `json:"viewport_preset,omitempty" yaml:"viewport_preset,omitempty"`
	DevicePreset      string               `json:"device_preset,omitempty" yaml:"device_preset,omitempty"`
	Comparison        WorkflowComparison   `json:"comparison" yaml:"comparison,omitempty"`
	SemanticDiff      WorkflowSemanticDiff `json:"semantic_diff" yaml:"semantic_diff,omitempty"`
	Readiness         ReadinessMode        `json:"readiness,omitempty" yaml:"readiness,omitempty"`
	ReadinessIdle     string               `json:"readiness_idle,omitempty" yaml:"readiness_idle,omitempty"`
	DisableAnimations bool                 `json:"disable_animations,omitempty" yaml:"disable_animations,omitempty"`
	ReducedMotion     bool                 `json:"reduced_motion,omitempty" yaml:"reduced_motion,omitempty"`
	WaitForFonts      bool                 `json:"wait_for_fonts,omitempty" yaml:"wait_for_fonts,omitempty"`
	Timeout           string               `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	Wait              string               `json:"wait,omitempty" yaml:"wait,omitempty"`
	WaitFor           string               `json:"wait_for,omitempty" yaml:"wait_for,omitempty"`
	WaitForFunction   string               `json:"wait_for_function,omitempty" yaml:"wait_for_function,omitempty"`
	FullPage          *bool                `json:"full_page,omitempty" yaml:"full_page,omitempty"`
	Auth              CaptureAuth          `json:"auth,omitzero" yaml:"auth,omitempty"`
	Guards            CaptureGuards        `json:"guards,omitzero" yaml:"guards,omitempty"`
	OversizePolicy    OversizePolicy       `json:"oversize_policy,omitempty" yaml:"oversize_policy,omitempty"`
	Tile              CaptureTileOptions   `json:"tile,omitzero" yaml:"tile,omitempty"`
}

type WorkflowEnvironment

type WorkflowEnvironment struct {
	BaseURL              string            `json:"base_url" yaml:"base_url"`
	Engine               string            `json:"engine,omitempty" yaml:"engine,omitempty"`
	BrowserPath          string            `json:"browser_path,omitempty" yaml:"browser_path,omitempty"`
	Headless             *bool             `json:"headless,omitempty" yaml:"headless,omitempty"`
	PlaywrightBrowser    string            `json:"playwright_browser,omitempty" yaml:"playwright_browser,omitempty"`
	PlaywrightNodeBinary string            `json:"playwright_node_binary,omitempty" yaml:"playwright_node_binary,omitempty"`
	PlaywrightRuntimeDir string            `json:"playwright_runtime_dir,omitempty" yaml:"playwright_runtime_dir,omitempty"`
	FixtureMode          string            `json:"fixture_mode,omitempty" yaml:"fixture_mode,omitempty"`
	ReportFormat         string            `json:"report_format,omitempty" yaml:"report_format,omitempty"`
	SelectedScenario     string            `json:"selected_scenario,omitempty" yaml:"selected_scenario,omitempty"`
	PresentationMode     string            `json:"presentation_mode,omitempty" yaml:"presentation_mode,omitempty"`
	RuntimeOverrides     map[string]string `json:"runtime_overrides,omitempty" yaml:"runtime_overrides,omitempty"`
	Query                map[string]string `json:"query,omitempty" yaml:"query,omitempty"`
}

type WorkflowEvidenceItem

type WorkflowEvidenceItem struct {
	ID                string   `json:"id" yaml:"id"`
	Text              string   `json:"text" yaml:"text"`
	Stories           []string `json:"stories,omitempty" yaml:"stories,omitempty"`
	ExpectedSelectors []string `json:"expected_selectors,omitempty" yaml:"expected_selectors,omitempty"`
	Notes             []string `json:"notes,omitempty" yaml:"notes,omitempty"`
}

type WorkflowHandoff

type WorkflowHandoff struct {
	SelectedScenario string            `json:"selected_scenario,omitempty"`
	PresentationMode string            `json:"presentation_mode,omitempty"`
	RuntimeOverrides map[string]string `json:"runtime_overrides,omitempty"`
}

type WorkflowHookSet

type WorkflowHookSet struct {
	BeforeNavigate string         `json:"before_navigate,omitempty" yaml:"before_navigate,omitempty"`
	AfterNavigate  string         `json:"after_navigate,omitempty" yaml:"after_navigate,omitempty"`
	BeforeCapture  string         `json:"before_capture,omitempty" yaml:"before_capture,omitempty"`
	Mode           string         `json:"mode,omitempty" yaml:"mode,omitempty"`
	Context        map[string]any `json:"context,omitempty" yaml:"context,omitempty"`
}

type WorkflowHooks

type WorkflowHooks struct {
	AuthSetup  WorkflowHookSet `json:"auth_setup" yaml:"auth_setup,omitempty"`
	StateSetup WorkflowHookSet `json:"state_setup" yaml:"state_setup,omitempty"`
	Navigation WorkflowHookSet `json:"navigation" yaml:"navigation,omitempty"`
}

type WorkflowOptions

type WorkflowOptions struct {
	DefaultSelectedScenario string
	DefaultPresentationMode string
	HandoffQueryParam       string
	BuildHandoff            func(WorkflowScenario) (string, error)
}

func DefaultWorkflowOptions

func DefaultWorkflowOptions() WorkflowOptions

type WorkflowReportEntry

type WorkflowReportEntry struct {
	ScreenID                   string                 `json:"screen_id"`
	Label                      string                 `json:"label"`
	Route                      string                 `json:"route"`
	TargetURL                  string                 `json:"target_url"`
	CurrentImagePath           string                 `json:"current_image_path,omitempty"`
	CurrentMetadata            string                 `json:"current_metadata,omitempty"`
	CurrentCapture             *CaptureResult         `json:"current_capture,omitempty"`
	ReferenceImage             string                 `json:"reference_image"`
	ComparisonMode             string                 `json:"comparison_mode,omitempty"`
	ComparedCurrentImagePath   string                 `json:"compared_current_image_path,omitempty"`
	ComparedReferenceImagePath string                 `json:"compared_reference_image_path,omitempty"`
	DiffImagePath              string                 `json:"diff_image_path,omitempty"`
	DiffMetadataPath           string                 `json:"diff_metadata_path,omitempty"`
	DiffEntry                  *DiffEntry             `json:"diff_entry,omitempty"`
	DiffSummary                *DiffSummary           `json:"diff_summary,omitempty"`
	SemanticDiff               *SemanticDiffResult    `json:"semantic_diff,omitempty"`
	SemanticMetadataPath       string                 `json:"semantic_metadata_path,omitempty"`
	SemanticFailure            bool                   `json:"semantic_failure,omitempty"`
	MissingCurrent             bool                   `json:"missing_current,omitempty"`
	MissingReference           bool                   `json:"missing_reference,omitempty"`
	PrimaryStories             []WorkflowStory        `json:"primary_stories,omitempty"`
	SupportingStories          []WorkflowStory        `json:"supporting_stories,omitempty"`
	Evidence                   []WorkflowEvidenceItem `json:"evidence,omitempty"`
	ExpectedElements           []string               `json:"expected_elements,omitempty"`
	Notes                      []string               `json:"notes,omitempty"`
	Annotations                []string               `json:"annotations,omitempty"`
	Warnings                   []CaptureWarning       `json:"warnings,omitempty"`
	Status                     WorkflowReviewStatus   `json:"status"`
}

type WorkflowReportRequest

type WorkflowReportRequest struct {
	Scenario WorkflowScenario `json:"scenario"`
}

type WorkflowReportResult

type WorkflowReportResult struct {
	ScenarioID   string                `json:"scenario_id"`
	ScenarioPath string                `json:"scenario_path"`
	ReportFormat string                `json:"report_format"`
	ReportPath   string                `json:"report_path"`
	MetadataPath string                `json:"metadata_path"`
	CurrentDir   string                `json:"current_dir"`
	DiffDir      string                `json:"diff_dir"`
	Entries      []WorkflowReportEntry `json:"entries"`
	Stories      []WorkflowStoryReport `json:"stories"`
	CreatedAt    time.Time             `json:"created_at"`
	Status       WorkflowReviewStatus  `json:"status"`
}

type WorkflowReviewStatus

type WorkflowReviewStatus struct {
	Level   string `json:"level"`
	Label   string `json:"label"`
	Summary string `json:"summary,omitempty"`
}

type WorkflowScenario

type WorkflowScenario struct {
	ID          string                   `json:"id" yaml:"id"`
	Label       string                   `json:"label,omitempty" yaml:"label,omitempty"`
	Description string                   `json:"description,omitempty" yaml:"description,omitempty"`
	StorySource string                   `json:"story_source,omitempty" yaml:"story_source,omitempty"`
	Environment WorkflowEnvironment      `json:"environment" yaml:"environment,omitempty"`
	Artifacts   WorkflowArtifactLayout   `json:"artifacts" yaml:"artifacts,omitempty"`
	Defaults    WorkflowDefaults         `json:"defaults" yaml:"defaults,omitempty"`
	Hooks       WorkflowHooks            `json:"hooks" yaml:"hooks,omitempty"`
	Stories     map[string]WorkflowStory `json:"stories,omitempty" yaml:"stories,omitempty"`
	Screens     []WorkflowScreen         `json:"screens" yaml:"screens"`
	Context     map[string]any           `json:"context,omitempty" yaml:"context,omitempty"`
	SourcePath  string                   `json:"-" yaml:"-"`
	SourceDir   string                   `json:"-" yaml:"-"`
}

func LoadWorkflowScenario

func LoadWorkflowScenario(path string) (WorkflowScenario, error)

func LoadWorkflowScenarioWithOptions

func LoadWorkflowScenarioWithOptions(path string, opts WorkflowOptions) (WorkflowScenario, error)

type WorkflowScreen

type WorkflowScreen struct {
	ID                string                 `json:"id" yaml:"id"`
	Label             string                 `json:"label,omitempty" yaml:"label,omitempty"`
	Route             string                 `json:"route" yaml:"route"`
	Query             map[string]string      `json:"query,omitempty" yaml:"query,omitempty"`
	OutputName        string                 `json:"output_name,omitempty" yaml:"output_name,omitempty"`
	Comparison        WorkflowComparison     `json:"comparison" yaml:"comparison,omitempty"`
	SemanticDiff      WorkflowSemanticDiff   `json:"semantic_diff" yaml:"semantic_diff,omitempty"`
	ReferenceImage    string                 `json:"reference_image" yaml:"reference_image"`
	PrimaryStories    []string               `json:"primary_stories,omitempty" yaml:"primary_stories,omitempty"`
	SupportingStories []string               `json:"supporting_stories,omitempty" yaml:"supporting_stories,omitempty"`
	ExpectedElements  []string               `json:"expected_elements,omitempty" yaml:"expected_elements,omitempty"`
	Notes             []string               `json:"notes,omitempty" yaml:"notes,omitempty"`
	Annotations       []string               `json:"annotations,omitempty" yaml:"annotations,omitempty"`
	Hooks             WorkflowHooks          `json:"hooks" yaml:"hooks,omitempty"`
	Evidence          []WorkflowEvidenceItem `json:"evidence,omitempty" yaml:"evidence,omitempty"`
	Capture           CaptureRequest         `json:"capture" yaml:"capture,omitempty"`
}

type WorkflowScreenCaptureResult

type WorkflowScreenCaptureResult struct {
	ScreenID         string        `json:"screen_id"`
	Label            string        `json:"label"`
	Route            string        `json:"route"`
	TargetURL        string        `json:"target_url"`
	OutputPath       string        `json:"output_path"`
	MetadataPath     string        `json:"metadata_path,omitempty"`
	ReferenceImage   string        `json:"reference_image"`
	Capture          CaptureResult `json:"capture"`
	ExpectedElements []string      `json:"expected_elements,omitempty"`
}

type WorkflowSemanticDiff

type WorkflowSemanticDiff struct {
	Enabled            *bool                      `json:"enabled,omitempty" yaml:"enabled,omitempty"`
	Provider           string                     `json:"provider,omitempty" yaml:"provider,omitempty"`
	Model              string                     `json:"model,omitempty" yaml:"model,omitempty"`
	Mode               SemanticDiffMode           `json:"mode,omitempty" yaml:"mode,omitempty"`
	Run                SemanticDiffRunPolicy      `json:"run,omitempty" yaml:"run,omitempty"`
	Focus              []string                   `json:"focus,omitempty" yaml:"focus,omitempty"`
	Prompt             string                     `json:"prompt,omitempty" yaml:"prompt,omitempty"`
	PromptPath         string                     `json:"prompt_path,omitempty" yaml:"prompt_path,omitempty"`
	Timeout            string                     `json:"timeout,omitempty" yaml:"timeout,omitempty"`
	MaxOutputTokens    int                        `json:"max_output_tokens,omitempty" yaml:"max_output_tokens,omitempty"`
	PersistRawResponse bool                       `json:"persist_raw_response,omitempty" yaml:"persist_raw_response,omitempty"`
	RawResponsePath    string                     `json:"raw_response_path,omitempty" yaml:"raw_response_path,omitempty"`
	AdvisoryPolicy     SemanticDiffAdvisoryPolicy `json:"advisory_policy,omitempty" yaml:"advisory_policy,omitempty"`
	FailureSeverity    SemanticDiffSeverity       `json:"failure_severity,omitempty" yaml:"failure_severity,omitempty"`
	FailureVerdicts    []SemanticDiffVerdict      `json:"failure_verdicts,omitempty" yaml:"failure_verdicts,omitempty"`
	APIKey             string                     `json:"-" yaml:"api_key,omitempty"`
	OpenAIAPIKey       string                     `json:"-" yaml:"openai_api_key,omitempty"`
	AnthropicAPIKey    string                     `json:"-" yaml:"anthropic_api_key,omitempty"`
}

type WorkflowService

type WorkflowService interface {
	CaptureScenario(ctx context.Context, scenario WorkflowScenario) (WorkflowCaptureResult, error)
	GenerateWorkflowReport(ctx context.Context, req WorkflowReportRequest) (WorkflowReportResult, error)
}

type WorkflowStory

type WorkflowStory struct {
	ID                 string   `json:"id" yaml:"id"`
	Priority           string   `json:"priority,omitempty" yaml:"priority,omitempty"`
	Title              string   `json:"title,omitempty" yaml:"title,omitempty"`
	AcceptanceCriteria []string `json:"acceptance_criteria,omitempty" yaml:"acceptance_criteria,omitempty"`
	Notes              []string `json:"notes,omitempty" yaml:"notes,omitempty"`
}

type WorkflowStoryReport

type WorkflowStoryReport struct {
	StoryID      string                 `json:"story_id"`
	Story        WorkflowStory          `json:"story"`
	ScreenIDs    []string               `json:"screen_ids,omitempty"`
	Evidence     []WorkflowEvidenceItem `json:"evidence,omitempty"`
	MissingPaths []string               `json:"missing_paths,omitempty"`
	Status       WorkflowReviewStatus   `json:"status"`
}

Directories

Path Synopsis
cmd
webcap command
commands
webcap
Package webcap provides go-command handlers for reusable webcap services.
Package webcap provides go-command handlers for reusable webcap services.
internal
Package mcp exposes a stdio MCP server over the webcap capture and diff services so AI clients can trigger artifact-oriented screenshot workflows.
Package mcp exposes a stdio MCP server over the webcap capture and diff services so AI clients can trigger artifact-oriented screenshot workflows.
pkg

Jump to

Keyboard shortcuts

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