npm version 2.8.11. [CB-3460] Serializing event hooks now fixed.
diff --git a/bin/cordova b/bin/cordova
index fc47ad1..60b2e09 100755
--- a/bin/cordova
+++ b/bin/cordova
@@ -19,6 +19,7 @@
 	console.error(err);
     process.exit(1);
 });
+cordova.on('result', console.log);
 if (verbose) {
     cordova.on('log', console.log);
     cordova.on('warn', console.warn);
@@ -31,20 +32,18 @@
     console.log(cordova.help());
 } else if (cordova.hasOwnProperty(cmd)) {
     var opts = Array.prototype.slice.call(tokens, 0);
-    var r;
     if (cmd == 'create' || cmd == 'docs' || cmd == 'serve') {
-        r = cordova[cmd].apply(this, opts);
+        cordova[cmd].apply(this, opts);
     } else if (cmd == 'emulate' || cmd == 'build' || cmd == 'prepare' || cmd == 'compile' || cmd == 'run') {
-        r = cordova[cmd].call(this, opts);
+        cordova[cmd].call(this, opts);
     } else {
         // platform or plugin cmds
         if (tokens.length > 2) {
             opts = [tokens.shift()];
             opts.push(tokens);
         }
-        r = cordova[cmd].apply(this, opts);
+        cordova[cmd].apply(this, opts);
     }
-    if (r) console.log(r);
 } else {
     throw new Error('Cordova does not know ' + cmd + '; try help for a list of all the available commands.');
 }
diff --git a/cordova.js b/cordova.js
index efdc4b2..694089c 100644
--- a/cordova.js
+++ b/cordova.js
@@ -52,6 +52,9 @@
     },
     off:       off,
     removeListener:off,
+    removeAllListeners:function() {
+        cordova_events.removeAllListeners.apply(cordova_events, arguments);
+    },
     emit:      emit,
     trigger:   emit,
     build:     function() {
diff --git a/package.json b/package.json
index 103ba49..fde2ff7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "cordova",
-  "version": "2.8.10",
+  "version": "2.8.11",
   "preferGlobal": "true",
   "description": "Cordova command line interface tool",
   "main": "cordova",
diff --git a/spec/cordova-cli/compile.spec.js b/spec/cordova-cli/compile.spec.js
index bd7dc42..0ebfc1a 100644
--- a/spec/cordova-cli/compile.spec.js
+++ b/spec/cordova-cli/compile.spec.js
@@ -96,51 +96,49 @@
     });
 
     describe('hooks', function() {
-        var s;
+        var hook_spy;
+        var shell_spy;
         beforeEach(function() {
-            s = spyOn(hooker.prototype, 'fire').andReturn(true);
+            hook_spy = spyOn(hooker.prototype, 'fire').andCallFake(function(hook, opts, cb) {
+                cb();
+            });
+            shell_spy = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
+                cb(0, 'yup'); // fake out shell so system thinks every shell-out is successful
+            });
+            cordova.create(tempDir);
+            process.chdir(tempDir);
+        });
+        afterEach(function() {
+            hook_spy.reset();
+            shell_spy.reset();
+            process.chdir(cwd);
         });
 
         describe('when platforms are added', function() {
             beforeEach(function() {
-                shell.mv('-f', path.join(cordova_project, 'platforms', 'android'), path.join(tempDir));
-                process.chdir(cordova_project);
-            });
-            afterEach(function() {
-                shell.mv('-f', path.join(tempDir, 'android'), path.join(cordova_project, 'platforms', 'android'));
-                process.chdir(cwd);
+                shell.mkdir(path.join(tempDir, 'platforms', 'android'));
+                shell.mkdir(path.join(tempDir, 'platforms', 'blackberry'));
             });
 
             it('should fire before hooks through the hooker module', function() {
-                spyOn(shell, 'exec');
                 cordova.compile();
-                expect(s).toHaveBeenCalledWith('before_compile');
+                expect(hook_spy).toHaveBeenCalledWith('before_compile', {platforms:['android', 'blackberry']}, jasmine.any(Function));
             });
             it('should fire after hooks through the hooker module', function(done) {
-                spyOn(shell, 'exec').andCallFake(function(cmd, options, callback) {
-                    callback(0, 'fucking eh');
-                });
                 cordova.compile('android', function() {
-                     expect(hooker.prototype.fire).toHaveBeenCalledWith('after_compile');
+                     expect(hook_spy).toHaveBeenCalledWith('after_compile', {platforms:['android']}, jasmine.any(Function));
                      done();
                 });
             });
         });
 
         describe('with no platforms added', function() {
-            beforeEach(function() {
-                cordova.create(tempDir);
-                process.chdir(tempDir);
-            });
-            afterEach(function() {
-                process.chdir(cwd);
-            });
             it('should not fire the hooker', function() {
                 expect(function() {
                     cordova.compile();
                 }).toThrow();
-                expect(s).not.toHaveBeenCalledWith('before_compile');
-                expect(s).not.toHaveBeenCalledWith('after_compile');
+                expect(hook_spy).not.toHaveBeenCalled();
+                expect(hook_spy).not.toHaveBeenCalled();
             });
         });
     });
diff --git a/spec/cordova-cli/emulate.spec.js b/spec/cordova-cli/emulate.spec.js
index 93eaa90..7e4850b 100644
--- a/spec/cordova-cli/emulate.spec.js
+++ b/spec/cordova-cli/emulate.spec.js
@@ -72,56 +72,56 @@
                 cordova.emulate();
                 a_spy.mostRecentCall.args[1](); // fake out android parser
                 expect(s).toHaveBeenCalled();
-                expect(s.mostRecentCall.args[0]).toMatch(/cordova.run" --debug --emulator$/gi);
+                expect(s.mostRecentCall.args[0]).toMatch(/cordova.run" --emulator$/gi);
             }).not.toThrow();
         });
     });
 
     describe('hooks', function() {
-        var s;
+        var hook_spy;
+        var shell_spy;
         beforeEach(function() {
-            s = spyOn(hooker.prototype, 'fire').andReturn(true);
+            hook_spy = spyOn(hooker.prototype, 'fire').andCallFake(function(hook, opts, cb) {
+                if (cb) cb();
+                else opts();
+            });
+            shell_spy = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
+                cb(0, 'yup'); // fake out shell so system thinks every shell-out is successful
+            });
+            process.chdir(tempDir);
+        });
+        afterEach(function() {
+            hook_spy.reset();
+            shell_spy.reset();
+            process.chdir(cwd);
         });
 
         describe('when platforms are added', function() {
+            var android_platform = path.join(tempDir, 'platforms', 'android');
             beforeEach(function() {
-                shell.cp('-Rf', path.join(cordova_project, 'platforms', 'android'), path.join(tempDir, 'platforms'));
-                process.chdir(tempDir);
+                shell.mkdir('-p', path.join(android_platform, 'assets', 'www'));
+                fs.writeFileSync(path.join(android_platform, 'AndroidManifest.xml'), '<xml></xml>', 'utf-8');
             });
-            afterEach(function() {
-                process.chdir(cwd);
-            });
-
             it('should fire before hooks through the hooker module', function() {
 
-                spyOn(shell, 'exec');
                 cordova.emulate();
-                expect(hooker.prototype.fire).toHaveBeenCalledWith('before_emulate');
+                expect(hook_spy).toHaveBeenCalledWith('before_emulate', {platforms:['android']}, jasmine.any(Function));
             });
-            it('should fire after hooks through the hooker module', function() {
-                spyOn(shell, 'exec').andCallFake(function(cmd, options, callback) {
-                    callback(0, 'fucking eh');
-                });
+            it('should fire after hooks through the hooker module', function(done) {
                 cordova.emulate('android', function() {
-                     expect(hooker.prototype.fire).toHaveBeenCalledWith('after_emulate');
+                     expect(hook_spy).toHaveBeenCalledWith('after_emulate', {platforms:['android']}, jasmine.any(Function));
+                     done();
                 });
             });
         });
 
         describe('with no platforms added', function() {
-            beforeEach(function() {
-                process.chdir(tempDir);
-            });
-            afterEach(function() {
-                process.chdir(cwd);
-            });
             it('should not fire the hooker', function() {
-                spyOn(shell, 'exec');
                 expect(function() {
                     cordova.emulate();
                 }).toThrow();
-                expect(s).not.toHaveBeenCalledWith('before_emulate');
-                expect(s).not.toHaveBeenCalledWith('after_emulate');
+                expect(hook_spy).not.toHaveBeenCalled();
+                expect(hook_spy).not.toHaveBeenCalled();
             });
         });
     });
diff --git a/spec/cordova-cli/hooker.spec.js b/spec/cordova-cli/hooker.spec.js
index ff92f43..6b0129a 100644
--- a/spec/cordova-cli/hooker.spec.js
+++ b/spec/cordova-cli/hooker.spec.js
@@ -63,12 +63,13 @@
         });
 
         describe('failure', function() {
-            it('should not throw if the hook is unrecognized', function() {
-                expect(function() {
-                    h.fire('CLEAN YOUR SHORTS GODDAMNIT LIKE A BIG BOY!');
-                }).not.toThrow();
+            it('should not error if the hook is unrecognized', function(done) {
+                h.fire('CLEAN YOUR SHORTS GODDAMNIT LIKE A BIG BOY!', function(err){
+                    expect(err).not.toBeDefined();
+                    done();
+                });
             });
-            it('should throw if any script exits with non-zero code', function() {
+            it('should error if any script exits with non-zero code', function(done) {
                 var script;
                 if (platform.match(/(win32|win64)/)) {
                     script = path.join(tempDir, '.cordova', 'hooks', 'before_build', 'fail.bat');
@@ -78,14 +79,15 @@
                     shell.cp(path.join(hooks, 'fail', 'fail.sh'), script);
                 }
                 fs.chmodSync(script, '754');
-                expect(function() {
-                    h.fire('before_build');
-                }).toThrow();
+                h.fire('before_build', function(err){
+                    expect(err).toBeDefined();
+                    done();
+                });
             });
         });
 
         describe('success', function() {
-            it('should execute all scripts in order and return true', function() {
+            it('should execute all scripts in order and fire callback', function(done) {
                 var hook = path.join(tempDir, '.cordova', 'hooks', 'before_build');
                 if (platform.match(/(win32|win64)/)) {
                     shell.cp(path.join(hooks, 'test', '0.bat'), hook);
@@ -98,20 +100,22 @@
                     fs.chmodSync(path.join(hook, script), '754');
                 });
                 var returnValue;
-                var s = spyOn(shell, 'exec').andReturn({code:0});
-                expect(function() {
-                    returnValue = h.fire('before_build');
-                }).not.toThrow();
-                expect(returnValue).toBe(true);
-                if (platform.match(/(win32|win64)/)) {
-                    expect(s.calls[0].args[0]).toMatch(/0.bat/);
-                    expect(s.calls[1].args[0]).toMatch(/1.bat/);
-                } else {
-                    expect(s.calls[0].args[0]).toMatch(/0.sh/);
-                    expect(s.calls[1].args[0]).toMatch(/1.sh/);
-                }
+                var s = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
+                    cb(0, '');
+                });
+                h.fire('before_build', function(err) {
+                    expect(err).not.toBeDefined();
+                    if (platform.match(/(win32|win64)/)) {
+                        expect(s.calls[0].args[0]).toMatch(/0.bat/);
+                        expect(s.calls[1].args[0]).toMatch(/1.bat/);
+                    } else {
+                        expect(s.calls[0].args[0]).toMatch(/0.sh/);
+                        expect(s.calls[1].args[0]).toMatch(/1.sh/);
+                    }
+                    done();
+                });
             });
-            it('should pass the project root folder as parameter into the project-level hooks', function() {
+            it('should pass the project root folder as parameter into the project-level hooks', function(done) {
                 var hook = path.join(tempDir, '.cordova', 'hooks', 'before_build');
                 if (platform.match(/(win32|win64)/)) {
                     shell.cp(path.join(hooks, 'test', '0.bat'), hook);
@@ -121,19 +125,20 @@
                 fs.readdirSync(hook).forEach(function(script) {
                     fs.chmodSync(path.join(hook, script), '754');
                 });
-                var returnValue;
-                var s = spyOn(shell, 'exec').andReturn({code:0});
-                expect(function() {
-                    returnValue = h.fire('before_build');
-                }).not.toThrow();
-                expect(returnValue).toBe(true);
-                var param_str;
-                if (platform.match(/(win32|win64)/)) {
-                    param_str = '0.bat "'+tempDir+'"';
-                } else { 
-                    param_str = '0.sh "'+tempDir+'"'; 
-                }
-                expect(s.calls[0].args[0].indexOf(param_str)).not.toEqual(-1);
+                var s = spyOn(shell, 'exec').andCallFake(function(cmd, opts, cb) {
+                    cb(0, '');
+                });
+                h.fire('before_build', function(err) {
+                    expect(err).not.toBeDefined();
+                    var param_str;
+                    if (platform.match(/(win32|win64)/)) {
+                        param_str = '0.bat "'+tempDir+'"';
+                    } else { 
+                        param_str = '0.sh "'+tempDir+'"'; 
+                    }
+                    expect(s.calls[0].args[0].indexOf(param_str)).not.toEqual(-1);
+                    done();
+                });
             });
             describe('module-level hooks', function() {
                 var handler = jasmine.createSpy();
@@ -143,21 +148,53 @@
                     handler.reset();
                 });
 
-                it('should fire handlers using cordova.on', function() {
+                it('should fire handlers using cordova.on', function(done) {
                     cordova.on(test_event, handler);
-                    h.fire(test_event);
-                    expect(handler).toHaveBeenCalled();
+                    h.fire(test_event, function(err) {
+                        expect(handler).toHaveBeenCalled();
+                        expect(err).not.toBeDefined();
+                        done();
+                    });
                 });
-                it('should pass the project root folder as parameter into the module-level handlers', function() {
+                it('should pass the project root folder as parameter into the module-level handlers', function(done) {
                     cordova.on(test_event, handler);
-                    h.fire('before_build');
-                    expect(handler).toHaveBeenCalledWith(tempDir);
+                    h.fire(test_event, function(err) {
+                        expect(handler).toHaveBeenCalledWith({root:tempDir});
+                        expect(err).not.toBeDefined();
+                        done();
+                    });
                 });
-                it('should be able to stop listening to events using cordova.off', function() {
+                it('should be able to stop listening to events using cordova.off', function(done) {
                     cordova.on(test_event, handler);
                     cordova.off(test_event, handler);
-                    h.fire('before_build');
-                    expect(handler).not.toHaveBeenCalled();
+                    h.fire(test_event, function(err) {
+                        expect(handler).not.toHaveBeenCalled();
+                        done();
+                    });
+                });
+                it('should allow for hook to opt into asynchronous execution and block further hooks from firing using the done callback', function(done) {
+                    var h1_fired = false;
+                    var h1 = function(root, cb) {
+                        h1_fired = true;
+                        setTimeout(cb, 100);
+                    };
+                    var h2_fired = false;
+                    var h2 = function() {
+                        h2_fired = true;
+                    };
+                    runs(function() {
+                        cordova.on(test_event, h1);
+                        cordova.on(test_event, h2);
+                        h.fire(test_event, function(err) {
+                            done();
+                        });
+                        expect(h1_fired).toBe(true);
+                        expect(h2_fired).toBe(false);
+                    });
+                    waits(100);
+                    runs(function() {
+                        expect(h2_fired).toBe(true);
+                    });
                 });
             });
         });
diff --git a/spec/cordova-cli/platform.spec.js b/spec/cordova-cli/platform.spec.js
index 301ec4b..4e21aad 100644
--- a/spec/cordova-cli/platform.spec.js
+++ b/spec/cordova-cli/platform.spec.js
@@ -75,18 +75,28 @@
 
         afterEach(function() {
             process.chdir(cwd);
+            cordova.removeAllListeners('results'); // clean up event listener
         });
 
-        it('should list out no platforms for a fresh project', function() {
+        it('should list out no platforms for a fresh project', function(done) {
             shell.rm('-rf', path.join(tempDir, 'platforms', '*'));
-            expect(cordova.platform('list').length).toEqual(0);
+            cordova.on('results', function(res) {
+                expect(res).toEqual('No platforms added. Use `cordova platform add <platform>`.');
+                done();
+            });
+            cordova.platform('list');
         });
 
-        it('should list out added platforms in a project', function() {
+        it('should list out added platforms in a project', function(done) {
             var platforms = path.join(tempDir, 'platforms');
             shell.mkdir(path.join(platforms, 'android'));
             shell.mkdir(path.join(platforms, 'ios'));
-            expect(cordova.platform('list').length).toEqual(2);
+            
+            cordova.on('results', function(res) {
+                expect(res.length).toEqual(2);
+                done();
+            });
+            cordova.platform('list');
         });
     });
 
@@ -121,21 +131,32 @@
 
         afterEach(function() {
             process.chdir(cwd);
+            cordova.removeAllListeners('results');
         });
 
-        it('should remove a supported and added platform', function() {
+        it('should remove a supported and added platform', function(done) {
             shell.mkdir(path.join(tempDir, 'platforms', 'android'));
             shell.mkdir(path.join(tempDir, 'platforms', 'ios'));
-            cordova.platform('remove', 'android');
-            expect(cordova.platform('ls').length).toEqual(1);
+            cordova.platform('remove', 'android', function() {
+                cordova.on('results', function(res) {
+                    expect(res.length).toEqual(1);
+                    done();
+                });
+                cordova.platform('list');
+            });
         });
 
-        it('should be able to remove multiple platforms', function() {
+        it('should be able to remove multiple platforms', function(done) {
             shell.mkdir(path.join(tempDir, 'platforms', 'android'));
             shell.mkdir(path.join(tempDir, 'platforms', 'blackberry'));
             shell.mkdir(path.join(tempDir, 'platforms', 'ios'));
-            cordova.platform('remove', ['android','blackberry']);
-            expect(cordova.platform('ls').length).toEqual(1);
+            cordova.platform('remove', ['android','blackberry'], function() {
+                cordova.on('results', function(res) {
+                    expect(res.length).toEqual(1);
+                    done();
+                });
+                cordova.platform('list');
+            });
         });
     });
 
@@ -144,7 +165,10 @@
         beforeEach(function() {
             cordova.create(tempDir);
             process.chdir(tempDir);
-            s = spyOn(hooker.prototype, 'fire').andReturn(true);
+            s = spyOn(hooker.prototype, 'fire').andCallFake(function(hook, opts, cb) {
+                if (cb) cb();
+                else opts();
+            });
         });
         afterEach(function() {
             process.chdir(cwd);
@@ -154,21 +178,21 @@
         describe('list (ls) hooks', function() {
             it('should fire before hooks through the hooker module', function() {
                 cordova.platform();
-                expect(s).toHaveBeenCalledWith('before_platform_ls');
+                expect(s).toHaveBeenCalledWith('before_platform_ls', jasmine.any(Function));
             });
             it('should fire after hooks through the hooker module', function() {
                 cordova.platform();
-                expect(s).toHaveBeenCalledWith('after_platform_ls');
+                expect(s).toHaveBeenCalledWith('after_platform_ls', jasmine.any(Function));
             });
         });
         describe('remove (rm) hooks', function() {
             it('should fire before hooks through the hooker module', function() {
                 cordova.platform('rm', 'android');
-                expect(s).toHaveBeenCalledWith('before_platform_rm');
+                expect(s).toHaveBeenCalledWith('before_platform_rm', {platforms:['android']}, jasmine.any(Function));
             });
             it('should fire after hooks through the hooker module', function() {
                 cordova.platform('rm', 'android');
-                expect(s).toHaveBeenCalledWith('after_platform_rm');
+                expect(s).toHaveBeenCalledWith('after_platform_rm', {platforms:['android']}, jasmine.any(Function));
             });
         });
         describe('add hooks', function() {
@@ -191,8 +215,8 @@
                 fake_reqs_check();
                 fake_create(path.join(tempDir, 'platforms', 'android'));
                 ap.mostRecentCall.args[1](); // fake out update_project
-                expect(s).toHaveBeenCalledWith('before_platform_add');
-                expect(s).toHaveBeenCalledWith('after_platform_add');
+                expect(s).toHaveBeenCalledWith('before_platform_add', {platforms:['android']}, jasmine.any(Function));
+                expect(s).toHaveBeenCalledWith('after_platform_add', {platforms:['android']}, jasmine.any(Function));
             });
         });
     });
diff --git a/spec/cordova-cli/plugin.spec.js b/spec/cordova-cli/plugin.spec.js
index 43d4a9a..d042841 100644
--- a/spec/cordova-cli/plugin.spec.js
+++ b/spec/cordova-cli/plugin.spec.js
@@ -69,6 +69,7 @@
 
        afterEach(function() {
            process.chdir(cwd);
+           cordova.removeAllListeners('results');
        });
 
        it('should not fail when the plugins directory is missing', function() {
@@ -79,33 +80,44 @@
            }).not.toThrow();
        });
 
-       it('should ignore files, like .gitignore, in the plugins directory', function() {
+       it('should ignore files, like .gitignore, in the plugins directory', function(done) {
            var someFile = path.join(tempDir, 'plugins', '.gitignore');
            fs.writeFileSync(someFile, 'not a plugin');
+           cordova.on('results', function(res) {
+               expect(res).toEqual('No plugins added. Use `cordova plugin add <plugin>`.');
+               done();
+           });
 
-           expect(cordova.plugin('list')).toEqual('No plugins added. Use `cordova plugin add <plugin>`.');
+           cordova.plugin('list');
        });
     });
 
     describe('`ls`', function() {
         beforeEach(function() {
             cordova.create(tempDir);
+            process.chdir(tempDir);
         });
 
         afterEach(function() {
             process.chdir(cwd);
+            cordova.removeAllListeners('results');
         });
 
-        it('should list out no plugins for a fresh project', function() {
-            process.chdir(tempDir);
-
-            expect(cordova.plugin('list')).toEqual('No plugins added. Use `cordova plugin add <plugin>`.');
+        it('should list out no plugins for a fresh project', function(done) {
+            cordova.on('results', function(res) {
+                expect(res).toEqual('No plugins added. Use `cordova plugin add <plugin>`.');
+                done();
+            });
+            cordova.plugin('list');
         });
-        it('should list out any added plugins in a project', function() {
-            process.chdir(tempDir);
+        it('should list out any added plugins in a project', function(done) {
             var random_plug = 'randomplug';
             shell.mkdir('-p', path.join(tempDir, 'plugins', random_plug));
-            expect(cordova.plugin('list')).toEqual([random_plug]);
+            cordova.on('results', function(res) {
+                expect(res).toEqual([random_plug]);
+                done();
+            });
+            cordova.plugin('list');
         });
     });
 
diff --git a/spec/cordova-cli/prepare.spec.js b/spec/cordova-cli/prepare.spec.js
index b420024..94ca13b 100644
--- a/spec/cordova-cli/prepare.spec.js
+++ b/spec/cordova-cli/prepare.spec.js
@@ -23,6 +23,7 @@
     path = require('path'),
     fs = require('fs'),
     config_parser = require('../../src/config_parser'),
+    android_parser = require('../../src/metadata/android_parser'),
     hooker = require('../../src/hooker'),
     fixtures = path.join(__dirname, '..', 'fixtures'),
     test_plugin = path.join(fixtures, 'plugins', 'android'),
@@ -127,7 +128,7 @@
 
             it('should fire before hooks through the hooker module', function() {
                 cordova.prepare();
-                expect(s).toHaveBeenCalledWith('before_prepare');
+                expect(s).toHaveBeenCalledWith('before_prepare', jasmine.any(Function));
             });
             it('should fire after hooks through the hooker module', function() {
                 spyOn(shell, 'exec');
diff --git a/spec/cordova-cli/run.spec.js b/spec/cordova-cli/run.spec.js
index 359a723..7e505ef 100644
--- a/spec/cordova-cli/run.spec.js
+++ b/spec/cordova-cli/run.spec.js
@@ -79,7 +79,7 @@
                 cb(0, 'yep');
             });
             cordova.run('android', function() {
-                 expect(spy.mostRecentCall.args[0]).toMatch(/cordova.run" --debug --device$/gi);
+                 expect(spy.mostRecentCall.args[0]).toMatch(/cordova.run" --device$/gi);
                  done();
             });
         });
@@ -89,7 +89,10 @@
     describe('hooks', function() {
         var s;
         beforeEach(function() {
-            s = spyOn(hooker.prototype, 'fire').andReturn(true);
+            s = spyOn(hooker.prototype, 'fire').andCallFake(function(hook, opts, cb) {
+                if (cb) cb();
+                else opts();
+            });
         });
 
         describe('when platforms are added', function() {
@@ -105,14 +108,14 @@
 
                 spyOn(shell, 'exec');
                 cordova.run();
-                expect(hooker.prototype.fire).toHaveBeenCalledWith('before_run');
+                expect(hooker.prototype.fire).toHaveBeenCalledWith('before_run', {platforms:['android']}, jasmine.any(Function));
             });
             it('should fire after hooks through the hooker module', function(done) {
                 spyOn(shell, 'exec').andCallFake(function(cmd, options, callback) {
                     callback(0, 'fucking eh');
                 });
                 cordova.run('android', function() {
-                     expect(hooker.prototype.fire).toHaveBeenCalledWith('after_run');
+                     expect(hooker.prototype.fire).toHaveBeenCalledWith('after_run', {platforms:['android']}, jasmine.any(Function));
                      done();
                 });
             });
diff --git a/src/compile.js b/src/compile.js
index f969624..39c78bc 100644
--- a/src/compile.js
+++ b/src/compile.js
@@ -67,27 +67,35 @@
         else throw err;
         return;
     }
+    var opts = {
+        platforms:platformList
+    };
 
     var hooks = new hooker(projectRoot);
-    hooks.fire('before_compile', function(err) {
+    hooks.fire('before_compile', opts, function(err) {
         if (err) {
-            var error = new Error('before_compile hooks exited with non-zero code or caused an error: ' + err.message);
-            if (callback) callback(error);
-            else throw error;
+            if (callback) callback(err);
+            else throw err;
         } else {
             var end = n(platformList.length, function() {
-                hooks.fire('after_compile', function(error) {
-                    if (error) {
-                        var arr = new Error('after_compile hooks exited with non-zero code. Aborting.');
-                        if (callback) callback(arr);
-                        else throw arr;
-                    } else if (callback) callback();
+                hooks.fire('after_compile', opts, function(err) {
+                    if (err) {
+                        if (callback) callback(err);
+                        else throw err;
+                    } else {
+                        if (callback) callback();
+                    }
                 });
             });
 
             // Iterate over each added platform
             platformList.forEach(function(platform) {
-                shell_out_to_build(projectRoot, platform, end);
+                try {
+                    shell_out_to_build(projectRoot, platform, end);
+                } catch(e) {
+                    if (callback) callback(e);
+                    else throw e;
+                }
             });
         }
     });
diff --git a/src/emulate.js b/src/emulate.js
index 2d902b2..b8bc4ad 100644
--- a/src/emulate.js
+++ b/src/emulate.js
@@ -31,7 +31,7 @@
     util              = require('util');
 
 function shell_out_to_emulate(root, platform, done) {
-    var cmd = '"' + path.join(root, 'platforms', platform, 'cordova', 'run') + '" --debug --emulator';
+    var cmd = '"' + path.join(root, 'platforms', platform, 'cordova', 'run') + '" --emulator';
     // TODO: inconsistent API for BB10 run command
     if (platform == 'blackberry') {
         var bb_project = path.join(root, 'platforms', 'blackberry')
@@ -83,40 +83,43 @@
         else throw err;
         return;
     }
+    var opts = {
+        platforms:platformList
+    };
 
     var hooks = new hooker(projectRoot);
-    if (!(hooks.fire('before_emulate'))) {
-        var err = new Error('before_emulate hooks exited with non-zero code. Aborting build.');
-        if (callback) callback(err);
-        else throw err;
-        return;
-    }
-
-    var end = n(platformList.length, function() {
-        if (!(hooks.fire('after_emulate'))) {
-            var err = new Error('after_emulate hooks exited with non-zero code. Aborting.');
-            if (callback) callback(err);
-            else throw err;
-            return;
-        }
-        if (callback) callback();
-    });
-
-    // Run a prepare first!
-    prepare(platformList, function(err) {
+    hooks.fire('before_emulate', opts, function(err) {
         if (err) {
             if (callback) callback(err);
             else throw err;
         } else {
-            platformList.forEach(function(platform) {
-                try {
-                    shell_out_to_emulate(projectRoot, platform, end);
-                } catch(e) {
-                    if (callback) callback(e);
-                    else throw e;
+            var end = n(platformList.length, function() {
+                hooks.fire('after_emulate', opts, function(err) {
+                    if (err) {
+                        if (callback) callback(err);
+                        else throw err;
+                    } else {
+                        if (callback) callback();
+                    }
+                });
+            });
+
+            // Run a prepare first!
+            prepare(platformList, function(err) {
+                if (err) {
+                    if (callback) callback(err);
+                    else throw err;
+                } else {
+                    platformList.forEach(function(platform) {
+                        try {
+                            shell_out_to_emulate(projectRoot, platform, end);
+                        } catch(e) {
+                            if (callback) callback(e);
+                            else throw e;
+                        }
+                    });
                 }
             });
         }
     });
 };
-
diff --git a/src/hooker.js b/src/hooker.js
index 15c9a6c..a483b8f 100644
--- a/src/hooker.js
+++ b/src/hooker.js
@@ -29,24 +29,32 @@
 }
 
 module.exports.prototype = {
-    fire:function fire(hook, callback) {
+    fire:function fire(hook, opts, callback) {
+        if (arguments.length == 2) {
+            callback = opts;
+            opts = {};
+        }
         var self = this;
         var dir = path.join(this.root, '.cordova', 'hooks', hook);
-        if (!(fs.existsSync(dir))) return true; // hooks directory got axed post-create; ignore.
+        opts.root = this.root;
 
         // Fire JS hook for the event
         // These ones need to "serialize" events, that is, each handler attached to the event needs to finish processing (if it "opted in" to the callback) before the next one will fire.
         var handlers = events.listeners(hook);
-        execute_handlers_serially(handlers, this.root, function() {
+        execute_handlers_serially(handlers, opts, function() {
             // Fire script-based hooks
-            var scripts = fs.readdirSync(dir);
-            execute_scripts_serially(scripts, self.root, dir, function(err) {
-                if (err) {
-                    callback(err);
-                } else {
-                    callback();
-                }
-            });
+            if (!(fs.existsSync(dir))) {
+                callback(); // hooks directory got axed post-create; ignore.
+            } else {
+                var scripts = fs.readdirSync(dir);
+                execute_scripts_serially(scripts, self.root, dir, function(err) {
+                    if (err) {
+                        callback(err);
+                    } else {
+                        callback();
+                    }
+                });
+            }
         });
     }
 }
@@ -74,16 +82,16 @@
     }
 }
 
-function execute_handlers_serially(handlers, root, callback) {
+function execute_handlers_serially(handlers, opts, callback) {
     if (handlers.length) {
         var h = handlers.shift();
         if (h.length > 1) {
             h(root, function() {
-                execute_handlers_serially(handlers, root, callback);
+                execute_handlers_serially(handlers, opts, callback);
             });
         } else {
-            h(root);
-            execute_handlers_serially(handlers, root, callback);
+            h(opts);
+            execute_handlers_serially(handlers, opts, callback);
         }
     } else {
         callback();
diff --git a/src/platform.js b/src/platform.js
index c36b582..82cc532 100644
--- a/src/platform.js
+++ b/src/platform.js
@@ -38,8 +38,7 @@
         return;
     }
 
-    var hooks = new hooker(projectRoot),
-        end;
+    var hooks = new hooker(projectRoot);
 
     var createOverrides = function(target) {
         shell.mkdir('-p', path.join(cordova_util.appDir(projectRoot), 'merges', target));
@@ -48,95 +47,128 @@
     if (arguments.length === 0) command = 'ls';
     if (targets) {
         if (!(targets instanceof Array)) targets = [targets];
-        end = n(targets.length, function() {
-            if (callback) callback();
-        });
     }
 
     var xml = cordova_util.projectConfig(projectRoot);
     var cfg = new config_parser(xml);
+    var opts = {
+        platforms:targets
+    };
 
     switch(command) {
         case 'ls':
         case 'list':
-            // TODO before+after hooks here are awkward
-            hooks.fire('before_platform_ls');
-            hooks.fire('after_platform_ls');
-            return fs.readdirSync(path.join(projectRoot, 'platforms'));
-            break;
-        case 'add':
-            targets.forEach(function(target) {
-                hooks.fire('before_platform_add');
-                var output = path.join(projectRoot, 'platforms', target);
-
-                // Check if output directory already exists.
-                if (fs.existsSync(output)) {
-                    var err = new Error('Platform "' + target + '" already exists' );
+            var platforms_on_fs = fs.readdirSync(path.join(projectRoot, 'platforms'));
+            hooks.fire('before_platform_ls', function(err) {
+                if (err) {
                     if (callback) callback(err);
                     else throw err;
-                    return;
+                } else {
+                    events.emit('results', (platforms_on_fs.length ? platforms_on_fs : 'No platforms added. Use `cordova platform add <platform>`.'));
+                    hooks.fire('after_platform_ls', function(err) {
+                        if (err) {
+                            if (callback) callback(err);
+                            else throw err;
+                        }
+                    });
                 }
-
-                // Make sure we have minimum requirements to work with specified platform
-                events.emit('log', 'Checking if platform "' + target + '" passes minimum requirements.');
-                module.exports.supports(target, function(err) {
+            });
+            break;
+        case 'add':
+            var end = n(targets.length, function() {
+                hooks.fire('after_platform_add', opts, function(err) {
                     if (err) {
-                        var err = new Error('Your system does not meet the requirements to create ' + target + ' projects: ' + err.message);
                         if (callback) callback(err);
                         else throw err;
-                        return;
                     } else {
-                        // Create a platform app using the ./bin/create scripts that exist in each repo.
-                        // Run platform's create script
-                        var bin = path.join(cordova_util.libDirectory, 'cordova-' + target, 'bin', 'create');
-                        var args = (target=='ios') ? '--arc' : '';
-                        var pkg = cfg.packageName().replace(/[^\w.]/g,'_');
-                        var name = cfg.name().replace(/\W/g,'_');
-                        // TODO: PLATFORM LIBRARY INCONSISTENCY: order/number of arguments to create
-                        // TODO: keep tabs on CB-2300
-                        var command = util.format('"%s" %s "%s" "%s" "%s"', bin, args, output, (target=='blackberry'?name:pkg), name);
-                        events.emit('log', 'Running bin/create for platform "' + target + '" with command: "' + command + '" (output to follow)');
-
-                        shell.exec(command, {silent:true,async:true}, function(code, create_output) {
-                            events.emit('log', create_output);
-                            if (code > 0) {
-                                var err = new Error('An error occured during creation of ' + target + ' sub-project. ' + create_output);
-                                if (callback) callback(err);
-                                else throw err;
-                                return;
-                            }
-
-                            var parser = new platforms[target].parser(output);
-                            events.emit('log', 'Updating ' + target + ' project from config.xml...');
-                            parser.update_project(cfg, function() {
-                                createOverrides(target);
-                                hooks.fire('after_platform_add');
-
-                                // Install all currently installed plugins into this new platform.
-                                var pluginsDir = path.join(projectRoot, 'plugins');
-                                var plugins = fs.readdirSync(pluginsDir);
-                                plugins && plugins.forEach(function(plugin) {
-                                    if (!fs.statSync(path.join(projectRoot, 'plugins', plugin)).isDirectory()) {
-                                        return;
-                                    }
-
-                                    events.emit('log', 'Installing plugin "' + plugin + '" following success platform add of ' + target);
-                                    plugman.install(target, output, path.basename(plugin), pluginsDir, { www_dir: parser.staging_dir() });
-                                });
-                                end();
-                            });
-                        });
+                        if (callback) callback();
                     }
                 });
             });
+            hooks.fire('before_platform_add', opts, function(err) {
+                if (err) {
+                    if (callback) callback(err);
+                    else throw err;
+                } else {
+                    targets.forEach(function(target) {
+                        var output = path.join(projectRoot, 'platforms', target);
+
+                        // Check if output directory already exists.
+                        if (fs.existsSync(output)) {
+                            var err = new Error('Platform "' + target + '" already exists at "' + output + '"');
+                            if (callback) callback(err);
+                            else throw err;
+                        } else {
+                            // Make sure we have minimum requirements to work with specified platform
+                            events.emit('log', 'Checking if platform "' + target + '" passes minimum requirements...');
+                            module.exports.supports(target, function(err) {
+                                if (err) {
+                                    if (callback) callback(err);
+                                    else throw err;
+                                } else {
+                                    // Create a platform app using the ./bin/create scripts that exist in each repo.
+                                    // Run platform's create script
+                                    var bin = path.join(cordova_util.libDirectory, 'cordova-' + target, 'bin', 'create');
+                                    var args = (target=='ios') ? '--arc' : '';
+                                    var pkg = cfg.packageName().replace(/[^\w.]/g,'_');
+                                    var name = cfg.name().replace(/\W/g,'_');
+                                    var command = util.format('"%s" %s "%s" "%s" "%s"', bin, args, output, pkg, name);
+                                    events.emit('log', 'Running bin/create for platform "' + target + '" with command: "' + command + '" (output to follow)');
+
+                                    shell.exec(command, {silent:true,async:true}, function(code, create_output) {
+                                        events.emit('log', create_output);
+                                        if (code > 0) {
+                                            var err = new Error('An error occured during creation of ' + target + ' sub-project. ' + create_output);
+                                            if (callback) callback(err);
+                                            else throw err;
+                                        } else {
+                                            var parser = new platforms[target].parser(output);
+                                            events.emit('log', 'Updating ' + target + ' project from config.xml...');
+                                            parser.update_project(cfg, function() {
+                                                createOverrides(target);
+                                                end(); //platform add is done by now.
+                                                // Install all currently installed plugins into this new platform.
+                                                var pluginsDir = path.join(projectRoot, 'plugins');
+                                                var plugins = fs.readdirSync(pluginsDir);
+                                                plugins && plugins.forEach(function(plugin) {
+                                                    if (fs.statSync(path.join(projectRoot, 'plugins', plugin)).isDirectory()) {
+                                                        events.emit('log', 'Installing plugin "' + plugin + '" following successful platform add of ' + target);
+                                                        plugman.install(target, output, path.basename(plugin), pluginsDir, { www_dir: parser.staging_dir() });
+                                                    }
+                                                });
+                                            });
+                                        }
+                                    });
+                                }
+                            });
+                        }
+                    });
+                }
+            });
             break;
         case 'rm':
         case 'remove':
-            targets.forEach(function(target) {
-                hooks.fire('before_platform_rm');
-                shell.rm('-rf', path.join(projectRoot, 'platforms', target));
-                shell.rm('-rf', path.join(cordova_util.appDir(projectRoot), 'merges', target));
-                hooks.fire('after_platform_rm');
+            var end = n(targets.length, function() {
+                hooks.fire('after_platform_rm', opts, function(err) {
+                    if (err) {
+                        if (callback) callback(err);
+                        else throw err;
+                    } else {
+                        if (callback) callback();
+                    }
+                });
+            });
+            hooks.fire('before_platform_rm', opts, function(err) {
+                if (err) {
+                    if (callback) callback(err);
+                    else throw err;
+                } else {
+                    targets.forEach(function(target) {
+                        shell.rm('-rf', path.join(projectRoot, 'platforms', target));
+                        shell.rm('-rf', path.join(cordova_util.appDir(projectRoot), 'merges', target));
+                        end();
+                    });
+                }
             });
             break;
         default:
diff --git a/src/plugin.js b/src/plugin.js
index 0851305..8fbe355 100644
--- a/src/plugin.js
+++ b/src/plugin.js
@@ -24,6 +24,7 @@
     shell         = require('shelljs'),
     platforms     = require('../platforms'),
     config_parser = require('./config_parser'),
+    n             = require('ncallbacks'),
     hooker        = require('./hooker'),
     events        = require('./events'),
     plugin_parser = require('./plugin_parser'),
@@ -55,92 +56,124 @@
     if (targets && !(targets instanceof Array)) {
         targets = [targets];
     }
+    var opts = {
+        plugins:targets
+    };
 
     switch(command) {
         case 'ls':
         case 'list':
-            // TODO awkward before+after hooks here
-            hooks.fire('before_plugin_ls');
-            hooks.fire('after_plugin_ls');
-            if (plugins.length) {
-                return plugins;
-            } else return 'No plugins added. Use `cordova plugin add <plugin>`.';
+            hooks.fire('before_plugin_ls', function(err) {
+                if (err) {
+                    if (callback) callback(err);
+                    else throw err;
+                } else {
+                    events.emit('results', (plugins.length ? plugins : 'No plugins added. Use `cordova plugin add <plugin>`.'));
+                    hooks.fire('after_plugin_ls', function(err) {
+                        if (err) {
+                            if (callback) callback(err);
+                            else throw err;
+                        }
+                    });
+                }
+            });
             break;
         case 'add':
-            targets.forEach(function(target, index) {
-                hooks.fire('before_plugin_add');
-                var pluginsDir = path.join(projectRoot, 'plugins');
-
-                if (target[target.length - 1] == path.sep) {
-                    target = target.substring(0, target.length - 1);
-                }
-
-                // Fetch the plugin first.
-                events.emit('log', 'Calling plugman.fetch on plugin "' + target + '"');
-                plugman.fetch(target, pluginsDir, {}, function(err, dir) {
+            var end = n(targets.length, function() {
+                hooks.fire('after_plugin_add', opts, function(err) {
                     if (err) {
-                        var err = new Error('Error fetching plugin: ' + err);
                         if (callback) callback(err);
                         else throw err;
-                        return;
+                    } else {
+                        if (callback) callback();
                     }
-
-                    // Iterate over all platforms in the project and install the plugin.
-                    platformList.forEach(function(platform) {
-                        var platformRoot = path.join(projectRoot, 'platforms', platform);
-                        var parser = new platforms[platform].parser(platformRoot);
-                        // TODO: unify use of blackberry in cli vs blackberry10 in plugman
-                        events.emit('log', 'Calling plugman.install on plugin "' + dir + '" for platform "' + platform + '"');
-                        plugman.install((platform=='blackberry'?'blackberry10':platform), platformRoot,
-                                        path.basename(dir), pluginsDir, { www_dir: parser.staging_dir() });
-                    });
-
-                    hooks.fire('after_plugin_add');
                 });
             });
-            if (callback) callback();
+            hooks.fire('before_plugin_add', opts, function(err) {
+                if (err) {
+                    if (callback) callback(err);
+                    else throw err;
+                } else {
+                    targets.forEach(function(target, index) {
+                        var pluginsDir = path.join(projectRoot, 'plugins');
+
+                        if (target[target.length - 1] == path.sep) {
+                            target = target.substring(0, target.length - 1);
+                        }
+
+                        // Fetch the plugin first.
+                        events.emit('log', 'Calling plugman.fetch on plugin "' + target + '"');
+                        plugman.fetch(target, pluginsDir, {}, function(err, dir) {
+                            if (err) {
+                                var err = new Error('Error fetching plugin: ' + err);
+                                if (callback) callback(err);
+                                else throw err;
+                            } else {
+                                // Iterate over all platforms in the project and install the plugin.
+                                platformList.forEach(function(platform) {
+                                    var platformRoot = path.join(projectRoot, 'platforms', platform);
+                                    var parser = new platforms[platform].parser(platformRoot);
+                                    // TODO: unify use of blackberry in cli vs blackberry10 in plugman
+                                    events.emit('log', 'Calling plugman.install on plugin "' + dir + '" for platform "' + platform + '"');
+                                    plugman.install((platform=='blackberry'?'blackberry10':platform), platformRoot,
+                                                    path.basename(dir), pluginsDir, { www_dir: parser.staging_dir() });
+                                });
+                                end();
+                            }
+                        });
+                    });
+                }
+            });
             break;
         case 'rm':
         case 'remove':
-            if (platformList.length === 0) {
-                var err = new Error('You need at least one platform added to your app. Use `cordova platform add <platform>`.');
-                if (callback) callback(err);
-                else throw err;
-                return;
-            }
-            targets.forEach(function(target, index) {
-                // Check if we have the plugin.
-                if (plugins.indexOf(target) > -1) {
-                    var targetPath = path.join(pluginPath, target);
-                    hooks.fire('before_plugin_rm');
-                    // Check if there is at least one match between plugin
-                    // supported platforms and app platforms
-                    var pluginXml = new plugin_parser(path.join(targetPath, 'plugin.xml'));
-                    var intersection = pluginXml.platforms.filter(function(e) {
-                        if (platformList.indexOf(e) == -1) return false;
-                        else return true;
-                    });
-
-                    // Iterate over all the common platforms between the plugin
-                    // and the app, and uninstall.
-                    // If this is a web-only plugin with no platform tags, this step
-                    // is not needed and we just --remove the plugin below.
-                    intersection.forEach(function(platform) {
-                        var platformRoot = path.join(projectRoot, 'platforms', platform);
-                        var parser = new platforms[platform].parser(platformRoot);
-                        events.emit('log', 'Calling plugman.uninstall on plugin "' + target + '" for platform "' + platform + '"');
-                        plugman.uninstall(platform, platformRoot, target, path.join(projectRoot, 'plugins'), { www_dir: parser.staging_dir() });
-                    });
-
-                    hooks.fire('after_plugin_rm');
-                } else {
-                    var err = new Error('Plugin "' + target + '" not added to project.');
+            var end = n(targets.length, function() {
+                hooks.fire('after_plugin_rm', opts, function(err) {
+                    if (err) {
+                        if (callback) callback(err);
+                        else throw err;
+                    } else {
+                        if (callback) callback();
+                    }
+                });
+            });
+            hooks.fire('before_plugin_rm', opts, function(err) {
+                if (err) {
                     if (callback) callback(err);
                     else throw err;
-                    return;
+                } else {
+                    targets.forEach(function(target, index) {
+                        // Check if we have the plugin.
+                        if (plugins.indexOf(target) > -1) {
+                            var targetPath = path.join(pluginPath, target);
+                            // Check if there is at least one match between plugin
+                            // supported platforms and app platforms
+                            var pluginXml = new plugin_parser(path.join(targetPath, 'plugin.xml'));
+                            var intersection = pluginXml.platforms.filter(function(e) {
+                                if (platformList.indexOf(e) == -1) return false;
+                                else return true;
+                            });
+
+                            // Iterate over all the common platforms between the plugin
+                            // and the app, and uninstall.
+                            // If this is a web-only plugin with no platform tags, this step
+                            // is not needed and we just --remove the plugin below.
+                            intersection.forEach(function(platform) {
+                                var platformRoot = path.join(projectRoot, 'platforms', platform);
+                                var parser = new platforms[platform].parser(platformRoot);
+                                events.emit('log', 'Calling plugman.uninstall on plugin "' + target + '" for platform "' + platform + '"');
+                                plugman.uninstall(platform, platformRoot, target, path.join(projectRoot, 'plugins'), { www_dir: parser.staging_dir() });
+                            });
+                            end();
+                        } else {
+                            var err = new Error('Plugin "' + target + '" not added to project.');
+                            if (callback) callback(err);
+                            else throw err;
+                            return;
+                        }
+                    });
                 }
             });
-            if (callback) callback();
             break;
         default:
             var err = new Error('Unrecognized command "' + command + '". Use either `add`, `remove`, or `list`.');
diff --git a/src/prepare.js b/src/prepare.js
index d1fbce7..2205186 100644
--- a/src/prepare.js
+++ b/src/prepare.js
@@ -60,46 +60,47 @@
     }
 
     var hooks = new hooker(projectRoot);
-    if (!(hooks.fire('before_prepare'))) {
-        var err = new Error('before_prepare hooks exited with non-zero code. Aborting.');
-        if (callback) callback(err);
-        else throw err;
-        return;
-    }
-
-    var end = n(platformList.length, function() {
-        if (!(hooks.fire('after_prepare'))) {
-            var err = new Error('after_prepare hooks exited with non-zero code. Aborting.');
+    hooks.fire('before_prepare', function(err) {
+        if (err) {
             if (callback) callback(err);
             else throw err;
-            return;
-        }
-        if (callback) callback();
-    });
-
-    // Iterate over each added platform
-    platformList.forEach(function(platform) {
-        var platformPath = path.join(projectRoot, 'platforms', platform);
-        var parser = new platforms[platform].parser(platformPath);
-        parser.update_project(cfg, function() {
-            // Call plugman --prepare for this platform. sets up js-modules appropriately.
-            var plugins_dir = path.join(projectRoot, 'plugins');
-            events.emit('log', 'Calling plugman.prepare for platform "' + platform + '"');
-            plugman.prepare(platformPath, (platform=='blackberry'?'blackberry10':platform), plugins_dir);
-            // Make sure that config changes for each existing plugin is in place
-            var plugins = cordova_util.findPlugins(plugins_dir);
-            var platform_json = plugman.config_changes.get_platform_json(plugins_dir, (platform=='blackberry'?'blackberry10':platform));
-            plugins && plugins.forEach(function(plugin_id) {
-                if (platform_json.installed_plugins[plugin_id]) {
-                    events.emit('log', 'Ensuring plugin "' + plugin_id + '" is installed correctly...');
-                    plugman.config_changes.add_plugin_changes((platform=='blackberry'?'blackberry10':platform), platformPath, plugins_dir, plugin_id, /* variables for plugin */ platform_json.installed_plugins[plugin_id], /* top level plugin? */ true, /* should increment config munge? cordova-cli never should, only plugman */ false);
-                } else if (platform_json.dependent_plugins[plugin_id]) {
-                    events.emit('log', 'Ensuring plugin "' + plugin_id + '" is installed correctly...');
-                    plugman.config_changes.add_plugin_changes((platform=='blackberry'?'blackberry10':platform), platformPath, plugins_dir, plugin_id, /* variables for plugin */ platform_json.dependent_plugins[plugin_id], /* top level plugin? */ false, /* should increment config munge? cordova-cli never should, only plugman */ false);
-                }
-                events.emit('log', 'Plugin "' + plugin_id + '" is good to go.');
+        } else {
+            var end = n(platformList.length, function() {
+                hooks.fire('after_prepare', function(err) {
+                    if (err) {
+                        if (callback) callback(err);
+                        else throw err;
+                    } else {
+                        if (callback) callback();
+                    }
+                });
             });
-            end();
-        });
+
+            // Iterate over each added platform
+            platformList.forEach(function(platform) {
+                var platformPath = path.join(projectRoot, 'platforms', platform);
+                var parser = new platforms[platform].parser(platformPath);
+                parser.update_project(cfg, function() {
+                    // Call plugman --prepare for this platform. sets up js-modules appropriately.
+                    var plugins_dir = path.join(projectRoot, 'plugins');
+                    events.emit('log', 'Calling plugman.prepare for platform "' + platform + '"');
+                    plugman.prepare(platformPath, (platform=='blackberry'?'blackberry10':platform), plugins_dir);
+                    // Make sure that config changes for each existing plugin is in place
+                    var plugins = cordova_util.findPlugins(plugins_dir);
+                    var platform_json = plugman.config_changes.get_platform_json(plugins_dir, (platform=='blackberry'?'blackberry10':platform));
+                    plugins && plugins.forEach(function(plugin_id) {
+                        if (platform_json.installed_plugins[plugin_id]) {
+                            events.emit('log', 'Ensuring plugin "' + plugin_id + '" is installed correctly...');
+                            plugman.config_changes.add_plugin_changes((platform=='blackberry'?'blackberry10':platform), platformPath, plugins_dir, plugin_id, /* variables for plugin */ platform_json.installed_plugins[plugin_id], /* top level plugin? */ true, /* should increment config munge? cordova-cli never should, only plugman */ false);
+                        } else if (platform_json.dependent_plugins[plugin_id]) {
+                            events.emit('log', 'Ensuring plugin "' + plugin_id + '" is installed correctly...');
+                            plugman.config_changes.add_plugin_changes((platform=='blackberry'?'blackberry10':platform), platformPath, plugins_dir, plugin_id, /* variables for plugin */ platform_json.dependent_plugins[plugin_id], /* top level plugin? */ false, /* should increment config munge? cordova-cli never should, only plugman */ false);
+                        }
+                        events.emit('log', 'Plugin "' + plugin_id + '" is good to go.');
+                    });
+                    end();
+                });
+            });
+        }
     });
 };
diff --git a/src/run.js b/src/run.js
index ff7ea17..6913a21 100644
--- a/src/run.js
+++ b/src/run.js
@@ -28,7 +28,7 @@
     n                 = require('ncallbacks');
 
 function shell_out_to_run(projectRoot, platform, callback) {
-    var cmd = '"' + path.join(projectRoot, 'platforms', platform, 'cordova', 'run') + '" --debug --device';
+    var cmd = '"' + path.join(projectRoot, 'platforms', platform, 'cordova', 'run') + '" --device';
     // TODO: inconsistent API for BB10 run command
     if (platform == 'blackberry') {
         var bb_project = path.join(projectRoot, 'platforms', 'blackberry')
@@ -83,35 +83,39 @@
     }
 
     var hooks = new hooker(projectRoot);
-    if (!(hooks.fire('before_run'))) {
-        var err = new Error('before_run hooks exited with non-zero code. Aborting.');
-        if (callback) callback(err);
-        else throw err;
-        return;
+    var opts = {
+        platforms:platformList
     }
-
-    var end = n(platformList.length, function() {
-        if (!(hooks.fire('after_run'))) {
-            var err = new Error('after_run hooks exited with non-zero code. Aborting.');
-            if (callback) callback(err);
-            else throw err;
-            return;
-        }
-        if (callback) callback();
-    });
-
-    // Run a prepare first, then shell out to run
-    require('../cordova').prepare(platformList, function(err) {
+    hooks.fire('before_run', opts, function(err) {
         if (err) {
             if (callback) callback(err);
             else throw err;
         } else {
-            platformList.forEach(function(platform) {
-                try {
-                    shell_out_to_run(projectRoot, platform, end);
-                } catch(e) {
-                    if (callback) callback(e);
-                    else throw e;
+            var end = n(platformList.length, function() {
+                hooks.fire('after_run', opts, function(err) {
+                    if (err) {
+                        if (callback) callback(err);
+                        else throw err;
+                    } else {
+                        if (callback) callback();
+                    }
+                });
+            });
+
+            // Run a prepare first, then shell out to run
+            require('../cordova').prepare(platformList, function(err) {
+                if (err) {
+                    if (callback) callback(err);
+                    else throw err;
+                } else {
+                    platformList.forEach(function(platform) {
+                        try {
+                            shell_out_to_run(projectRoot, platform, end);
+                        } catch(e) {
+                            if (callback) callback(e);
+                            else throw e;
+                        }
+                    });
                 }
             });
         }