diff --git a/scripts/command.js b/scripts/command.js
new file mode 100644
--- /dev/null
+++ b/scripts/command.js
@@ -0,0 +1,36 @@
+/*
+ * A plugin to execute commands.
+ *
+ * Requires 'jquery' to be available through RequireJS.
+ */
+define(['jquery'], function ($) {
+  "use strict";
+  return {
+    /**
+     * Spawn a command, do not wait for completion.
+     * @param {String} command Command to launch
+     */
+    spawn: function (command) {
+      $.ajax('tianbar:///spawn', {
+        data: {
+          command: command,
+          random: new Date().getTime()
+        }
+      });
+    },
+
+    /**
+     * Execute a command.
+     * @param {String} command Command to execute
+     * @return {String} Command output
+     */
+    execute: function (command) {
+      return $.ajax('tianbar:///execute', {
+        data: {
+          command: command,
+          random: new Date().getTime()
+        }
+      });
+    }
+  };
+});
diff --git a/scripts/ibus.js b/scripts/ibus.js
--- a/scripts/ibus.js
+++ b/scripts/ibus.js
@@ -3,7 +3,7 @@
  *
  * Requires 'jquery' to be available through RequireJS.
  */
-define(['jquery', './dbus'], function ($, dbus) {
+define(['jquery', './command', './dbus'], function ($, command, dbus) {
   "use strict";
 
   var self = {};
@@ -60,11 +60,7 @@
   }
 
   $(document).ready(function () {
-    $.ajax('tianbar:///execute', {
-      data: {
-        command: 'ibus address'
-      }
-    }).then(function (address) {
+    command.execute('ibus address').then(function (address) {
       address = address.trim();
       dbus.connect(address).then(function (connectedBus) {
         bus = connectedBus;
diff --git a/scripts/network.js b/scripts/network.js
--- a/scripts/network.js
+++ b/scripts/network.js
@@ -1,4 +1,4 @@
-define(['jquery', './dbus'], function ($, dbus) {
+define(['jquery', './command', './dbus'], function ($, command, dbus) {
   "use strict";
 
   const self = {};
@@ -319,11 +319,7 @@
 
   $(document).ready(function () {
     self.widget().click(function () {
-      $.ajax('tianbar:///spawn', {
-        data: {
-          command: self.settings_command
-        }
-      });
+      command.spawn(self.settings_command);
     });
 
     watch_properties(NM_OBJECT).then(function (evt) {
diff --git a/scripts/volume.js b/scripts/volume.js
--- a/scripts/volume.js
+++ b/scripts/volume.js
@@ -3,7 +3,7 @@
  *
  * Requires 'jquery' to be available through RequireJS.
  */
-define(['jquery', './dbus'], function ($, dbus) {
+define(['jquery', './command', './dbus'], function ($, command, dbus) {
   "use strict";
 
   const MAX_VOLUME = 0x10000;
@@ -90,11 +90,7 @@
     widget.attr('title', percentage + '%');
   };
 
-  $.ajax('tianbar:///execute', {
-    data: {
-      command: 'pacmd load-module module-dbus-protocol'
-    }
-  }).then(function () {
+  command.execute('pacmd load-module module-dbus-protocol').then(function () {
     return dbus.session.getProperty(
       'org.PulseAudio1',
       dbus.toObjectPath('/org/pulseaudio/server_lookup1'),
@@ -174,11 +170,7 @@
 
   $(document).ready(function () {
     self.widget().click(function () {
-      $.ajax('tianbar:///spawn', {
-        data: {
-          command: self.settings_command
-        }
-      });
+      command.spawn(self.settings_command);
     });
   });
 
diff --git a/tianbar.cabal b/tianbar.cabal
--- a/tianbar.cabal
+++ b/tianbar.cabal
@@ -1,5 +1,5 @@
 name:                tianbar
-version:             1.2.4
+version:             1.2.5
 synopsis:            A desktop bar based on WebKit
 description:
   A desktop bar using WebKit for rendering as much as possible.
@@ -14,6 +14,7 @@
 cabal-version:       >=1.10
 data-files:          README.md
                    , index.html
+                   , scripts/command.js
                    , scripts/dbus.js
                    , scripts/ibus.js
                    , scripts/network.js
