Swiftui Pro
twostraws/SwiftUI-Agent-Skill
Comprehensively reviews SwiftUI code for best practices on modern APIs, maintainability, and performance.
Guide for creating custom types in Hammerspoon 2 — both shared engine types and module-specific types
$ npx skills add cmsj/Hammerspoon2 --skill hs2type -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install cmsj/Hammerspoon2 hs2type --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/cmsj/Hammerspoon2.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/HSType .claude/skills/hs2type && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "hs2type" agent skill from https://github.com/cmsj/Hammerspoon2/tree/main/.claude/skills/HSType into .claude/skills/hs2type/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "hs2type", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/cmsj/Hammerspoon2/tree/main/.claude/skills/HSTypeType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add cmsj/Hammerspoon2 --skill hs2type -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install cmsj/Hammerspoon2 hs2type --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cmsj/Hammerspoon2.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/HSType .agents/skills/hs2type && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "hs2type" agent skill from https://github.com/cmsj/Hammerspoon2/tree/main/.claude/skills/HSType into .agents/skills/hs2type/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "hs2type", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add cmsj/Hammerspoon2 --skill hs2type -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install cmsj/Hammerspoon2 hs2type --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cmsj/Hammerspoon2.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/HSType .cursor/skills/hs2type && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "hs2type" agent skill from https://github.com/cmsj/Hammerspoon2/tree/main/.claude/skills/HSType into .cursor/skills/hs2type/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "hs2type", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/cmsj/Hammerspoon2.git --path .claude/skills/HSType--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add cmsj/Hammerspoon2 --skill hs2type -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install cmsj/Hammerspoon2 hs2type --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cmsj/Hammerspoon2.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/HSType .gemini/skills/hs2type && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "hs2type" agent skill from https://github.com/cmsj/Hammerspoon2/tree/main/.claude/skills/HSType into .gemini/skills/hs2type/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "hs2type", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install cmsj/Hammerspoon2 hs2typeInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add cmsj/Hammerspoon2 --skill hs2type -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/cmsj/Hammerspoon2.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/HSType .github/skills/hs2type && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "hs2type" agent skill from https://github.com/cmsj/Hammerspoon2/tree/main/.claude/skills/HSType into .github/skills/hs2type/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "hs2type", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add cmsj/Hammerspoon2 --skill hs2type -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install cmsj/Hammerspoon2 hs2type --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cmsj/Hammerspoon2.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/HSType .opencode/skills/hs2type && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "hs2type" agent skill from https://github.com/cmsj/Hammerspoon2/tree/main/.claude/skills/HSType into .opencode/skills/hs2type/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "hs2type", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
hs2typeGuide for creating custom types in Hammerspoon 2 — both shared engine types and module-specific types
Hs2type is an agent skill from cmsj/Hammerspoon2. Guide for creating custom types in Hammerspoon 2 — both shared engine types and module-specific types
Its SKILL.md is about 2.6k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Mobile, covering iOS development. The licence is MIT.
Read from SKILL.md and the folder at commit f426cf3. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are swift).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Hs2type loads about 2.6k tokens when it runs. Until then it costs about 27 tokens; SKILL.md has 789 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from cmsj/Hammerspoon2 at commit f426cf3, republished under its MIT licence (© cmsj). 789 words, ~2,649 tokens.
.claude/skills/hs2type/SKILL.md (or your agent's skills folder).Types are Swift objects exposed to JavaScript via JSExport. There are two distinct categories with different rules. Choose the right one before writing any code.
Engine/Types/)Engine types are directly usable from JavaScript — users can call their static factory methods or constructors at any time, without going through a module. They are global JS objects registered at engine startup.
When to create one: the type represents a fundamental, module-agnostic value (geometry, colour, font, image, string) that multiple modules will share.
When NOT to create one: the type wraps a module-specific OS resource (a running application, a display, a timer). Use a module type instead.
Hammerspoon 2/Engine/Types/HSFoo.swift
import Foundation
import JavaScriptCore
// import whatever the wrapped native type needs
/// User-facing docstring for the type. The protocol docstring is what the
/// docs generator reads — the class is hidden so its docstring is irrelevant.
@objc protocol HSFooAPI: HSTypeAPI, JSExport {
// Declare every property and method the user can call from JavaScript.
// All same @objc bridging rules as module API protocols apply.
/// Create a new HSFoo
/// - Parameters:
/// - x: description
@objc init(x: Double) // use static factory methods if init can fail
@objc var x: Double { get set }
@objc func doSomething() -> String
}
// No @_documentation here — engine types are intentionally public
@objc class HSFoo: NSObject, HSFooAPI {
@objc var typeName = "HSFoo" // REQUIRED — satisfies HSTypeAPI
// Internal storage (the wrapped native type)
var nativeThing: NativeFoo
required init(x: Double) {
nativeThing = NativeFoo(x: x)
super.init()
}
var x: Double {
get { nativeThing.x }
set { nativeThing.x = newValue }
}
@objc func doSomething() -> String { ... }
}JSConvertible — bridging to/from native Swift typesWhen the engine type wraps a standard Swift/CoreGraphics value type (CGPoint, CGSize,
CGRect, SwiftUI.Color, etc.), implement JSConvertible on the native type:
extension CGPoint: JSConvertible {
typealias BridgeType = HSPoint
init(from bridge: HSPoint) {
self.init(x: bridge.x, y: bridge.y)
}
func toBridge() -> HSPoint {
HSPoint(x: Double(x), y: Double(y))
}
}This lets any Swift code that receives a CGPoint call .toBridge() to get a
JS-passable HSPoint, and vice versa.
Also add a JSValue extension for ergonomic unboxing in module code:
extension JSValue {
func toCGPoint() -> CGPoint? {
guard let bridge = toObjectOf(HSPoint.self) as? HSPoint else { return nil }
return CGPoint(from: bridge)
}
}Every engine type that should be accessible as a global JS constructor or namespace
must be registered in Engine/InjectTypes.swift:
struct TypeBridgesInstaller: JSContextInstallable {
func install(in context: JSContext) throws {
let typeBridges: [String: AnyClass] = [
"HSFoo": HSFoo.self,
// ...
]
typeBridges.forEach { key, value in
context.setObject(value, forKeyedSubscript: key as NSString)
}
}
}Only add types here that make sense for users to construct or reference directly. Types that are only ever returned from module methods do NOT belong here.
@Observable)If the type's value needs to drive SwiftUI re-renders (e.g. HSColor, HSString,
HSImage), use @Observable instead of ObservableObject.
Key constraint: @Observable cannot track @objc stored properties. Work around
this with a private backing store:
import Observation // required in files that don't import SwiftUI
@Observable
@objc class HSFoo: NSObject, HSFooAPI {
@objc var typeName = "HSFoo"
// @Observable tracks _value (not @objc).
// The computed @objc var forwards to it; SwiftUI sees: value → _value.
private var _value: String
@objc var value: String { _value } // read-only from JS is fine; set() mutates
init(value: String) {
self._value = value
super.init()
}
@objc func set(_ newValue: String) {
_value = newValue // triggers @Observable tracking
}
}Non-reactive properties (e.g. HSColor.color: Color) that are not @objc are
tracked normally by @Observable with no workaround needed.
Prefer factory methods over failable inits when construction can fail or requires complex resolution (loading a file, parsing a hex string, etc.):
@objc protocol HSFooAPI: HSTypeAPI, JSExport {
@objc static func fromPath(_ path: String) -> HSFoo?
@objc static func named(_ name: String) -> HSFoo
}Return nil for failure rather than throwing; errors are logged with AKError.
Modules/hs.xxx/HSFoo.swift)Module types are objects returned by module methods — users never construct them directly. They wrap module-specific OS resources (a running application, a display, a hotkey handle, a timer object, etc.).
When to create one: a module method needs to return something the user can hold a reference to and call further methods on. If the result is a plain dictionary or primitive, don't create a type — just return the value directly.
Same directory as the module that owns them:
Hammerspoon 2/Modules/hs.xxx/HSFoo.swift
import Foundation
import JavaScriptCore
// import whatever OS framework the type wraps
/// User-facing docstring. The protocol is the documented public surface.
/// Mention that users should not instantiate these directly.
@objc protocol HSFooAPI: HSTypeAPI, JSExport {
/// The unique identifier assigned to this object (UUID string).
@objc var identifier: String { get }
// Declare all user-callable properties and methods.
// Apply the same @objc bridging rules as module API protocols.
@objc var someProperty: String { get }
@objc func doSomething()
}
// REQUIRED: hide the implementation class from generated docs
@_documentation(visibility: private)
// Add @MainActor only if the type accesses actor-isolated state (timers, UI, etc.)
@objc class HSFoo: NSObject, HSFooAPI {
@objc var typeName = "HSFoo" // REQUIRED — satisfies HSTypeAPI
@objc let identifier = UUID().uuidString // if applicable
// Internal state — not exposed to JS
private let wrapped: NativeFooObject
init(wrapped: NativeFooObject) {
self.wrapped = wrapped
super.init()
}
// For @MainActor types, use isolated deinit:
isolated deinit {
print("deinit of HSFoo")
}
// For non-@MainActor types, use plain deinit:
// deinit { print("deinit of HSFoo") }
@objc var someProperty: String { wrapped.name }
@objc func doSomething() { wrapped.doThing() }
}@_documentation(visibility: private) is mandatoryThe class is always hidden. The protocol is the public-facing API that the docs generator reads. Never put documentation on the class — put it on the protocol.
@MainActor usageAdd @MainActor to the class when it:
JSValue callbacks from OS delegate methodsNon-@MainActor examples: HSApplication (reads NSRunningApplication properties
synchronously), HSScreen, HSAudioDevice.
@MainActor examples: HSTimer, HSTask, HSHotkey, HSLocationWatcher.
isolated deinit vs deinit@MainActor class → isolated deinit (lets deinit safely access actor state)@MainActor class → plain deinitAlways log in deinit (AKDebug or print). Clean up OS resources (invalidate
timers, stop observers, close file handles) in deinit if the module hasn't already.
identifier propertyAny type the user might hold multiple instances of should have:
@objc let identifier = UUID().uuidStringThis satisfies HSTypeAPI's typeName requirement implicitly through the pattern
and gives users a stable handle to correlate objects.
When a module type wraps a specific OS class, put the conversion in an extension
on the OS class in Hammerspoon 2/Extensions/:
// Extensions/NSRunningApplication.swift
extension NSRunningApplication {
func asHSApplication() -> HSApplication? {
return HSApplication(runningApplication: self)
}
}This keeps construction logic out of the module and lets it be reused across files. Only create an Extensions file if the extension is genuinely shared; single-use conversions can live in the module file.
@objc protocol HSFooAPI: HSTypeAPI, JSExport — protocol inherits both@objc var typeName = "HSFoo" on the class — satisfies HSTypeAPI- Example: on every membersuper.init() called at the end of every init@objc on every property and method in the protocolnew, alloc or copy@_documentation(visibility: private) on the class (engine types are public)TypeBridgesInstaller in InjectTypes.swift (if user-constructable)JSConvertible extension on the native type (if wrapping a value type)JSValue extension for ergonomic unboxing (e.g. toCGPoint())@Observable, private _value backing store, import Observation@_documentation(visibility: private) on the class — mandatoryModules/hs.xxx/HSFoo.swift alongside its owning module@MainActor if the type schedules timers, calls JS callbacks, or touches UIisolated deinit if @MainActor; plain deinit otherwiseTypeBridgesInstaller (module types are never directly constructable)private var foos: [HSFoo] = []© cmsj, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/HSType of cmsj/Hammerspoon2.
Open the folder on GitHubat commit f426cf3
Hs2type next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Hs2type this skillcmsj/Hammerspoon2 | 115 | — | ~2.6k | Automated safety check: Pass | MIT | |
| Swiftui Protwostraws/SwiftUI-Agent-Skill | 5.1k | 2 repos | ~1.5k | Automated safety check: Pass | MIT | |
| Swiftui UI PatternsAFK-surf/OpenBridge | 430 | 4 repos | ~887 | Automated safety check: Pass | MIT | |
| Swiftui Performance Auditharperreed/dotfiles | 334 | 8 repos | ~1.4k | Automated safety check: Pass | None | |
| Swift Testing ExpertAvdLee/Swift-Testing-Agent-Skill | 469 | 1 repos | ~1.2k | Automated safety check: Pass | MIT | |
| Hig Project Contextraintree-technology/hig-doctor | 143 | 5 repos | ~1.2k | Automated safety check: Pass | MIT |
twostraws/SwiftUI-Agent-Skill
Comprehensively reviews SwiftUI code for best practices on modern APIs, maintainability, and performance.
AFK-surf/OpenBridge
Best practices and example-driven guidance for building SwiftUI views and components.
harperreed/dotfiles
Audit and improve SwiftUI runtime performance from code review and architecture.
AvdLee/Swift-Testing-Agent-Skill
Expert guidance for Swift Testing: test structure, expect/require macros, traits and tags, parameterized tests, test plans, parallel execution, async waiting patterns, and XCTest migration.
raintree-technology/hig-doctor
Create or update a shared Apple design context document that other HIG skills use to tailor guidance.
twostraws/Swift-Testing-Agent-Skill
Writes, reviews, and improves Swift Testing code using modern APIs and best practices.
Categories
Guide for creating custom types in Hammerspoon 2 — both shared engine types and module-specific types. Hs2type is an agent skill from cmsj/Hammerspoon2.
Hs2type fits situations like: tasks that involve iOS development.
Run `npx skills add cmsj/Hammerspoon2 --skill hs2type -a claude-code`. Or copy the skill folder (.claude/skills/HSType in cmsj/Hammerspoon2) into .claude/skills/hs2type in your project. Claude Code loads it when a task matches its description.
Run `npx skills add cmsj/Hammerspoon2 --skill hs2type -a codex`. Or copy the skill folder (.claude/skills/HSType in cmsj/Hammerspoon2) into .agents/skills/hs2type in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add cmsj/Hammerspoon2 --skill hs2type -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/hs2type, .gemini/skills/hs2type, .github/skills/hs2type and .opencode/skills/hs2type in your project.
SKILL.md names no scripts, command-line tools or credentials: Hs2type is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Hs2type is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 2.6k tokens (SKILL.md is roughly 11k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Hs2type: Swiftui Pro (twostraws/SwiftUI-Agent-Skill, 5.1k stars), Swiftui UI Patterns (AFK-surf/OpenBridge, 430 stars), Swiftui Performance Audit (harperreed/dotfiles, 334 stars) and Swift Testing Expert (AvdLee/Swift-Testing-Agent-Skill, 469 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
cmsj (a GitHub user) maintains it in cmsj/Hammerspoon2, which has 115 GitHub stars. The repository holds 3 skills in this directory. The repository was last updated on October 7, 2026.
Source: cmsj/Hammerspoon2 on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.