skills/ahk/SKILL.md
AutoHotkey v2 scripting standards for Windows automation, hotkeys, and game macros. Built from the official AHK v2 docs and the AHK community conventions. v1 reached EOL in March 2024.
npx skillsauth add abix-/claude-blueprints ahkInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Current stable: v2.0.26 (May 2026). v1 reached EOL March 2024 and should be ported. There is no v3.
Every script starts with:
#Requires AutoHotkey v2.0
#SingleInstance Force ; reload kills the previous instance
#Warn All, MsgBox ; lint catches typos, undeclared vars
; SetTitleMatchMode 2 ; substring match by default; explicit only if needed
; CoordMode "Mouse", "Screen" ; declare coordinate frame ONCE at the top
#Requires is non-optional. Without it, a v1 user double-clicking
the script gets a cryptic error.#SingleInstance Force means re-running replaces; the alternative
Off allows multiple, Prompt asks. Force is the right default.#Warn catches typos before runtime. Production scripts keep
warnings on.Send, hello -> v2: Send("hello")MsgBox, hi -> v2: MsgBox("hi"):= for expressions, = is comparison (legacy =
assignment is gone). `n newline, `t tab, `" literal quote, ` ``backtick.; line, /* ... */ block.name . " says hi".
Implicit concat (name " says hi") works for adjacent literals
but the dot reads better., or operator. Or wrap in parentheses.^!j::MyFunction() ; Ctrl+Alt+J
^!k:: { ; multi-line hotkey
WinActivate("ahk_exe code.exe")
Send("^p")
}
#HotIf WinActive("ahk_exe Atlas.exe") ; scope to Atlas window
F8::Reload()
#HotIf ; end scope
^ Ctrl, ! Alt, + Shift, # Win.* wildcard: *F8:: matches F8 regardless of modifiers.~ passthrough: ~LButton:: fires without blocking the click.$ block self-trigger: $F8::Send("F8") won't loop.Up suffix for release: F8 Up::.&: LControl & RShift::.#HotIf <expr> scopes everything until the next #HotIf (with no
arg = end scope). The expression evaluates every time the key
fires.::btw::by the way
:*:btn::button ; * = no end char required
:?:abc::xyz ; ? = trigger inside other words
:c:NASA::NASA ; c = case-sensitive
:b0:omw::On my way ; b0 = don't auto-backspace
:opts: doesn't matter.::dt::{
SendInput(FormatTime(, "yyyy-MM-dd"))
}
Three send modes, in increasing reliability and decreasing speed:
| Function | Use when |
|----------|----------|
| SendInput() | Default. Atomic, fast, undisturbed by user input. |
| Send() | Routes via SendMode. Use SendMode "Input" for the same effect as SendInput(). |
| SendEvent() | Slow, plays nicely with games and finicky targets. |
| SendPlay() | Legacy. Avoid unless SendEvent fails. |
| ControlSend() | Targets a specific control regardless of focus. Best for background ops. |
SendInput("hello{Enter}")
Send("^c") ; default SendInput mode
SendEvent("{Tab 5}") ; 5x Tab
ControlSend("{Enter}", , "ahk_exe game.exe") ; background
SetKeyDelay(-1, -1) for instant keystrokes globally. Default
delay is 10ms which adds up.SetMouseDelay(-1) for instant mouse moves.BlockInput("On") to lock user input during a critical sequence;
always pair with BlockInput("Off") (use try ... finally).CoordMode "Mouse", "Screen" ; absolute pixel coords
CoordMode "Mouse", "Window" ; relative to active window
CoordMode "Mouse", "Client" ; relative to client area (no title bar)
#DllCall("SetThreadDpiAwarenessContext", "ptr", -4, "ptr")
at script top if running on 125%/150% Windows scaling. Otherwise
pixel coords lie.WinActivate("ahk_exe code.exe") ; identify by exe (most reliable)
WinActivate("ahk_class CabinetWClass") ; or by class (Explorer)
WinWait("ahk_exe game.exe", , 10) ; wait up to 10s for it to exist
WinWaitActive("ahk_exe game.exe") ; wait until it's focused
WinClose("Untitled - Notepad") ; title match
if WinExist("ahk_exe game.exe") {
pid := WinGetPID() ; uses the matched window
title := WinGetTitle()
}
ProcessExist("game.exe") ; returns PID or 0
ahk_exe over title matching. Titles change with content.ahk_class for system windows (Explorer, dialog boxes).WinWait / WinWaitActive before sending input to a window that
may not be focused yet. Without it, your keys go to the wrong app.WinExist returns 0 (falsy) or the HWND; subsequent Win* calls
operate on the "last found" window.global var or via static var.static var: persists across calls. Initialized once.local count := 0 at the function
top for clarity.{key: value}. Access with obj.key or
obj["key"].[1, 2, 3]. 1-indexed.m := Map(); m["k"] := "v". Order-preserving, beats
objects for arbitrary keys.class HotkeyManager {
__New(name) {
this.name := name
this.count := 0
}
Fire() {
this.count++
ToolTip(this.name . ": " . this.count)
SetTimer(() => ToolTip(), -1500) ; clear after 1.5s
}
}
mgr := HotkeyManager("paste")
^!v::mgr.Fire()
__New is the constructor.this inside methods. Arrow functions () => capture this from
enclosing scope.static members shared across instances.g := Gui("+AlwaysOnTop", "My Tool")
g.Add("Text", , "Name:")
nameEdit := g.Add("Edit", "w200", "default")
g.Add("Button", "Default", "OK").OnEvent("Click", OnOK)
g.OnEvent("Close", (*) => ExitApp())
g.Show()
OnOK(*) {
MsgBox("hi " . nameEdit.Value)
}
Gui() constructor, .Add() for controls.(*) => catches all args (event handlers pass extras).OnEvent("Close", ...) is essential; without it, the script
keeps running after the window closes.configFile := A_ScriptDir . "\config.ini"
IniWrite(value, configFile, "Section", "Key")
value := IniRead(configFile, "Section", "Key", "default")
; JSON: use cJson.ahk or write a small encoder
FileAppend(jsonStr, A_ScriptDir . "\state.json", "UTF-8")
content := FileRead(A_ScriptDir . "\state.json", "UTF-8")
A_ScriptDir for the script's directory. Never hardcode C:\....cJson.ahk is the
standard.FileEncoding "UTF-8" at script top to default all File*
operations to UTF-8.SetTimer(CheckSomething, 5000) ; every 5s
SetTimer(CheckSomething, -2000) ; once after 2s (negative = one-shot)
SetTimer(CheckSomething, 0) ; disable
CheckSomething() {
if SomeCondition()
ToolTip("hit")
}
SetTimer(fn, 0) to disable.try {
WinActivate("ahk_exe target.exe")
Send("hello")
}
catch as e {
MsgBox("activate failed: " . e.Message)
}
finally {
BlockInput("Off")
}
try / catch as e / finally. Standard structure.e.Message, e.What, e.Line, e.Extra.throw ValueError("bad arg") to raise; built-in error classes:
Error, OSError, MemoryError, TypeError, ValueError,
IndexError, KeyError, MethodError, PropertyError,
TargetError, TimeoutError, UnsetError, ZeroDivisionError.SendInput beats Send when atomic input matters. Buffers the
keystrokes and emits them as one event stream.SetKeyDelay(-1, -1) removes the 10ms per-key delay. For long
sequences, this is the difference between snappy and laggy.WinActive / WinExist checks out of Loop bodies.
Each call walks the window list.#HotIf <expr> re-evaluates on every key press. Keep the
expression cheap (WinActive("ahk_exe game.exe") is fine; a regex
walk is not).Sleep 0 in tight loops. Use Sleep 1 or rework with
a timer. Sleep 0 yields once per loop and still burns CPU.Map over Object for arbitrary keys -- Maps are
hash-table-backed with predictable performance; objects pay
property-lookup overhead.Buffer for kilobyte-scale output. AHK strings are
immutable; repeated concat is O(n^2).SetBatchLines -1 (legacy) is replaced in v2 by SetWinDelay -1,
SetKeyDelay -1, SetMouseDelay -1, SetControlDelay -1. Set the
ones you need; don't set all blindly.#HotIf WinActive("ahk_exe Atlas.exe")
F8::Reload()
NumpadAdd::{
Loop 10 {
SendEvent("{Space}")
Sleep 50
}
}
#HotIf
#HotIf so they don't fire in other apps.SendInput via cursor capture or hook detection.
SendEvent is sometimes accepted. ControlSend works against
background windows.PixelGetColor(x, y, "Slow"). The "Slow"
flag uses a different method that works through DWM compositing;
the default fails on some setups.ImageSearch for template matching but slow on large screens;
prefer pixel checks at known positions.MsgBox("got here: " . val) is the default REPL.OutputDebug "value=" val writes to DebugView (sysinternals).ListVars, ListHotkeys, ListLines -- runtime introspection
windows.KeyHistory shows the last 40 keystrokes; invaluable for hotkey
conflicts.AutoHotkey v2 Language Support
extension supports DBGp. Set breakpoints, inspect variables.Lib/ next to the script. AHK auto-includes
Lib/<func>.ahk when <func> is called and not yet defined.Send, text, IfWinActive, = for assignment). Port
and delete.Loop with no Sleep. Burns a CPU core; the OS will starve
other AHK threads.CoordMode. Breaks on
different DPI / resolution / window size.WinGetTitle for matching. Use ahk_exe / ahk_class instead.Buffer. O(n^2).Run with a user-supplied string -- shell injection.#Requires AutoHotkey v2.0. The next user will run it
on v1 and waste an hour.development
YAML standards for config files, Ansible playbooks, k8s manifests, GitHub Actions, docker-compose, and any project config. Built from the YAML 1.2 spec, yamllint defaults, and the practical pitfalls (Norway problem, type coercion, anchor gotchas).
development
--- name: ueforge description: ueforge framework: the base layer every UE4SS Rust mod in the Grounded2Mods workspace builds on. Authoritative on the composition model (Effect/Trigger/Skill), the Def/Registry/Instance/Controller pattern, hot reload, discovery, hardening doctrine, and the five framework modules (rpg, stacks, difficulty, inventory, damage). Use when writing or modifying code under `ueforge/` in [abix-/Grounded2Mods](https://github.com/abix-/Grounded2Mods), or when promoting a patte
tools
TypeScript and JavaScript standards. Sourced from [abix-/chromium-extensions](https://github.com/abix-/chromium-extensions) (Hush + filter-anything-everywhere). Use when writing TS/JS, including browser extension bootstrap shims, MV3 service workers, and small web frontends.
development
--- name: schedule1 description: Modding Schedule 1 (TVGS, IL2CPP Unity + MelonLoader + Harmony). Authoritative on Schedule 1 game specifics: engine type, MelonLoader/Il2CppInterop references, eMployee mod root-cause findings, vanilla CookRoutine + StartMixingStationBehaviour internals, certainty-tracking discipline. Mod code lives in [`abix-/Schedule1Mods`](https://github.com/abix-/Schedule1Mods) (the `EmployeeReset` sidecar is the current shipped mod). Not for playing the game. user-invocable: