diff --git a/Makefile.tools b/Makefile.tools
index df4cad065..6125e57f7 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -315,7 +315,7 @@ tools_ibeacon_LDADD = src/libshared-mainloop.la
 
 tools_btgatt_client_SOURCES = tools/btgatt-client.c src/uuid-helper.c
 tools_btgatt_client_LDADD = src/libshared-mainloop.la \
-						lib/libbluetooth-internal.la
+						lib/libbluetooth-internal.la -lreadline
 
 tools_btgatt_server_SOURCES = tools/btgatt-server.c src/uuid-helper.c
 tools_btgatt_server_LDADD = src/libshared-mainloop.la \
diff --git a/src/shared/shell.h b/src/shared/shell.h
index 87fb5c415..8793835c0 100644
--- a/src/shared/shell.h
+++ b/src/shared/shell.h
@@ -15,6 +15,7 @@
 #define COLOR_GREEN	"\001\x1B[0;92m\002"
 #define COLOR_YELLOW	"\001\x1B[0;93m\002"
 #define COLOR_BLUE	"\001\x1B[0;94m\002"
+#define COLOR_MAGENTA	"\x1B[0;95m"
 #define COLOR_BOLDGRAY	"\001\x1B[1;30m\002"
 #define COLOR_BOLDWHITE	"\001\x1B[1;37m\002"
 #define COLOR_HIGHLIGHT	"\001\x1B[1;39m\002"
diff --git a/tools/btgatt-client.c b/tools/btgatt-client.c
index 2a0cb5181..ecfe3f3f1 100644
--- a/tools/btgatt-client.c
+++ b/tools/btgatt-client.c
@@ -28,6 +28,7 @@
 #include "lib/uuid.h"
 
 #include "src/shared/mainloop.h"
+#include "src/shared/shell.h"
 #include "src/shared/util.h"
 #include "src/shared/att.h"
 #include "src/shared/queue.h"
@@ -37,19 +38,29 @@
 #define ATT_CID 4
 #define ATT_PSM 31
 
-#define PRLOG(...) \
-	printf(__VA_ARGS__); print_prompt();
-
-#define COLOR_OFF	"\x1B[0m"
-#define COLOR_RED	"\x1B[0;91m"
-#define COLOR_GREEN	"\x1B[0;92m"
-#define COLOR_YELLOW	"\x1B[0;93m"
-#define COLOR_BLUE	"\x1B[0;94m"
-#define COLOR_MAGENTA	"\x1B[0;95m"
-#define COLOR_BOLDGRAY	"\x1B[1;30m"
-#define COLOR_BOLDWHITE	"\x1B[1;37m"
+#define MAX_LEN_LINE 512
 
+struct client *cli;
 static bool verbose = false;
+static bool shell_running = false;
+
+#define print(fmt, arg...) do { \
+	if (shell_running) \
+		bt_shell_printf(fmt "\n", ## arg); \
+	else \
+		printf(fmt "\n", ## arg); \
+} while (0)
+
+#define error(fmt, arg...) do { \
+	if (shell_running) \
+		bt_shell_printf(COLOR_RED fmt "\n" COLOR_OFF, ## arg); \
+	else \
+		fprintf(stderr, COLOR_RED fmt "\n" COLOR_OFF, ## arg); \
+} while (0)
+
+#define append(str, fmt, arg...) do { \
+	sprintf(strchr(str, '\0'), fmt, ## arg); \
+} while (0)
 
 struct client {
 	int fd;
@@ -60,10 +71,11 @@ struct client {
 	unsigned int reliable_session_id;
 };
 
-static void print_prompt(void)
+static void update_prompt(void)
 {
-	printf(COLOR_BLUE "[GATT client]" COLOR_OFF "# ");
-	fflush(stdout);
+	char str[32];
+	sprintf(str, COLOR_BLUE "[GATT client]" COLOR_OFF "# ");
+	bt_shell_set_prompt(str);
 }
 
 static const char *ecode_to_string(uint8_t ecode)
@@ -116,7 +128,7 @@ static const char *ecode_to_string(uint8_t ecode)
 
 static void att_disconnect_cb(int err, void *user_data)
 {
-	printf("Device disconnected: %s\n", strerror(err));
+	print("Device disconnected: %s", strerror(err));
 
 	mainloop_quit();
 }
@@ -125,14 +137,14 @@ static void att_debug_cb(const char *str, void *user_data)
 {
 	const char *prefix = user_data;
 
-	PRLOG(COLOR_BOLDGRAY "%s" COLOR_BOLDWHITE "%s\n" COLOR_OFF, prefix, str);
+	print(COLOR_BOLDGRAY "%s" COLOR_BOLDWHITE "%s" COLOR_OFF, prefix, str);
 }
 
 static void gatt_debug_cb(const char *str, void *user_data)
 {
 	const char *prefix = user_data;
 
-	PRLOG(COLOR_GREEN "%s%s\n" COLOR_OFF, prefix, str);
+	print(COLOR_GREEN "%s%s" COLOR_OFF, prefix, str);
 }
 
 static void ready_cb(bool success, uint8_t att_ecode, void *user_data);
@@ -150,7 +162,7 @@ static void log_service_event(struct gatt_db_attribute *attr, const char *str)
 
 	gatt_db_attribute_get_service_handles(attr, &start, &end);
 
-	PRLOG("%s - UUID: %s start: 0x%04x end: 0x%04x\n", str, uuid_str,
+	print("%s - UUID: %s start: 0x%04x end: 0x%04x", str, uuid_str,
 								start, end);
 }
 
@@ -170,20 +182,20 @@ static struct client *client_create(int fd, uint16_t mtu)
 
 	cli = new0(struct client, 1);
 	if (!cli) {
-		fprintf(stderr, "Failed to allocate memory for client\n");
+		error("Failed to allocate memory for client");
 		return NULL;
 	}
 
 	cli->att = bt_att_new(fd, false);
 	if (!cli->att) {
-		fprintf(stderr, "Failed to initialze ATT transport layer\n");
+		error("Failed to initialze ATT transport layer");
 		bt_att_unref(cli->att);
 		free(cli);
 		return NULL;
 	}
 
 	if (!bt_att_set_close_on_unref(cli->att, true)) {
-		fprintf(stderr, "Failed to set up ATT transport layer\n");
+		error("Failed to set up ATT transport layer");
 		bt_att_unref(cli->att);
 		free(cli);
 		return NULL;
@@ -191,7 +203,7 @@ static struct client *client_create(int fd, uint16_t mtu)
 
 	if (!bt_att_register_disconnect(cli->att, att_disconnect_cb, NULL,
 								NULL)) {
-		fprintf(stderr, "Failed to set ATT disconnect handler\n");
+		error("Failed to set ATT disconnect handler");
 		bt_att_unref(cli->att);
 		free(cli);
 		return NULL;
@@ -200,7 +212,7 @@ static struct client *client_create(int fd, uint16_t mtu)
 	cli->fd = fd;
 	cli->db = gatt_db_new();
 	if (!cli->db) {
-		fprintf(stderr, "Failed to create GATT database\n");
+		error("Failed to create GATT database");
 		bt_att_unref(cli->att);
 		free(cli);
 		return NULL;
@@ -208,7 +220,7 @@ static struct client *client_create(int fd, uint16_t mtu)
 
 	cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu, 0);
 	if (!cli->gatt) {
-		fprintf(stderr, "Failed to create GATT client\n");
+		error("Failed to create GATT client");
 		gatt_db_unref(cli->db);
 		bt_att_unref(cli->att);
 		free(cli);
@@ -225,8 +237,8 @@ static struct client *client_create(int fd, uint16_t mtu)
 									NULL);
 	}
 
-	bt_gatt_client_ready_register(cli->gatt, ready_cb, cli, NULL);
-	bt_gatt_client_set_service_changed(cli->gatt, service_changed_cb, cli,
+	bt_gatt_client_ready_register(cli->gatt, ready_cb, NULL, NULL);
+	bt_gatt_client_set_service_changed(cli->gatt, service_changed_cb, NULL,
 									NULL);
 
 	/* bt_gatt_client already holds a reference */
@@ -242,7 +254,7 @@ static void client_destroy(struct client *cli)
 	free(cli);
 }
 
-static void print_uuid(const bt_uuid_t *uuid)
+static void append_uuid(char *str, const bt_uuid_t *uuid)
 {
 	char uuid_str[MAX_LEN_UUID_STR];
 	bt_uuid_t uuid128;
@@ -250,15 +262,15 @@ static void print_uuid(const bt_uuid_t *uuid)
 	bt_uuid_to_uuid128(uuid, &uuid128);
 	bt_uuid_to_string(&uuid128, uuid_str, sizeof(uuid_str));
 
-	printf("%s\n", uuid_str);
+	append(str, "%s", uuid_str);
 }
 
 static void print_incl(struct gatt_db_attribute *attr, void *user_data)
 {
-	struct client *cli = user_data;
 	uint16_t handle, start, end;
 	struct gatt_db_attribute *service;
 	bt_uuid_t uuid;
+	char line[MAX_LEN_LINE] = {0};
 
 	if (!gatt_db_attribute_get_incl_data(attr, &handle, &start, &end))
 		return;
@@ -269,18 +281,21 @@ static void print_incl(struct gatt_db_attribute *attr, void *user_data)
 
 	gatt_db_attribute_get_service_uuid(service, &uuid);
 
-	printf("\t  " COLOR_GREEN "include" COLOR_OFF " - handle: "
+	append(line, "\t  " COLOR_GREEN "include" COLOR_OFF " - handle: "
 					"0x%04x, - start: 0x%04x, end: 0x%04x,"
 					"uuid: ", handle, start, end);
-	print_uuid(&uuid);
+	append_uuid(line, &uuid);
+	print("%s", line);
 }
 
 static void print_desc(struct gatt_db_attribute *attr, void *user_data)
 {
-	printf("\t\t  " COLOR_MAGENTA "descr" COLOR_OFF
+	char line[MAX_LEN_LINE] = {0};
+	append(line, "\t\t  " COLOR_MAGENTA "descr" COLOR_OFF
 					" - handle: 0x%04x, uuid: ",
 					gatt_db_attribute_get_handle(attr));
-	print_uuid(gatt_db_attribute_get_type(attr));
+	append_uuid(line, gatt_db_attribute_get_type(attr));
+	print("%s", line);
 }
 
 static void print_chrc(struct gatt_db_attribute *attr, void *user_data)
@@ -289,6 +304,7 @@ static void print_chrc(struct gatt_db_attribute *attr, void *user_data)
 	uint8_t properties;
 	uint16_t ext_prop;
 	bt_uuid_t uuid;
+	char line[MAX_LEN_LINE] = {0};
 
 	if (!gatt_db_attribute_get_char_data(attr, &handle,
 								&value_handle,
@@ -297,170 +313,133 @@ static void print_chrc(struct gatt_db_attribute *attr, void *user_data)
 								&uuid))
 		return;
 
-	printf("\t  " COLOR_YELLOW "charac" COLOR_OFF
+	append(line, "\t  " COLOR_YELLOW "charac" COLOR_OFF
 				" - start: 0x%04x, value: 0x%04x, "
 				"props: 0x%02x, ext_props: 0x%04x, uuid: ",
 				handle, value_handle, properties, ext_prop);
-	print_uuid(&uuid);
+	append_uuid(line, &uuid);
+	print("%s", line);
 
 	gatt_db_service_foreach_desc(attr, print_desc, NULL);
 }
 
 static void print_service(struct gatt_db_attribute *attr, void *user_data)
 {
-	struct client *cli = user_data;
 	uint16_t start, end;
 	bool primary;
 	bt_uuid_t uuid;
+	char line[MAX_LEN_LINE] = {0};
 
 	if (!gatt_db_attribute_get_service_data(attr, &start, &end, &primary,
 									&uuid))
 		return;
 
-	printf(COLOR_RED "service" COLOR_OFF " - start: 0x%04x, "
+	append(line, COLOR_RED "service" COLOR_OFF " - start: 0x%04x, "
 				"end: 0x%04x, type: %s, uuid: ",
 				start, end, primary ? "primary" : "secondary");
-	print_uuid(&uuid);
+	append_uuid(line, &uuid);
+	print("%s", line);
 
-	gatt_db_service_foreach_incl(attr, print_incl, cli);
+	gatt_db_service_foreach_incl(attr, print_incl, NULL);
 	gatt_db_service_foreach_char(attr, print_chrc, NULL);
-
-	printf("\n");
 }
 
 static void print_services(struct client *cli)
 {
-	printf("\n");
-
-	gatt_db_foreach_service(cli->db, NULL, print_service, cli);
+	gatt_db_foreach_service(cli->db, NULL, print_service, NULL);
 }
 
-static void print_services_by_uuid(struct client *cli, const bt_uuid_t *uuid)
+static void print_services_by_uuid(const bt_uuid_t *uuid)
 {
-	printf("\n");
-
-	gatt_db_foreach_service(cli->db, uuid, print_service, cli);
+	gatt_db_foreach_service(cli->db, uuid, print_service, NULL);
 }
 
-static void print_services_by_handle(struct client *cli, uint16_t handle)
+static void print_services_by_handle(uint16_t handle)
 {
-	printf("\n");
-
 	/* TODO: Filter by handle */
 	gatt_db_foreach_service(cli->db, NULL, print_service, cli);
 }
 
 static void ready_cb(bool success, uint8_t att_ecode, void *user_data)
 {
-	struct client *cli = user_data;
-
 	if (!success) {
-		PRLOG("GATT discovery procedures failed - error code: 0x%02x\n",
+		error("GATT discovery procedures failed - error code: 0x%02x",
 								att_ecode);
 		return;
 	}
 
-	PRLOG("GATT discovery procedures complete\n");
+	print("GATT discovery procedures complete");
 
 	print_services(cli);
-	print_prompt();
 }
 
 static void service_changed_cb(uint16_t start_handle, uint16_t end_handle,
 								void *user_data)
 {
-	struct client *cli = user_data;
-
-	printf("\nService Changed handled - start: 0x%04x end: 0x%04x\n",
+	print("Service Changed handled - start: 0x%04x end: 0x%04x",
 						start_handle, end_handle);
 
-	gatt_db_foreach_service_in_range(cli->db, NULL, print_service, cli,
+	gatt_db_foreach_service_in_range(cli->db, NULL, print_service, NULL,
 						start_handle, end_handle);
-	print_prompt();
-}
-
-static void services_usage(void)
-{
-	printf("Usage: services [options]\nOptions:\n"
-		"\t -u, --uuid <uuid>\tService UUID\n"
-		"\t -a, --handle <handle>\tService start handle\n"
-		"\t -h, --help\t\tShow help message\n"
-		"e.g.:\n"
-		"\tservices\n\tservices -u 0x180d\n\tservices -a 0x0009\n");
 }
 
-static bool parse_args(char *str, int expected_argc,  char **argv, int *argc)
-{
-	char **ap;
-
-	for (ap = argv; (*ap = strsep(&str, " \t")) != NULL;) {
-		if (**ap == '\0')
-			continue;
-
-		(*argc)++;
-		ap++;
-
-		if (*argc > expected_argc)
-			return false;
-	}
-
-	return true;
-}
+static struct option services_options[] = {
+	{ "uuid",	1, 0, 'u' },
+	{ "handle",	1, 0, 'a' },
+	{ "help",	0, 0, 'h' },
+	{ 0, 0, 0, 0 }
+};
 
-static void cmd_services(struct client *cli, char *cmd_str)
+static void cmd_services(int argc, char **argv)
 {
-	char *argv[3];
-	int argc = 0;
+	int opt;
+	bool use_uuid = false;
+	bt_uuid_t tmp, uuid;
+	uint16_t handle = 0;
+	char *endptr = NULL;
 
 	if (!bt_gatt_client_is_ready(cli->gatt)) {
-		printf("GATT client not initialized\n");
-		return;
-	}
-
-	if (!parse_args(cmd_str, 2, argv, &argc)) {
-		services_usage();
-		return;
-	}
-
-	if (!argc) {
-		print_services(cli);
+		print("GATT client not initialized");
 		return;
 	}
 
-	if (argc != 2) {
-		services_usage();
-		return;
-	}
-
-	if (!strcmp(argv[0], "-u") || !strcmp(argv[0], "--uuid")) {
-		bt_uuid_t tmp, uuid;
-
-		if (bt_string_to_uuid(&tmp, argv[1]) < 0) {
-			printf("Invalid UUID: %s\n", argv[1]);
-			return;
-		}
-
-		bt_uuid_to_uuid128(&tmp, &uuid);
-
-		print_services_by_uuid(cli, &uuid);
-	} else if (!strcmp(argv[0], "-a") || !strcmp(argv[0], "--handle")) {
-		uint16_t handle;
-		char *endptr = NULL;
-
-		handle = strtol(argv[1], &endptr, 0);
-		if (!endptr || *endptr != '\0') {
-			printf("Invalid start handle: %s\n", argv[1]);
-			return;
+	while ((opt = getopt_long(argc, argv, "u:a:", services_options,
+								NULL)) != -1) {
+		switch (opt) {
+		case 'u':
+			if (bt_string_to_uuid(&tmp, optarg) < 0) {
+				error("Invalid UUID: %s", optarg);
+				optind = 0;
+				return bt_shell_noninteractive_quit(EXIT_FAILURE);
+			}
+			bt_uuid_to_uuid128(&tmp, &uuid);
+			use_uuid = true;
+			break;
+		case 'a':
+			handle = strtol(optarg, &endptr, 0);
+			if (!endptr || *endptr != '\0') {
+				error("Invalid start handle: %s", optarg);
+				optind = 0;
+				return bt_shell_noninteractive_quit(EXIT_FAILURE);
+			}
+			break;
+		case 'h':
+			bt_shell_usage();
+			optind = 0;
+			return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+		default:
+			bt_shell_usage();
+			optind = 0;
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
 		}
+	}
 
-		print_services_by_handle(cli, handle);
-	} else
-		services_usage();
-}
+	optind = 0;
 
-static void read_multiple_usage(void)
-{
-	printf("Usage: read-multiple <handle_1> <handle_2> ...\n");
+	if (use_uuid)
+		print_services_by_uuid(&uuid);
+	else
+		print_services_by_handle(handle);
 }
 
 static void read_multiple_cb(bool success, uint8_t att_ecode,
@@ -468,43 +447,37 @@ static void read_multiple_cb(bool success, uint8_t att_ecode,
 					void *user_data)
 {
 	int i;
+	char line[MAX_LEN_LINE] = {0};
 
 	if (!success) {
-		PRLOG("\nRead multiple request failed: 0x%02x\n", att_ecode);
+		error("Read multiple request failed: 0x%02x", att_ecode);
 		return;
 	}
 
-	printf("\nRead multiple value (%u bytes):", length);
+	append(line, "Read multiple value (%u bytes):", length);
 
 	for (i = 0; i < length; i++)
-		printf("%02x ", value[i]);
+		append(line, "%02x ", value[i]);
 
-	PRLOG("\n");
+	print("%s", line);
 }
 
-static void cmd_read_multiple(struct client *cli, char *cmd_str)
+static void cmd_read_multiple(int argc, char **argv)
 {
-	int argc = 0;
 	uint16_t *value;
-	char *argv[512];
 	int i;
 	char *endptr = NULL;
 
-	if (!parse_args(cmd_str, sizeof(argv), argv, &argc) || argc < 2) {
-		read_multiple_usage();
-		return;
-	}
-
 	value = malloc(sizeof(uint16_t) * argc);
 	if (!value) {
-		printf("Failed to construct value\n");
+		error("Failed to construct value");
 		return;
 	}
 
-	for (i = 0; i < argc; i++) {
+	for (i = 1; i < argc; i++) {
 		value[i] = strtol(argv[i], &endptr, 0);
 		if (endptr == argv[i] || *endptr != '\0' || !value[i]) {
-			printf("Invalid value byte: %s\n", argv[i]);
+			error("Invalid value byte: %s", argv[i]);
 			free(value);
 			return;
 		}
@@ -512,133 +485,98 @@ static void cmd_read_multiple(struct client *cli, char *cmd_str)
 
 	if (!bt_gatt_client_read_multiple(cli->gatt, value, argc,
 						read_multiple_cb, NULL, NULL))
-		printf("Failed to initiate read multiple procedure\n");
+		error("Failed to initiate read multiple procedure");
 
 	free(value);
 }
 
-static void read_value_usage(void)
-{
-	printf("Usage: read-value <value_handle>\n");
-}
-
 static void read_cb(bool success, uint8_t att_ecode, const uint8_t *value,
 					uint16_t length, void *user_data)
 {
 	int i;
+	char line[MAX_LEN_LINE] = {0};
 
 	if (!success) {
-		PRLOG("\nRead request failed: %s (0x%02x)\n",
+		error("Read request failed: %s (0x%02x)",
 				ecode_to_string(att_ecode), att_ecode);
 		return;
 	}
 
-	printf("\nRead value");
+	append(line, "Read value");
 
 	if (length == 0) {
-		PRLOG(": 0 bytes\n");
+		print("%s: 0 bytes", line);
 		return;
 	}
 
-	printf(" (%u bytes): ", length);
+	append(line, " (%u bytes): ", length);
 
 	for (i = 0; i < length; i++)
-		printf("%02x ", value[i]);
+		append(line, "%02x ", value[i]);
 
-	PRLOG("\n");
+	print("%s", line);
 }
 
-static void cmd_read_value(struct client *cli, char *cmd_str)
+static void cmd_read_value(int argc, char **argv)
 {
-	char *argv[2];
-	int argc = 0;
 	uint16_t handle;
 	char *endptr = NULL;
 
-	if (!parse_args(cmd_str, 1, argv, &argc) || argc != 1) {
-		read_value_usage();
-		return;
-	}
-
-	handle = strtol(argv[0], &endptr, 0);
+	handle = strtol(argv[1], &endptr, 0);
 	if (!endptr || *endptr != '\0' || !handle) {
-		printf("Invalid value handle: %s\n", argv[0]);
+		error("Invalid value handle: %s", argv[1]);
 		return;
 	}
 
 	if (!bt_gatt_client_read_value(cli->gatt, handle, read_cb,
 								NULL, NULL))
-		printf("Failed to initiate read value procedure\n");
-}
-
-static void read_long_value_usage(void)
-{
-	printf("Usage: read-long-value <value_handle> <offset>\n");
+		error("Failed to initiate read value procedure");
 }
 
-static void cmd_read_long_value(struct client *cli, char *cmd_str)
+static void cmd_read_long_value(int argc, char **argv)
 {
-	char *argv[3];
-	int argc = 0;
 	uint16_t handle;
 	uint16_t offset;
 	char *endptr = NULL;
 
-	if (!parse_args(cmd_str, 2, argv, &argc) || argc != 2) {
-		read_long_value_usage();
-		return;
-	}
-
-	handle = strtol(argv[0], &endptr, 0);
+	handle = strtol(argv[1], &endptr, 0);
 	if (!endptr || *endptr != '\0' || !handle) {
-		printf("Invalid value handle: %s\n", argv[0]);
+		error("Invalid value handle: %s", argv[1]);
 		return;
 	}
 
 	endptr = NULL;
-	offset = strtol(argv[1], &endptr, 0);
+	offset = strtol(argv[2], &endptr, 0);
 	if (!endptr || *endptr != '\0') {
-		printf("Invalid offset: %s\n", argv[1]);
+		error("Invalid offset: %s", argv[2]);
 		return;
 	}
 
 	if (!bt_gatt_client_read_long_value(cli->gatt, handle, offset, read_cb,
 								NULL, NULL))
-		printf("Failed to initiate read long value procedure\n");
-}
-
-static void write_value_usage(void)
-{
-	printf("Usage: write-value [options] <value_handle> <value>\n"
-		"Options:\n"
-		"\t-w, --without-response\tWrite without response\n"
-		"\t-s, --signed-write\tSigned write command\n"
-		"e.g.:\n"
-		"\twrite-value 0x0001 00 01 00\n");
+		error("Failed to initiate read long value procedure");
 }
 
 static struct option write_value_options[] = {
 	{ "without-response",	0, 0, 'w' },
 	{ "signed-write",	0, 0, 's' },
+	{ "help",	0, 0, 'h' },
 	{ }
 };
 
 static void write_cb(bool success, uint8_t att_ecode, void *user_data)
 {
 	if (success) {
-		PRLOG("\nWrite successful\n");
+		print("Write successful");
 	} else {
-		PRLOG("\nWrite failed: %s (0x%02x)\n",
+		error("Write failed: %s (0x%02x)",
 				ecode_to_string(att_ecode), att_ecode);
 	}
 }
 
-static void cmd_write_value(struct client *cli, char *cmd_str)
+static void cmd_write_value(int argc, char **argv)
 {
 	int opt, i, val;
-	char *argvbuf[516];
-	char **argv = argvbuf;
-	int argc = 1;
 	uint16_t handle;
 	char *endptr = NULL;
 	int length;
@@ -646,14 +584,6 @@ static void cmd_write_value(struct client *cli, char *cmd_str)
 	bool without_response = false;
 	bool signed_write = false;
 
-	if (!parse_args(cmd_str, 514, argv + 1, &argc)) {
-		printf("Too many arguments\n");
-		write_value_usage();
-		return;
-	}
-
-	optind = 0;
-	argv[0] = "write-value";
 	while ((opt = getopt_long(argc, argv, "+ws", write_value_options,
 								NULL)) != -1) {
 		switch (opt) {
@@ -663,23 +593,24 @@ static void cmd_write_value(struct client *cli, char *cmd_str)
 		case 's':
 			signed_write = true;
 			break;
+		case 'h':
+			bt_shell_usage();
+			optind = 0;
+			return bt_shell_noninteractive_quit(EXIT_SUCCESS);
 		default:
-			write_value_usage();
-			return;
+			bt_shell_usage();
+			optind = 0;
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
 		}
 	}
 
 	argc -= optind;
 	argv += optind;
-
-	if (argc < 1) {
-		write_value_usage();
-		return;
-	}
+	optind = 0;
 
 	handle = strtol(argv[0], &endptr, 0);
 	if (!endptr || *endptr != '\0' || !handle) {
-		printf("Invalid handle: %s\n", argv[0]);
+		error("Invalid handle: %s", argv[1]);
 		return;
 	}
 
@@ -687,13 +618,13 @@ static void cmd_write_value(struct client *cli, char *cmd_str)
 
 	if (length > 0) {
 		if (length > UINT16_MAX) {
-			printf("Write value too long\n");
+			error("Write value too long");
 			return;
 		}
 
 		value = malloc(length);
 		if (!value) {
-			printf("Failed to construct write value\n");
+			error("Failed to construct write value");
 			return;
 		}
 
@@ -701,7 +632,7 @@ static void cmd_write_value(struct client *cli, char *cmd_str)
 			val = strtol(argv[i], &endptr, 0);
 			if (endptr == argv[i] || *endptr != '\0'
 				|| errno == ERANGE || val < 0 || val > 255) {
-				printf("Invalid value byte: %s\n",
+				error("Invalid value byte: %s",
 								argv[i]);
 				goto done;
 			}
@@ -712,36 +643,27 @@ static void cmd_write_value(struct client *cli, char *cmd_str)
 	if (without_response) {
 		if (!bt_gatt_client_write_without_response(cli->gatt, handle,
 						signed_write, value, length)) {
-			printf("Failed to initiate write without response "
-								"procedure\n");
+			error("Failed to initiate write without response "
+								"procedure");
 			goto done;
 		}
 
-		printf("Write command sent\n");
+		print("Write command sent");
 		goto done;
 	}
 
 	if (!bt_gatt_client_write_value(cli->gatt, handle, value, length,
 								write_cb,
 								NULL, NULL))
-		printf("Failed to initiate write procedure\n");
+		error("Failed to initiate write procedure");
 
 done:
 	free(value);
 }
 
-static void write_long_value_usage(void)
-{
-	printf("Usage: write-long-value [options] <value_handle> <offset> "
-				"<value>\n"
-				"Options:\n"
-				"\t-r, --reliable-write\tReliable write\n"
-				"e.g.:\n"
-				"\twrite-long-value 0x0001 0 00 01 00\n");
-}
-
 static struct option write_long_value_options[] = {
 	{ "reliable-write",	0, 0, 'r' },
+	{ "help", 0, 0, 'h' },
 	{ }
 };
 
@@ -749,21 +671,18 @@ static void write_long_cb(bool success, bool reliable_error, uint8_t att_ecode,
 								void *user_data)
 {
 	if (success) {
-		PRLOG("Write successful\n");
+		print("Write successful");
 	} else if (reliable_error) {
-		PRLOG("Reliable write not verified\n");
+		error("Reliable write not verified");
 	} else {
-		PRLOG("\nWrite failed: %s (0x%02x)\n",
+		error("Write failed: %s (0x%02x)",
 				ecode_to_string(att_ecode), att_ecode);
 	}
 }
 
-static void cmd_write_long_value(struct client *cli, char *cmd_str)
+static void cmd_write_long_value(int argc, char **argv)
 {
 	int opt, i, val;
-	char *argvbuf[516];
-	char **argv = argvbuf;
-	int argc = 1;
 	uint16_t handle;
 	uint16_t offset;
 	char *endptr = NULL;
@@ -771,44 +690,44 @@ static void cmd_write_long_value(struct client *cli, char *cmd_str)
 	uint8_t *value = NULL;
 	bool reliable_writes = false;
 
-	if (!parse_args(cmd_str, 514, argv + 1, &argc)) {
-		printf("Too many arguments\n");
-		write_value_usage();
-		return;
-	}
-
-	optind = 0;
-	argv[0] = "write-long-value";
 	while ((opt = getopt_long(argc, argv, "+r", write_long_value_options,
 								NULL)) != -1) {
 		switch (opt) {
 		case 'r':
 			reliable_writes = true;
 			break;
+		case 'h':
+			bt_shell_usage();
+			optind = 0;
+			return bt_shell_noninteractive_quit(EXIT_SUCCESS);
 		default:
-			write_long_value_usage();
-			return;
+			bt_shell_usage();
+			optind = 0;
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
 		}
 	}
 
 	argc -= optind;
 	argv += optind;
+	optind = 0;
 
-	if (argc < 2) {
-		write_long_value_usage();
+	if (argc > 514) {
+		error("Too many arguments");
+		bt_shell_usage();
+		optind = 0;
 		return;
 	}
 
 	handle = strtol(argv[0], &endptr, 0);
 	if (!endptr || *endptr != '\0' || !handle) {
-		printf("Invalid handle: %s\n", argv[0]);
+		error("Invalid handle: %s", argv[1]);
 		return;
 	}
 
 	endptr = NULL;
 	offset = strtol(argv[1], &endptr, 0);
 	if (!endptr || *endptr != '\0' || errno == ERANGE) {
-		printf("Invalid offset: %s\n", argv[1]);
+		error("Invalid offset: %s", argv[2]);
 		return;
 	}
 
@@ -816,13 +735,13 @@ static void cmd_write_long_value(struct client *cli, char *cmd_str)
 
 	if (length > 0) {
 		if (length > UINT16_MAX) {
-			printf("Write value too long\n");
+			error("Write value too long");
 			return;
 		}
 
 		value = malloc(length);
 		if (!value) {
-			printf("Failed to construct write value\n");
+			error("Failed to construct write value");
 			return;
 		}
 
@@ -830,7 +749,7 @@ static void cmd_write_long_value(struct client *cli, char *cmd_str)
 			val = strtol(argv[i], &endptr, 0);
 			if (endptr == argv[i] || *endptr != '\0'
 				|| errno == ERANGE || val < 0 || val > 255) {
-				printf("Invalid value byte: %s\n",
+				error("Invalid value byte: %s",
 								argv[i]);
 				free(value);
 				return;
@@ -843,32 +762,20 @@ static void cmd_write_long_value(struct client *cli, char *cmd_str)
 							offset, value, length,
 							write_long_cb,
 							NULL, NULL))
-		printf("Failed to initiate long write procedure\n");
+		error("Failed to initiate long write procedure");
 
 	free(value);
 }
 
-static void write_prepare_usage(void)
-{
-	printf("Usage: write-prepare [options] <value_handle> <offset> "
-				"<value>\n"
-				"Options:\n"
-				"\t-s, --session-id\tSession id\n"
-				"e.g.:\n"
-				"\twrite-prepare -s 1 0x0001 00 01 00\n");
-}
-
 static struct option write_prepare_options[] = {
 	{ "session-id",		1, 0, 's' },
+	{ "help", 0, 0, 'h' },
 	{ }
 };
 
-static void cmd_write_prepare(struct client *cli, char *cmd_str)
+static void cmd_write_prepare(int argc, char **argv)
 {
 	int opt, i, val;
-	char *argvbuf[516];
-	char **argv = argvbuf;
-	int argc = 0;
 	unsigned int id = 0;
 	uint16_t handle;
 	uint16_t offset;
@@ -876,59 +783,50 @@ static void cmd_write_prepare(struct client *cli, char *cmd_str)
 	unsigned int length;
 	uint8_t *value = NULL;
 
-	if (!parse_args(cmd_str, 514, argv + 1, &argc)) {
-		printf("Too many arguments\n");
-		write_value_usage();
-		return;
-	}
-
-	/* Add command name for getopt_long */
-	argc++;
-	argv[0] = "write-prepare";
-
-	optind = 0;
 	while ((opt = getopt_long(argc, argv , "s:", write_prepare_options,
 								NULL)) != -1) {
 		switch (opt) {
 		case 's':
-			if (!optarg) {
-				write_prepare_usage();
-				return;
-			}
-
 			id = atoi(optarg);
-
 			break;
+		case 'h':
+			bt_shell_usage();
+			optind = 0;
+			return bt_shell_noninteractive_quit(EXIT_SUCCESS);
 		default:
-			write_prepare_usage();
-			return;
+			bt_shell_usage();
+			optind = 0;
+			return bt_shell_noninteractive_quit(EXIT_FAILURE);
 		}
 	}
 
 	argc -= optind;
 	argv += optind;
+	optind = 0;
 
-	if (argc < 3) {
-		write_prepare_usage();
+	if (argc > 514) {
+		error("Too many arguments");
+		bt_shell_usage();
+		optind = 0;
 		return;
 	}
 
 	if (cli->reliable_session_id != id) {
-		printf("Session id != Ongoing session id (%u!=%u)\n", id,
+		error("Session id != Ongoing session id (%u!=%u)", id,
 						cli->reliable_session_id);
 		return;
 	}
 
 	handle = strtol(argv[0], &endptr, 0);
 	if (!endptr || *endptr != '\0' || !handle) {
-		printf("Invalid handle: %s\n", argv[0]);
+		error("Invalid handle: %s", argv[1]);
 		return;
 	}
 
 	endptr = NULL;
 	offset = strtol(argv[1], &endptr, 0);
 	if (!endptr || *endptr != '\0' || errno == ERANGE) {
-		printf("Invalid offset: %s\n", argv[1]);
+		error("Invalid offset: %s", argv[2]);
 		return;
 	}
 
@@ -942,13 +840,13 @@ static void cmd_write_prepare(struct client *cli, char *cmd_str)
 		goto done;
 
 	if (length > UINT16_MAX) {
-		printf("Write value too long\n");
+		error("Write value too long");
 		return;
 	}
 
 	value = malloc(length);
 	if (!value) {
-		printf("Failed to allocate memory for value\n");
+		error("Failed to allocate memory for value");
 		return;
 	}
 
@@ -956,7 +854,7 @@ static void cmd_write_prepare(struct client *cli, char *cmd_str)
 		val = strtol(argv[i], &endptr, 0);
 		if (endptr == argv[i] || *endptr != '\0' || errno == ERANGE
 						|| val < 0 || val > 255) {
-			printf("Invalid value byte: %s\n", argv[i]);
+			error("Invalid value byte: %s", argv[i]);
 			free(value);
 			return;
 		}
@@ -971,64 +869,43 @@ done:
 							write_long_cb, NULL,
 							NULL);
 	if (!cli->reliable_session_id)
-		printf("Failed to proceed prepare write\n");
+		error("Failed to proceed prepare write");
 	else
-		printf("Prepare write success.\n"
-				"Session id: %d to be used on next write\n",
+		print("Prepare write success."
+				"Session id: %d to be used on next write",
 						cli->reliable_session_id);
 
 	free(value);
 }
 
-static void write_execute_usage(void)
-{
-	printf("Usage: write-execute <session_id> <execute>\n"
-				"e.g.:\n"
-				"\twrite-execute 1 0\n");
-}
-
-static void cmd_write_execute(struct client *cli, char *cmd_str)
+static void cmd_write_execute(int argc, char **argv)
 {
-	char *argvbuf[516];
-	char **argv = argvbuf;
-	int argc = 0;
 	char *endptr = NULL;
 	unsigned int session_id;
 	bool execute;
 
-	if (!parse_args(cmd_str, 514, argv, &argc)) {
-		printf("Too many arguments\n");
-		write_value_usage();
-		return;
-	}
-
-	if (argc < 2) {
-		write_execute_usage();
-		return;
-	}
-
-	session_id = strtol(argv[0], &endptr, 0);
+	session_id = strtol(argv[1], &endptr, 0);
 	if (!endptr || *endptr != '\0') {
-		printf("Invalid session id: %s\n", argv[0]);
+		error("Invalid session id: %s", argv[1]);
 		return;
 	}
 
 	if (session_id != cli->reliable_session_id) {
-		printf("Invalid session id: %u != %u\n", session_id,
+		error("Invalid session id: %u != %u", session_id,
 						cli->reliable_session_id);
 		return;
 	}
 
-	execute = !!strtol(argv[1], &endptr, 0);
+	execute = !!strtol(argv[2], &endptr, 0);
 	if (!endptr || *endptr != '\0') {
-		printf("Invalid execute: %s\n", argv[1]);
+		error("Invalid execute: %s", argv[2]);
 		return;
 	}
 
 	if (execute) {
 		if (!bt_gatt_client_write_execute(cli->gatt, session_id,
 							write_cb, NULL, NULL))
-			printf("Failed to proceed write execute\n");
+			error("Failed to proceed write execute");
 	} else {
 		bt_gatt_client_cancel(cli->gatt, session_id);
 	}
@@ -1036,46 +913,40 @@ static void cmd_write_execute(struct client *cli, char *cmd_str)
 	cli->reliable_session_id = 0;
 }
 
-static void register_notify_usage(void)
-{
-	printf("Usage: register-notify <chrc value handle>\n");
-}
-
 static void notify_cb(uint16_t value_handle, const uint8_t *value,
 					uint16_t length, void *user_data)
 {
 	int i;
+	char line[MAX_LEN_LINE] = {0};
 
-	printf("\n\tHandle Value Not/Ind: 0x%04x - ", value_handle);
+	append(line, "\tHandle Value Not/Ind: 0x%04x - ", value_handle);
 
 	if (length == 0) {
-		PRLOG("(0 bytes)\n");
+		print("%s(0 bytes)", line);
 		return;
 	}
 
-	printf("(%u bytes): ", length);
+	append(line, "(%u bytes): ", length);
 
 	for (i = 0; i < length; i++)
-		printf("%02x ", value[i]);
+		append(line, "%02x ", value[i]);
 
-	PRLOG("\n");
+	print("%s", line);
 }
 
 static void register_notify_cb(uint16_t att_ecode, void *user_data)
 {
 	if (att_ecode) {
-		PRLOG("Failed to register notify handler "
-					"- error code: 0x%02x\n", att_ecode);
+		error("Failed to register notify handler "
+					"- error code: 0x%02x", att_ecode);
 		return;
 	}
 
-	PRLOG("Registered notify handler!\n");
+	print("Registered notify handler!");
 }
 
-static void cmd_register_notify(struct client *cli, char *cmd_str)
+static void cmd_register_notify(int argc, char **argv)
 {
-	char *argv[2];
-	int argc = 0;
 	uint16_t value_handle;
 	unsigned int id;
 	char *endptr = NULL;
@@ -1085,14 +956,9 @@ static void cmd_register_notify(struct client *cli, char *cmd_str)
 		return;
 	}
 
-	if (!parse_args(cmd_str, 1, argv, &argc) || argc != 1) {
-		register_notify_usage();
-		return;
-	}
-
-	value_handle = strtol(argv[0], &endptr, 0);
+	value_handle = strtol(argv[1], &endptr, 0);
 	if (!endptr || *endptr != '\0' || !value_handle) {
-		printf("Invalid value handle: %s\n", argv[0]);
+		error("Invalid value handle: %s", argv[1]);
 		return;
 	}
 
@@ -1100,22 +966,15 @@ static void cmd_register_notify(struct client *cli, char *cmd_str)
 							register_notify_cb,
 							notify_cb, NULL, NULL);
 	if (!id) {
-		printf("Failed to register notify handler\n");
+		error("Failed to register notify handler");
 		return;
 	}
 
-	printf("Registering notify handler with id: %u\n", id);
+	print("Registering notify handler with id: %u", id);
 }
 
-static void unregister_notify_usage(void)
+static void cmd_unregister_notify(int argc, char **argv)
 {
-	printf("Usage: unregister-notify <notify id>\n");
-}
-
-static void cmd_unregister_notify(struct client *cli, char *cmd_str)
-{
-	char *argv[2];
-	int argc = 0;
 	unsigned int id;
 	char *endptr = NULL;
 
@@ -1124,72 +983,46 @@ static void cmd_unregister_notify(struct client *cli, char *cmd_str)
 		return;
 	}
 
-	if (!parse_args(cmd_str, 1, argv, &argc) || argc != 1) {
-		unregister_notify_usage();
-		return;
-	}
-
-	id = strtol(argv[0], &endptr, 0);
+	id = strtol(argv[1], &endptr, 0);
 	if (!endptr || *endptr != '\0' || !id) {
-		printf("Invalid notify id: %s\n", argv[0]);
+		error("Invalid notify id: %s", argv[1]);
 		return;
 	}
 
 	if (!bt_gatt_client_unregister_notify(cli->gatt, id)) {
-		printf("Failed to unregister notify handler with id: %u\n", id);
+		error("Failed to unregister notify handler with id: %u", id);
 		return;
 	}
 
-	printf("Unregistered notify handler with id: %u\n", id);
-}
-
-static void set_security_usage(void)
-{
-	printf("Usage: set-security <level>\n"
-		"level: 1-3\n"
-		"e.g.:\n"
-		"\tset-security 2\n");
+	print("Unregistered notify handler with id: %u", id);
 }
 
-static void cmd_set_security(struct client *cli, char *cmd_str)
+static void cmd_set_security(int argc, char **argv)
 {
-	char *argv[2];
-	int argc = 0;
 	char *endptr = NULL;
 	int level;
 
-	if (!parse_args(cmd_str, 1, argv, &argc)) {
-		printf("Too many arguments\n");
-		set_security_usage();
-		return;
-	}
-
-	if (argc < 1) {
-		set_security_usage();
-		return;
-	}
-
-	level = strtol(argv[0], &endptr, 0);
+	level = strtol(argv[1], &endptr, 0);
 	if (!endptr || *endptr != '\0' || level < 1 || level > 3) {
-		printf("Invalid level: %s\n", argv[0]);
+		error("Invalid level: %s", argv[1]);
 		return;
 	}
 
 	if (!bt_gatt_client_set_security(cli->gatt, level))
-		printf("Could not set sec level\n");
+		error("Could not set sec level");
 	else
-		printf("Setting security level %d success\n", level);
+		print("Setting security level %d success", level);
 }
 
-static void cmd_get_security(struct client *cli, char *cmd_str)
+static void cmd_get_security(int argc, char **argv)
 {
 	int level;
 
 	level = bt_gatt_client_get_security(cli->gatt);
 	if (level < 0)
-		printf("Could not set sec level\n");
+		error("Could not get sec level");
 	else
-		printf("Security level: %u\n", level);
+		print("Security level: %u", level);
 }
 
 static bool convert_sign_key(char *optarg, uint8_t key[16])
@@ -1197,7 +1030,7 @@ static bool convert_sign_key(char *optarg, uint8_t key[16])
 	int i;
 
 	if (strlen(optarg) != 32) {
-		printf("sign-key length is invalid\n");
+		error("sign-key length is invalid");
 		return false;
 	}
 
@@ -1209,14 +1042,6 @@ static bool convert_sign_key(char *optarg, uint8_t key[16])
 	return true;
 }
 
-static void set_sign_key_usage(void)
-{
-	printf("Usage: set-sign-key [options]\nOptions:\n"
-		"\t -c, --sign-key <csrk>\tCSRK\n"
-		"e.g.:\n"
-		"\tset-sign-key -c D8515948451FEA320DC05A2E88308188\n");
-}
-
 static bool local_counter(uint32_t *sign_cnt, void *user_data)
 {
 	static uint32_t cnt = 0;
@@ -1226,141 +1051,74 @@ static bool local_counter(uint32_t *sign_cnt, void *user_data)
 	return true;
 }
 
-static void cmd_set_sign_key(struct client *cli, char *cmd_str)
+static void cmd_set_sign_key(int argc, char **argv)
 {
-	char *argv[3];
-	int argc = 0;
 	uint8_t key[16];
 
 	memset(key, 0, 16);
 
-	if (!parse_args(cmd_str, 2, argv, &argc)) {
-		set_sign_key_usage();
-		return;
-	}
-
-	if (argc != 2) {
-		set_sign_key_usage();
-		return;
-	}
-
-	if (!strcmp(argv[0], "-c") || !strcmp(argv[0], "--sign-key")) {
-		if (convert_sign_key(argv[1], key))
+	if (!strcmp(argv[1], "-c") || !strcmp(argv[1], "--sign-key")) {
+		if (convert_sign_key(argv[2], key))
 			bt_att_set_local_key(cli->att, key, local_counter, cli);
-	} else
-		set_sign_key_usage();
-}
-
-static void cmd_help(struct client *cli, char *cmd_str);
-
-typedef void (*command_func_t)(struct client *cli, char *cmd_str);
-
-static struct {
-	char *cmd;
-	command_func_t func;
-	char *doc;
-} command[] = {
-	{ "help", cmd_help, "\tDisplay help message" },
-	{ "services", cmd_services, "\tShow discovered services" },
-	{ "read-value", cmd_read_value,
-				"\tRead a characteristic or descriptor value" },
-	{ "read-long-value", cmd_read_long_value,
-		"\tRead a long characteristic or desctriptor value" },
-	{ "read-multiple", cmd_read_multiple, "\tRead Multiple" },
-	{ "write-value", cmd_write_value,
-			"\tWrite a characteristic or descriptor value" },
-	{ "write-long-value", cmd_write_long_value,
-			"Write long characteristic or descriptor value" },
-	{ "write-prepare", cmd_write_prepare,
-			"\tWrite prepare characteristic or descriptor value" },
-	{ "write-execute", cmd_write_execute,
-			"\tExecute already prepared write" },
-	{ "register-notify", cmd_register_notify,
-			"\tSubscribe to not/ind from a characteristic" },
-	{ "unregister-notify", cmd_unregister_notify,
-						"Unregister a not/ind session"},
-	{ "set-security", cmd_set_security,
-				"\tSet security level on le connection"},
-	{ "get-security", cmd_get_security,
-				"\tGet security level on le connection"},
-	{ "set-sign-key", cmd_set_sign_key,
-				"\tSet signing key for signed write command"},
-	{ }
-};
-
-static void cmd_help(struct client *cli, char *cmd_str)
-{
-	int i;
-
-	printf("Commands:\n");
-	for (i = 0; command[i].cmd; i++)
-		printf("\t%-15s\t%s\n", command[i].cmd, command[i].doc);
-}
-
-static void prompt_read_cb(int fd, uint32_t events, void *user_data)
-{
-	ssize_t read;
-	size_t len = 0;
-	char *line = NULL;
-	char *cmd = NULL, *args;
-	struct client *cli = user_data;
-	int i;
-
-	if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
-		mainloop_quit();
-		return;
-	}
-
-	read = getline(&line, &len, stdin);
-	if (read < 0) {
-		free(line);
-		return;
-	}
-
-	if (read <= 1) {
-		cmd_help(cli, NULL);
-		print_prompt();
-		free(line);
-		return;
-	}
-
-	line[read-1] = '\0';
-	args = line;
-
-	while ((cmd = strsep(&args, " \t")))
-		if (*cmd != '\0')
-			break;
-
-	if (!cmd)
-		goto failed;
-
-	for (i = 0; command[i].cmd; i++) {
-		if (strcmp(command[i].cmd, cmd) == 0)
-			break;
+	} else {
+		bt_shell_usage();
+		optind = 0;
 	}
-
-	if (command[i].cmd)
-		command[i].func(cli, args);
-	else
-		fprintf(stderr, "Unknown command: %s\n", line);
-
-failed:
-	print_prompt();
-
-	free(line);
 }
 
-static void signal_cb(int signum, void *user_data)
-{
-	switch (signum) {
-	case SIGINT:
-	case SIGTERM:
-		mainloop_quit();
-		break;
-	default:
-		break;
-	}
-}
+static const struct bt_shell_menu main_menu = {
+	.name = "main",
+	.entries = {
+	{ "services", "[options...]", cmd_services,
+		"Show discovered services\n"
+		"Options:\n"
+			"\t -u, --uuid <uuid>\tService UUID\n"
+			"\t -a, --handle <handle>\tService start handle\n"
+		"e.g.:\n"
+			"\tservices\n\tservices -u 0x180d\n\tservices -a 0x0009"
+	},
+	{ "read-value", "<value_handle>",
+		cmd_read_value, "Read a characteristic or descriptor value" },
+	{ "read-long-value", "<value_handle> <offset>",
+		cmd_read_long_value, "Read a long characteristic or desctriptor value" },
+	{ "read-multiple", "<handles...>",
+		cmd_read_multiple, "Read Multiple" },
+	{ "write-value", " [-w|-s] <value_handle> <value...>",
+		cmd_write_value, "Write a characteristic or descriptor value\n"
+		"Options:\n"
+			"\t-w, --without-response\tWrite without response\n"
+			"\t-s, --signed-write\tSigned write command\n"
+		"e.g.:\n"
+			"\twrite-value 0x0001 00 01 00"
+	},
+	{ "write-long-value", "[-r] <value_handle> <offset>",
+		cmd_write_long_value, "Write long characteristic or descriptor value\n"
+		"Options:\n"
+			"\t-r, --reliable-write\tReliable write\n"
+		"e.g.:\n"
+			"\twrite-long-value 0x0001 0 00 01 00"
+	},
+	{ "write-prepare", " [options...] <value_handle> <value>",
+		cmd_write_prepare, "Write prepare characteristic or descriptor value\n"
+		"Options:\n"
+			"\t-s, --session-id\tSession id\n"
+		"e.g.:\n"
+			"\twrite-prepare -s 1 0x0001 00 01 00"
+	},
+	{ "write-execute", " <session_id> <execute>",
+		cmd_write_execute, "Execute already prepared write" },
+	{ "register-notify", "<chrc_value_handle>",
+		cmd_register_notify, "Subscribe to not/ind from a characteristic" },
+	{ "unregister-notify", "<notify_id>",
+		cmd_unregister_notify, "Unregister a not/ind session"},
+	{ "set-security", "<level 1-3>",
+		cmd_set_security, "Set security level on connection"},
+	{ "get-security", NULL,
+		cmd_get_security, "Get security level on connection"},
+	{ "set-sign-key", "<csrk>",
+		cmd_set_sign_key, "Set signing key for signed write command"},
+	{} },
+};
 
 static int l2cap_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type,
 									int sec)
@@ -1375,15 +1133,15 @@ static int l2cap_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type,
 		ba2str(src, srcaddr_str);
 		ba2str(dst, dstaddr_str);
 
-		printf("btgatt-client: Opening L2CAP %s connection on ATT "
-					"channel:\n\t src: %s\n\tdest: %s\n",
+		print("btgatt-client: Opening L2CAP %s connection on ATT "
+					"channel:\n\t src: %s\n\tdest: %s",
 					(dst_type == BDADDR_BREDR ? "BR/EDR" : "LE"),
 					srcaddr_str, dstaddr_str);
 	}
 
 	sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
 	if (sock < 0) {
-		perror("Failed to create L2CAP socket");
+		error("Failed to create L2CAP socket");
 		return -1;
 	}
 
@@ -1398,7 +1156,7 @@ static int l2cap_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type,
 	bacpy(&srcaddr.l2_bdaddr, src);
 
 	if (bind(sock, (struct sockaddr *)&srcaddr, sizeof(srcaddr)) < 0) {
-		perror("Failed to bind L2CAP socket");
+		error("Failed to bind L2CAP socket");
 		close(sock);
 		return -1;
 	}
@@ -1408,7 +1166,7 @@ static int l2cap_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type,
 	btsec.level = sec;
 	if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &btsec,
 							sizeof(btsec)) != 0) {
-		fprintf(stderr, "Failed to set L2CAP security level\n");
+		error("Failed to set L2CAP security level");
 		close(sock);
 		return -1;
 	}
@@ -1423,164 +1181,144 @@ static int l2cap_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type,
 	dstaddr.l2_bdaddr_type = dst_type;
 	bacpy(&dstaddr.l2_bdaddr, dst);
 
-	printf("Connecting to device...");
+	print("Connecting to device...");
 	fflush(stdout);
 
 	if (connect(sock, (struct sockaddr *) &dstaddr, sizeof(dstaddr)) < 0) {
-		perror(" Failed to connect");
+		error("Failed to connect");
 		close(sock);
 		return -1;
 	}
 
-	printf(" Done\n");
+	print("Done");
 
 	return sock;
 }
 
-static void usage(void)
-{
-	printf("btgatt-client\n");
-	printf("Usage:\n\tbtgatt-client [options]\n");
-
-	printf("Options:\n"
-		"\t-i, --index <id>\t\tSpecify adapter index, e.g. hci0\n"
-		"\t-d, --dest <addr>\t\tSpecify the destination address\n"
-		"\t-t, --type [random|public|bredr] \tSpecify the address type\n"
-		"\t-m, --mtu <mtu> \t\tThe ATT MTU to use\n"
-		"\t-s, --security-level <sec> \tSet security level (low|medium|"
-								"high|fips)\n"
-		"\t-v, --verbose\t\t\tEnable extra logging\n"
-		"\t-h, --help\t\t\tDisplay help\n");
-}
-
 static struct option main_options[] = {
-	{ "index",		1, 0, 'i' },
-	{ "dest",		1, 0, 'd' },
-	{ "type",		1, 0, 't' },
-	{ "mtu",		1, 0, 'm' },
-	{ "security-level",	1, 0, 's' },
-	{ "verbose",		0, 0, 'v' },
-	{ "help",		0, 0, 'h' },
+	{ "index",     required_argument, NULL, 'i' },
+	{ "dst-addr",  required_argument, NULL, 'd' },
+	{ "type",      required_argument, NULL, 'T' },
+	{ "mtu",       required_argument, NULL, 'M' },
+	{ "sec-level", required_argument, NULL, 's' },
+	{ "verbose",   no_argument,       NULL, 'V' },
 	{ }
 };
 
+static const char *index_option;
+static const char *dst_addr_option;
+static const char *type_option;
+static const char *mtu_option;
+static const char *security_level_option;
+static const char *verbose_option;
+
+static const char **optargs[] = {
+	&index_option,
+	&dst_addr_option,
+	&type_option,
+	&mtu_option,
+	&security_level_option,
+	&verbose_option,
+};
+
+static const char *help[] = {
+	"Specify adapter index, e.g. hci0",
+	"Specify the destination address",
+	"Specify the address type (random|public|bredr)",
+	"The ATT MTU to use",
+	"Set security level (low|medium|high|fips)",
+	"Enable extra logging"
+};
+
+static const struct bt_shell_opt opt = {
+	.options = main_options,
+	.optno = sizeof(main_options) / sizeof(struct option),
+	.optstr = "i:d:T:M:s:V",
+	.optarg = optargs,
+	.help = help,
+};
+
 int main(int argc, char *argv[])
 {
-	int opt;
 	int sec = BT_SECURITY_LOW;
 	uint16_t mtu = 0;
 	uint8_t dst_type = BDADDR_LE_PUBLIC;
-	bool dst_addr_given = false;
 	bdaddr_t src_addr, dst_addr;
 	int dev_id = -1;
 	int fd;
-	struct client *cli;
-
-	while ((opt = getopt_long(argc, argv, "+hvs:m:t:d:i:",
-						main_options, NULL)) != -1) {
-		switch (opt) {
-		case 'h':
-			usage();
-			return EXIT_SUCCESS;
-		case 'v':
-			verbose = true;
-			break;
-		case 's':
-			if (strcmp(optarg, "low") == 0)
-				sec = BT_SECURITY_LOW;
-			else if (strcmp(optarg, "medium") == 0)
-				sec = BT_SECURITY_MEDIUM;
-			else if (strcmp(optarg, "high") == 0)
-				sec = BT_SECURITY_HIGH;
-			else if (strcmp(optarg, "fips") == 0)
-				sec = BT_SECURITY_FIPS;
-			else {
-				fprintf(stderr, "Invalid security level\n");
-				return EXIT_FAILURE;
-			}
-			break;
-		case 'm': {
-			int arg;
-
-			arg = atoi(optarg);
-			if (arg <= 0) {
-				fprintf(stderr, "Invalid MTU: %d\n", arg);
-				return EXIT_FAILURE;
-			}
-
-			if (arg > UINT16_MAX) {
-				fprintf(stderr, "MTU too large: %d\n", arg);
-				return EXIT_FAILURE;
-			}
-
-			mtu = (uint16_t)arg;
-			break;
+	int status;
+
+	bt_shell_init(argc, argv, &opt);
+	bt_shell_set_menu(&main_menu);
+
+	if (verbose_option)
+		verbose = true;
+	if (security_level_option) {
+		if (strcmp(security_level_option, "low") == 0)
+			sec = BT_SECURITY_LOW;
+		else if (strcmp(security_level_option, "medium") == 0)
+			sec = BT_SECURITY_MEDIUM;
+		else if (strcmp(security_level_option, "high") == 0)
+			sec = BT_SECURITY_HIGH;
+		else if (strcmp(security_level_option, "fips") == 0)
+			sec = BT_SECURITY_FIPS;
+		else {
+			error("Invalid security level");
+			return EXIT_FAILURE;
 		}
-		case 't':
-			if (strcmp(optarg, "random") == 0)
-				dst_type = BDADDR_LE_RANDOM;
-			else if (strcmp(optarg, "public") == 0)
-				dst_type = BDADDR_LE_PUBLIC;
-			else if (strcmp(optarg, "bredr") == 0)
-				dst_type = BDADDR_BREDR;
-			else {
-				fprintf(stderr,
-					"Allowed types: random, public, bredr\n");
-				return EXIT_FAILURE;
-			}
-			break;
-		case 'd':
-			if (str2ba(optarg, &dst_addr) < 0) {
-				fprintf(stderr, "Invalid remote address: %s\n",
-									optarg);
-				return EXIT_FAILURE;
-			}
+	}
+	if (mtu_option) {
+		int arg;
 
-			dst_addr_given = true;
-			break;
+		arg = atoi(mtu_option);
+		if (arg <= 0) {
+			error("Invalid MTU: %d", arg);
+			return EXIT_FAILURE;
+		}
 
-		case 'i':
-			dev_id = hci_devid(optarg);
-			if (dev_id < 0) {
-				perror("Invalid adapter");
-				return EXIT_FAILURE;
-			}
+		if (arg > UINT16_MAX) {
+			error("MTU too large: %d", arg);
+			return EXIT_FAILURE;
+		}
 
-			break;
-		default:
-			fprintf(stderr, "Invalid option: %c\n", opt);
+		mtu = (uint16_t)arg;
+	}
+	if (type_option) {
+		if (strcmp(type_option, "random") == 0)
+			dst_type = BDADDR_LE_RANDOM;
+		else if (strcmp(type_option, "public") == 0)
+			dst_type = BDADDR_LE_PUBLIC;
+		else if (strcmp(type_option, "bredr") == 0)
+			dst_type = BDADDR_BREDR;
+		else {
+			error("Allowed types: random, public, bredr");
 			return EXIT_FAILURE;
 		}
 	}
-
-	if (!argc) {
-		usage();
-		return EXIT_SUCCESS;
+	if (dst_addr_option) {
+		if (str2ba(dst_addr_option, &dst_addr) < 0) {
+			error("Invalid remote address: %s", dst_addr_option);
+			return EXIT_FAILURE;
+		}
+	} else {
+		error("Destination address required!");
+		return EXIT_FAILURE;
 	}
-
-	argc -= optind;
-	argv += optind;
-	optind = 0;
-
-	if (argc) {
-		usage();
-		return EXIT_SUCCESS;
+	if (index_option) {
+		dev_id = hci_devid(index_option);
+		if (dev_id < 0) {
+			error("Invalid adapter");
+			return EXIT_FAILURE;
+		}
 	}
 
 	if (dev_id == -1)
 		bacpy(&src_addr, BDADDR_ANY);
 	else if (hci_devba(dev_id, &src_addr) < 0) {
-		perror("Adapter not available");
+		error("Adapter not available");
 		return EXIT_FAILURE;
 	}
 
-	if (!dst_addr_given) {
-		fprintf(stderr, "Destination address required!\n");
-		return EXIT_FAILURE;
-	}
-
-	mainloop_init();
-
 	fd = l2cap_att_connect(&src_addr, &dst_addr, dst_type, sec);
 	if (fd < 0)
 		return EXIT_FAILURE;
@@ -1591,20 +1329,15 @@ int main(int argc, char *argv[])
 		return EXIT_FAILURE;
 	}
 
-	if (mainloop_add_fd(fileno(stdin),
-				EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR,
-				prompt_read_cb, cli, NULL) < 0) {
-		fprintf(stderr, "Failed to initialize console\n");
-		return EXIT_FAILURE;
-	}
-
-	print_prompt();
-
-	mainloop_run_with_signal(signal_cb, NULL);
+	bt_shell_attach(fileno(stdin));
+	update_prompt();
+	shell_running = true;
+	status = bt_shell_run();
+	shell_running = false;
 
-	printf("\n\nShutting down...\n");
+	print("Shutting down...");
 
 	client_destroy(cli);
 
-	return EXIT_SUCCESS;
+	return status;
 }
