kinax

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 24, 2026 License: MIT Imports: 15 Imported by: 0

README

kinax-go

Pure-Go binding to the macOS Accessibility (AX) API.

Navigate and manipulate the system-wide UI tree — inspect running applications, find buttons by semantic identity, read text field contents, click elements without hardcoded pixel coordinates.

Single binary, zero cgo, go install-able. Built on AXUIElement via purego + an embedded companion dylib — the same pattern as sckit-go, kinrec, and input-go.

kinax-go is part of KinKit — the pure-Go macOS system library family powering the LocalKin agent swarm. It pairs with input-go: kinax-go sees the UI, input-go moves it.

go install github.com/LocalKinAI/kinax-go/cmd/kinax@latest

kinax tree --bundle com.apple.Safari --depth 4
kinax find AXButton --bundle com.apple.Safari
kinax click "Continue"
kinax at-point 200 100

Features

  • Element tree navigationChildren, Parent, Windows, FocusedWindow, FocusedElement, AttributeElement.
  • Typed attribute readersAttribute (string), AttributeInt, AttributeBool, AttributePoint (CGPoint), AttributeSize (CGSize), AttributeElements (array).
  • Attribute + action introspectionAttributeNames, ActionNames return live lists from the element.
  • Semantic searchFindFirst / FindAll with composable matchers (MatchRole, MatchTitle, MatchTitleContains, MatchIdentifier, MatchAll, MatchAny).
  • Hit testingElementAtPoint(x, y) returns whatever AX element is at the given global coords (what Accessibility Inspector shows when you hover).
  • App targetingFocusedApplication, ApplicationByPID, ApplicationByBundleID, SystemWide.
  • Action + writePerform("AXPress"), SetString, SetBool.
  • No cgo: downstream projects stay pure Go. The ObjC companion dylib is //go:embedded (~70 KB universal arm64+x86_64) and extracted to ~/Library/Caches on first call.

Install

# CLI
go install github.com/LocalKinAI/kinax-go/cmd/kinax@latest

# Library
go get github.com/LocalKinAI/kinax-go

Requires macOS 12+ and Go 1.22+.

Permission

macOS requires the invoking binary to be listed in System Settings → Privacy & Security → Accessibility for AX* calls to succeed. Unlike input-go (which silently no-ops without permission), kinax-go returns real errors — every accessor will fail until permission is granted.

if err := kinax.RequireTrust(); err != nil {
    kinax.PromptTrust() // shows system dialog
    log.Fatal("grant Accessibility permission, then rerun")
}

Library usage

package main

import (
    "fmt"
    "log"

    "github.com/LocalKinAI/kinax-go"
)

func main() {
    if err := kinax.Load(); err != nil {
        log.Fatal(err)
    }
    if err := kinax.RequireTrust(); err != nil {
        log.Fatal(err)
    }

    // Attach to Safari (must be running)
    app, err := kinax.ApplicationByBundleID("com.apple.Safari")
    if err != nil { log.Fatal(err) }
    defer app.Close()

    // Walk all windows
    wins, _ := app.Windows()
    for _, w := range wins {
        t, _ := w.Title()
        fmt.Println("window:", t)
        w.Close()
    }

    // Find every text field, print its value
    fields := app.FindAll(kinax.MatchRole(kinax.RoleTextField), 30)
    for _, f := range fields {
        v, _ := f.Value()
        fmt.Println("field:", v)
        f.Close()
    }

    // Click the "New Tab" button
    if btn, ok := app.FindFirst(kinax.MatchTitle("New Tab"), 20); ok {
        defer btn.Close()
        btn.Perform(kinax.ActionPress)
    }
}

CLI usage

# Dump the AX tree of the focused app (default target)
kinax tree --depth 4

# Dump a specific app's tree
kinax tree --bundle com.apple.Safari --depth 5
kinax tree --pid 1234

# Inspect one attribute of the app element
kinax attr AXTitle --bundle com.apple.Safari

# List every attribute or action the app exposes
kinax attrs --focused
kinax actions --focused

# Find every button (optionally with a specific title)
kinax find AXButton --bundle com.apple.Safari
kinax find AXButton "New Tab" --bundle com.apple.Safari --depth 25

# Click a button by title
kinax click "Continue"
kinax click "OK" --role AXButton --bundle com.apple.SystemPreferences

# Hit-test a screen coordinate
kinax at-point 200 100
# → AXMenuBar    Apple

# Permission
kinax trust              # prints 1 or 0
kinax trust --prompt     # shows system dialog

Memory ownership

Every *Element returned by kinax-go wraps a retained CFTypeRef. The caller must call (*Element).Close — forgetting to leaks a handle for the process lifetime.

Traversal helpers (FindFirst, FindAll) return fresh handles; any siblings they walk past are closed automatically. You only need to close what's returned to you.

// Correct
wins, _ := app.Windows()
for _, w := range wins {
    defer w.Close()
}

// Also correct — find returns a fresh handle
if btn, ok := app.FindFirst(kinax.MatchTitle("OK"), 20); ok {
    defer btn.Close()
    btn.Perform(kinax.ActionPress)
}

Combining with input-go

kinax finds the element; input drives the cursor there. This is the natural UI-automation loop:

import (
    "github.com/LocalKinAI/kinax-go"
    "github.com/LocalKinAI/input-go"
)

btn, _ := app.FindFirst(kinax.MatchTitle("Save"), 20)
defer btn.Close()

pos, _ := btn.Position()
size, _ := btn.Size()
cx := float64(pos.X + size.X/2)
cy := float64(pos.Y + size.Y/2)

input.MoveSmooth(ctx, cx, cy, 300*time.Millisecond)
input.ClickAt(ctx, cx, cy)

For many cases btn.Perform(kinax.ActionPress) is enough — clicking via AX is faster and doesn't require Accessibility permission on the binary that runs input.Click (though it does require it on the one that runs kinax.Perform). Use input-go for pixel-level drag gestures, hover highlights, and scrolling.

How it works

kinax-go follows the embedded dylib pattern documented in Paper #9 of localkin.dev/papers.

Go code  ─── purego.Dlopen ────► libkinax_sync.dylib (embedded)
                                     │
                                     └──► AXUIElement* APIs
  • objc/kinax_ax.m — ~450 LOC ObjC shim exposing 20 C-ABI functions (kinax_element_attr_string, kinax_element_perform, etc.).
  • internal/dylib/libkinax_sync.dylib — universal Mach-O, committed.
  • Opaque uintptr handles for elements; CFRetain on the ObjC side, CFRelease via kinax_element_release when Go Closes.
  • JSON encoding for list attributes (attribute names, action names) — avoids shipping a full CF→Go type system across the FFI.

Known limitations (v0.1)

  • macOS only. The Accessibility API is macOS-specific — no cross-platform ambitions.
  • Read-heavy API. Writing is limited to string and bool attribute sets. CGPoint/CGSize/CGRect AXValue setters (e.g. to move a window programmatically) are deferred to v0.2.
  • No observers / notifications. AXObserverRef + kAXFocusedWindowChangedNotification etc. are planned for v0.2 — they enable agents to react to UI changes rather than polling.
  • Numeric attributes only as int64. Float-valued attributes (AXValue on sliders) currently string-stringify. Dedicated AttributeFloat planned for v0.2.
  • Single main thread assumption for some CF calls. In practice kinax works from any goroutine because we don't use CFRunLoop.
  • Tested only on macOS 26.3 arm64 so far; Intel + macOS 14/15 verification pending CI.

Roadmap

  • v0.2 — AXObserver subscription (OnNotification callback), more typed setters (SetPoint, SetSize), AttributeFloat.
  • v0.3 — helpers for common idioms: WaitForWindow(bundleID, title, timeout), TypeInFieldLabeled(app, label, text), "did the UI change" snapshots.
  • Cross-app automation recipes — Safari URL read, screenshot a specific window via sckit-go + AXFrame, etc.

Contributing

git clone https://github.com/LocalKinAI/kinax-go
cd kinax-go
make dylib            # rebuild universal Mach-O after ObjC changes
make test             # unit tests (no Accessibility permission needed)
make test-integration # requires Accessibility permission
make lint             # go vet + staticcheck + golangci-lint

License

MIT. See LICENSE.

See also

Documentation

Overview

Package kinax is a pure-Go binding to the macOS Accessibility (AX) API.

kinax lets Go programs read and manipulate the system-wide UI tree — inspecting running applications, finding buttons, reading text field contents, clicking on elements by semantic identity rather than pixel coordinates. It's the foundation for UI automation agents, screen readers, and accessibility-aware tools.

Quick start

ctx := context.Background()
if err := kinax.Load(); err != nil { log.Fatal(err) }
if err := kinax.RequireTrust(); err != nil { log.Fatal(err) }

app, _ := kinax.FocusedApplication()
defer app.Close()

// Walk windows
wins, _ := app.Windows()
for _, w := range wins {
    title, _ := w.Title()
    fmt.Println(title)
    w.Close()
}

// Click a button by title
if btn, ok := app.FindFirst(kinax.MatchTitle("OK")); ok {
    btn.Perform(kinax.ActionPress)
    btn.Close()
}

Permission

macOS requires the invoking binary to be listed in System Settings → Privacy & Security → Accessibility for AX calls to succeed. Without permission, every call returns an error (unlike `input-go`, where events silently no-op). Use Trusted / PromptTrust / RequireTrust.

Memory ownership

Every Element returned by kinax wraps a retained CFTypeRef. Callers MUST call (*Element).Close to release it. Forgetting to Close leaks a handle for the lifetime of the process — on the order of tens of bytes per element, but it adds up if you're walking the UI tree on a timer.

Dylib placement

kinax-go ships a universal (arm64+x86_64) companion dylib via //go:embed. On the first call into the package, the embedded bytes are extracted to ~/Library/Caches/kinax-go/<hash>/libkinax_sync.dylib and Dlopened. Set DylibPath to a non-empty value before the first call if you ship a custom-built or patched dylib.

Index

Constants

View Source
const (
	AttrRole             = "AXRole"
	AttrSubrole          = "AXSubrole"
	AttrRoleDescription  = "AXRoleDescription"
	AttrTitle            = "AXTitle"
	AttrDescription      = "AXDescription"
	AttrHelp             = "AXHelp"
	AttrValue            = "AXValue"
	AttrValueDescription = "AXValueDescription"
	AttrPlaceholder      = "AXPlaceholderValue"
	AttrIdentifier       = "AXIdentifier"
	AttrEnabled          = "AXEnabled"
	AttrFocused          = "AXFocused"
	AttrSelected         = "AXSelected"
	AttrVisible          = "AXVisible"
	AttrExpanded         = "AXExpanded"
	AttrMain             = "AXMain" // primary window
	AttrMinimized        = "AXMinimized"
	AttrFullscreen       = "AXFullscreen"

	// Geometry
	AttrPosition = "AXPosition" // CGPoint
	AttrSize     = "AXSize"     // CGSize
	AttrFrame    = "AXFrame"    // CGRect (not all elements expose this)

	// Tree navigation
	AttrParent          = "AXParent"
	AttrChildren        = "AXChildren"
	AttrChildrenInOrder = "AXChildrenInNavigationOrder"
	AttrWindows         = "AXWindows"
	AttrMainWindow      = "AXMainWindow"
	AttrFocusedWindow   = "AXFocusedWindow"
	AttrFocusedElement  = "AXFocusedUIElement"
	AttrMenuBar         = "AXMenuBar"
	AttrTopLevelElement = "AXTopLevelUIElement"

	// Containers
	AttrRows               = "AXRows"
	AttrColumns            = "AXColumns"
	AttrCell               = "AXCell"
	AttrTabs               = "AXTabs"
	AttrSelectedText       = "AXSelectedText"
	AttrNumberOfCharacters = "AXNumberOfCharacters"
)

Standard AXUIElement attribute names. These are stable strings from <HIServices/AXAttributeConstants.h>. Use them rather than string literals so typos fail at compile time.

View Source
const (
	RoleApplication = "AXApplication"
	RoleWindow      = "AXWindow"
	RoleButton      = "AXButton"
	RoleCheckBox    = "AXCheckBox"
	RoleRadioButton = "AXRadioButton"
	RoleTextField   = "AXTextField"
	RoleTextArea    = "AXTextArea"
	RoleStaticText  = "AXStaticText"
	RolePopUpButton = "AXPopUpButton"
	RoleMenu        = "AXMenu"
	RoleMenuItem    = "AXMenuItem"
	RoleMenuBar     = "AXMenuBar"
	RoleMenuBarItem = "AXMenuBarItem"
	RoleGroup       = "AXGroup"
	RoleList        = "AXList"
	RoleTable       = "AXTable"
	RoleRow         = "AXRow"
	RoleCell        = "AXCell"
	RoleColumn      = "AXColumn"
	RoleScrollArea  = "AXScrollArea"
	RoleScrollBar   = "AXScrollBar"
	RoleSlider      = "AXSlider"
	RoleToolbar     = "AXToolbar"
	RoleImage       = "AXImage"
	RoleLink        = "AXLink"
	RoleTabGroup    = "AXTabGroup"
	RoleSheet       = "AXSheet"
	RoleSplitGroup  = "AXSplitGroup"
	RoleOutline     = "AXOutline"
	RoleWebArea     = "AXWebArea"
)

Standard AX role values (stable constants from AXRoleConstants.h). Use with Element.Role comparisons.

View Source
const (
	ActionPress           = "AXPress"
	ActionIncrement       = "AXIncrement"
	ActionDecrement       = "AXDecrement"
	ActionConfirm         = "AXConfirm"
	ActionCancel          = "AXCancel"
	ActionShowMenu        = "AXShowMenu"
	ActionRaise           = "AXRaise"
	ActionShowAlternateUI = "AXShowAlternateUI"
	ActionShowDefaultUI   = "AXShowDefaultUI"
	ActionPick            = "AXPick"
)

Standard AX actions (stable constants from AXActionConstants.h).

View Source
const Version = "0.1.0"

Version is the semantic-version tag of this package.

Variables

View Source
var DylibPath = ""

DylibPath is an optional override for the location of libkinax_sync.dylib. Default (empty): extract embedded copy to cache directory.

View Source
var ErrClosed = errors.New("kinax: element closed")

ErrClosed is returned when a method is called on an Element after Close.

View Source
var ErrInvalidType = errors.New("kinax: attribute has wrong type")

ErrInvalidType is returned when an attribute exists but has the wrong type for the requested accessor (e.g. calling AttributeInt on a string attribute).

View Source
var ErrNotFound = errors.New("kinax: not found")

ErrNotFound is returned when a requested element, attribute, or app isn't present. Callers typically want to distinguish this from a real error (e.g. permission denied).

View Source
var ErrNotTrusted = errors.New("kinax: accessibility permission not granted")

ErrNotTrusted is returned when the current process lacks Accessibility permission. This is the dominant error shape for kinax — virtually every call fails without permission.

Functions

func FrontmostPID

func FrontmostPID() int

FrontmostPID returns the PID of the currently-active application. Returns 0 if none (rare — happens during login and app transitions).

func Load

func Load() error

Load explicitly loads the companion dylib. Idempotent.

func PromptTrust

func PromptTrust() bool

PromptTrust triggers the system "wants to control your computer" dialog if permission has not been granted. Returns the resulting trust state (true if already granted, false otherwise — including the common case where the user hasn't yet responded to the dialog).

func RequireTrust

func RequireTrust() error

RequireTrust returns nil if Accessibility permission is granted, or ErrNotTrusted otherwise. Convenience for callers that want a hard fail at startup rather than opaque failures deep in a traversal.

func ResolvedDylibPath

func ResolvedDylibPath() string

ResolvedDylibPath returns the path Load used (or would use) to Dlopen the dylib. Intended for diagnostics.

func Trusted

func Trusted() bool

Trusted returns true if the current process has Accessibility permission. Does not prompt.

Types

type Element

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

Element is a reference to a single AXUIElement in the system UI tree. Elements are opaque handles onto a retained Core Foundation object — callers MUST call Element.Close when done to avoid leaking.

Element is safe for concurrent use by multiple goroutines; all AX API calls are thread-safe per Apple docs.

func ApplicationByBundleID

func ApplicationByBundleID(bundleID string) (*Element, error)

ApplicationByBundleID returns the Element for the first running app with the given bundle identifier (e.g. "com.apple.Safari"). Returns ErrNotFound if no such app is running. Caller must Close.

func ApplicationByPID

func ApplicationByPID(pid int) (*Element, error)

ApplicationByPID returns the AX Element for the running application with the given Unix PID. Returns a valid Element even if no such process exists — subsequent AX calls on it will simply fail. Caller must Close.

func ElementAtPoint

func ElementAtPoint(x, y float64) (*Element, error)

ElementAtPoint returns the AX element at the given global screen coordinates. This is how you implement "inspect element under cursor" (hold ⌥ hover over anything → show its AX info). Returns ErrNotFound if no element is at the point (e.g. on the desktop wallpaper in some configurations). Caller must Close.

func FocusedApplication

func FocusedApplication() (*Element, error)

FocusedApplication returns the Element for the currently active (frontmost) application. Returns ErrNotFound if there is none — this can happen briefly during login screens and app transitions. Caller must Close.

func SystemWide

func SystemWide() (*Element, error)

SystemWide returns an Element representing the system-wide AX root. Useful primarily to query AXFocusedApplication or to hit-test points. Caller must Close.

func (*Element) ActionNames

func (e *Element) ActionNames() ([]string, error)

ActionNames returns all action names this element responds to.

func (*Element) Attribute

func (e *Element) Attribute(name string) (string, error)

Attribute reads a string-valued attribute. The return convention:

  • empty string + nil error → attribute absent OR value is empty string
  • non-empty string + nil error → attribute present with value
  • "" + non-nil error → real failure (permission, etc.)

This collapses "absent" and "empty" into one case; if you need to distinguish, use Element.AttributeNames first.

func (*Element) AttributeBool

func (e *Element) AttributeBool(name string) (bool, error)

AttributeBool reads a bool-valued attribute.

func (*Element) AttributeElement

func (e *Element) AttributeElement(name string) (*Element, error)

AttributeElement reads an element-valued attribute (e.g. AXFocusedWindow, AXParent) and returns a new *Element that the caller must Close. Returns (nil, nil) if the attribute is absent — not an error, because "no focused window" is a perfectly normal state.

func (*Element) AttributeElements

func (e *Element) AttributeElements(name string) ([]*Element, error)

AttributeElements reads an array-of-elements attribute (e.g. AXChildren, AXWindows). Each returned Element must be closed by the caller.

func (*Element) AttributeInt

func (e *Element) AttributeInt(name string) (int64, error)

AttributeInt reads a number-valued attribute as int64.

func (*Element) AttributeNames

func (e *Element) AttributeNames() ([]string, error)

AttributeNames returns all attribute names this element exposes. Useful for dumping the full AX state of an element during debugging.

func (*Element) AttributePoint

func (e *Element) AttributePoint(name string) (image.Point, error)

AttributePoint reads a CGPoint-valued attribute (e.g. AXPosition).

func (*Element) AttributeSize

func (e *Element) AttributeSize(name string) (image.Point, error)

AttributeSize reads a CGSize-valued attribute (e.g. AXSize).

func (*Element) Children

func (e *Element) Children() ([]*Element, error)

Children returns the element's direct children. Caller must Close each.

func (*Element) Close

func (e *Element) Close()

Close releases the underlying CFTypeRef. Safe to call multiple times; subsequent calls are no-ops. After Close, all other methods return ErrClosed.

func (*Element) Description

func (e *Element) Description() (string, error)

Description returns the element's AXDescription.

func (*Element) Enabled

func (e *Element) Enabled() (bool, error)

Enabled reports whether the element is enabled (vs. grayed out).

func (*Element) FindAll

func (e *Element) FindAll(m Matcher, maxDepth int) []*Element

FindAll collects every descendant of e matching `m` (excluding e). Caller must Close every returned element.

func (*Element) FindFirst

func (e *Element) FindFirst(m Matcher, maxDepth int) (*Element, bool)

FindFirst walks the descendants of e (depth-first, excluding e itself) and returns the first match. Traversal is bounded by `maxDepth` to prevent runaway on pathological trees.

The returned Element is a fresh handle owned by the caller — Close it when done. Returns (nil, false) if no match.

Note: e itself is NOT considered a match candidate. This keeps ownership semantics clean — the caller's existing handle is never returned, so there's no risk of a double-Close.

func (*Element) Focused

func (e *Element) Focused() (bool, error)

Focused reports whether the element currently has keyboard focus.

func (*Element) FocusedElement

func (e *Element) FocusedElement() (*Element, error)

FocusedElement returns the element within an app (or window) that currently has keyboard focus. For the system-wide element, this is the globally-focused UI element across all apps.

func (*Element) FocusedWindow

func (e *Element) FocusedWindow() (*Element, error)

FocusedWindow returns the app's currently-focused window.

func (*Element) Handle

func (e *Element) Handle() uintptr

Handle returns the opaque pointer value — useful for identity comparisons (though two handles may point to the same element in some cases — prefer comparing semantic identity via Title/Role).

func (*Element) Identifier

func (e *Element) Identifier() (string, error)

Identifier returns the element's AXIdentifier — the stable string ID set by the app developer for automation.

func (*Element) MainWindow

func (e *Element) MainWindow() (*Element, error)

MainWindow returns the app's main window (usually the frontmost). Returns (nil, nil) if the app has no main window right now.

func (*Element) Parent

func (e *Element) Parent() (*Element, error)

Parent returns the element's parent, or nil if it's a root.

func (*Element) Perform

func (e *Element) Perform(action string) error

Perform fires the named action (e.g. AXPress). Returns nil on success or an error with the underlying AXError code.

func (*Element) Position

func (e *Element) Position() (image.Point, error)

Position returns the element's top-left corner in global screen coordinates.

func (*Element) Role

func (e *Element) Role() (string, error)

Role returns the element's AXRole (e.g. "AXButton"). Empty if absent.

func (*Element) SetBool

func (e *Element) SetBool(attr string, value bool) error

SetBool sets a bool-valued attribute (e.g. AXFocused=true to request keyboard focus).

func (*Element) SetString

func (e *Element) SetString(attr, value string) error

SetString sets a string-valued attribute. Typically used to set AXValue on a text field:

field.SetString(kinax.AttrValue, "new text")

func (*Element) Size

func (e *Element) Size() (image.Point, error)

Size returns the element's width and height in pixels.

func (*Element) Subrole

func (e *Element) Subrole() (string, error)

Subrole returns the element's AXSubrole (e.g. "AXCloseButton").

func (*Element) Title

func (e *Element) Title() (string, error)

Title returns the element's AXTitle.

func (*Element) Value

func (e *Element) Value() (string, error)

Value returns the element's AXValue as a string (works for text fields, sliders with a stringified number, etc.).

func (*Element) Windows

func (e *Element) Windows() ([]*Element, error)

Windows returns the top-level windows of an app element. Convenience for (*Element).AttributeElements(AttrWindows).

type Matcher

type Matcher func(e *Element) bool

Matcher is a predicate over an Element, used by Element.FindFirst / Element.FindAll to select elements during tree traversal.

A Matcher may freely call accessors (Role, Title, attributes). It MUST NOT Close the element — ownership stays with the traversal.

func MatchAll

func MatchAll(ms ...Matcher) Matcher

MatchAll combines matchers with AND.

func MatchAny

func MatchAny(ms ...Matcher) Matcher

MatchAny combines matchers with OR.

func MatchIdentifier

func MatchIdentifier(id string) Matcher

MatchIdentifier selects elements with AXIdentifier == `id`. App developers set this for automation-stable lookup — prefer it over titles when available.

func MatchRole

func MatchRole(role string) Matcher

MatchRole returns a Matcher that selects elements whose AXRole equals `role`. Use the Role* constants:

app.FindFirst(kinax.MatchRole(kinax.RoleButton), 20)

func MatchRoleAndTitle

func MatchRoleAndTitle(role, title string) Matcher

MatchRoleAndTitle selects elements matching both role and exact title.

func MatchTitle

func MatchTitle(title string) Matcher

MatchTitle selects elements whose AXTitle equals `title` exactly.

func MatchTitleContains

func MatchTitleContains(sub string) Matcher

MatchTitleContains selects elements whose title contains `sub` as a substring (case-insensitive).

Directories

Path Synopsis
cmd
kinax command
Command kinax inspects and manipulates the macOS UI tree via the Accessibility API.
Command kinax inspects and manipulates the macOS UI tree via the Accessibility API.
internal
dylib
Package dylib embeds the ObjC companion library so downstream users can simply `go get` kinax-go without building C code on their machine.
Package dylib embeds the ObjC companion library so downstream users can simply `go get` kinax-go without building C code on their machine.

Jump to

Keyboard shortcuts

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