diff --git a/ibis/ibiscapabilities.h b/ibis/ibiscapabilities.h
--- a/ibis/ibiscapabilities.h
+++ b/ibis/ibiscapabilities.h
@@ -30,58 +30,99 @@
 #include <glib.h>
 #include <glib-object.h>
 
-#include <purple.h>
-
+#include "ibismessage.h"
 #include "ibisversion.h"
 
 G_BEGIN_DECLS
 
-/* https://ircv3.net/specs/extensions/capability-negotiation */
+/**
+ * IBIS_CAPABILITY_CAP_LS_VERSION: (skip)
+ *
+ * The version to pass to the `CAP LS` command.
+ *
+ * See https://ircv3.net/specs/extensions/capability-negotiation for more
+ * information.
+ *
+ * Since: 0.1
+ */
 #define IBIS_CAPABILITY_CAP_LS_VERSION "302"
 
-/* https://ircv3.net/specs/extensions/sasl-3.2 */
+/**
+ * IBIS_CAPABILITY_SASL: (skip)
+ *
+ * The constant string to use when negotiating the SASL capability.
+ *
+ * See https://ircv3.net/specs/extensions/sasl-3.2 for more information.
+ *
+ * Since: 0.1
+ */
 #define IBIS_CAPABILITY_SASL "sasl"
 
-/* https://ircv3.net/specs/extensions/server-time */
+/**
+ * IBIS_CAPABILITY_SERVER_TIME: (skip)
+ *
+ * The constant string to use when negotiating the server-time capability.
+ *
+ * See https://ircv3.net/specs/extensions/server-time for more information.
+ *
+ * Since: 0.1
+ */
 #define IBIS_CAPABILITY_SERVER_TIME "server-time"
 
+/**
+ * IbisCapabilities:
+ *
+ * A object that handles capability negotiation.
+ *
+ * Since: 0.1
+ */
+
 #define IBIS_TYPE_CAPABILITIES (ibis_capabilities_get_type())
 
 IBIS_AVAILABLE_IN_0_1
 G_DECLARE_FINAL_TYPE(IbisCapabilities, ibis_capabilities,
                      IBIS, CAPABILITIES, GObject)
 
-#include "purpleircv3connection.h"
-#include "purpleircv3message.h"
+#include "ibisclient.h"
 
 /**
- * ibis_capabilities_register: (skip)
- * @plugin: The [class@GPlugin.NativePlugin] instance.
- *
- * Dynamically registers the IbisCapabilities type.
+ * ibis_capabilities_new:
+ * @client: The client instance.
  *
- * Since: 0.1
- */
-G_GNUC_INTERNAL void ibis_capabilities_register(GPluginNativePlugin *plugin);
-
-G_GNUC_INTERNAL IbisCapabilities *ibis_capabilities_new(IbisConnection *connection);
-
-G_GNUC_INTERNAL void ibis_capabilities_start(IbisCapabilities *capabilities);
-
-G_GNUC_INTERNAL gboolean ibis_capabilities_message_handler(IbisMessage *message, GError **error, gpointer data);
-
-/**
- * ibis_capabilities_get_connection:
- * @capabilities: The instance.
+ * Creates a new capabilities object for @client.
  *
- * Gets the IbisConnection object that @capabilities was created with.
- *
- * Returns: (transfer none): The connection instance.
+ * Returns: (transfer full): The new instance.
  *
  * Since: 0.1
  */
 IBIS_AVAILABLE_IN_0_1
-IbisConnection *ibis_capabilities_get_connection(IbisCapabilities *capabilities);
+IbisCapabilities *ibis_capabilities_new(IbisClient *client);
+
+/**
+ * ibis_capabilities_start:
+ * @capabilities: The instance.
+ *
+ * Starts capability negotiation.
+ *
+ * This will send the `CAP LS` command.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+void ibis_capabilities_start(IbisCapabilities *capabilities);
+
+/**
+ * ibis_capabilities_get_client:
+ * @capabilities: The instance.
+ *
+ * Gets the [class@Client] instance that @capabilities was created with.
+ *
+ * Returns: (transfer none): The client instance.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+IbisClient *ibis_capabilities_get_client(IbisCapabilities *capabilities);
 
 /**
  * ibis_capabilities_request:
@@ -119,11 +160,11 @@
  * @capabilities: The instance.
  * @name: The name of the capability to look for.
  *
- * A helper function to call [method@Ibis.Capabilities.Lookup] and if
- * found, call [method@Ibis.Capabilities.Request].
+ * A helper function to call [method@Capabilities.lookup] and if found, call
+ * [method@Capabilities.request].
  *
  * This method ignores the advertised value, so to get that you'll need to call
- * [method@Ibis.Capabilities.Lookup] yourself.
+ * [method@Capabilities.lookup] yourself.
  *
  * Also if you need to do something when the server ACK's or NAK's your
  * request, you're probably better off just using the methods yourself.
@@ -136,7 +177,7 @@
 gboolean ibis_capabilities_lookup_and_request(IbisCapabilities *capabilities, const char *name);
 
 /**
- * ibis_capabilties_add_wait:
+ * ibis_capabilities_add_wait:
  * @capabilities: The instance.
  *
  * Adds a wait counter to @capabilities. This counter is used to delay the
@@ -149,7 +190,7 @@
 void ibis_capabilities_add_wait(IbisCapabilities *capabilities);
 
 /**
- * ibis_capabilties_remove_wait:
+ * ibis_capabilities_remove_wait:
  * @capabilities: The instance.
  *
  * Removes a wait counter from @capabilities. Only when this counter reaches 0,
@@ -166,4 +207,3 @@
 G_END_DECLS
 
 #endif /* IBIS_CAPABILITIES_H */
-
# HG changeset patch
# Node ID 6910f0d671c6
# Parent  ee571f6e8e75
diff --git a/ibis/ibiscapabilities.c b/ibis/ibiscapabilities.c
--- a/ibis/ibiscapabilities.c
+++ b/ibis/ibiscapabilities.c
@@ -23,10 +23,11 @@
 #include "ibiscapabilities.h"
 
 #include "ibiscore.h"
+#include "ibisstring.h"
 
 enum {
 	PROP_0,
-	PROP_CONNECTION,
+	PROP_CLIENT,
 	N_PROPERTIES,
 };
 static GParamSpec *properties[N_PROPERTIES] = {NULL, };
@@ -53,7 +54,7 @@
 struct _IbisCapabilities {
 	GObject parent;
 
-	IbisConnection *connection;
+	IbisClient *client;
 
 	GHashTable *caps;
 	GPtrArray *requests;
@@ -65,20 +66,20 @@
  * Helpers
  *****************************************************************************/
 static void
-ibis_capabilities_set_connection(IbisCapabilities *capabilities,
-                                 IbisConnection *connection)
+ibis_capabilities_set_client(IbisCapabilities *capabilities,
+                             IbisClient *client)
 {
-	g_return_if_fail(IBIS_IS_CAPABILITIES(capabilities));
+	g_return_if_fail(IBIS_IS_CLIENT(client));
 
-	if(g_set_object(&capabilities->connection, connection)) {
+	if(g_set_object(&capabilities->client, client)) {
 		g_object_notify_by_pspec(G_OBJECT(capabilities),
-		                         properties[PROP_CONNECTION]);
+		                         properties[PROP_CLIENT]);
 	}
 }
 
 static void
 ibis_capabilities_finish(IbisCapabilities *capabilities) {
-	ibis_connection_writef(capabilities->connection, "CAP END");
+	ibis_client_writef(capabilities->client, "CAP END");
 
 	g_signal_emit(capabilities, signals[SIG_DONE], 0);
 }
@@ -122,11 +123,9 @@
 	/* message-tags is used for a lot of stuff so we need to tell everyone we
 	 * do in fact support it.
 	 */
-	if(ibis_capabilities_lookup_and_request(capabilities,
-	                                                "message-tags"))
-	{
+	if(ibis_capabilities_lookup_and_request(capabilities, "message-tags")) {
 		g_signal_connect(capabilities, "ack::message-tags",
-		                 G_CALLBACK(ircv3_capabilities_message_tags_ack_cb),
+		                 G_CALLBACK(ibis_capabilities_message_tags_ack_cb),
 		                 NULL);
 	}
 
@@ -141,9 +140,7 @@
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
-G_DEFINE_DYNAMIC_TYPE_EXTENDED(IbisCapabilities,
-                               ibis_capabilities, G_TYPE_OBJECT,
-                               G_TYPE_FLAG_FINAL, {})
+G_DEFINE_FINAL_TYPE(IbisCapabilities, ibis_capabilities, G_TYPE_OBJECT)
 
 static void
 ibis_capabilities_get_property(GObject *obj, guint param_id, GValue *value,
@@ -152,9 +149,8 @@
 	IbisCapabilities *capabilities = IBIS_CAPABILITIES(obj);
 
 	switch(param_id) {
-	case PROP_CONNECTION:
-		g_value_set_object(value,
-		                   ibis_capabilities_get_connection(capabilities));
+	case PROP_CLIENT:
+		g_value_set_object(value, ibis_capabilities_get_client(capabilities));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
@@ -169,9 +165,8 @@
 	IbisCapabilities *capabilities = IBIS_CAPABILITIES(obj);
 
 	switch(param_id) {
-	case PROP_CONNECTION:
-		ibis_capabilities_set_connection(capabilities,
-		                                 g_value_get_object(value));
+	case PROP_CLIENT:
+		ibis_capabilities_set_client(capabilities, g_value_get_object(value));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
@@ -180,17 +175,10 @@
 }
 
 static void
-ibis_capabilities_dispose(GObject *obj) {
+ibis_capabilities_finalize(GObject *obj) {
 	IbisCapabilities *capabilities = IBIS_CAPABILITIES(obj);
 
-	g_clear_object(&capabilities->connection);
-
-	G_OBJECT_CLASS(ibis_capabilities_parent_class)->dispose(obj);
-}
-
-static void
-ibis_capabilities_finalize(GObject *obj) {
-	IbisCapabilities *capabilities = IBIS_CAPABILITIES(obj);
+	g_clear_object(&capabilities->client);
 
 	g_hash_table_destroy(capabilities->caps);
 	g_ptr_array_free(capabilities->requests, TRUE);
@@ -208,30 +196,24 @@
 }
 
 static void
-ibis_capabilities_class_finalize(G_GNUC_UNUSED IbisCapabilitiesClass *klass) {
-}
-
-static void
 ibis_capabilities_class_init(IbisCapabilitiesClass *klass) {
 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
 
-	obj_class->dispose = ibis_capabilities_dispose;
 	obj_class->finalize = ibis_capabilities_finalize;
 	obj_class->get_property = ibis_capabilities_get_property;
 	obj_class->set_property = ibis_capabilities_set_property;
 
 	/**
-	 * IbisCapabilities:connection:
+	 * IbisCapabilities:client:
 	 *
-	 * The IbisConnection object that this capabilities was created
-	 * with.
+	 * The [class@Client] that this instance was created with.
 	 *
 	 * Since: 0.1
 	 */
-	properties[PROP_CONNECTION] = g_param_spec_object(
-		"connection", "connection",
-		"The connection this capabilities was created for.",
-		IBIS_TYPE_CONNECTION,
+	properties[PROP_CLIENT] = g_param_spec_object(
+		"client", "client",
+		"The client this capabilities was created with.",
+		IBIS_TYPE_CLIENT,
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
@@ -393,7 +375,7 @@
 	gchar **parts = NULL;
 
 	/* Check if we have more messages coming. */
-	if(n_params > 1 && purple_strequal(params[0], "*")) {
+	if(n_params > 1 && ibis_str_equal(params[0], "*")) {
 		parts = g_strsplit(params[1], " ", -1);
 		done = FALSE;
 	} else {
@@ -464,10 +446,8 @@
 }
 
 static gboolean
-ibis_capabilities_handle_del(IbisCapabilities *capabilities,
-                                     guint n_params,
-                                     GStrv params,
-                                     G_GNUC_UNUSED GError **error)
+ibis_capabilities_handle_del(IbisCapabilities *capabilities, guint n_params,
+                             GStrv params, G_GNUC_UNUSED GError **error)
 {
 	for(guint i = 0; i < n_params; i++) {
 		g_hash_table_remove(capabilities->caps, params[i]);
@@ -479,33 +459,27 @@
 }
 
 /******************************************************************************
- * Internal API
+ * Public API
  *****************************************************************************/
-void
-ibis_capabilities_register(GPluginNativePlugin *plugin) {
-	ibis_capabilities_register_type(G_TYPE_MODULE(plugin));
-}
-
 IbisCapabilities *
-ibis_capabilities_new(IbisConnection *connection) {
+ibis_capabilities_new(IbisClient *client) {
 	return g_object_new(
 		IBIS_TYPE_CAPABILITIES,
-		"connection", connection,
+		"client", client,
 		NULL);
 }
 
 void
 ibis_capabilities_start(IbisCapabilities *capabilities) {
-	ibis_connection_writef(capabilities->connection, "CAP LS %s",
-	                               IBIS_CAPABILITY_CAP_LS_VERSION);
+	ibis_client_writef(capabilities->client, "CAP LS %s",
+	                   IBIS_CAPABILITY_CAP_LS_VERSION);
 }
 
 gboolean
-ibis_capabilities_message_handler(IbisMessage *message,
-                                          GError **error,
-                                          gpointer data)
+ibis_capabilities_message_handler(IbisMessage *message, GError **error,
+                                  gpointer data)
 {
-	IbisConnection *connection = data;
+	IbisClient *client = data;
 	IbisCapabilities *capabilities = NULL;
 	GStrv params = NULL;
 	GStrv subparams = NULL;
@@ -522,7 +496,7 @@
 		return FALSE;
 	}
 
-	capabilities = ibis_connection_get_capabilities(connection);
+	capabilities = ibis_client_get_capabilities(client);
 
 	/* Initialize some variables to make it easier to call our sub command
 	 * handlers.
@@ -544,29 +518,24 @@
 	subparams = params + 2;
 
 	/* Dispatch the subcommand. */
-	if(purple_strequal(subcommand, "LS") ||
-	   purple_strequal(subcommand, "LIST"))
+	if(ibis_str_equal(subcommand, "LS") || ibis_str_equal(subcommand, "LIST"))
 	{
 		return ibis_capabilities_handle_list(capabilities, n_subparams,
-		                                             subparams, error);
-	} else if(purple_strequal(subcommand, "ACK")) {
-		return ibis_capabilities_handle_ack_nak(capabilities,
-		                                                subparams,
-		                                                signals[SIG_ACK],
-		                                                "ACK",
-		                                                error);
-	} else if(purple_strequal(subcommand, "NAK")) {
-		return ibis_capabilities_handle_ack_nak(capabilities,
-		                                                subparams,
-		                                                signals[SIG_NAK],
-		                                                "NAK",
-		                                                error);
-	} else if(purple_strequal(subcommand, "NEW")) {
+		                                     subparams, error);
+	} else if(ibis_str_equal(subcommand, "ACK")) {
+		return ibis_capabilities_handle_ack_nak(capabilities, subparams,
+		                                        signals[SIG_ACK], "ACK",
+		                                        error);
+	} else if(ibis_str_equal(subcommand, "NAK")) {
+		return ibis_capabilities_handle_ack_nak(capabilities, subparams,
+		                                        signals[SIG_NAK], "NAK",
+		                                        error);
+	} else if(ibis_str_equal(subcommand, "NEW")) {
 		return ibis_capabilities_handle_new(capabilities, n_subparams,
-		                                            subparams, error);
-	} else if(purple_strequal(subcommand, "DEL")) {
+		                                    subparams, error);
+	} else if(ibis_str_equal(subcommand, "DEL")) {
 		return ibis_capabilities_handle_del(capabilities, n_subparams,
-		                                            subparams, error);
+		                                    subparams, error);
 	}
 
 	g_set_error(error, IBIS_DOMAIN, 0,
@@ -575,33 +544,28 @@
 	return FALSE;
 }
 
-/******************************************************************************
- * Public API
- *****************************************************************************/
-IbisConnection *
-ibis_capabilities_get_connection(IbisCapabilities *capabilities)
-{
+IbisClient *
+ibis_capabilities_get_client(IbisCapabilities *capabilities) {
 	g_return_val_if_fail(IBIS_IS_CAPABILITIES(capabilities), NULL);
 
-	return capabilities->connection;
+	return capabilities->client;
 }
 
 void
 ibis_capabilities_request(IbisCapabilities *capabilities,
-                                  const char *capability)
+                          const char *capability)
 {
 	g_return_if_fail(IBIS_IS_CAPABILITIES(capabilities));
 	g_return_if_fail(capability != NULL);
 
 	g_ptr_array_add(capabilities->requests, g_strdup(capability));
 
-	ibis_connection_writef(capabilities->connection, "CAP REQ :%s",
-	                               capability);
+	ibis_client_writef(capabilities->client, "CAP REQ :%s", capability);
 }
 
 const char *
-ibis_capabilities_lookup(IbisCapabilities *capabilities,
-                                 const char *name, gboolean *found)
+ibis_capabilities_lookup(IbisCapabilities *capabilities, const char *name,
+                         gboolean *found)
 {
 	gpointer value = NULL;
 	gboolean real_found = FALSE;
@@ -621,7 +585,7 @@
 
 gboolean
 ibis_capabilities_lookup_and_request(IbisCapabilities *capabilities,
-                                             const char *name)
+                                     const char *name)
 {
 	gboolean found = FALSE;
 
diff --git a/ibis/ibisclient.h b/ibis/ibisclient.h
new file mode 100644
--- /dev/null
+++ b/ibis/ibisclient.h
@@ -0,0 +1,95 @@
+/*
+ * Ibis - IRCv3 Library
+ * Copyright (C) 2022-2024 Ibis Developers <devel@pidgin.im>
+ *
+ * Ibis is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(IBIS_GLOBAL_HEADER_INSIDE) && !defined(IBIS_COMPILATION)
+# error "only <ibis.h> may be included directly"
+#endif
+
+#ifndef IBIS_CLIENT_H
+#define IBIS_CLIENT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "ibisversion.h"
+
+G_BEGIN_DECLS
+
+#define IBIS_TYPE_CLIENT (ibis_client_get_type())
+
+/**
+ * IbisClient:
+ *
+ * A representation of an IRCv3 client.
+ *
+ * Since: 0.1
+ */
+
+IBIS_AVAILABLE_IN_0_1
+G_DECLARE_FINAL_TYPE(IbisClient, ibis_client, IBIS, CLIENT, GObject)
+
+#include "ibiscapabilities.h"
+
+/**
+ * ibis_client_new:
+ *
+ * Creates a new client.
+ *
+ * Returns: (transfer full): The new client.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+IbisClient *ibis_client_new(void);
+
+/**
+ * ibis_client_get_capabilities:
+ * @client: The instance.
+ *
+ * Gets the [class@Capabilities] from @client.
+ *
+ * Returns: (transfer none): The capabilities.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+IbisCapabilities *ibis_client_get_capabilities(IbisClient *client);
+
+/**
+ * ibis_client_writef:
+ * @client: The instance.
+ * @format: The format string.
+ * @...: The arguments for @format.
+ *
+ * Similar to C `printf()` but writes the format string out to @client.
+ *
+ * This will add the proper line termination, so you do not need to worry about
+ * that.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+void ibis_client_writef(IbisClient *client, const char *format, ...) G_GNUC_PRINTF(2, 3);
+
+G_END_DECLS
+
+#endif /* IBIS_CLIENT_H */
diff --git a/ibis/ibisclient.c b/ibis/ibisclient.c
new file mode 100644
--- /dev/null
+++ b/ibis/ibisclient.c
@@ -0,0 +1,115 @@
+/*
+ * Ibis - IRCv3 Library
+ * Copyright (C) 2022-2024 Ibis Developers <devel@pidgin.im>
+ *
+ * Ibis is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ibisclient.h"
+
+enum {
+	PROP_0,
+	PROP_CAPABILITIES,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+struct _IbisClient {
+	GObject parent;
+
+	IbisCapabilities *capabilities;
+};
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_FINAL_TYPE(IbisClient, ibis_client, G_TYPE_OBJECT)
+
+static void
+ibis_client_finalize(GObject *obj) {
+	IbisClient *client = IBIS_CLIENT(obj);
+
+	g_clear_object(&client->capabilities);
+
+	G_OBJECT_CLASS(ibis_client_parent_class)->finalize(obj);
+}
+
+static void
+ibis_client_get_property(GObject *obj, guint param_id, GValue *value,
+                         GParamSpec *pspec)
+{
+	IbisClient *client = IBIS_CLIENT(obj);
+
+	switch(param_id) {
+	case PROP_CAPABILITIES:
+		g_value_set_object(value, ibis_client_get_capabilities(client));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+ibis_client_init(IbisClient *client) {
+	client->capabilities = ibis_capabilities_new(client);
+}
+
+static void
+ibis_client_class_init(IbisClientClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->finalize = ibis_client_finalize;
+	obj_class->get_property = ibis_client_get_property;
+
+	/**
+	 * IbisClient:capabilities:
+	 *
+	 * The [class@Capabilities] that this client is using.
+	 *
+	 * Since: 0.1
+	 */
+	properties[PROP_CAPABILITIES] = g_param_spec_object(
+		"capabilities", "capabilities",
+		"The capabilities for this client.",
+		IBIS_TYPE_CAPABILITIES,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+IbisClient *
+ibis_client_new(void) {
+	return g_object_new(IBIS_TYPE_CLIENT, NULL);
+}
+
+IbisCapabilities *
+ibis_client_get_capabilities(IbisClient *client) {
+	g_return_val_if_fail(IBIS_IS_CLIENT(client), NULL);
+
+	return client->capabilities;
+}
+
+void
+ibis_client_writef(IbisClient *client, const char *format, ...)
+{
+#pragma message not implemented
+}
diff --git a/ibis/ibistring.h b/ibis/ibistring.h
new file mode 100644
--- /dev/null
+++ b/ibis/ibistring.h
@@ -0,0 +1,64 @@
+/*
+ * Ibis - IRCv3 Library
+ * Copyright (C) 2022-2024 Ibis Developers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef IBIS_STRING_H
+#define IBIS_STRING_H
+
+#include <glib.h>
+
+#include "ibisversion.h"
+
+G_BEGIN_DECLS
+
+/**
+ * ibis_str_is_empty:
+ * @str: (nullable): The string to check.
+ *
+ * Determines if @str is empty or not.
+ *
+ * Returns: %TRUE if @str is empty, otherwise %FALSE.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_STATIC_INLINE_IN_0_1
+static inline gboolean
+ibis_str_is_empty(const char *str) {
+	return (str == NULL || str[0] == '\0');
+}
+
+/**
+ * ibis_str_equal:
+ * @str1: (nullable): The first string.
+ * @str2: (nullable): The second string.
+ *
+ * Checks if @str1 is equal to @str2.
+ *
+ * Returns: %TRUE if the strings are equal or %FALSE.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_STATIC_INLINE_IN_0_1
+static inline gboolean
+ibis_str_equal(const char *str1, const char *str2) {
+	return (g_strcmp0(str1, str2) == 0);
+}
+
+G_END_DECLS
+
+
+#endif /* IBIS_STRING_H */
diff --git a/ibis/meson.build b/ibis/meson.build
--- a/ibis/meson.build
+++ b/ibis/meson.build
@@ -1,4 +1,6 @@
 IBIS_SOURCES = [
+  'ibiscapabilities.c',
+  'ibisclient.c',
   'ibisformatting.c',
   'ibismessage.c',
   'ibisparser.c',
@@ -7,6 +9,8 @@
 ]
 
 IBIS_HEADERS = [
+  'ibiscapabilities.h',
+  'ibisclient.h',
   'ibisconstants.h',
   'ibiscore.h',
   'ibisformatting.h',
