diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
index 4d490907a5206fb864a9454935f0c685ebfef3cb..18005a83e7a229ad439d0864dcaa9e191103ea5b 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHandler.java
@@ -50,6 +50,18 @@ public class ConfigClientsHandler extends FormHandler {
                 startWebApp(app);
             return;
         }
+
+        // value
+        if (_action.startsWith("Delete ")) {
+            String app = _action.substring(7);
+            int appnum = -1;
+            try {
+                appnum = Integer.parseInt(app);
+            } catch (NumberFormatException nfe) {}
+            if (appnum >= 0)
+                deleteClient(appnum);
+            return;
+        }
         // label (IE)
         String xStart = _("Start");
         if (_action.toLowerCase().startsWith(xStart + "<span class=hide> ") &&
@@ -72,28 +84,81 @@ public class ConfigClientsHandler extends FormHandler {
     public void setSettings(Map settings) { _settings = new HashMap(settings); }
     
     private void saveClientChanges() {
-        List clients = ClientAppConfig.getClientApps(_context);
+        List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
         for (int cur = 0; cur < clients.size(); cur++) {
-            ClientAppConfig ca = (ClientAppConfig) clients.get(cur);
+            ClientAppConfig ca = clients.get(cur);
             Object val = _settings.get(cur + ".enabled");
             if (! ("webConsole".equals(ca.clientName) || "Web console".equals(ca.clientName)))
                 ca.disabled = val == null;
+            // edit of an existing entry
+            String desc = getString("desc" + cur);
+            if (desc != null) {
+                int spc = desc.indexOf(" ");
+                String clss = desc;
+                String args = null;
+                if (spc >= 0) {
+                    clss = desc.substring(0, spc);
+                    args = desc.substring(spc + 1);
+                }
+                ca.className = clss;
+                ca.args = args;
+                ca.clientName = getString("name" + cur);
+            }
         }
+
+        int newClient = clients.size();
+        String newDesc = getString("desc" + newClient);
+        if (newDesc != null) {
+            // new entry
+            int spc = newDesc.indexOf(" ");
+            String clss = newDesc;
+            String args = null;
+            if (spc >= 0) {
+                clss = newDesc.substring(0, spc);
+                args = newDesc.substring(spc + 1);
+            }
+            String name = getString("name" + newClient);
+            if (name == null) name = "new client";
+            ClientAppConfig ca = new ClientAppConfig(clss, name, args, 2*60*1000,
+                                                     _settings.get(newClient + ".enabled") != null);
+            clients.add(ca);
+            addFormNotice(_("New client added") + ": " + name + " (" + clss + ").");
+        }
+
         ClientAppConfig.writeClientAppConfig(_context, clients);
         addFormNotice(_("Client configuration saved successfully - restart required to take effect."));
     }
 
+    /** curses Jetty for returning arrays */
+    private String getString(String key) {
+        String[] arr = (String[]) _settings.get(key);
+        if (arr == null)
+            return null;
+        return arr[0];
+    }
+
     private void startClient(int i) {
-        List clients = ClientAppConfig.getClientApps(_context);
+        List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
         if (i >= clients.size()) {
             addFormError(_("Bad client index."));
             return;
         }
-        ClientAppConfig ca = (ClientAppConfig) clients.get(i);
+        ClientAppConfig ca = clients.get(i);
         LoadClientAppsJob.runClient(ca.className, ca.clientName, LoadClientAppsJob.parseArgs(ca.args), configClient_log);
         addFormNotice(_("Client") + ' ' + _(ca.clientName) + ' ' + _("started") + '.');
     }
 
+    private void deleteClient(int i) {
+        List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
+        if (i < 0 || i >= clients.size()) {
+            addFormError(_("Bad client index."));
+            return;
+        }
+        ClientAppConfig ca = clients.remove(i);
+        ClientAppConfig.writeClientAppConfig(_context, clients);
+        addFormNotice(_("Client") + ' ' + _(ca.clientName) + ' ' + _("deleted") + '.');
+    }
+
     private void saveWebAppChanges() {
         Properties props = RouterConsoleRunner.webAppProperties();
         Set keys = props.keySet();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
index 90738b15bb4011427ea8f9b7c4b37739128fdd42..b6d253745ce448a9be61a039a81b18fa759b5506 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigClientsHelper.java
@@ -9,21 +9,42 @@ import java.util.TreeSet;
 import net.i2p.router.startup.ClientAppConfig;
 
 public class ConfigClientsHelper extends HelperBase {
+    private String _edit;
+
     public ConfigClientsHelper() {}
     
+    public void setEdit(String edit) {
+         if (edit == null)
+             return;
+        String xStart = _("Edit");
+        if (edit.startsWith(xStart + "<span class=hide> ") &&
+            edit.endsWith("</span>")) {
+            // IE sucks
+            _edit = edit.substring(xStart.length() + 18, edit.length() - 7);
+        } else if (edit.startsWith("Edit ")) {
+            _edit = edit.substring(5);
+        } else if (edit.startsWith(xStart + ' ')) {
+            _edit = edit.substring(xStart.length() + 1);
+        } else if ((_("Add Client")).equals(edit)) {
+            _edit = "new";
+        }
+    }
+
     public String getForm1() {
         StringBuilder buf = new StringBuilder(1024);
         buf.append("<table>\n");
         buf.append("<tr><th align=\"right\">" + _("Client") + "</th><th>" + _("Run at Startup?") + "</th><th>" + _("Start Now") + "</th><th align=\"left\">" + _("Class and arguments") + "</th></tr>\n");
         
-        List clients = ClientAppConfig.getClientApps(_context);
+        List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
         for (int cur = 0; cur < clients.size(); cur++) {
-            ClientAppConfig ca = (ClientAppConfig) clients.get(cur);
+            ClientAppConfig ca = clients.get(cur);
             renderForm(buf, ""+cur, ca.clientName, false, !ca.disabled,
                        "webConsole".equals(ca.clientName) || "Web console".equals(ca.clientName),
-                       ca.className + ((ca.args != null) ? " " + ca.args : ""));
+                       ca.className + ((ca.args != null) ? " " + ca.args : ""), (""+cur).equals(_edit), true);
         }
         
+        if ("new".equals(_edit))
+            renderForm(buf, "" + clients.size(), "", false, false, false, "", true, false);
         buf.append("</table>\n");
         return buf.toString();
     }
@@ -33,26 +54,33 @@ public class ConfigClientsHelper extends HelperBase {
         buf.append("<table>\n");
         buf.append("<tr><th align=\"right\">" + _("WebApp") + "</th><th>" + _("Run at Startup?") + "</th><th>" + _("Start Now") + "</th><th align=\"left\">" + _("Description") + "</th></tr>\n");
         Properties props = RouterConsoleRunner.webAppProperties();
-        Set keys = new TreeSet(props.keySet());
-        for (Iterator iter = keys.iterator(); iter.hasNext(); ) {
-            String name = (String)iter.next();
+        Set<String> keys = new TreeSet(props.keySet());
+        for (Iterator<String> iter = keys.iterator(); iter.hasNext(); ) {
+            String name = iter.next();
             if (name.startsWith(RouterConsoleRunner.PREFIX) && name.endsWith(RouterConsoleRunner.ENABLED)) {
                 String app = name.substring(RouterConsoleRunner.PREFIX.length(), name.lastIndexOf(RouterConsoleRunner.ENABLED));
                 String val = props.getProperty(name);
-                renderForm(buf, app, app, !"addressbook".equals(app), "true".equals(val), RouterConsoleRunner.ROUTERCONSOLE.equals(app), app + ".war");
+                renderForm(buf, app, app, !"addressbook".equals(app),
+                           "true".equals(val), RouterConsoleRunner.ROUTERCONSOLE.equals(app), app + ".war", false, false);
             }
         }
         buf.append("</table>\n");
         return buf.toString();
     }
 
-    private void renderForm(StringBuilder buf, String index, String name, boolean urlify, boolean enabled, boolean ro, String desc) {
+    /** ro trumps edit and showEditButton */
+    private void renderForm(StringBuilder buf, String index, String name, boolean urlify,
+                            boolean enabled, boolean ro, String desc, boolean edit, boolean showEditButton) {
         buf.append("<tr><td class=\"mediumtags\" align=\"right\" width=\"25%\">");
         if (urlify && enabled) {
             String link = "/";
             if (! RouterConsoleRunner.ROUTERCONSOLE.equals(name))
                 link += name + "/";
             buf.append("<a href=\"").append(link).append("\">").append(_(name)).append("</a>");
+        } else if (edit && !ro) {
+            buf.append("<input type=\"text\" name=\"name").append(index).append("\" value=\"");
+            buf.append(_(name));
+            buf.append("\" >");
         } else {
             buf.append(_(name));
         }
@@ -63,9 +91,21 @@ public class ConfigClientsHelper extends HelperBase {
                 buf.append("disabled=\"true\" ");
         }
         buf.append("/></td><td align=\"center\" width=\"15%\">");
-        if (!enabled) {
+        if ((!enabled) && !edit) {
             buf.append("<button type=\"submit\" name=\"action\" value=\"Start ").append(index).append("\" >" + _("Start") + "<span class=hide> ").append(index).append("</span></button>");
         }
-        buf.append("</td><td align=\"left\" width=\"50%\">").append(desc).append("</td></tr>\n");
+        if (showEditButton && (!edit) && !ro) {
+            buf.append("<button type=\"submit\" name=\"edit\" value=\"Edit ").append(index).append("\" >" + _("Edit") + "<span class=hide> ").append(index).append("</span></button>");
+            buf.append("<button type=\"submit\" name=\"action\" value=\"Delete ").append(index).append("\" >" + _("Delete") + "<span class=hide> ").append(index).append("</span></button>");
+        }
+        buf.append("</td><td align=\"left\" width=\"50%\">");
+        if (edit && !ro) {
+            buf.append("<input type=\"text\" size=\"80\" name=\"desc").append(index).append("\" value=\"");
+            buf.append(desc);
+            buf.append("\" >");
+        } else {
+            buf.append(desc);
+        }
+        buf.append("</td></tr>\n");
     }
 }
diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp
index b2f39c198be0ab9cd5243c9ace465a28b33df4be..1148a8d691cd503c1521e39129c01e503c8805dd 100644
--- a/apps/routerconsole/jsp/configclients.jsp
+++ b/apps/routerconsole/jsp/configclients.jsp
@@ -15,6 +15,7 @@ button span.hide{
 
 <jsp:useBean class="net.i2p.router.web.ConfigClientsHelper" id="clientshelper" scope="request" />
 <jsp:setProperty name="clientshelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+<jsp:setProperty name="clientshelper" property="edit" value="<%=request.getParameter("edit")%>" />
 <h1><%=intl._("I2P Client Configuration")%></h1>
 <div class="main" id="main">
  <%@include file="confignav.jsi" %>
@@ -38,6 +39,8 @@ button span.hide{
  <%=net.i2p.router.startup.ClientAppConfig.configFile(net.i2p.I2PAppContext.getGlobalContext()).getAbsolutePath()%>.
  <%=intl._("All changes require restart to take effect.")%></i>
  </p><hr><div class="formaction">
+ <input type="submit" name="foo" value="<%=intl._("Cancel")%>" />
+ <input type="submit" name="edit" value="<%=intl._("Add Client")%>" />
  <input type="submit" name="action" value="<%=intl._("Save Client Configuration")%>" />
 </div></div><h3><a name="webapp"></a><%=intl._("WebApp Configuration")%></h3><p>
  <%=intl._("The Java web applications listed below are started by the webConsole client and run in the same JVM as the router. They are usually web applications accessible through the router console. They may be complete applications (e.g. i2psnark),front-ends to another client or application which must be separately enabled (e.g. susidns, i2ptunnel), or have no web interface at all (e.g. addressbook).")%>
diff --git a/router/java/src/net/i2p/router/startup/ClientAppConfig.java b/router/java/src/net/i2p/router/startup/ClientAppConfig.java
index 94b824671b1c89d7e2c82645e9d80349772f743a..af2c3101e0fb5274e1a0dc263dde024610aad63d 100644
--- a/router/java/src/net/i2p/router/startup/ClientAppConfig.java
+++ b/router/java/src/net/i2p/router/startup/ClientAppConfig.java
@@ -109,7 +109,8 @@ public class ClientAppConfig {
                 ClientAppConfig app = (ClientAppConfig) apps.get(i);
                 buf.append(PREFIX).append(i).append(".main=").append(app.className).append("\n");
                 buf.append(PREFIX).append(i).append(".name=").append(app.clientName).append("\n");
-                buf.append(PREFIX).append(i).append(".args=").append(app.args).append("\n");
+                if (app.args != null)
+                    buf.append(PREFIX).append(i).append(".args=").append(app.args).append("\n");
                 buf.append(PREFIX).append(i).append(".delay=").append(app.delay / 1000).append("\n");
                 buf.append(PREFIX).append(i).append(".startOnLoad=").append(!app.disabled).append("\n");
             }