diff --git a/build/actions/ssh.js b/build/actions/ssh.js
index 2a53b296..ffa26ca0 100644
--- a/build/actions/ssh.js
+++ b/build/actions/ssh.js
@@ -35,9 +35,9 @@ limitations under the License.
   };
 
   module.exports = {
-    signature: 'ssh [destination]',
+    signature: 'ssh [uuid]',
     description: '(beta) get a shell into the running app container of a device',
-    help: 'WARNING: If you\'re running Windows, this command only supports `cmd.exe`.\n\nUse this command to get a shell into the running application container of\nyour device.\n\nThe `destination` argument can be either a device uuid or an application name.\n\nExamples:\n\n	$ resin ssh MyApp\n	$ resin ssh 7cf02a6\n	$ resin ssh 7cf02a6 --port 8080\n	$ resin ssh 7cf02a6 -v',
+    help: 'WARNING: If you\'re running Windows, this command only supports `cmd.exe`.\n\nUse this command to get a shell into the running application container of\nyour device.\n\nExamples:\n\n	$ resin ssh MyApp\n	$ resin ssh 7cf02a6\n	$ resin ssh 7cf02a6 --port 8080\n	$ resin ssh 7cf02a6 -v',
     permission: 'user',
     primary: true,
     options: [
@@ -45,7 +45,7 @@ limitations under the License.
         signature: 'port',
         parameter: 'port',
         description: 'ssh gateway port',
-        alias: 't'
+        alias: 'p'
       }, {
         signature: 'verbose',
         boolean: true,
@@ -64,11 +64,11 @@ limitations under the License.
         options.port = 22;
       }
       verbose = options.verbose ? '-vvv' : '';
-      return resin.models.device.has(params.destination).then(function(isValidUUID) {
+      return resin.models.device.has(params.uuid).then(function(isValidUUID) {
         if (isValidUUID) {
-          return params.destination;
+          return params.uuid;
         }
-        return patterns.inferOrSelectDevice(params.destination);
+        return patterns.inferOrSelectDevice();
       }).then(function(uuid) {
         console.info("Connecting with: " + uuid);
         return resin.models.device.get(uuid);
@@ -88,7 +88,7 @@ limitations under the License.
           }
           return Promise["try"](function() {
             var command, spawn, subShellCommand;
-            command = "ssh " + verbose + " -t -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p " + options.port + " " + username + "@ssh." + (settings.get('proxyUrl')) + " enter " + uuid + " " + containerId;
+            command = "ssh " + verbose + " -t -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=no -p " + options.port + " " + username + "@ssh." + (settings.get('proxyUrl')) + " enter " + uuid + " " + containerId;
             subShellCommand = getSubShellCommand(command);
             return spawn = child_process.spawn(subShellCommand.program, subShellCommand.args, {
               stdio: 'inherit'
diff --git a/build/actions/sync.js b/build/actions/sync.js
index 2d8b8263..ff84ee6a 100644
--- a/build/actions/sync.js
+++ b/build/actions/sync.js
@@ -16,38 +16,79 @@ limitations under the License.
  */
 
 (function() {
+  var loadConfig;
+
+  loadConfig = function(source) {
+    var _, config, configPath, error, error1, fs, jsYaml, path, result;
+    fs = require('fs');
+    path = require('path');
+    _ = require('lodash');
+    jsYaml = require('js-yaml');
+    configPath = path.join(source, '.resin-sync.yml');
+    try {
+      config = fs.readFileSync(configPath, {
+        encoding: 'utf8'
+      });
+      result = jsYaml.safeLoad(config);
+    } catch (error1) {
+      error = error1;
+      if (error.code === 'ENOENT') {
+        return {};
+      }
+      throw error;
+    }
+    if (!_.isPlainObject(result)) {
+      throw new Error("Invalid configuration file: " + configPath);
+    }
+    return result;
+  };
+
   module.exports = {
-    signature: 'sync [destination]',
-    description: '(beta) sync your changes with a device',
-    help: 'WARNING: If you\'re running Windows, this command only supports `cmd.exe`.\n\nUse this command to sync your local changes to a certain device on the fly.\n\nThe `destination` argument can be either a device uuid or an application name.\n\nYou can save all the options mentioned below in a `resin-sync.yml` file,\nby using the same option names as keys. For example:\n\n	$ cat $PWD/resin-sync.yml\n	source: src/\n	before: \'echo Hello\'\n	ignore:\n		- .git\n		- node_modules/\n	progress: true\n	verbose: false\n\nNotice that explicitly passed command options override the ones set in the configuration file.\n\nExamples:\n\n	$ resin sync MyApp\n	$ resin sync 7cf02a6\n	$ resin sync 7cf02a6 --port 8080\n	$ resin sync 7cf02a6 --ignore foo,bar\n	$ resin sync 7cf02a6 -v',
+    signature: 'sync [uuid]',
+    description: '(beta) sync your changes to a device',
+    help: 'WARNING: If you\'re running Windows, this command only supports `cmd.exe`.\n\nUse this command to sync your local changes to a certain device on the fly.\n\nAfter every \'resin sync\' the updated settings will be saved in\n\'<source>/.resin-sync.yml\' and will be used in later invocations. You can\nalso change any option by editing \'.resin-sync.yml\' directly.\n\nHere is an example \'.resin-sync.yml\' :\n\n	$ cat $PWD/.resin-sync.yml\n	uuid: 7cf02a6\n	destination: \'/usr/src/app\'\n	before: \'echo Hello\'\n	after: \'echo Done\'\n	ignore:\n		- .git\n		- node_modules/\n\nCommand line options have precedence over the ones saved in \'.resin-sync.yml\'.\n\nIf \'.gitignore\' is found in the source directory then all explicitly listed files will be\nexcluded from the syncing process. You can choose to change this default behavior with the\n\'--skip-gitignore\' option.\n\nExamples:\n\n	$ resin sync 7cf02a6 --source . --destination /usr/src/app\n	$ resin sync 7cf02a6 -s /home/user/myResinProject -d /usr/src/app --before \'echo Hello\' --after \'echo Done\'\n	$ resin sync --ignore lib/\n	$ resin sync --verbose false\n	$ resin sync',
     permission: 'user',
     primary: true,
     options: [
       {
         signature: 'source',
         parameter: 'path',
-        description: 'custom source path',
+        description: 'local directory path to synchronize to device',
         alias: 's'
+      }, {
+        signature: 'destination',
+        parameter: 'path',
+        description: 'destination path on device',
+        alias: 'd'
       }, {
         signature: 'ignore',
         parameter: 'paths',
         description: 'comma delimited paths to ignore when syncing',
         alias: 'i'
+      }, {
+        signature: 'skip-gitignore',
+        boolean: true,
+        description: 'do not parse excluded/included files from .gitignore'
       }, {
         signature: 'before',
         parameter: 'command',
         description: 'execute a command before syncing',
         alias: 'b'
       }, {
-        signature: 'progress',
-        boolean: true,
-        description: 'show progress',
-        alias: 'p'
+        signature: 'after',
+        parameter: 'command',
+        description: 'execute a command after syncing',
+        alias: 'a'
       }, {
         signature: 'port',
         parameter: 'port',
         description: 'ssh port',
         alias: 't'
+      }, {
+        signature: 'progress',
+        boolean: true,
+        description: 'show progress',
+        alias: 'p'
       }, {
         signature: 'verbose',
         boolean: true,
@@ -56,20 +97,43 @@ limitations under the License.
       }
     ],
     action: function(params, options, done) {
-      var patterns, resin, resinSync;
+      var Promise, fs, path, patterns, resin, resinSync;
+      fs = require('fs');
+      path = require('path');
       resin = require('resin-sdk');
+      Promise = require('bluebird');
       resinSync = require('resin-sync');
       patterns = require('../utils/patterns');
-      if (options.ignore != null) {
-        options.ignore = options.ignore.split(',');
-      }
-      return resin.models.device.has(params.destination).then(function(isValidUUID) {
-        if (isValidUUID) {
-          return params.destination;
+      return Promise["try"](function() {
+        var error1;
+        try {
+          fs.accessSync(path.join(process.cwd(), '.resin-sync.yml'));
+        } catch (error1) {
+          if (options.source == null) {
+            throw new Error('No --source option passed and no \'.resin-sync.yml\' file found in current directory.');
+          }
         }
-        return patterns.inferOrSelectDevice(params.destination);
-      }).then(function(uuid) {
-        return resinSync.sync(uuid, options);
+        if (options.source == null) {
+          options.source = process.cwd();
+        }
+        if (options.ignore != null) {
+          options.ignore = options.ignore.split(',');
+        }
+        return Promise.resolve(params.uuid).then(function(uuid) {
+          var savedUuid;
+          if (uuid == null) {
+            savedUuid = loadConfig(options.source).uuid;
+            return patterns.inferOrSelectDevice(savedUuid);
+          }
+          return resin.models.device.has(uuid).then(function(hasDevice) {
+            if (!hasDevice) {
+              throw new Error("Device not found: " + uuid);
+            }
+            return uuid;
+          });
+        }).then(function(uuid) {
+          return resinSync.sync(uuid, options);
+        });
       }).nodeify(done);
     }
   };
diff --git a/build/utils/patterns.js b/build/utils/patterns.js
index 33b60be6..0fb977f7 100644
--- a/build/utils/patterns.js
+++ b/build/utils/patterns.js
@@ -16,7 +16,8 @@ limitations under the License.
  */
 
 (function() {
-  var Promise, _, chalk, form, messages, resin, validation, visuals;
+  var Promise, _, chalk, form, messages, resin, validation, visuals,
+    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
 
   _ = require('lodash');
 
@@ -191,25 +192,18 @@ limitations under the License.
     });
   };
 
-  exports.inferOrSelectDevice = function(applicationName) {
-    return Promise["try"](function() {
-      if (applicationName != null) {
-        return resin.models.device.getAllByApplication(applicationName);
-      }
-      return resin.models.device.getAll();
-    }).filter(function(device) {
+  exports.inferOrSelectDevice = function(preferredUuid) {
+    return resin.models.device.getAll().filter(function(device) {
       return device.is_online;
-    }).then(function(devices) {
-      if (_.isEmpty(devices)) {
-        throw new Error('You don\'t have any devices');
-      }
-      if (devices.length === 1) {
-        return _.first(devices).uuid;
+    }).then(function(onlineDevices) {
+      if (_.isEmpty(onlineDevices)) {
+        throw new Error('You don\'t have any devices online');
       }
       return form.ask({
         message: 'Select a device',
         type: 'list',
-        choices: _.map(devices, function(device) {
+        "default": indexOf.call(_.map(onlineDevices, 'uuid'), preferredUuid) >= 0 ? preferredUuid : onlineDevices[0].uuid,
+        choices: _.map(onlineDevices, function(device) {
           return {
             name: (device.name || 'Untitled') + " (" + (device.uuid.slice(0, 7)) + ")",
             value: device.uuid