diff --git a/build.gradle.kts b/build.gradle.kts
index 7fafccc96c9d1f633b4bb0ae9ac07b84eadcc094..8051e2f1bc9038c79da7efee2c12b087a1f94ded 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -30,6 +30,7 @@ allprojects {
   apply(plugin = "org.jetbrains.dokka")
   repositories {
     mavenCentral()
+    google()
     exclusiveContent {
       forRepository {
         maven {
diff --git a/libquassel-generator/build.gradle.kts b/libquassel-generator/build.gradle.kts
new file mode 100644
index 0000000000000000000000000000000000000000..cc08d9d541c544b247c042b07c10199763b517dc
--- /dev/null
+++ b/libquassel-generator/build.gradle.kts
@@ -0,0 +1,19 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+repositories {
+  google()
+}
+
+dependencies {
+  implementation("com.google.devtools.ksp:symbol-processing-api:1.4.30-1.0.0-alpha02")
+  implementation(project(":libquassel-annotations"))
+  implementation("com.squareup", "kotlinpoet", "1.7.2")
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9e3c1a2fa456c9f18cc529b96f02301b749cb325
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt
@@ -0,0 +1,158 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator
+
+import com.google.devtools.ksp.getDeclaredFunctions
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.KSPLogger
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSFunctionDeclaration
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSValueParameter
+import com.google.devtools.ksp.symbol.KSVisitorVoid
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.annotations.SyncedCall
+import de.justjanne.libquassel.annotations.SyncedObject
+import de.justjanne.libquassel.generator.annotation.SyncedCallAnnotationModel
+import de.justjanne.libquassel.generator.annotation.SyncedObjectAnnotationModel
+import de.justjanne.libquassel.generator.model.SyncedCallModel
+import de.justjanne.libquassel.generator.model.SyncedObjectModel
+import de.justjanne.libquassel.generator.model.SyncedParameterModel
+import de.justjanne.libquassel.generator.util.findAnnotationWithType
+import de.justjanne.libquassel.generator.util.getMember
+import de.justjanne.libquassel.generator.util.toEnum
+
+class InvokerProcessor : SymbolProcessor {
+  lateinit var codeGenerator: CodeGenerator
+  lateinit var logger: KSPLogger
+
+  override fun init(
+    options: Map<String, String>,
+    kotlinVersion: KotlinVersion,
+    codeGenerator: CodeGenerator,
+    logger: KSPLogger
+  ) {
+    this.logger = logger
+    this.codeGenerator = codeGenerator
+  }
+
+  override fun process(resolver: Resolver): List<KSAnnotated> {
+    val classes = resolver.getSymbolsWithAnnotation(SyncedObject::class.java.canonicalName)
+      .flatMap {
+        val visitor = QuasselRpcVisitor(resolver)
+        it.accept(visitor, Unit)
+        visitor.classes
+      }
+
+    val message = classes.flatMap {
+      listOf(
+        "@SyncedObject(name=${it.rpcName})",
+        it.name,
+      ) + it.methods.flatMap {
+        listOf(
+          "@SyncedCall(name=${it.rpcName}, target=${it.side})",
+          it.name,
+        ) + it.parameters.flatMap {
+          listOf(
+            "${it.name}: ${it.type}"
+          )
+        }.map { "  $it" }
+      }.map { "  $it" }
+    }.joinToString("\n")
+    logger.logging("Data: \n\n$message\n\n")
+
+    return emptyList()
+  }
+
+  override fun finish() {
+    // TODO
+  }
+
+  inner class QuasselRpcVisitor(
+    private val resolver: Resolver
+  ) : KSVisitorVoid() {
+    val classes = mutableListOf<SyncedObjectModel>()
+    var functions = mutableListOf<SyncedCallModel>()
+    var parameters = mutableListOf<SyncedParameterModel>()
+
+    private fun collectFunctions(): List<SyncedCallModel> {
+      val result = functions
+      functions = mutableListOf()
+      return result
+    }
+
+    private fun collectParameters(): List<SyncedParameterModel> {
+      val result = parameters
+      parameters = mutableListOf()
+      return result
+    }
+
+    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
+      try {
+        val annotation = classDeclaration.findAnnotationWithType<SyncedObject>(resolver) ?: return
+        val syncedObjectModel = SyncedObjectAnnotationModel(
+          name = annotation.getMember("name"),
+        )
+        classDeclaration.getDeclaredFunctions().map { it.accept(this, Unit) }
+        classes.add(
+          SyncedObjectModel(
+            classDeclaration.qualifiedName?.asString() ?: return,
+            syncedObjectModel.name,
+            collectFunctions()
+          )
+        )
+      } catch (t: Throwable) {
+        logger.error("Error processing class ${classDeclaration.qualifiedName?.asString()}", classDeclaration)
+        logger.exception(t)
+      }
+    }
+
+    override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
+      val annotation = function.findAnnotationWithType<SyncedCall>(resolver) ?: return
+      try {
+        val syncedCallModel = SyncedCallAnnotationModel(
+          name = annotation.getMember("name"),
+          target = annotation.getMember<KSType>("target")?.toEnum<ProtocolSide>(),
+        )
+        function.parameters.map { it.accept(this, Unit) }
+        functions.add(
+          SyncedCallModel(
+            function.simpleName.asString(),
+            syncedCallModel.name,
+            syncedCallModel.target,
+            collectParameters()
+          )
+        )
+      } catch (t: Throwable) {
+        logger.error("Error processing function ${function.qualifiedName?.asString()}", function)
+        logger.exception(t)
+      }
+    }
+
+    override fun visitValueParameter(valueParameter: KSValueParameter, data: Unit) {
+      try {
+        super.visitValueParameter(valueParameter, data)
+        parameters.add(
+          SyncedParameterModel(
+            valueParameter.name?.asString(),
+            valueParameter.type.resolve()
+          )
+        )
+      } catch (t: Throwable) {
+        logger.error("Error processing parameter ${valueParameter.name?.asString()}", valueParameter)
+        logger.exception(t)
+      }
+    }
+  }
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedCallAnnotationModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedCallAnnotationModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..536fb79c944fd9382e8ec620fd83cc3f69569de0
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedCallAnnotationModel.kt
@@ -0,0 +1,18 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.annotation
+
+import de.justjanne.libquassel.annotations.ProtocolSide
+
+data class SyncedCallAnnotationModel(
+  val name: String?,
+  val target: ProtocolSide?
+)
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d2ce55a4212257f27b51efed76ed27e303fc4992
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt
@@ -0,0 +1,15 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.annotation
+
+data class SyncedObjectAnnotationModel(
+  val name: String?
+)
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedCallModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedCallModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e55a86ebf2a9d1706ee1e47d4f48e2dd58582204
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedCallModel.kt
@@ -0,0 +1,20 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.model
+
+import de.justjanne.libquassel.annotations.ProtocolSide
+
+data class SyncedCallModel(
+  val name: String,
+  val rpcName: String?,
+  val side: ProtocolSide?,
+  val parameters: List<SyncedParameterModel>
+)
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..04f52dc2caa9d4d736545f25172cfd7f94412886
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt
@@ -0,0 +1,17 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.model
+
+data class SyncedObjectModel(
+  val name: String,
+  val rpcName: String?,
+  val methods: List<SyncedCallModel>
+)
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e7242ab971f6df3dd3377e5bc0e277dcdb7a3c6a
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt
@@ -0,0 +1,18 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.model
+
+import com.google.devtools.ksp.symbol.KSType
+
+data class SyncedParameterModel(
+  val name: String?,
+  val type: KSType
+)
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/asType.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/asType.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f562c99a6fd84d21cb451ff066089613b5c84bb5
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/asType.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 Zac Sweers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.justjanne.libquassel.generator.util
+
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+
+internal fun KSClassDeclaration.asType() = asType(emptyList())
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/findAnnotationWithType.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/findAnnotationWithType.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2a37e0b670583c22cddf244c677f9a832b7eb325
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/findAnnotationWithType.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 Zac Sweers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.justjanne.libquassel.generator.util
+
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSType
+
+internal inline fun <reified T : Annotation> KSAnnotated.findAnnotationWithType(
+  resolver: Resolver,
+): KSAnnotation? {
+  return findAnnotationWithType(resolver.getClassDeclarationByName<T>().asType())
+}
+
+internal fun KSAnnotated.findAnnotationWithType(target: KSType): KSAnnotation? {
+  return annotations.find { it.annotationType.resolve() == target }
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getClassDeclarationByName.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getClassDeclarationByName.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2a5694b057fbed0ee36e867c265fa26f9334677c
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getClassDeclarationByName.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 Zac Sweers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.justjanne.libquassel.generator.util
+
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+
+internal inline fun <reified T> Resolver.getClassDeclarationByName(): KSClassDeclaration {
+  return getClassDeclarationByName(T::class.qualifiedName!!)
+}
+
+internal fun Resolver.getClassDeclarationByName(fqcn: String): KSClassDeclaration {
+  return getClassDeclarationByName(getKSNameFromString(fqcn)) ?: error("Class '$fqcn' not found.")
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getMember.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getMember.kt
new file mode 100644
index 0000000000000000000000000000000000000000..520994a195ac2eef3403004fff34aad139e46553
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getMember.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 Zac Sweers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.justjanne.libquassel.generator.util
+
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSType
+import com.squareup.kotlinpoet.ClassName
+
+internal inline fun <reified T> KSAnnotation.getMember(name: String): T? {
+  val matchingArg = arguments.find { it.name?.asString() == name }
+    ?: error(
+      "No member name found for '$name'. All arguments: ${arguments.map { it.name?.asString() }}"
+    )
+  return when (val argValue = matchingArg.value) {
+    is T -> argValue
+    is KSType ->
+      when {
+        T::class.java != ClassName::class.java -> null
+        else -> argValue.toClassName() as T
+      }
+    else -> null
+  }
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/hasAnnotation.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/hasAnnotation.kt
new file mode 100644
index 0000000000000000000000000000000000000000..16f3cff2364c0ad33fdeb82d7713ff531f67c421
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/hasAnnotation.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 Zac Sweers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package de.justjanne.libquassel.generator.util
+
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.KSType
+
+internal fun KSAnnotated.hasAnnotation(target: KSType): Boolean {
+  return findAnnotationWithType(target) != null
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4542f82560360654ecb63e2b92b23ee4f0cd3b2d
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt
@@ -0,0 +1,42 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.util
+
+import com.google.devtools.ksp.isLocal
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSType
+import com.squareup.kotlinpoet.ClassName
+
+internal fun KSType.toClassName(): ClassName {
+  val decl = declaration
+  check(decl is KSClassDeclaration)
+  return decl.toClassName()
+}
+
+internal fun KSClassDeclaration.toClassName(): ClassName {
+  require(!isLocal()) {
+    "Local/anonymous classes are not supported!"
+  }
+  val pkgName = packageName.asString()
+  val typesString = qualifiedName!!.asString().removePrefix("$pkgName.")
+
+  val simpleNames = typesString
+    .split(".")
+  return ClassName(pkgName, simpleNames)
+}
+
+fun Class<*>.toClassName(): ClassName {
+  val packageName = `package`.name
+  val names = canonicalName.substring(packageName.length)
+    .trimStart('.')
+    .split('.')
+  return ClassName(packageName, names)
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toEnum.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toEnum.kt
new file mode 100644
index 0000000000000000000000000000000000000000..115a1f7a01d042a63130e5d5a9637d2418088e39
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toEnum.kt
@@ -0,0 +1,29 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.util
+
+import com.google.devtools.ksp.symbol.KSType
+import com.squareup.kotlinpoet.ClassName
+
+internal inline fun <reified T : Enum<T>> KSType.toEnum(): T? {
+  return toClassName().toEnum(T::class.java)
+}
+
+internal inline fun <reified T : Enum<T>> ClassName.toEnum(): T? {
+  return toEnum(T::class.java)
+}
+
+internal fun <T : Enum<T>> ClassName.toEnum(clazz: Class<T>): T? {
+  val enumClassName = clazz.toClassName()
+  return clazz.enumConstants.find {
+    this.canonicalName == enumClassName.nestedClass(it.name).canonicalName
+  }
+}
diff --git a/libquassel-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessor b/libquassel-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessor
new file mode 100644
index 0000000000000000000000000000000000000000..3720f23ceb0706e898b05f19e202cd213cc002c7
--- /dev/null
+++ b/libquassel-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessor
@@ -0,0 +1 @@
+de.justjanne.libquassel.generator.InvokerProcessor
diff --git a/libquassel-state/build.gradle.kts b/libquassel-state/build.gradle.kts
index ac709bd82221efc352e403bd581612ce221cb43b..9d0afbdb400b7348a58fc0d72ec9c36203183598 100644
--- a/libquassel-state/build.gradle.kts
+++ b/libquassel-state/build.gradle.kts
@@ -10,8 +10,10 @@
 
 plugins {
   id("com.vanniktech.maven.publish")
+  id("com.google.devtools.ksp") version "1.4.30-1.0.0-alpha02"
 }
 
 dependencies {
   api(project(":libquassel-protocol"))
+  ksp(project(":libquassel-generator"))
 }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 0f0d93227259cc79712cbf2ebf31204db827538a..e0c6a2fa7fe66a50f89cb2a69e5772f6de11e517 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -14,5 +14,13 @@ include(
   ":libquassel-annotations",
   ":libquassel-protocol",
   ":libquassel-state",
+  ":libquassel-generator",
   ":libquassel-client"
 )
+
+pluginManagement {
+  repositories {
+    gradlePluginPortal()
+    google()
+  }
+}