02 · Quick start
From nothing to a running plugin
Each language has a template: the same small plugin, ready to build, test, and make your own.
-
01
Choose a language
-
02
Start from the template The Kotlin template is a working plugin, a word count, with tests and a release workflow, built with the kit from Maven Central. To try it, clone it; to make it yours, choose Use this template on its GitHub page. Start from the template The Rust template is a working plugin, a word count, with tests and a release workflow, built with the kit from crates.io. To try it, clone it; to make it yours, choose Use this template on its GitHub page. Start from the template The C template is a working plugin, a word count, with tests and a release workflow, with the kit’s headers included. To try it, clone it; to make it yours, choose Use this template on its GitHub page.
-
03
Build a plugin The Gradle wrapper runs its tests and builds the package; you need only a JDK. To make it your own plugin, rename it in
settings.gradle.ktsandmanifest.tomland replace the example. The Kotlin kit covers the rest.cargo testruns its tests, andbuild.shbuilds it for WebAssembly and packages it; you need rustup and zip. To make it your own plugin, rename it inCargo.tomlandmanifest.tomland replace the example. The Rust kit covers the rest.test.shruns its tests natively, andbuild.shcompiles it to WebAssembly with clang, no libc needed, and packages it; you need clang, lld and zip. To make it your own plugin, rename it inmanifest.tomland replace the example. The C kit covers the rest. -
04
Install it in Hammer On desktop: Settings, Plugins, Install plugin. Pick the
.hammerplugin, approve its permissions, and restart Hammer.
$ git clone https://github.com/Darkrock-Studios/hammer-plugin-kotlin
$ cd hammer-plugin-kotlin
$ ./gradlew jvmTest hammerPlugin
→ build/word-count.hammerplugin
$ git clone https://github.com/Darkrock-Studios/hammer-plugin-rust
$ cd hammer-plugin-rust
$ rustup target add wasm32-unknown-unknown
$ cargo test && ./build.sh
→ build/word-count.hammerplugin
$ git clone https://github.com/Darkrock-Studios/hammer-plugin-c
$ cd hammer-plugin-c
$ ./test.sh && ./build.sh
→ build/word-count.hammerplugin
FIG. 2a
src/commonMain/kotlin/Very.kt
import hammer.DiagnoseRequest
import hammer.Diagnostic
import hammer.Fix
import hammer.Severity
import hammer.words
fun findVery(request: DiagnoseRequest): List<Diagnostic> = request.paragraphs.flatMapIndexed { paragraph, text ->
text.words().filter { it.text.equals("very", ignoreCase = true) }.map { word ->
val end = if (text.getOrNull(word.end) == ' ') word.end + 1 else word.end
Diagnostic(paragraph, word.start, end, "“Very” rarely helps", listOf(Fix("", "Remove “very”")), Severity.Suggestion)
}
}
src/wasmWasiMain/kotlin/Exports.kt
import hammer.diagnose
import kotlin.wasm.WasmExport
@WasmExport
fun diagnose(): Int = diagnose(::findVery)
src/lib.rs
use hammer::diagnose::{Diagnostic, Fix, Reply, Request, Severity};
fn check(request: &Request) -> Reply {
let mut reply = Reply::default();
for (paragraph, text) in request.paragraphs.iter().enumerate() {
for word in hammer::words(text).filter(|it| it.text.eq_ignore_ascii_case("very")) {
let end = word.end + text[word.end..].starts_with(' ') as usize;
reply.diagnostics.push(Diagnostic {
paragraph,
start: word.start,
end,
message: "“Very” rarely helps".into(),
fixes: vec![Fix::Labelled { replacement: String::new(), label: "Remove “very”".into() }],
severity: Severity::Suggestion,
});
}
}
reply
}
#[unsafe(no_mangle)]
pub extern "C" fn diagnose() -> i32 {
hammer::run(|| {
let request: Request = hammer::input_json()?;
Ok(serde_json::to_vec(&check(&request))?)
})
}
very.c
#define HAMMER_IMPLEMENTATION
#include "hammer.h"
#include "hammer_diagnose.h"
int hammer_diagnose_begin(const HammerDiagnoseRequest *request) {
(void)request;
return 1;
}
void hammer_diagnose_paragraph(HammerText text) {
uint64_t count;
HammerWord *words = hammer_words(text, &count);
for (uint64_t i = 0; i < count; i++) {
if (!hammer_word_is(text, words[i], "very")) continue;
uint64_t end = words[i].end;
if (end < text.length && text.data[end] == ' ') end++;
HammerFix fix = {"", 0, "Remove “very”"};
hammer_suggest(words[i].start, end, "“Very” rarely helps", &fix, 1);
}
}
id = "very"
name = "Very"
version = "0.1.0"
api = 1
description = "Underlines “very”, which rarely helps."
[[diagnostics]]
name = "very"
label = "Very"
03 · Anatomy
What’s in a plugin
A .hammerplugin file is a zip archive. The build tools make it for you.
manifest.toml
Manifest
Names your plugin, says what it adds to Hammer, and lists the operations it needs. Writers see that list and approve it when they install your plugin.
plugin.wasm
Module
Your compiled code, as WebAssembly. It follows the Extism plugin convention, and exports a function for each kind of extension it provides.
settings.toml
Settings
Optional. Declare switches, numbers, text or choices, and Hammer gives writers a form for them in Settings. Your plugin gets the current values with every call.
locales/ resources/
Translations and files
Optional. Files in
locales/ translate your plugin’s labels, and resources/ can hold word lists or other data your plugin reads while it runs (up to 1,000 files and 64 MiB).
04 · Extension points
Where a plugin can attach
You declare each one in your manifest and handle it with a function in your plugin. One plugin can provide any mix of them.
EXT-01
Export formats
Add a format to the Export dialog. Hammer hands your plugin the story’s chapters, and your plugin returns the finished file.
Example: plaintext
[[exporters]] → export()
EXT-02
Actions
Add items to Hammer’s menus. An action can ask the writer a few questions, show a report they can save as a note, or walk them through several pages.
Example: style, first-meeting
[[actions]] → action()
EXT-03
Text checks
Underline problems as the writer types, such as grammar slips, house style, or a misspelled character name, and offer fixes in the right-click menu.
Example: harper-grammar, name-checker
[[diagnostics]] → diagnose()
EXT-04
Commands
Add a
hammer <name> command on desktop. Your plugin gets each line of input and can write a line back.
Example: mcp
[[commands]] → command()
05 · Publish
Getting it in front of writers
Step 1
Package
./gradlew hammerPlugin
The template’s build.sh
The template’s build.sh
builds the .hammerplugin file, with your manifest, your compiled code, and any settings, translations and resources.
Step 2
Share the file
Attach the file to a release and share the link. Writers install it from Settings, Plugins, and see what it asks permission for before it’s installed.
Step 3
Listed Coming
A directory of reviewed plugins at plugins.hammer.ink, and later, installing them from inside Hammer.
Submissions open soon
Read the official plugins