diff --git a/rocolib/__main__.py b/rocolib/__main__.py
index 32c397df2f6e1e1c86ab2d47226055a938769d74..fc980a6f6d0ecd89ee182bfcf05298ad8d551d08 100644
--- a/rocolib/__main__.py
+++ b/rocolib/__main__.py
@@ -4,6 +4,7 @@ import yaml
 import sys
 import logging
 import argparse
+from os.path import basename
 from pprint import pprint
 from textwrap import shorten
 from dash import Dash
@@ -11,7 +12,7 @@ from rocolib.library import getComponent, getComponentTree
 from rocolib.api.composables.graph.Joint import FingerJoint
 
 
-def test(component, params, thickness, outdir=None, display=False):
+def test(component, params, thickness, outdir=None, display=False, outputs=None):
     f = getComponent(component)
     if params is not None:
         for p in params:
@@ -32,11 +33,16 @@ def test(component, params, thickness, outdir=None, display=False):
 
     if outdir:
         filedir = f"{outdir}/{component}"
-        ret = f.makeOutput(outputs = True, filedir=filedir, thickness=t, joint=j, remake=False)
+        outputs = outputs or True
+    else:
+        filedir = None
+        outputs = outputs or ()
+    ret = f.makeOutput(outputs = outputs, filedir=filedir, thickness=t, joint=j, remake=False)
 
     for composable, outs in ret.items():
-        print(f"Composable: {composable}")
-        pprint({k: shorten(str(v), 60) for k, v in outs.items()})
+        if outs:
+            print(f"Composable: {composable}")
+            pprint({k: shorten(str(v), 60) for k, v in outs.items()})
 
     if display:
         app = Dash(__name__)
@@ -48,6 +54,8 @@ def cli_argparse():
     LOG_LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
     DEFAULT_LOG_LEVEL = "WARNING"
 
+    rocoview = (basename(sys.argv[0]) == "rocoview")
+
     parser = argparse.ArgumentParser()
 
     parser.add_argument("component", nargs='?',
@@ -66,7 +74,11 @@ def cli_argparse():
     parser.add_argument("-P", help="List component parameters (use multiple times for more detail)",
                         dest="param_list", action="append_const", const=1)
     parser.add_argument("-t", type=float, help="Thickness (i.e. making with wood)")
-    parser.add_argument("-d", action='store_true', help="Display visualizations")
+    if not rocoview:
+        parser.add_argument("-d", action='store_true', help="Display visualizations")
+    parser.add_argument("-s", type=str, action='append', help="Select specific outputs to save")
+    parser.add_argument("-S", help="List composable outputs (use multiple times for more detail)",
+                        dest="output_list", action="append_const", const=1)
     parser.add_argument("-p", nargs=2, action='append',
         metavar=("NAME", "VALUE"),
         help="Set component parameter NAME to value VALUE")
@@ -76,6 +88,8 @@ def cli_argparse():
         sys.exit(1)
 
     args = parser.parse_args()
+    if rocoview:
+        args.d = True
 
     log_level = LOG_LEVELS.index(DEFAULT_LOG_LEVEL)
     # For each "-q" and "-v" flag, adjust the logging verbosity accordingly
@@ -108,34 +122,41 @@ def cli_argparse():
         else:
             outdir = args.output
     else:
-        outdir = "output"
+        outdir = not rocoview and "output" or None
 
     if args.component:
-        if args.interface_list:
+        def printList(arg, lsfn, make=False):
+            if not arg:
+                return
             f = getComponent(args.component)
-            info = f.getInterfaceInfo(len(args.interface_list))
-            if len(args.interface_list) == 1:
-                print("\n".join(info))
-            else:
-                pprint(info)
-
+            if make:
+                f.make()
+            ls = lsfn(f)
+            ind = min(len(arg), len(ls)) - 1
+            pprint(ls[ind])
             if not args.p or args.t or args.d:
                 exit(0)
-        if args.param_list:
-            f = getComponent(args.component)
-            info = f.getParameterInfo()
-            if len(args.param_list) == 1:
-                print("\n".join(info.keys()))
-            elif len(args.param_list) == 2:
-                pprint({k: v['defaultValue'] for k,v in info.items()})
-            elif len(args.param_list) == 3:
-                pprint({k: {x: v[x] for x in ('defaultValue', 'spec')} for k,v in info.items()})
-            else:
-                pprint(info)
 
-            if not args.p or args.t or args.d:
-                exit(0)
-        test(args.component, args.p, thickness=args.t, outdir=outdir, display=args.d)
+        printList(args.interface_list, lambda c : (lambda info : (
+            list(info.keys()),
+            {k: v["port"] for k, v in info.items()},
+            info,
+        ))(c.getInterfaceInfo()))
+
+        printList(args.param_list, lambda c : (lambda info : (
+            list(info.keys()),
+            {k: v["defaultValue"] for k, v in info.items()},
+            {k: {x: v[x] for x in ('defaultValue', 'spec')} for k,v in info.items()},
+            info,
+        ))(c.getParameterInfo()))
+
+        printList(args.output_list, lambda c : (lambda info : (
+            {composable: list(outputs.keys()) for composable, outputs in info.items()},
+            {composable: {k: {x: v[x] for x in ('stub', 'widget')} for k, v in outputs.items()} for composable, outputs in info.items()},
+            info,
+        ))(c.listOutputs()), make=True)
+
+        test(args.component, args.p, thickness=args.t, outdir=outdir, display=args.d, outputs=args.s)
         acted = True
 
     if not acted:
diff --git a/rocolib/api/components/Component.py b/rocolib/api/components/Component.py
index b94d56c7e8bfc6364b717c53ddbc7f00aa895b74..e2546838d0269377abce29d96b96c95c467292de 100644
--- a/rocolib/api/components/Component.py
+++ b/rocolib/api/components/Component.py
@@ -121,12 +121,6 @@ class Component(Parameterized):
         pass
 
     def getInterfaceInfo(self, detail=1):
-        if detail == 1:
-            return list(self.interfaces.keys())
-
-        if detail == 2:
-            return {x: self.getInterface(x, transient=True).__class__.__name__ for x in self.interfaces}
-
         i =  {x: dict(port = self.getInterface(x, transient=True).__class__.__name__) for x in self.interfaces}
         for k, v in self.interfaces.items():
             if isinstance(v, dict):
@@ -549,6 +543,13 @@ class Component(Parameterized):
         elts.append(html.Div(dcc.Tabs(tabs)))
         return html.Div(elts)
 
+    def listOutputs(self):
+        rets = {}
+        for (name, composable) in self.composables.items():
+            ss = composable.listOutputs(None)
+            rets[name] = ss
+        return rets
+
     def makeOutput(self, outputs=(), filedir=None, widgets=False, **ka):
         if filedir:
             log.info(f"Compiling robot designs to directory {filedir} ...")
@@ -569,7 +570,6 @@ class Component(Parameterized):
             pass
 
         rets = {}
-        
         for (name, composable) in self.composables.items():
             ss = composable.makeOutput(name, outputs, filedir, widgets, **ka)
             rets[name] = ss
diff --git a/rocolib/api/composables/Composable.py b/rocolib/api/composables/Composable.py
index 8b51fec41e414970542ae286d74fb4fb7953dbaf..35d4e501a2f27ad00289aeaeca12f4b8b37faec2 100644
--- a/rocolib/api/composables/Composable.py
+++ b/rocolib/api/composables/Composable.py
@@ -32,7 +32,7 @@ class Composable:
             if hasattr(method, 'output'):
                 out = method.output
                 keyword = out['stub'].split('.')[0]
-                if out['widget'] == widgets:
+                if out['widget'] == widgets or widgets is None:
                     outputs[keyword] = out
         return outputs
 
diff --git a/setup.py b/setup.py
index c17117ffeb993ef68fb85d51ff2441a0b73e8dbb..beedf6f39189b618d91a41884bcb41e46b2cf51f 100644
--- a/setup.py
+++ b/setup.py
@@ -55,6 +55,7 @@ setup(
     entry_points={
         'console_scripts': [
             'roco = rocolib.__main__:cli_argparse',
+            'rocoview = rocolib.__main__:cli_argparse',
         ]
     },
 )