Clean a bit the Job, introduce a Process interface
ThreadIO becomes optional, as the current streams can be obtained using Process.current().in/out/err()

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736052 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
index 1d7051f..4bcdd4f 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
@@ -49,8 +49,9 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.apache.felix.gogo.api.Job;
+import org.apache.felix.gogo.api.Process;
 import org.apache.felix.gogo.runtime.CommandSessionImpl;
-import org.apache.felix.gogo.runtime.Job;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Converter;
 import org.apache.felix.service.command.Function;
@@ -95,8 +96,9 @@
     }
 
     public CharSequence format(CommandSession session, Object arg) {
+        Process process = Process.current();
         CharSequence result = session.format(arg, Converter.INSPECT);
-        System.out.println(result);
+        process.out().println(result);
         return result;
     }
 
@@ -182,10 +184,11 @@
                 "  +x                       unset xtrace option",
                 "If PREFIX given, then only show variable(s) starting with PREFIX"};
 
+        Process process = Process.current();
         Options opt = Options.compile(usage).parse(argv);
 
         if (opt.isSet("help")) {
-            opt.usage(System.err);
+            opt.usage(process.err());
             return;
         }
 
@@ -216,7 +219,7 @@
                 }
 
                 String trunc = value == null || value.length() < 55 ? "" : "...";
-                System.out.println(String.format("%-15.15s %-15s %.45s%s", type, key,
+                process.out().println(String.format("%-15.15s %-15s %.45s%s", type, key,
                         value, trunc));
             }
         }
@@ -235,10 +238,11 @@
                 "  -l --list                return List<String>",
                 "  -? --help                show help"};
 
+        Process process = Process.current();
         Options opt = Options.compile(usage).parse(argv);
 
         if (opt.isSet("help")) {
-            opt.usage(System.err);
+            opt.usage(process.err());
             return null;
         }
 
@@ -259,7 +263,7 @@
         }
 
         StringWriter sw = new StringWriter();
-        BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in));
+        BufferedReader rdr = new BufferedReader(new InputStreamReader(process.in()));
 
         ArrayList<String> list = null;
 
@@ -305,11 +309,12 @@
                 "  -s --scope=NAME          list all commands in named scope",
                 "  -t --types               show full java type names"};
 
+        Process process = Process.current();
         Options opt = Options.compile(usage).parse(argv);
         List<String> args = opt.args();
 
         if (opt.isSet("help")) {
-            opt.usage(System.err);
+            opt.usage(process.err());
             return true;
         }
 
@@ -337,7 +342,7 @@
             }
 
             for (String sname : snames) {
-                System.out.println(sname);
+                process.out().println(sname);
             }
 
             return true;
@@ -357,7 +362,7 @@
             }
 
             for (Entry<String, Integer> entry : scopes.entrySet()) {
-                System.out.println(entry.getKey() + ":" + entry.getValue());
+                process.out().println(entry.getKey() + ":" + entry.getValue());
             }
 
             return true;
@@ -446,13 +451,13 @@
 
         if (buf.length() > 0) {
             if (!opt.isSet("quiet")) {
-                System.out.println(buf);
+                process.out().println(buf);
             }
             return true;
         }
 
         if (!opt.isSet("quiet")) {
-            System.err.println("type: " + name + " not found.");
+            process.err().println("type: " + name + " not found.");
         }
 
         return false;
@@ -464,21 +469,22 @@
                 "Usage: jobs [OPTIONS]",
                 "  -? --help                show help",
         };
+        Process process = Process.current();
         Options opt = Options.compile(usage).parse(argv);
         if (opt.isSet("help")) {
-            opt.usage(System.err);
+            opt.usage(process.err());
             return;
         }
         if (!opt.args().isEmpty()) {
-            System.err.println("usage: jobs");
-            session.error(2);
+            process.err().println("usage: jobs");
+            process.error(2);
             return;
         }
         List<Job> jobs = session.jobs();
-        Job current = session.currentJob();
+        Job current = Job.current();
         for (Job job : jobs) {
             if (job != current) {
-                System.out.println("[" + job.id() + "] " + job.status().toString().toLowerCase()
+                process.out().println("[" + job.id() + "] " + job.status().toString().toLowerCase()
                         + " " + job.command());
             }
         }
@@ -490,27 +496,28 @@
                 "Usage: fg [OPTIONS] [jobid]",
                 "  -? --help                show help",
         };
+        Process process = Process.current();
         Options opt = Options.compile(usage).parse(argv);
         if (opt.isSet("help")) {
-            opt.usage(System.err);
+            opt.usage(process.err());
             return;
         }
         if (opt.args().size() > 1) {
-            System.err.println("usage: fg [jobid]");
-            session.error(2);
+            process.err().println("usage: fg [jobid]");
+            process.error(2);
             return;
         }
         List<Job> jobs = session.jobs();
         Collections.reverse(jobs);
-        Job current = session.currentJob();
+        Job current = Job.current();
         if (argv.length == 0) {
             Job job = jobs.stream().filter(j -> j != current)
                     .findFirst().orElse(null);
             if (job != null) {
                 job.foreground();
             } else {
-                System.err.println("fg: no current job");
-                session.error(1);
+                process.err().println("fg: no current job");
+                process.error(1);
             }
         } else {
             Job job = jobs.stream().filter(j -> j != current && argv[0].equals(Integer.toString(j.id())))
@@ -518,8 +525,8 @@
             if (job != null) {
                 job.foreground();
             } else {
-                System.err.println("fg: job not found: " + argv[0]);
-                session.error(1);
+                process.err().println("fg: job not found: " + argv[0]);
+                process.error(1);
             }
         }
     }
@@ -530,27 +537,28 @@
                 "Usage: bg [OPTIONS] [jobid]",
                 "  -? --help                show help",
         };
+        Process process = Process.current();
         Options opt = Options.compile(usage).parse(argv);
         if (opt.isSet("help")) {
-            opt.usage(System.err);
+            opt.usage(process.err());
             return;
         }
         if (opt.args().size() > 1) {
-            System.err.println("usage: bg [jobid]");
-            session.error(2);
+            process.err().println("usage: bg [jobid]");
+            process.error(2);
             return;
         }
         List<Job> jobs = session.jobs();
         Collections.reverse(jobs);
-        Job current = session.currentJob();
+        Job current = Job.current();
         if (argv.length == 0) {
             Job job = jobs.stream().filter(j -> j != current)
                     .findFirst().orElse(null);
             if (job != null) {
                 job.background();
             } else {
-                System.err.println("bg: no current job");
-                session.error(1);
+                process.err().println("bg: no current job");
+                process.error(1);
             }
         } else {
             Job job = jobs.stream().filter(j -> j != current && argv[0].equals(Integer.toString(j.id())))
@@ -558,8 +566,8 @@
             if (job != null) {
                 job.background();
             } else {
-                System.err.println("bg: job not found: " + argv[0]);
-                session.error(1);
+                process.err().println("bg: job not found: " + argv[0]);
+                process.error(1);
             }
         }
     }
@@ -631,11 +639,13 @@
     }
 
     public void history(CommandSession session, String[] argv) throws IOException {
-        Commands.history(Shell.getReader(session), System.out, System.err, argv);
+        Process process = Process.current();
+        Commands.history(Shell.getReader(session), process.out(), process.err(), argv);
     }
 
     public void complete(CommandSession session, String[] argv) {
-        Commands.complete(Shell.getReader(session), System.out, System.err, Shell.getCompletions(session), argv);
+        Process process = Process.current();
+        Commands.complete(Shell.getReader(session), process.out(), process.err(), Shell.getCompletions(session), argv);
     }
 
     public void widget(final CommandSession session, String[] argv) throws Exception {
@@ -648,19 +658,23 @@
             }
             return true;
         };
-        Commands.widget(Shell.getReader(session), System.out, System.err, creator, argv);
+        Process process = Process.current();
+        Commands.widget(Shell.getReader(session), process.out(), process.err(), creator, argv);
     }
 
     public void keymap(CommandSession session, String[] argv) {
-        Commands.keymap(Shell.getReader(session), System.out, System.err, argv);
+        Process process = Process.current();
+        Commands.keymap(Shell.getReader(session), process.out(), process.err(), argv);
     }
 
     public void setopt(CommandSession session, String[] argv) {
-        Commands.setopt(Shell.getReader(session), System.out, System.err, argv);
+        Process process = Process.current();
+        Commands.setopt(Shell.getReader(session), process.out(), process.err(), argv);
     }
 
     public void unsetopt(CommandSession session, String[] argv) {
-        Commands.unsetopt(Shell.getReader(session), System.out, System.err, argv);
+        Process process = Process.current();
+        Commands.unsetopt(Shell.getReader(session), process.out(), process.err(), argv);
     }
 
     public List<Candidate> __files(CommandSession session) {
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
index 0946f5f..bbb52ad 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
@@ -71,6 +71,7 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.apache.felix.gogo.api.Process;
 import org.apache.felix.gogo.jline.Shell.Context;
 import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
@@ -120,17 +121,18 @@
         if (argv == null || argv.length < 1) {
             throw new IllegalArgumentException();
         }
+        Process process = Process.current();
         try {
-            run(session, argv);
+            run(session, process, argv);
         } catch (IllegalArgumentException e) {
-            System.err.println(e.getMessage());
-            session.error(2);
+            process.err().println(e.getMessage());
+            process.error(2);
         } catch (HelpException e) {
-            System.err.println(e.getMessage());
-            session.error(0);
+            process.err().println(e.getMessage());
+            process.error(0);
         } catch (Exception e) {
-            System.err.println(argv[0] + ": " + e.getMessage());
-            session.error(1);
+            process.err().println(argv[0] + ": " + e.getMessage());
+            process.error(1);
         }
     }
 
@@ -155,64 +157,64 @@
         return o != null ? o.toString() : null;
     }
 
-    protected Object run(CommandSession session, String[] argv) throws Exception {
+    protected Object run(CommandSession session, Process process, String[] argv) throws Exception {
         switch (argv[0]) {
             case "cat":
-                cat(session, argv);
+                cat(session, process, argv);
                 break;
             case "echo":
-                echo(session, argv);
+                echo(session, process, argv);
                 break;
             case "grep":
-                grep(session, argv);
+                grep(session, process, argv);
                 break;
             case "sort":
-                sort(session, argv);
+                sort(session, process, argv);
                 break;
             case "sleep":
-                sleep(session, argv);
+                sleep(session, process, argv);
                 break;
             case "cd":
-                cd(session, argv);
+                cd(session, process, argv);
                 break;
             case "pwd":
-                pwd(session, argv);
+                pwd(session, process, argv);
                 break;
             case "ls":
-                ls(session, argv);
+                ls(session, process, argv);
                 break;
             case "less":
-                less(session, argv);
+                less(session, process, argv);
                 break;
             case "watch":
-                watch(session, argv);
+                watch(session, process, argv);
                 break;
             case "nano":
-                nano(session, argv);
+                nano(session, process, argv);
                 break;
             case "tmux":
-                tmux(session, argv);
+                tmux(session, process, argv);
                 break;
             case "clear":
-                clear(session, argv);
+                clear(session, process, argv);
                 break;
             case "head":
-                head(session, argv);
+                head(session, process, argv);
                 break;
             case "tail":
-                tail(session, argv);
+                tail(session, process, argv);
                 break;
             case "wc":
-                wc(session, argv);
+                wc(session, process, argv);
                 break;
             case "date":
-                date(session, argv);
+                date(session, process, argv);
                 break;
         }
         return null;
     }
 
-    protected void date(CommandSession session, String[] argv) throws Exception {
+    protected void date(CommandSession session, Process process, String[] argv) throws Exception {
         String[] usage = {
                 "date -  display date",
                 "Usage: date [-r seconds] [-v[+|-]val[mwdHMS] ...] [-f input_fmt new_date] [+output_fmt]",
@@ -259,7 +261,7 @@
             output = "%c";
         }
         // Print output
-        System.out.println(new SimpleDateFormat(toJavaDateFormat(output)).format(input));
+        process.out().println(new SimpleDateFormat(toJavaDateFormat(output)).format(input));
     }
 
     private String toJavaDateFormat(String format) {
@@ -339,7 +341,7 @@
         return sb.toString();
     }
 
-    protected void wc(CommandSession session, String[] argv) throws Exception {
+    protected void wc(CommandSession session, Process process, String[] argv) throws Exception {
         String[] usage = {
                 "wc -  word, line, character, and byte count",
                 "Usage: wc [OPTIONS] [FILES]",
@@ -459,7 +461,7 @@
                 if (!lastNl.get()) {
                     lines.incrementAndGet();
                 }
-                System.out.println(String.format(format, lines.get(), words.get(), chars.get(), bytes.get(), src.getName()));
+                process.out().println(String.format(format, lines.get(), words.get(), chars.get(), bytes.get(), src.getName()));
                 totalBytes += bytes.get();
                 totalChars += chars.get();
                 totalWords += words.get();
@@ -467,11 +469,11 @@
             }
         }
         if (sources.size() > 1) {
-            System.out.println(String.format(format, totalLines, totalWords, totalChars, totalBytes, "total"));
+            process.out().println(String.format(format, totalLines, totalWords, totalChars, totalBytes, "total"));
         }
     }
 
-    protected void head(CommandSession session, String[] argv) throws Exception {
+    protected void head(CommandSession session, Process process, String[] argv) throws Exception {
         String[] usage = {
                 "head -  displays first lines of file",
                 "Usage: head [-n lines | -c bytes] [file ...]",
@@ -508,9 +510,9 @@
             int lines = nbLines;
             if (sources.size() > 1) {
                 if (src != sources.get(0)) {
-                    System.out.println();
+                    process.out().println();
                 }
-                System.out.println("==> " + src.getName() + " <==");
+                process.out().println("==> " + src.getName() + " <==");
             }
             try (InputStream is = src.read()) {
                 byte[] buf = new byte[1024];
@@ -526,14 +528,14 @@
                             }
                         }
                         bytes -= nb;
-                        System.out.write(buf, 0, nb);
+                        process.out().write(buf, 0, nb);
                     }
                 } while (nb > 0 && lines > 0 && bytes > 0);
             }
         }
     }
 
-    protected void tail(CommandSession session, String[] argv) throws Exception {
+    protected void tail(CommandSession session, Process process, String[] argv) throws Exception {
         String[] usage = {
                 "tail -  displays last lines of file",
                 "Usage: tail [-f] [-q] [-c # | -n #] [file ...]",
@@ -703,10 +705,10 @@
 
             private void print(String toPrint) {
                 if (lastPrinted.get() != this && opt.args().size() > 1 && !opt.isSet("quiet")) {
-                    System.out.println();
-                    System.out.println("==> " + name + " <==");
+                    process.out().println();
+                    process.out().println("==> " + name + " <==");
                 }
-                System.out.print(toPrint);
+                process.out().print(toPrint);
                 lastPrinted.set(this);
             }
         }
@@ -741,22 +743,22 @@
         }
     }
 
-    protected void clear(CommandSession session, String[] argv) throws Exception {
+    protected void clear(CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "clear -  clear screen",
                 "Usage: clear [OPTIONS]",
                 "  -? --help                    Show help",
         };
         Options opt = parseOptions(session, usage, argv);
-        if (session.isTty(1)) {
+        if (process.isTty(1)) {
             Shell.getTerminal(session).puts(Capability.clear_screen);
             Shell.getTerminal(session).flush();
         }
     }
 
-    protected void tmux(final CommandSession session, String[] argv) throws Exception {
+    protected void tmux(final CommandSession session, Process process, String[] argv) throws Exception {
         Commands.tmux(Shell.getTerminal(session),
-                System.out, System.err,
+                process.out(), System.err,
                 () -> session.get(".tmux"),
                 t -> session.put(".tmux", t),
                 c -> startShell(session, c),
@@ -794,7 +796,7 @@
         }
     }
 
-    protected void nano(final CommandSession session, String[] argv) throws Exception {
+    protected void nano(final CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "nano -  edit files",
                 "Usage: nano [FILES]",
@@ -806,7 +808,7 @@
         edit.run();
     }
 
-    protected void watch(final CommandSession session, String[] argv) throws Exception {
+    protected void watch(final CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "watch - watches & refreshes the output of a command",
                 "Usage: watch [OPTIONS] COMMAND",
@@ -863,7 +865,7 @@
         }
     }
 
-    protected void less(CommandSession session, String[] argv) throws Exception {
+    protected void less(CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "less -  file pager",
                 "Usage: less [OPTIONS] [FILES]",
@@ -891,10 +893,10 @@
             }
         }
 
-        if (!session.isTty(1)) {
+        if (!process.isTty(1)) {
             for (Source source : sources) {
                 try (BufferedReader reader = new BufferedReader(new InputStreamReader(source.read()))) {
-                    cat(reader, opt.isSet("LINE-NUMBERS"));
+                    cat(process, reader, opt.isSet("LINE-NUMBERS"));
                 }
             }
             return;
@@ -915,7 +917,7 @@
         less.run(sources);
     }
 
-    protected void sort(CommandSession session, String[] argv) throws Exception {
+    protected void sort(CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "sort -  writes sorted standard input to standard output.",
                 "Usage: sort [OPTIONS] [FILES]",
@@ -941,7 +943,7 @@
                 }
             }
         } else {
-            BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
+            BufferedReader r = new BufferedReader(new InputStreamReader(process.in()));
             read(r, lines);
         }
 
@@ -958,13 +960,13 @@
         String last = null;
         for (String s : lines) {
             if (!unique || last == null || !s.equals(last)) {
-                System.out.println(s);
+                process.out().println(s);
             }
             last = s;
         }
     }
 
-    protected void pwd(CommandSession session, String[] argv) throws Exception {
+    protected void pwd(CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "pwd - get current directory",
                 "Usage: pwd [OPTIONS]",
@@ -974,10 +976,10 @@
         if (!opt.args().isEmpty()) {
             throw new IllegalArgumentException("usage: pwd");
         }
-        System.out.println(session.currentDir());
+        process.out().println(session.currentDir());
     }
 
-    protected void cd(CommandSession session, String[] argv) throws Exception {
+    protected void cd(CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "cd - get current directory",
                 "Usage: cd [OPTIONS] DIRECTORY",
@@ -997,7 +999,7 @@
         session.currentDir(cwd);
     }
 
-    protected void ls(CommandSession session, String[] argv) throws Exception {
+    protected void ls(CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "ls - list files",
                 "Usage: ls [OPTIONS] [PATTERNS...]",
@@ -1094,7 +1096,7 @@
                 }
                 String col = colors.get(type);
                 boolean addSuffix = opt.isSet("F");
-                if (col != null && !col.isEmpty() && session.isTty(1)) { // TODO: ability to force colors if piped
+                if (col != null && !col.isEmpty() && process.isTty(1)) { // TODO: ability to force colors if piped
                     return "\033[" + col + "m" + path.toString() + "\033[m" + (addSuffix ? suffix : "") + link;
                 } else {
                     return path.toString() + (addSuffix ? suffix : "") + link;
@@ -1222,14 +1224,14 @@
         List<PathEntry> files = all.stream()
                 .filter(PathEntry::isNotDirectory)
                 .collect(Collectors.toList());
-        PrintStream out = System.out;
+        PrintStream out = process.out();
         Consumer<Stream<PathEntry>> display = s -> {
             boolean optLine  = opt.isSet("1");
             boolean optComma = opt.isSet("m");
             boolean optLong  = opt.isSet("l");
             boolean optCol   = opt.isSet("C");
             if (!optLine && !optComma && !optLong && !optCol) {
-                if (session.isTty(1)) {
+                if (process.isTty(1)) {
                     optCol = true;
                 }
                 else {
@@ -1250,7 +1252,7 @@
             }
             // Column listing
             else if (optCol) {
-                toColumn(session, out, s.map(PathEntry::display), opt.isSet("x"));
+                toColumn(session, process, out, s.map(PathEntry::display), opt.isSet("x"));
             }
         };
         boolean space = false;
@@ -1279,9 +1281,9 @@
         }
     }
 
-    private void toColumn(CommandSession session, PrintStream out, Stream<String> ansi, boolean horizontal) {
+    private void toColumn(CommandSession session, Process process, PrintStream out, Stream<String> ansi, boolean horizontal) {
         Terminal terminal = Shell.getTerminal(session);
-        int width = session.isTty(1) ? terminal.getWidth() : 80;
+        int width = process.isTty(1) ? terminal.getWidth() : 80;
         List<AttributedString> strings = ansi.map(AttributedString::fromAnsi).collect(Collectors.toList());
         if (!strings.isEmpty()) {
             int max = strings.stream().mapToInt(AttributedString::columnLength).max().getAsInt();
@@ -1318,7 +1320,7 @@
         }
     }
 
-    protected void cat(CommandSession session, String[] argv) throws Exception {
+    protected void cat(CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "cat - concatenate and print FILES",
                 "Usage: cat [OPTIONS] [FILES]",
@@ -1334,15 +1336,15 @@
         for (String arg : args) {
             InputStream is;
             if ("-".equals(arg)) {
-                is = System.in;
+                is = process.in();
             } else {
                 is = cwd.toUri().resolve(arg).toURL().openStream();
             }
-            cat(new BufferedReader(new InputStreamReader(is)), opt.isSet("n"));
+            cat(process, new BufferedReader(new InputStreamReader(is)), opt.isSet("n"));
         }
     }
 
-    protected void echo(CommandSession session, Object[] argv) throws Exception {
+    protected void echo(CommandSession session, Process process, Object[] argv) throws Exception {
         final String[] usage = {
                 "echo - echoes or prints ARGUMENT to standard output",
                 "Usage: echo [OPTIONS] [ARGUMENTS]",
@@ -1425,13 +1427,13 @@
             }
         }
         if (opt.isSet("n")) {
-            System.out.print(buf);
+            process.out().print(buf);
         } else {
-            System.out.println(buf);
+            process.out().println(buf);
         }
     }
 
-    protected void grep(CommandSession session, String[] argv) throws Exception {
+    protected void grep(CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "grep -  search for PATTERN in each FILE or standard input.",
                 "Usage: grep [OPTIONS] PATTERN [FILES]",
@@ -1544,12 +1546,12 @@
                         if (lineMatch != 0 & lineMatch + after + before <= lines.size()) {
                             if (!count) {
                                 if (!firstPrint && before + after > 0) {
-                                    System.out.println("--");
+                                    process.out().println("--");
                                 } else {
                                     firstPrint = false;
                                 }
                                 for (int i = 0; i < lineMatch + after; i++) {
-                                    System.out.println(lines.get(i));
+                                    process.out().println(lines.get(i));
                                 }
                             }
                             while (lines.size() > before) {
@@ -1566,24 +1568,24 @@
                 }
                 if (!count && lineMatch > 0) {
                     if (!firstPrint && before + after > 0) {
-                        System.out.println("--");
+                        process.out().println("--");
                     } else {
                         firstPrint = false;
                     }
                     for (int i = 0; i < lineMatch + after && i < lines.size(); i++) {
-                        System.out.println(lines.get(i));
+                        process.out().println(lines.get(i));
                     }
                 }
                 if (count) {
-                    System.out.println(nb);
+                    process.out().println(nb);
                 }
                 match |= nb > 0;
             }
         }
-        session.error(match ? 0 : 1);
+        Process.current().error(match ? 0 : 1);
     }
 
-    protected void sleep(CommandSession session, String[] argv) throws Exception {
+    protected void sleep(CommandSession session, Process process, String[] argv) throws Exception {
         final String[] usage = {
                 "sleep -  suspend execution for an interval of time",
                 "Usage: sleep seconds",
@@ -1605,15 +1607,15 @@
         }
     }
 
-    private static void cat(final BufferedReader reader, boolean displayLineNumbers) throws IOException {
+    private static void cat(Process process, final BufferedReader reader, boolean displayLineNumbers) throws IOException {
         String line;
         int lineno = 1;
         try {
             while ((line = reader.readLine()) != null) {
                 if (displayLineNumbers) {
-                    System.out.print(String.format("%6d  ", lineno++));
+                    process.out().print(String.format("%6d  ", lineno++));
                 }
-                System.out.println(line);
+                process.out().println(line);
             }
         } finally {
             reader.close();
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
index 0515b1b..67cad88 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
@@ -25,6 +25,7 @@
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.felix.gogo.api.Process;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Function;
 import org.jline.builtins.Options;
@@ -37,16 +38,17 @@
         if (argv == null || argv.length < 1) {
             throw new IllegalArgumentException();
         }
+        Process process = Process.current();
         try {
-            run(session, argv);
+            run(session, process, argv);
         } catch (OptionException e) {
-            System.err.println(e.getMessage());
-            session.error(2);
+            process.err().println(e.getMessage());
+            process.error(2);
         } catch (HelpException e) {
-            System.err.println(e.getMessage());
-            session.error(0);
+            process.err().println(e.getMessage());
+            process.error(0);
         } catch (ThrownException e) {
-            session.error(1);
+            process.error(1);
             throw e.getCause();
         }
     }
@@ -94,33 +96,34 @@
         return o != null ? o.toString() : null;
     }
 
-    protected Object run(CommandSession session, Object[] argv) throws Throwable {
+    protected Object run(CommandSession session, Process process, Object[] argv) throws Throwable {
         switch (argv[0].toString()) {
             case "each":
-                return doEach(session, argv);
+                return doEach(session, process, argv);
             case "if":
-                return doIf(session, argv);
+                return doIf(session, process, argv);
             case "not":
-                return doNot(session, argv);
+                return doNot(session, process, argv);
             case "throw":
-                return doThrow(session, argv);
+                return doThrow(session, process, argv);
             case "try":
-                return doTry(session, argv);
+                return doTry(session, process, argv);
             case "until":
-                return doUntil(session, argv);
+                return doUntil(session, process, argv);
             case "while":
-                return doWhile(session, argv);
+                return doWhile(session, process, argv);
             case "break":
-                return doBreak(session, argv);
+                return doBreak(session, process, argv);
             case "continue":
-                return doContinue(session, argv);
+                return doContinue(session, process, argv);
             default:
                 throw new UnsupportedOperationException();
         }
     }
 
     protected List<Object> doEach(CommandSession session,
-                                Object[] argv) throws Exception {
+                                  Process process,
+                                  Object[] argv) throws Exception {
         String[] usage = {
                 "each -  loop over the elements",
                 "Usage: each [-r] elements { closure }",
@@ -135,10 +138,10 @@
         List<Function> functions = getFunctions(opt);
 
         if (elements == null || functions == null || functions.size() != 1) {
-            System.err.println("usage: each elements { closure }");
-            System.err.println("       elements: an array to iterate on");
-            System.err.println("       closure: a function or closure to call");
-            session.error(2);
+            process.err().println("usage: each elements { closure }");
+            process.err().println("       elements: an array to iterate on");
+            process.err().println("       closure: a function or closure to call");
+            process.error(2);
             return null;
         }
 
@@ -161,7 +164,7 @@
         return opt.isSet("result") ? results : null;
     }
 
-    protected Object doIf(CommandSession session, Object[] argv) throws Exception {
+    protected Object doIf(CommandSession session, Process process, Object[] argv) throws Exception {
         String[] usage = {
                 "if -  if / then / else construct",
                 "Usage: if {condition} {if-action} ... {else-action}",
@@ -170,8 +173,8 @@
         Options opt = parseOptions(session, usage, argv);
         List<Function> functions = getFunctions(opt);
         if (functions == null || functions.size() < 2) {
-            System.err.println("usage: if {condition} {if-action} ... {else-action}");
-            session.error(2);
+            process.err().println("usage: if {condition} {if-action} ... {else-action}");
+            process.error(2);
             return null;
         }
         for (int i = 0, length = functions.size(); i < length; ++i) {
@@ -182,7 +185,7 @@
         return null;
     }
 
-    protected Boolean doNot(CommandSession session, Object[] argv) throws Exception {
+    protected Boolean doNot(CommandSession session, Process process, Object[] argv) throws Exception {
         String[] usage = {
                 "not -  return the opposite condition",
                 "Usage: not { condition }",
@@ -191,15 +194,15 @@
         Options opt = parseOptions(session, usage, argv);
         List<Function> functions = getFunctions(opt);
         if (functions == null || functions.size() != 1) {
-            System.err.println("usage: not { condition }");
-            session.error(2);
+            process.err().println("usage: not { condition }");
+            process.error(2);
             return null;
         }
         return !isTrue(session, functions.get(0));
 
     }
 
-    protected Object doThrow(CommandSession session, Object[] argv) throws ThrownException, HelpException, OptionException {
+    protected Object doThrow(CommandSession session, Process process, Object[] argv) throws ThrownException, HelpException, OptionException {
         String[] usage = {
                 "throw -  throw an exception",
                 "Usage: throw [ message [ cause ] ]",
@@ -230,7 +233,7 @@
         }
     }
 
-    protected Object doTry(CommandSession session, Object[] argv) throws Exception {
+    protected Object doTry(CommandSession session, Process process, Object[] argv) throws Exception {
         String[] usage = {
                 "try -  try / catch / finally construct",
                 "Usage: try { try-action } [ { catch-action } [ { finally-action } ]  ]",
@@ -239,8 +242,8 @@
         Options opt = parseOptions(session, usage, argv);
         List<Function> functions = getFunctions(opt);
         if (functions == null || functions.size() < 1 || functions.size() > 3) {
-            System.err.println("usage: try { try-action } [ { catch-action } [ { finally-action } ] ]");
-            session.error(2);
+            process.err().println("usage: try { try-action } [ { catch-action } [ { finally-action } ] ]");
+            process.error(2);
             return null;
         }
         try {
@@ -260,7 +263,7 @@
         }
     }
 
-    protected Object doWhile(CommandSession session, Object[] argv) throws Exception {
+    protected Object doWhile(CommandSession session, Process process, Object[] argv) throws Exception {
         String[] usage = {
                 "while -  while loop",
                 "Usage: while { condition } { action }",
@@ -269,8 +272,8 @@
         Options opt = parseOptions(session, usage, argv);
         List<Function> functions = getFunctions(opt);
         if (functions == null || functions.size() != 2) {
-            System.err.println("usage: while { condition } { action }");
-            session.error(2);
+            process.err().println("usage: while { condition } { action }");
+            process.error(2);
             return null;
         }
         while (isTrue(session, functions.get(0))) {
@@ -285,7 +288,7 @@
         return null;
     }
 
-    protected Object doUntil(CommandSession session, Object[] argv) throws Exception {
+    protected Object doUntil(CommandSession session, Process process, Object[] argv) throws Exception {
         String[] usage = {
                 "until -  until loop",
                 "Usage: until { condition } { action }",
@@ -304,8 +307,8 @@
         }
         int length = opt.argObjects().size();
         if (length != 2 || functions == null) {
-            System.err.println("usage: until { condition } { action }");
-            session.error(2);
+            process.err().println("usage: until { condition } { action }");
+            process.error(2);
             return null;
         }
         while (!isTrue(session, functions.get(0))) {
@@ -320,7 +323,7 @@
         return null;
     }
 
-    protected Object doBreak(CommandSession session, Object[] argv) throws Exception {
+    protected Object doBreak(CommandSession session, Process process, Object[] argv) throws Exception {
         String[] usage = {
                 "break -  break from loop",
                 "Usage: break",
@@ -330,7 +333,7 @@
         throw new BreakException();
     }
 
-    protected Object doContinue(CommandSession session, Object[] argv) throws Exception {
+    protected Object doContinue(CommandSession session, Process process, Object[] argv) throws Exception {
         String[] usage = {
                 "continue -  continue loop",
                 "Usage: continue",
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
index 7e962e6..cf91985 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
@@ -42,8 +42,8 @@
 import org.apache.felix.gogo.runtime.Closure;
 import org.apache.felix.gogo.runtime.CommandProxy;
 import org.apache.felix.gogo.runtime.CommandSessionImpl;
-import org.apache.felix.gogo.runtime.Job;
-import org.apache.felix.gogo.runtime.Job.Status;
+import org.apache.felix.gogo.api.Job;
+import org.apache.felix.gogo.api.Job.Status;
 import org.apache.felix.gogo.runtime.Reflective;
 import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Job.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/api/Job.java
similarity index 75%
rename from gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Job.java
rename to gogo/runtime/src/main/java/org/apache/felix/gogo/api/Job.java
index 721fa8c..3b09044 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Job.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/api/Job.java
@@ -16,12 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.felix.gogo.runtime;
+package org.apache.felix.gogo.api;
 
-import org.apache.felix.gogo.runtime.Pipe.Result;
+import java.util.List;
+
+import org.apache.felix.service.command.CommandSession;
 
 public interface Job {
 
+    /**
+     * Get the job running in the current thead or null.
+     */
+    static Job current() {
+        Process p = Process.current();
+        Job j = p != null ? p.job() : null;
+        while (j != null && j.parent() != null) {
+            j = j.parent();
+        }
+        return j;
+    }
+
     enum Status {
         Created,
         Suspended,
@@ -59,4 +73,10 @@
      */
     Result start(Status status) throws InterruptedException;
 
+    List<Process> processes();
+
+    CommandSession session();
+
+    Job parent();
+
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/api/JobListener.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/api/JobListener.java
index 4596176..a5d7ca6 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/api/JobListener.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/api/JobListener.java
@@ -18,8 +18,7 @@
  */
 package org.apache.felix.gogo.api;
 
-import org.apache.felix.gogo.runtime.Job;
-import org.apache.felix.gogo.runtime.Job.Status;
+import org.apache.felix.gogo.api.Job.Status;
 
 /**
  * Listener for command executions.
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/api/Process.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/api/Process.java
new file mode 100644
index 0000000..71c1305
--- /dev/null
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/api/Process.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.api;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+
+import org.apache.felix.gogo.runtime.Pipe;
+
+public interface Process {
+
+    static Process current() {
+        return Pipe.getCurrentPipe();
+    }
+
+    InputStream in();
+
+    PrintStream out();
+
+    PrintStream err();
+
+    /**
+     * Get the job controlling this process
+     */
+    Job job();
+
+    /**
+     * Check if the given descriptor for the currently running pipe is the terminal or not.
+     */
+    boolean isTty(int fd);
+
+    /**
+     * Set the error code for the currently running pipe.
+     */
+    void error(int error);
+
+}
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/api/Result.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/api/Result.java
new file mode 100644
index 0000000..6b4e7c2
--- /dev/null
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/api/Result.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.api;
+
+public interface Result {
+
+    boolean isSuccess();
+
+    Object result();
+
+    Exception exception();
+
+    int error();
+}
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
index 34f70a0..de8a330 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
@@ -33,7 +33,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 
-import org.apache.felix.gogo.runtime.Job.Status;
+import org.apache.felix.gogo.api.Job.Status;
 import org.apache.felix.gogo.runtime.Parser.Array;
 import org.apache.felix.gogo.runtime.Parser.Executable;
 import org.apache.felix.gogo.runtime.Parser.Operator;
@@ -224,10 +224,14 @@
                 toclose[1] = true;
             }
 
-            List<Pipe> pipes = new ArrayList<>();
+            CommandSessionImpl.JobImpl job;
             if (executable instanceof Pipeline) {
                 Pipeline pipeline = (Pipeline) executable;
                 List<Executable> exec = pipeline.tokens();
+                Token s = exec.get(0);
+                Token e = exec.get(exec.size() - 1);
+                Token t = program.subSequence(s.start - program.start, e.start + e.length - program.start);
+                job = session().createJob(t);
                 for (int i = 0; i < exec.size(); i++) {
                     Statement ex = (Statement) exec.get(i);
                     Operator op = i < exec.size() - 1 ? (Operator) exec.get(++i) : null;
@@ -257,18 +261,15 @@
                     } else {
                         throw new IllegalStateException("Unrecognized pipe operator: '" + op + "'");
                     }
-                    pipes.add(new Pipe(this, ex, nstreams, ntoclose));
+                    Pipe pipe = new Pipe(this, job, ex, nstreams, ntoclose);
+                    job.addPipe(pipe);
                 }
             } else {
-                pipes.add(new Pipe(this, (Statement) executable, streams, toclose));
+                job = session().createJob(executable);
+                Pipe pipe = new Pipe(this, job, (Statement) executable, streams, toclose);
+                job.addPipe(pipe);
             }
 
-            // Create job
-            Token s = pipes.get(0).statement;
-            Token e = pipes.get(pipes.size() - 1).statement;
-            Token t = program.subSequence(s.start - program.start, e.start + e.length - program.start);
-            Job job = session().createJob(t, pipes);
-
             // Start pipe in background
             if (operator != null && Token.eq("&", operator)) {
                 job.start(Status.Background);
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
index e12a2d0..eb0588b 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
@@ -50,6 +50,11 @@
     protected final WeakHashMap<CommandSession, Object> sessions = new WeakHashMap<>();
     protected boolean stopped;
 
+    public CommandProcessorImpl()
+    {
+        this(null);
+    }
+
     public CommandProcessorImpl(ThreadIO tio)
     {
         threadIO = tio;
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
index 0375a81..85370bb 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
@@ -51,8 +51,10 @@
 import java.util.concurrent.Future;
 import java.util.stream.Collectors;
 
+import org.apache.felix.gogo.api.Job;
+import org.apache.felix.gogo.api.Job.Status;
 import org.apache.felix.gogo.api.JobListener;
-import org.apache.felix.gogo.runtime.Job.Status;
+import org.apache.felix.gogo.api.Process;
 import org.apache.felix.gogo.runtime.Pipe.Result;
 import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
@@ -80,7 +82,6 @@
     protected final ConcurrentMap<String, Object> variables = new ConcurrentHashMap<>();
     private volatile boolean closed;
     private final List<JobImpl> jobs = new ArrayList<>();
-    private final ThreadLocal<JobImpl> currentJob = new InheritableThreadLocal<>();
     private JobListener jobListener;
 
     private final ExecutorService executor;
@@ -152,7 +153,6 @@
     public Object execute(CharSequence commandline) throws Exception
     {
         assert processor != null;
-        assert processor.threadIO != null;
 
         if (closed)
         {
@@ -494,17 +494,12 @@
     @Override
     public List<Job> jobs() {
         synchronized (jobs) {
-            return new ArrayList<>(jobs);
+            return Collections.unmodifiableList(jobs);
         }
     }
 
-    @Override
-    public JobImpl currentJob() {
-        JobImpl job = currentJob.get();
-        while (job != null && job.parent != null) {
-            job = job.parent;
-        }
-        return job;
+    public static JobImpl currentJob() {
+        return (JobImpl) Job.current();
     }
 
     @Override
@@ -526,7 +521,7 @@
         }
     }
 
-    public Job createJob(CharSequence command, List<Pipe> pipes) {
+    public JobImpl createJob(CharSequence command) {
         synchronized (jobs) {
             int id = 1;
             synchronized (jobs) {
@@ -543,7 +538,7 @@
                 } while (found);
             }
             JobImpl cur = currentJob();
-            JobImpl job = new JobImpl(id, cur, command, pipes);
+            JobImpl job = new JobImpl(id, cur, command);
             if (cur == null) {
                 jobs.add(job);
             }
@@ -551,20 +546,23 @@
         }
     }
 
-    private class JobImpl implements Job {
+    class JobImpl implements Job {
         private final int id;
         private final JobImpl parent;
         private final CharSequence command;
-        private final List<Pipe> pipes;
+        private final List<Pipe> pipes = new ArrayList<>();
         private Status status = Status.Created;
         private Future<?> future;
         private Result result;
 
-        public JobImpl(int id, JobImpl parent, CharSequence command, List<Pipe> pipes) {
+        public JobImpl(int id, JobImpl parent, CharSequence command) {
             this.id = id;
             this.parent = parent;
             this.command = command;
-            this.pipes = pipes;
+        }
+
+        void addPipe(Pipe pipe) {
+            pipes.add(pipe);
         }
 
         @Override
@@ -606,7 +604,7 @@
             if (status == Status.Done) {
                 throw new IllegalStateException("Job is finished");
             }
-            JobImpl cr = currentJob();
+            JobImpl cr = CommandSessionImpl.currentJob();
             JobImpl fg = foregroundJob();
             if (parent == null && fg != null && fg != this && fg != cr) {
                 throw new IllegalStateException("A job is already in foreground");
@@ -667,6 +665,11 @@
         }
 
         @Override
+        public Job parent() {
+            return parent;
+        }
+
+        @Override
         public synchronized Result start(Status status) throws InterruptedException {
             if (status == Status.Created || status == Status.Done) {
                 throw new IllegalArgumentException("Illegal start status");
@@ -692,13 +695,22 @@
             return result;
         }
 
+        public List<Process> processes() {
+            return Collections.unmodifiableList(pipes);
+        }
+
+        @Override
+        public CommandSession session() {
+            return CommandSessionImpl.this;
+        }
+
         private Void call() throws Exception {
             Thread thread = Thread.currentThread();
             String name = thread.getName();
             try {
                 thread.setName("job controller " + id);
 
-                List<Callable<Result>> wrapped = pipes.stream().map(this::wrap).collect(Collectors.toList());
+                List<Callable<Result>> wrapped = pipes.stream().collect(Collectors.toList());
                 List<Future<Result>> results = executor.invokeAll(wrapped);
 
                 // Get pipe exceptions
@@ -729,27 +741,6 @@
             return null;
         }
 
-        private Callable<Result> wrap(Pipe pipe) {
-            return () -> {
-                JobImpl prevJob = currentJob.get();
-                try {
-                    currentJob.set(this);
-                    return pipe.call();
-                } finally {
-                    currentJob.set(prevJob);
-                }
-            };
-        }
-
     }
 
-    @Override
-    public boolean isTty(int fd) {
-        return Pipe.isTty(fd);
-    }
-
-    @Override
-    public void error(int error) {
-        Pipe.error(error);
-    }
 }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
index 0073894..bae9d3b 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
@@ -42,16 +42,20 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.felix.gogo.runtime.Job.Status;
+import org.apache.felix.gogo.runtime.CommandSessionImpl.JobImpl;
+import org.apache.felix.gogo.api.Job;
+import org.apache.felix.gogo.api.Job.Status;
+import org.apache.felix.gogo.api.Process;
 import org.apache.felix.gogo.runtime.Parser.Statement;
 import org.apache.felix.gogo.runtime.Pipe.Result;
 import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.threadio.ThreadIO;
 
-public class Pipe implements Callable<Result>
+public class Pipe implements Callable<Result>, Process
 {
     private static final ThreadLocal<Pipe> CURRENT = new ThreadLocal<>();
 
-    public static class Result {
+    public static class Result implements org.apache.felix.gogo.api.Result {
         public final Object result;
         public final Exception exception;
         public final int error;
@@ -77,24 +81,27 @@
         public boolean isSuccess() {
             return exception == null && error == 0;
         }
+
+        @Override
+        public Object result() {
+            return result;
+        }
+
+        @Override
+        public Exception exception() {
+            return exception;
+        }
+
+        @Override
+        public int error() {
+            return error;
+        }
     }
 
     public static Pipe getCurrentPipe() {
         return CURRENT.get();
     }
 
-    public static boolean isTty(int fd) {
-        Pipe current = getCurrentPipe();
-        return current != null && !current.toclose[fd];
-    }
-
-    public static void error(int error) {
-        Pipe current = getCurrentPipe();
-        if (current != null) {
-            current.error = error;
-        }
-    }
-
     private static Pipe setCurrentPipe(Pipe pipe) {
         Pipe previous = CURRENT.get();
         CURRENT.set(pipe);
@@ -102,14 +109,20 @@
     }
 
     final Closure closure;
+    final Job job;
     final Statement statement;
     final Channel[] streams;
     final boolean[] toclose;
     int error;
 
-    public Pipe(Closure closure, Statement statement, Channel[] streams, boolean[] toclose)
+    InputStream in;
+    PrintStream out;
+    PrintStream err;
+
+    public Pipe(Closure closure, JobImpl job, Statement statement, Channel[] streams, boolean[] toclose)
     {
         this.closure = closure;
+        this.job = job;
         this.statement = statement;
         this.streams = streams;
         this.toclose = toclose;
@@ -169,6 +182,35 @@
     }
 
     @Override
+    public InputStream in() {
+        return in;
+    }
+
+    @Override
+    public PrintStream out() {
+        return out;
+    }
+
+    @Override
+    public PrintStream err() {
+        return err;
+    }
+
+    @Override
+    public Job job() {
+        return job;
+    }
+
+    public boolean isTty(int fd) {
+        // TODO: this assumes that the session is always created with input/output tty streams
+        return !toclose[fd];
+    }
+
+    public void error(int error) {
+        this.error = error;
+    }
+
+    @Override
     public Result call() throws Exception {
         Thread thread = Thread.currentThread();
         String name = thread.getName();
@@ -182,10 +224,6 @@
 
     private Result doCall()
     {
-        InputStream in;
-        PrintStream out = null;
-        PrintStream err = null;
-
         // The errChannel will be used to print errors to the error stream
         // Before the command is actually executed (i.e. during the initialization,
         // including the redirection processing), it will be the original error stream.
@@ -195,6 +233,8 @@
 
         boolean endOfPipe = !toclose[1];
 
+        ThreadIO threadIo = closure.session().threadIO();
+
         try
         {
             List<Token> tokens = statement.redirections();
@@ -283,7 +323,9 @@
             // the command is about to be executed.
             errChannel = (WritableByteChannel) streams[2];
 
-            closure.session().threadIO().setStreams(in, out, err);
+            if (threadIo != null) {
+                threadIo.setStreams(in, out, err);
+            }
 
             Pipe previous = setCurrentPipe(this);
             try {
@@ -332,7 +374,9 @@
             if (err != null) {
                 err.flush();
             }
-            closure.session().threadIO().close();
+            if (threadIo != null) {
+                threadIo.close();
+            }
 
             try
             {
@@ -458,28 +502,23 @@
         }
 
         private void checkSuspend(Channel ch) throws IOException {
-            Job cur = closure.session().currentJob();
-            if (cur != null) {
-                Channel[] sch = closure.session().channels;
-                if (ch == sch[0] || ch == sch[1] || ch == sch[2]) {
-                    synchronized (cur) {
-                        if (cur.status() == Status.Background) {
-                            // TODO: Send SIGTIN / SIGTOU
-                            cur.suspend();
-                        }
+            Channel[] sch = closure.session().channels;
+            if (ch == sch[0] || ch == sch[1] || ch == sch[2]) {
+                synchronized (job) {
+                    if (job.status() == Status.Background) {
+                        // TODO: Send SIGTIN / SIGTOU
+                        job.suspend();
                     }
                 }
-                synchronized (cur) {
-                    while (cur.status() == Status.Suspended) {
-                        try {
-                            cur.wait();
-                        } catch (InterruptedException e) {
-                            throw (IOException) new InterruptedIOException().initCause(e);
-                        }
+            }
+            synchronized (job) {
+                while (job.status() == Status.Suspended) {
+                    try {
+                        job.wait();
+                    } catch (InterruptedException e) {
+                        throw (IOException) new InterruptedIOException().initCause(e);
                     }
                 }
-            } else {
-                String msg = "This is definitely not expected";
             }
         }
     }
diff --git a/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java b/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
index 01fc644..3cb5068 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandSession.java
@@ -23,8 +23,8 @@
 import java.nio.file.Path;
 import java.util.List;
 
+import org.apache.felix.gogo.api.Job;
 import org.apache.felix.gogo.api.JobListener;
-import org.apache.felix.gogo.runtime.Job;
 
 public interface CommandSession
 {
@@ -113,11 +113,6 @@
     List<Job> jobs();
 
     /**
-     * Get the job running in the current thead or null.
-     */
-    Job currentJob();
-
-    /**
      * Get the current foreground job or null.
      */
     Job foregroundJob();
@@ -127,18 +122,13 @@
      */
     void setJobListener(JobListener listener);
 
-    //
-    // Process access
-    //
-
     /**
-     * Check if the given descriptor for the currently running pipe is the terminal or not.
+     * Return the current session.
+     * Available inside from a command call.
      */
-    boolean isTty(int fd);
-
-    /**
-     * Set the error code for the currently running pipe.
-     */
-    void error(int error);
+    static CommandSession current() {
+        Job j = Job.current();
+        return j != null ? j.session() : null;
+    }
 
 }
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
index ed8fbf3..48a99af 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestParser.java
@@ -27,6 +27,7 @@
 import java.util.List;
 import java.util.regex.Pattern;
 
+import org.apache.felix.gogo.api.Process;
 import org.apache.felix.gogo.runtime.Parser.Pipeline;
 import org.apache.felix.gogo.runtime.Parser.Program;
 import org.apache.felix.gogo.runtime.Parser.Sequence;
@@ -467,7 +468,7 @@
 
     public boolean istty(CommandSession session, int fd)
     {
-        return session.isTty(fd);
+        return Process.current().isTty(fd);
     }
 
     void each(CommandSession session, Collection<Object> list, Function closure)