Running an external command from Firefox right now is only supported with the nsIProcess API. You figured out this bit for yourself already.
Packaging a python script.
The SDK will only package certain files/locations. It would be easiest to just place the python script in the data/ folder, because that is one of the locations the SDK will package all files from.
nsIProcess
nsIProcess needs an executable file, and that file actually needs to be a real file (not just something contained within the XPI, which by default is not unpacked).
So there are two cases we might want to handle:
- File is a real file - Just execute it.
- File is packaged - Need to extract/copy the data into a (temporary) real file and execute that.
The following code deals with both. I tested and it works for me on OSX (*nix, so should work on Linux or BSDs as well) and Windows, with and without em:unpack (and you should avoid em:unpack).
const self = require("sdk/self");
const {Cc, Ci, Cu} = require("chrome");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
const ChromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].
                       getService(Ci.nsIChromeRegistry);
const ResProtoHandler = Services.io.getProtocolHandler("resource").
                        QueryInterface(Ci.nsIResProtocolHandler);
function copyToTemp(uri, callback) {
  // Based on https://stackoverflow.com/a/24850643/484441
  let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
  file.append(self.name + "_" + uri.spec.replace(/^.+\//, ""));
  file.createUnique(Ci.nsIFile, 0o0700);
  NetUtil.asyncFetch(uri, function(istream) {
    let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
                  createInstance(Ci.nsIFileOutputStream);
    ostream.init(file, -1, -1, Ci.nsIFileOutputStream.DEFER_OPEN);
    NetUtil.asyncCopy(istream, ostream, function(result) {
      callback && callback(file, result);
    });
  });
}
function runProcessAndThen(file, callback) {
  console.log("running", file.path);
  let proc = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
  try {
    // Set executable bit on unix
    file.permissions = file.permissions | 0o0500;
  }
  catch (ex) {
    // Might throw?!
  }
  proc.init(file);
  proc.runAsync([], 0, callback);
}
function runFromURIWithPotentialCopy(uri, callback) {
  if (!uri.spec) {
    uri = Services.io.newURI(uri, null, null);
  }
  if (uri.scheme === "resource") {
    // Need to resolve futher. Strip one layer of indirection and recursively
    // call ourselves.
    uri = Services.io.newURI(ResProtoHandler.resolveURI(uri), null, null);
    return runFromURIWithPotentialCopy(uri, callback);
  }
  if (uri.scheme === "chrome") {
    // Need to resolve futher. Strip one layer of indirection and recursively
    // call ourselves.
    return runFromURIWithPotentialCopy(ChromeRegistry.convertChromeURL(uri), callback);
  }
  if (uri instanceof Ci.nsIFileURL) {
    // A plain file we can execute directly.
    return runProcessAndThen(uri.file, callback);
  }
  if (uri instanceof Ci.nsIJARURI) {
    // A packaged file (in an XPI most likely).
    // Need to copy the data into some plain file and run the result.
    return copyToTemp(uri, function(f) {
      runProcessAndThen(f, function() {
        try {
          // Clean up after ourselves.
          f.remove(false);
        }
        catch (ex) {
          console.error("Failed to remove tmp file again", ex);
        }
        callback.apply(null, arguments);
      });
    });
  }
  throw new Error("Cannot handle URI");
}
function afterRun(subject, topic, data) {
  console.log(subject, topic, data);
}
function runFileFromDataDirectory(name, callback) {
  try {
    runFromURIWithPotentialCopy(self.data.url(name), callback);
  }
  catch (ex) {
    console.error(ex);
  }
}
runFileFromDataDirectory("test.py", afterRun);
Running a script
Running a script (as opposed to a full-blown binary) can be tricky. In case of Python e.g. the *nix OSes needs to be told that there is an interpreter and what this is, which is done by a shebang.
On Windows, Python needs to be installed with .py file type registration, which the default installer will do, but not "portable" versions".