diff --git a/src/Makefile b/src/Makefile
index c891bcaa2fd4dbd2c7360ccab9e58186e0856b23..e307ceba4936801b03053a8f279a96a122c3edfd 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -23,7 +23,7 @@ install-LUA_LIB-y	:= $(INSTALLDIR) $(DESTDIR)$(LUA_LIBDIR) && \
 endif
 
 progs-y			+= apk
-apk-objs		:= apk.o add.o del.o fix.o update.o info.o \
+apk-objs		:= apk.o add.o del.o fix.o update.o info.o list.o \
 			   search.o upgrade.o cache.o ver.o index.o fetch.o \
 			   audit.o verify.o dot.o policy.o stats.o manifest.o
 
diff --git a/src/list.c b/src/list.c
new file mode 100644
index 0000000000000000000000000000000000000000..08c02a130e59a3a3e2bd362355c8c94017e61740
--- /dev/null
+++ b/src/list.c
@@ -0,0 +1,235 @@
+/* list.c - Alpine Package Keeper (APK)
+ *
+ * Copyright (C) 2005-2009 Natanael Copa <n@tanael.org>
+ * Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
+ * Copyright (C) 2018 William Pitcock <nenolod@dereferenced.org>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation. See http://www.gnu.org/ for details.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+#include "apk_defines.h"
+#include "apk_applet.h"
+#include "apk_package.h"
+#include "apk_database.h"
+#include "apk_print.h"
+
+struct list_ctx {
+	unsigned int installed : 1;
+	unsigned int orphaned : 1;
+	unsigned int available : 1;
+	unsigned int upgradable : 1;
+	unsigned int match_origin : 1;
+
+	struct apk_string_array *filters;
+};
+
+static int origin_matches(const struct list_ctx *ctx, const struct apk_package *pkg)
+{
+	char **pmatch;
+
+	if (pkg->origin == NULL)
+		return 0;
+
+	foreach_array_item(pmatch, ctx->filters)
+	{
+		if (apk_blob_compare(APK_BLOB_STR(*pmatch), *pkg->origin) == 0)
+			return 1;
+	}
+
+	return 0;
+}
+
+static int is_orphaned(const struct apk_name *name)
+{
+	struct apk_provider *p;
+	unsigned int repos = 0;
+
+	if (name == NULL)
+		return 0;
+
+	foreach_array_item(p, name->providers)
+		repos |= p->pkg->repos;
+
+	/* repo 1 is always installed-db, so if other bits are set it means the package is available somewhere
+	 * (either cache or in a proper repo)
+	 */
+	return (repos & ~BIT(1)) == 0;
+}
+
+/* returns the currently installed package if there is a newer package that satisfies `name` */
+static const struct apk_package *is_upgradable(struct apk_name *name, const struct apk_package *pkg0)
+{
+	struct apk_provider *p;
+	struct apk_package *ipkg;
+	apk_blob_t *latest = apk_blob_atomize(APK_BLOB_STR(""));
+
+	if (name == NULL)
+		return NULL;
+
+	ipkg = apk_pkg_get_installed(name);
+	if (ipkg == NULL)
+		return NULL;
+
+	if (pkg0 == NULL)
+	{
+		foreach_array_item(p, name->providers)
+		{
+			pkg0 = p->pkg;
+			int r;
+
+			if (pkg0 == ipkg)
+				continue;
+
+			r = apk_version_compare_blob(*pkg0->version, *latest);
+			if (r == APK_VERSION_GREATER)
+				latest = pkg0->version;
+		}
+	}
+	else
+		latest = pkg0->version;
+
+	return apk_version_compare_blob(*ipkg->version, *latest) == APK_VERSION_LESS ? ipkg : NULL;
+}
+
+static void print_package(const struct apk_package *pkg, const struct list_ctx *ctx)
+{
+	printf(PKG_VER_FMT " " BLOB_FMT " {" BLOB_FMT "} (" BLOB_FMT ")",
+		PKG_VER_PRINTF(pkg), BLOB_PRINTF(*pkg->arch), BLOB_PRINTF(*pkg->origin),
+		BLOB_PRINTF(*pkg->license));
+
+	if (pkg->ipkg)
+		printf(" [installed]");
+	else
+	{
+		const struct apk_package *u;
+
+		u = is_upgradable(pkg->name, pkg);
+		if (u != NULL)
+			printf(" [upgradable from: " PKG_VER_FMT "]", PKG_VER_PRINTF(u));
+	}
+
+
+	if (apk_verbosity > 1)
+	{
+		printf("\n  %s\n", pkg->description);
+		if (apk_verbosity > 2)
+			printf("  <%s>\n", pkg->url);
+	}
+
+	printf("\n");
+}
+
+static void print_result(struct apk_database *db, const char *match, struct apk_name *name, void *pctx)
+{
+	struct list_ctx *ctx = pctx;
+	struct apk_provider *p;
+	struct apk_package *pkg;
+
+	if (name == NULL)
+		return;
+
+	foreach_array_item(p, name->providers)
+	{
+		pkg = p->pkg;
+
+		if (ctx->match_origin && !origin_matches(ctx, pkg))
+			continue;
+
+		if (ctx->installed && pkg->ipkg == NULL)
+			continue;
+
+		if (ctx->orphaned && !is_orphaned(name))
+			continue;
+
+		if (ctx->available && pkg->repos == BIT(1))
+			continue;
+
+		if (ctx->upgradable && !is_upgradable(name, pkg))
+			continue;
+
+		print_package(pkg, ctx);
+	}
+}
+
+static int option_parse_applet(void *pctx, struct apk_db_options *dbopts, int optch, const char *optarg)
+{
+	struct list_ctx *ctx = pctx;
+
+	switch (optch)
+	{
+	case 'I':
+		ctx->installed = 1;
+		break;
+	case 'O':
+		ctx->installed = 1;
+		ctx->orphaned = 1;
+		break;
+	case 'u':
+		ctx->available = 1;
+		ctx->orphaned = 0;
+		ctx->installed = 0;
+		ctx->upgradable = 1;
+		break;
+	case 'a':
+		ctx->available = 1;
+		ctx->orphaned = 0;
+		break;
+	case 'o':
+		ctx->match_origin = 1;
+		break;
+	default:
+		return -1;
+	}
+
+	return 0;
+}
+
+static const struct apk_option options_applet[] = {
+	{ 'I', "installed", "List installed packages only" },
+	{ 'O', "orphaned", "List orphaned packages only" },
+	{ 'a', "available", "List available packages only" },
+	{ 'u', "upgradable", "List upgradable packages only" },
+	{ 'o', "origin", "List packages by origin" },
+};
+
+static const struct apk_option_group optgroup_applet = {
+	.name = "List",
+	.options = options_applet,
+	.num_options = ARRAY_SIZE(options_applet),
+	.parse = option_parse_applet,
+};
+
+static int list_main(void *pctx, struct apk_database *db, struct apk_string_array *args)
+{
+	struct list_ctx *ctx = pctx;
+
+	ctx->filters = args;
+
+	if (ctx->match_origin)
+		args = NULL;
+
+	apk_name_foreach_matching(
+		db, args, APK_FOREACH_NULL_MATCHES_ALL | apk_foreach_genid(),
+		print_result, ctx);
+
+	return 0;
+}
+
+static struct apk_applet apk_list = {
+	.name = "list",
+	.help = "List packages by PATTERN and other criteria",
+	.arguments = "PATTERN",
+	.open_flags = APK_OPENF_READ,
+	.command_groups = APK_COMMAND_GROUP_QUERY,
+	.context_size = sizeof(struct list_ctx),
+	.optgroups = { &optgroup_global, &optgroup_applet },
+	.main = list_main,
+};
+
+APK_DEFINE_APPLET(apk_list);