diff --git a/include/maestro/core.h b/include/maestro/core.h
index c22cad6a9e83eb98e79eb738ba9533a81f9f85c8..4954c18343e471514f1471f8cf1de7bbc5ad4453 100644
--- a/include/maestro/core.h
+++ b/include/maestro/core.h
@@ -111,6 +111,7 @@ extern "C" {
 #include "maestro/pool_manager.h"
 #include "maestro/attributes.h"
 #include "maestro/groups.h"
+#include "maestro/env.h"
   
 
 #ifdef __cplusplus
diff --git a/include/maestro/i_ketopt.h b/include/maestro/i_ketopt.h
new file mode 100644
index 0000000000000000000000000000000000000000..166938b8311778941a0424916f59eed77a721f14
--- /dev/null
+++ b/include/maestro/i_ketopt.h
@@ -0,0 +1,146 @@
+/* taken from https://github.com/attractivechaos/klib/blob/master/ketopt.h */
+
+/* This file is originally part of klib, which is licensed under the
+ * X11/MIT licnense: */
+
+/* The MIT License
+   Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
+   Permission is hereby granted, free of charge, to any person obtaining
+   a copy of this software and associated documentation files (the
+   "Software"), to deal in the Software without restriction, including
+   without limitation the rights to use, copy, modify, merge, publish,
+   distribute, sublicense, and/or sell copies of the Software, and to
+   permit persons to whom the Software is furnished to do so, subject to
+   the following conditions:
+   The above copyright notice and this permission notice shall be
+   included in all copies or substantial portions of the Software.
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+   SOFTWARE.
+*/
+   
+#ifndef I_KETOPT_H
+#define I_KETOPT_H
+
+#include <string.h> /* for strchr() and strncmp() */
+
+#define ko_no_argument       0
+#define ko_required_argument 1
+#define ko_optional_argument 2
+
+typedef struct {
+	int ind;   /* equivalent to optind */
+	int opt;   /* equivalent to optopt */
+	char *arg; /* equivalent to optarg */
+	int longidx; /* index of a long option; or -1 if short */
+	/* private variables not intended for external uses */
+	int i, pos, n_args;
+} ketopt_t;
+
+typedef struct {
+	char *name;
+	int has_arg;
+	int val;
+} ko_longopt_t;
+
+static ketopt_t KETOPT_INIT = { 1, 0, 0, -1, 1, 0, 0 };
+
+static void ketopt_permute(char *argv[], int j, int n) /* move argv[j] over n elements to the left */
+{
+	int k;
+	char *p = argv[j];
+	for (k = 0; k < n; ++k)
+		argv[j - k] = argv[j - k - 1];
+	argv[j - k] = p;
+}
+
+/**
+ * Parse command-line options and arguments
+ *
+ * This fuction has a similar interface to GNU's getopt_long(). Each call
+ * parses one option and returns the option name.  s->arg points to the option
+ * argument if present. The function returns -1 when all command-line arguments
+ * are parsed. In this case, s->ind is the index of the first non-option
+ * argument.
+ *
+ * @param s         status; shall be initialized to KETOPT_INIT on the first call
+ * @param argc      length of argv[]
+ * @param argv      list of command-line arguments; argv[0] is ignored
+ * @param permute   non-zero to move options ahead of non-option arguments
+ * @param ostr      option string
+ * @param longopts  long options
+ *
+ * @return ASCII for a short option; ko_longopt_t::val for a long option; -1 if
+ *         argv[] is fully processed; '?' for an unknown option or an ambiguous
+ *         long option; ':' if an option argument is missing
+ */
+static int ketopt(ketopt_t *s, int argc, char *argv[], int permute, const char *ostr, const ko_longopt_t *longopts)
+{
+	int opt = -1, i0, j;
+	if (permute) {
+		while (s->i < argc && (argv[s->i][0] != '-' || argv[s->i][1] == '\0'))
+			++s->i, ++s->n_args;
+	}
+	s->arg = 0, s->longidx = -1, i0 = s->i;
+	if (s->i >= argc || argv[s->i][0] != '-' || argv[s->i][1] == '\0') {
+		s->ind = s->i - s->n_args;
+		return -1;
+	}
+	if (argv[s->i][0] == '-' && argv[s->i][1] == '-') { /* "--" or a long option */
+		if (argv[s->i][2] == '\0') { /* a bare "--" */
+			ketopt_permute(argv, s->i, s->n_args);
+			++s->i, s->ind = s->i - s->n_args;
+			return -1;
+		}
+		s->opt = 0, opt = '?', s->pos = -1;
+		if (longopts) { /* parse long options */
+			int k, n_exact = 0, n_partial = 0;
+			const ko_longopt_t *o = 0, *o_exact = 0, *o_partial = 0;
+			for (j = 2; argv[s->i][j] != '\0' && argv[s->i][j] != '='; ++j) {} /* find the end of the option name */
+			for (k = 0; longopts[k].name != 0; ++k)
+				if (strncmp(&argv[s->i][2], longopts[k].name, j - 2) == 0) {
+					if (longopts[k].name[j - 2] == 0) ++n_exact, o_exact = &longopts[k];
+					else ++n_partial, o_partial = &longopts[k];
+				}
+			if (n_exact > 1 || (n_exact == 0 && n_partial > 1)) return '?';
+			o = n_exact == 1? o_exact : n_partial == 1? o_partial : 0;
+			if (o) {
+				s->opt = opt = o->val, s->longidx = o - longopts;
+				if (argv[s->i][j] == '=') s->arg = &argv[s->i][j + 1];
+				if (o->has_arg == 1 && argv[s->i][j] == '\0') {
+					if (s->i < argc - 1) s->arg = argv[++s->i];
+					else opt = ':'; /* missing option argument */
+				}
+			}
+		}
+	} else { /* a short option */
+		const char *p;
+		if (s->pos == 0) s->pos = 1;
+		opt = s->opt = argv[s->i][s->pos++];
+		p = strchr((char*)ostr, opt);
+		if (p == 0) {
+			opt = '?'; /* unknown option */
+		} else if (p[1] == ':') {
+			if (argv[s->i][s->pos] == 0) {
+				if (s->i < argc - 1) s->arg = argv[++s->i];
+				else opt = ':'; /* missing option argument */
+			} else s->arg = &argv[s->i][s->pos];
+			s->pos = -1;
+		}
+	}
+	if (s->pos < 0 || argv[s->i][s->pos] == 0) {
+		++s->i, s->pos = 0;
+		if (s->n_args > 0) /* permute */
+			for (j = i0; j < s->i; ++j)
+				ketopt_permute(argv, j, s->n_args);
+	}
+	s->ind = s->i - s->n_args;
+	return opt;
+}
+
+#endif
diff --git a/maestro/ofi.c b/maestro/ofi.c
index 8d77f63c5db7c703291904e6a2cebb3dc675fc9a..f842cab72f4844b1b8672355e3886bba1052dc88 100644
--- a/maestro/ofi.c
+++ b/maestro/ofi.c
@@ -2874,8 +2874,10 @@ mstro_pm_attach(const char *remote_pm_info)
   mstro_status s=mstro_ep_desc_deserialize(&pm_epd, remote_pm_info);
   
   if(s!=MSTRO_OK) {
-    ERR("Failed to parse pool manager info: %d (%s)\n",
-        s, mstro_status_description(s));
+    ERR("Failed to parse pool manager info: %d (%s%s)\n",
+        s, mstro_status_description(s),
+        /* parser errrors also if a NULL result */
+        s==MSTRO_NOMEM ? " or invalid pool-manager info data" : "");
     goto BAILOUT;
   }
 
diff --git a/tests/simple_telemetry_listener.c b/tests/simple_telemetry_listener.c
index 795e48691a81b8bf5a13723bf7175a28b06a0708..fc30d5656111f285ac3f952b90463be0f6e266f8 100644
--- a/tests/simple_telemetry_listener.c
+++ b/tests/simple_telemetry_listener.c
@@ -36,21 +36,181 @@
 
 #include "maestro.h"
 #include "maestro/i_statistics.h"
+#include "maestro/i_ketopt.h"
 
 #include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
 
-const mstro_nanosec_t  MAX_WAIT = ((mstro_nanosec_t)15)*1000*1000*1000; /* 10s */
-int
-main(int argc, char ** argv)
+#define NSEC_PER_SEC ((mstro_nanosec_t)1000*1000*1000)
+const mstro_nanosec_t  MAX_WAIT = ((mstro_nanosec_t)15)*NSEC_PER_SEC; /* 15s */
+
+/** Configurable settings */
+char *g_conf_workflow_name = NULL;
+char *g_conf_component_name = NULL;
+char *g_conf_terminate_after = NULL;
+mstro_nanosec_t g_conf_max_wait = MAX_WAIT;
+char *g_conf_pminfo = NULL;
+#define LOGDST_STDOUT 1
+#define LOGDST_STDERR 2
+#define LOGDST_MAESTRO 3
+#define LOGDST_SYSLOG 4
+int g_conf_logdst = LOGDST_STDOUT;
+bool g_verbose = true;
+
+
+/** usage */
+
+void
+print_usage(const char *argv0)
 {
-  /* FIXME: parse arguments */
-  char *component_name = argv[0];
+  fprintf(stderr, "Usage: %s [OPTIONS]\n", argv0);
+  fprintf(stderr, " Initialization OPTIONS:\n");
+  fprintf(stderr, " (environment variables that are used as default in {curly brackets})\n");
+  fprintf(stderr,
+          " --workflow NAME, -w NAME         Maestro Workflow name to attach to {MSTRO_WORKFLOW_NAME}\n"
+          " --component NAME, -c NAME        Maestro component name for this listener {MSTRO_COMPONENT_NAME}\n"
+          " --terminate-after NAME, -q NAME  Terminate after observing LEAVE of component NAME\n"
+          " --max-idle SECONDS, -i SECONDS   Terminate after no events for SECONDS {%g}\n"
+          " --destination DEST, -d DEST      Logging destination: 'syslog', 'stdout', 'stderr', or 'mstro' {stdout}\n"
+          " --pm-info PMINFO, -p PMINFO      Pool Manager OOB info {MSTRO_POOL_MANAGER_INFO}\n"
+          " --help, -h                       This help\n"
+          " --verbose, -v                    Verbose output\n"
+          " --version, -V                    Version information\n"
+          ,((double)MAX_WAIT)/NSEC_PER_SEC);
+}
+/** argument parsing and setting configuration */
+mstro_status
+parse_arguments(int argc, char **argv)
+{
+  /* default */
+  g_conf_component_name = argv[0];
+
+  static ko_longopt_t longopts[] = {
+    { "workflow",        ko_required_argument,       'w' },
+    { "component",       ko_required_argument,       'c' },
+    { "terminate-after", ko_required_argument,       'q' },
+    { "max-idle",        ko_required_argument,       'i' },
+    { "destination",     ko_required_argument,       'd' },
+    { "pm-info",         ko_required_argument,       'p' },
+    { "pm_info",         ko_required_argument,       'p' },
+    { "help",            ko_no_argument,             'h' },
+    { "verbose",         ko_no_argument,             'v' },
+    { "version",         ko_no_argument,             'V' },
+    { NULL, 0, 0 }
+  };
+  ketopt_t opt = KETOPT_INIT;
+  int c;
   
-  mstro_status s=mstro_init(NULL, component_name, 0);
+  while ((c = ketopt(&opt, argc, argv, 1, "w:c:q:i:d:hv", longopts)) >= 0) {
+    switch(c) {
+      case 'w':
+        if(!opt.arg) {
+          fprintf(stderr, "--workflow option is missing its argument\n");
+          print_usage(argv[0]);
+          exit(EXIT_FAILURE);
+        }
+        g_conf_workflow_name = opt.arg;
+        break;
+      case 'c':
+        if(!opt.arg) {
+          fprintf(stderr, "--component option is missing its argument\n");
+          print_usage(argv[0]);
+          exit(EXIT_FAILURE);
+        }
+        g_conf_component_name = opt.arg;
+        break;
+      case 'q':
+        if(!opt.arg) {
+          fprintf(stderr, "--terminate-after option is missing its argument\n");
+          print_usage(argv[0]);
+          exit(EXIT_FAILURE);
+        }
+        g_conf_terminate_after = opt.arg;
+        break;
+      case 'i':
+        if(!opt.arg) {
+          fprintf(stderr, "--max-idle option is missing its argument\n");
+          print_usage(argv[0]);
+          exit(EXIT_FAILURE);
+        }
+        g_conf_max_wait = atoi(opt.arg)* NSEC_PER_SEC;
+        break;
+      case 'd': {
+        const char *dst = opt.arg;
+        if(strcasecmp(dst,"sy")==0) {
+          g_conf_logdst=LOGDST_SYSLOG;
+        } else if(strcasecmp(dst,"stdo")==0) {
+          g_conf_logdst=LOGDST_STDOUT;
+        } else if(strcasecmp(dst,"stde")==0) {
+          g_conf_logdst=LOGDST_STDERR;
+        } else if(strcasecmp(dst,"m")==0) {
+          g_conf_logdst=LOGDST_MAESTRO;
+        } else {
+          fprintf(stderr, "Illegal output destination (or none) specified: %s\n", dst);
+          print_usage(argv[0]);
+          return MSTRO_FAIL;
+        }
+        break;
+      }
+      case 'p': {
+        if(!opt.arg) {
+          fprintf(stderr, "--pm-info option is missing its argument\n");
+          print_usage(argv[0]);
+          exit(EXIT_FAILURE);
+        }
+        size_t len = strlen(MSTRO_ENV_POOL_INFO) + 1 + strlen(opt.arg) + 1;
+        g_conf_pminfo = malloc(len);
+        if(g_conf_pminfo==NULL) {
+          fprintf(stderr, "pm-info argument too large\n");
+          exit(EXIT_FAILURE);
+        }
+        strcpy(g_conf_pminfo, MSTRO_ENV_POOL_INFO);
+        strcat(g_conf_pminfo, "=");
+        strcat(g_conf_pminfo, opt.arg);
+        if(putenv(g_conf_pminfo)!=0) {
+          fprintf(stderr, "Failed to set PM-INFO: %d (%s)\n", errno, strerror(errno));
+          exit(EXIT_FAILURE);
+        }
+        break;
+      }
+      case 'v':
+        g_verbose = true;
+        break;
+      case 'h':
+        print_usage(argv[0]);
+        exit(EXIT_SUCCESS);
+      case 'V':
+        fprintf(stdout, "%s, part of %s\n", argv[0], mstro_version());
+        exit(EXIT_SUCCESS);
+        break;
+      default:
+        fprintf(stderr, "Unrecognized option: %c\n", optopt? optopt : ' ');
+        print_usage(argv[0]);
+        return MSTRO_FAIL;
+    }
+  }
+  if(opt.ind<argc) {
+    fprintf(stderr, "Unexpected non-option arguments: %s (...)\n", argv[opt.ind]);
+    print_usage(argv[0]);
+    return MSTRO_FAIL;
+  }
 
+  if(g_verbose) {
+    fprintf(stderr, "Configuration: %s/%s/%s/%llu/%d\n",
+            g_conf_workflow_name, g_conf_component_name,
+            g_conf_terminate_after, g_conf_max_wait, g_conf_logdst);
+  }
+  return MSTRO_OK;
+}
+
+/** main loop: handle events and log them */
+mstro_status
+event_loop(void)
+{
   /* wildcard selector: any CDO */
   mstro_cdo_selector selector=NULL;
-  s=mstro_cdo_selector_create(
+  mstro_status s=mstro_cdo_selector_create(
       NULL, NULL,
       "(has .maestro.core.cdo.name)",
       &selector);
@@ -156,10 +316,53 @@ main(int argc, char ** argv)
   }
   s= mstro_subscription_dispose(cdo_subscription);
   s= mstro_subscription_dispose(join_leave_subscription);
-  
 
 BAILOUT:
-  ;
+  return s;
+}
+
+/** main entrypoint */
+char *g_default_loglevel=NULL;
+
+int
+main(int argc, char ** argv)
+{
+
+  mstro_status s = parse_arguments(argc, argv);
+  if(s!=MSTRO_OK) {
+    fprintf(stderr, "Failed to parse arguments\n");
+    exit(EXIT_FAILURE);
+  }
+
+  /** ensure pool info is set (by user or args) */
+  if(getenv(MSTRO_ENV_POOL_INFO)==NULL ) {
+    fprintf(stderr, "No pool manager info provided, cannot attach to anything\n");
+    exit(EXIT_FAILURE);
+  }
+
+  /** try to reduce logging, unless user set something */
+  if(getenv(MSTRO_ENV_LOG_LEVEL)==NULL) {
+    int l = strlen(MSTRO_ENV_LOG_LEVEL) + 1 + strlen("err") + 1;
+    g_default_loglevel=malloc(l);
+    if(g_default_loglevel!=NULL) {
+      strcpy(g_default_loglevel, MSTRO_ENV_LOG_LEVEL);
+      strcat(g_default_loglevel, "=err");
+      putenv(g_default_loglevel);
+    } else {
+      ;/* well, then try without reduced logging */
+    }
+  }
+
+  /** start */
+  s=mstro_init(g_conf_workflow_name, g_conf_component_name, 0);
+  if(s!=MSTRO_OK) {
+    fprintf(stderr, "Failed to initialize Maestro: %d (%s)\n",
+            s, mstro_status_description(s));
+    exit(EXIT_FAILURE);
+  }
+
+  s = event_loop();
+
   mstro_status s1=mstro_finalize();
 
   if(s1!=MSTRO_OK || s!=MSTRO_OK)