Agent skill

Hs2type

by cmsj in cmsj/Hammerspoon2

Guide for creating custom types in Hammerspoon 2 — both shared engine types and module-specific types

MITAuto-check passedMobile

Install Hs2type

skills CLI
$ npx skills add cmsj/Hammerspoon2 --skill hs2type -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install cmsj/Hammerspoon2 hs2type --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ 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-src

Use ~/.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/

Facts

Skill name
hs2type
GitHub stars
115
Token cost
~2.6k tokens
SKILL.md length
789 words
Files
1
Skills in repo
3
Repo updated
First seen
Licence
MIT

At a glance

Guide for creating custom types in Hammerspoon 2 — both shared engine types and module-specific types

  • Tasks that involve iOS development
  • SKILL.md covers Category 1 — Engine types…, Category 2 — Module types… and Checklist for any new type
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

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.

When your agent uses it

  • Tasks that involve iOS development

Example prompts

  • “/hs2type”

What it can do on your machine

Read from SKILL.md and the folder at commit f426cf3. It shows what the files ask for, not the result of running them.

  • Tool permissions

    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.

  • Runs code

    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.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

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.

Always · name and description, kept in context so the agent knows when to use it
~27
When it runs · the whole SKILL.md, loaded when a task matches
~2.6k

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.

Safety

Auto-check passed

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.

SKILL.md

The full file from cmsj/Hammerspoon2 at commit f426cf3, republished under its MIT licence (© cmsj). 789 words, ~2,649 tokens.

Download SKILL.mdSave it as .claude/skills/hs2type/SKILL.md (or your agent's skills folder).
name
hs2type
description
Guide for creating custom types in Hammerspoon 2 — both shared engine types and module-specific types

Hammerspoon 2 Type System

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.


Category 1 — Engine types (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.

File location

Hammerspoon 2/Engine/Types/HSFoo.swift

Structural template
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 types

When the engine type wraps a standard Swift/CoreGraphics value type (CGPoint, CGSize, CGRect, SwiftUI.Color, etc.), implement JSConvertible on the native type:

swift
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:

swift
extension JSValue {
    func toCGPoint() -> CGPoint? {
        guard let bridge = toObjectOf(HSPoint.self) as? HSPoint else { return nil }
        return CGPoint(from: bridge)
    }
}
Registering with the JS context

Every engine type that should be accessible as a global JS constructor or namespace must be registered in Engine/InjectTypes.swift:

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.

Reactive engine types (@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:

swift
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.

Static factory methods

Prefer factory methods over failable inits when construction can fail or requires complex resolution (loading a file, parsing a hex string, etc.):

swift
@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.


Category 2 — Module types (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.

File location

Same directory as the module that owns them: Hammerspoon 2/Modules/hs.xxx/HSFoo.swift

Structural template
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 mandatory

The 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 usage

Add @MainActor to the class when it:

  • Schedules or invalidates timers
  • Calls UIKit/AppKit/SwiftUI APIs
  • Holds or calls JSValue callbacks from OS delegate methods
  • Is a CLLocationManagerDelegate, AVAudioSession delegate, etc.

Non-@MainActor examples: HSApplication (reads NSRunningApplication properties synchronously), HSScreen, HSAudioDevice.

@MainActor examples: HSTimer, HSTask, HSHotkey, HSLocationWatcher.

Show full SKILL.md (303 more words)Show less
isolated deinit vs deinit
  • @MainActor class → isolated deinit (lets deinit safely access actor state)
  • Non-@MainActor class → plain deinit

Always 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 property

Any type the user might hold multiple instances of should have:

swift
@objc let identifier = UUID().uuidString

This satisfies HSTypeAPI's typeName requirement implicitly through the pattern and gives users a stable handle to correlate objects.

Factory/conversion extensions on OS types

When a module type wraps a specific OS class, put the conversion in an extension on the OS class in Hammerspoon 2/Extensions/:

swift
// 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.


Checklist for any new type

Both categories
  • @objc protocol HSFooAPI: HSTypeAPI, JSExport — protocol inherits both
  • @objc var typeName = "HSFoo" on the class — satisfies HSTypeAPI
  • Protocol has docstrings with - Example: on every member
  • super.init() called at the end of every init
  • @objc on every property and method in the protocol
  • There are no methods with names that start with new, alloc or copy
Engine types only
  • No @_documentation(visibility: private) on the class (engine types are public)
  • Added to 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())
  • If reactive: @Observable, private _value backing store, import Observation
Module types only
  • @_documentation(visibility: private) on the class — mandatory
  • Lives in Modules/hs.xxx/HSFoo.swift alongside its owning module
  • @MainActor if the type schedules timers, calls JS callbacks, or touches UI
  • isolated deinit if @MainActor; plain deinit otherwise
  • NOT added to TypeBridgesInstaller (module types are never directly constructable)
  • If the module tracks instances (for shutdown cleanup), add to 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

Files

Just SKILL.md in .claude/skills/HSType of cmsj/Hammerspoon2.

Open the folder on GitHubat commit f426cf3

Compare with similar skills

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.

Hs2type compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Hs2type this skillcmsj/Hammerspoon2115—~2.6kAutomated safety check: PassMIT
Swiftui Protwostraws/SwiftUI-Agent-Skill5.1k2 repos~1.5kAutomated safety check: PassMIT
Swiftui UI PatternsAFK-surf/OpenBridge4304 repos~887Automated safety check: PassMIT
Swiftui Performance Auditharperreed/dotfiles3348 repos~1.4kAutomated safety check: PassNone
Swift Testing ExpertAvdLee/Swift-Testing-Agent-Skill4691 repos~1.2kAutomated safety check: PassMIT
Hig Project Contextraintree-technology/hig-doctor1435 repos~1.2kAutomated safety check: PassMIT

Similar skills

  • Swiftui Pro

    twostraws/SwiftUI-Agent-Skill

    Comprehensively reviews SwiftUI code for best practices on modern APIs, maintainability, and performance.

    5.1k GitHub starsUsed in 2 repos~1.5k tokens
    MobileAuto-check passed
  • Swiftui UI Patterns

    AFK-surf/OpenBridge

    Best practices and example-driven guidance for building SwiftUI views and components.

    430 GitHub starsUsed in 4 repos~887 tokens
    MobileAuto-check passed
  • Swiftui Performance Audit

    harperreed/dotfiles

    Audit and improve SwiftUI runtime performance from code review and architecture.

    334 GitHub starsUsed in 8 repos~1.4k tokens
    MobileAuto-check passed
  • Swift Testing Expert

    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.

    469 GitHub starsUsed in 1 repo~1.2k tokens
    MobileAuto-check passed
  • Hig Project Context

    raintree-technology/hig-doctor

    Create or update a shared Apple design context document that other HIG skills use to tailor guidance.

    143 GitHub starsUsed in 5 repos~1.2k tokens
    MobileAuto-check passed
  • Swift Testing Pro

    twostraws/Swift-Testing-Agent-Skill

    Writes, reviews, and improves Swift Testing code using modern APIs and best practices.

    448 GitHub stars~1.1k tokensUpdated 4 mo ago
    MobileAuto-check passed

More from cmsj/Hammerspoon2

  • Hs2module

    cmsj/Hammerspoon2

    Ensure all Hammerspoon v2 modules follow established patterns

    115 GitHub stars~8.6k tokensUpdated today
    Auto-check passed
  • Hs2tests

    cmsj/Hammerspoon2

    Guide for writing tests in Hammerspoon 2 — framework, structure, JSTestHarness patterns, async, environment guards, and what not to test

    115 GitHub stars~5.4k tokensUpdated today
    Auto-check passed

Categories

Questions about Hs2type

What does Hs2type do?

Guide for creating custom types in Hammerspoon 2 — both shared engine types and module-specific types. Hs2type is an agent skill from cmsj/Hammerspoon2.

When should I use Hs2type?

Hs2type fits situations like: tasks that involve iOS development.

How do I install Hs2type in Claude Code?

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.

How do I install Hs2type in Codex?

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.

Can I use Hs2type in Cursor, Gemini CLI or GitHub Copilot?

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.

What does Hs2type need to run?

SKILL.md names no scripts, command-line tools or credentials: Hs2type is instructions for the agent only.

Does Hs2type access the network?

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.

Is Hs2type safe to install?

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.

What licence does Hs2type use?

Hs2type is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Hs2type use?

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.

What are the alternatives to Hs2type?

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.

Who maintains Hs2type?

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.