diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..0981094d091ca14e431837a31e7292ae7ad6e209
--- /dev/null
+++ b/package.json
@@ -0,0 +1,10 @@
+{
+  "private": true,
+  "dependencies": {
+    "got": "^6.6.3",
+    "mkdirp": "^0.5.1",
+    "nodegit": "^0.16.0",
+    "rimraf": "^2.5.4",
+    "semver": "^5.3.0"
+  }
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..a8108f2ce687e16b60057ff24fb4f00234931f43
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,233 @@
+'use strict';
+
+const Git = require('nodegit');
+const got = require('got');
+const semver = require('semver');
+const fs = require('fs');
+const mkdirp = require('mkdirp');
+const rimraf = require('rimraf');
+
+const authorName = 'firstName lastName'; // commit credentials
+const authorEmail = 'admin@example.com'; // commit credentials
+
+const sshPublicKeyPath = `${process.env.HOME}/.ssh/id_rsa.pub`;
+const sshPrivateKeyPath = `${process.env.HOME}/.ssh/id_rsa`;
+
+if (!module.parent) {
+  // https://github.com/settings/tokens/new
+  const token = process.argv[2];
+  const repoName = process.argv[3];
+
+  if (!token || !repoName) {
+    console.error(`Usage: node index.js <token> <repo>`);
+    process.exit(1);
+  }
+
+  updateRepo({ token, repoName })
+    .catch(err => console.log(err.stack || err));
+}
+
+function updateRepo({ token, repoName }) {
+  const repoPath = `tmp/${repoName}`;
+  mkdirp.sync(repoPath);
+
+  let repo;
+  let headCommit;
+
+  return Git
+    .Clone(`git@github.com:${repoName}.git`, repoPath, {
+      fetchOpts: {
+        callbacks: {
+          credentials: getCredentials,
+          certificateCheck: () => 1
+        }
+      }
+    })
+    .then(_repo => {
+      repo = _repo;
+      return repo.fetch('origin', {
+        callbacks: {
+          credentials: getCredentials
+        }
+      });
+    })
+    .then(() => {
+      return repo.getHeadCommit();
+    })
+    .then(commit => {
+      headCommit = commit;
+      return readFile(headCommit, 'package.json');
+    })
+    .then(blob => {
+      const pkg = JSON.parse(blob);
+      return iterateDependencies(pkg, 'dependencies')
+        .then(() => iterateDependencies(pkg, 'devDependencies'));
+    })
+    .then(() => {
+      rimraf.sync(repoPath);
+    });
+
+  function iterateDependencies(pkg, depType) {
+    const deps = pkg[depType];
+
+    return Object.keys(deps).reduce((total, depName) => {
+      return total.then(() => {
+        const currentVersion = deps[depName].replace(/[^\d.]/g, '');
+
+        if (!semver.valid(currentVersion)) {
+          return;
+        }
+
+        // supports scoped packages, e.g. @user/package
+        return got(`https://registry.npmjs.org/${depName.replace('/', '%2F')}`, { json: true })
+          .then(res => {
+            const latestAvailable = res.body['dist-tags'].latest;
+
+            if (semver.gt(latestAvailable, currentVersion)) {
+              return updateDependency(depType, depName, latestAvailable)
+            }
+          });
+      });
+    }, Promise.resolve());
+  }
+
+  function updateDependency(depType, depName, nextVersion) {
+    const branchName = `upgrade/${depName}`;
+
+    // try to checkout remote branche
+    try {
+      nativeCall(`git checkout ${branchName}`);
+    } catch (e) {
+      nativeCall(`git checkout -b ${branchName}`);
+    }
+
+    return updateBranch(branchName, depType, depName, nextVersion)
+      .then(() => nativeCall(`git checkout master`));
+  }
+
+  function updateBranch(branchName, depType, depName, nextVersion) {
+    let commit;
+
+    return repo.getBranchCommit(branchName)
+      .then(_commit => {
+        commit = _commit;
+        return readFile(commit, 'package.json');
+      })
+      .then(blob => {
+        const pkg = JSON.parse(String(blob));
+
+        if (pkg[depType][depName] === nextVersion) {
+          return;
+        }
+
+        pkg[depType][depName] = nextVersion;
+        fs.writeFileSync(`${repoPath}/package.json`, JSON.stringify(pkg, null, 2) + '\n');
+
+        return commitAndPush(commit, depName, nextVersion, branchName);
+      });
+  }
+
+  function commitAndPush(commit, depName, nextVersion, branchName) {
+    const updateMessage = `Update ${depName} to version ${nextVersion}`;
+    console.log(updateMessage);
+
+    let index;
+
+    return repo
+      .refreshIndex()
+      .then(indexResult => {
+        index = indexResult;
+        return index.addByPath('package.json');
+      })
+      .then(() => index.write())
+      .then(() => index.writeTree())
+      .then(oid => {
+        let author;
+
+        if (authorName && authorEmail) {
+          const date = new Date();
+
+          author = Git.Signature.create(
+            authorName,
+            authorEmail,
+            Math.floor(date.getTime() / 1000),
+            -date.getTimezoneOffset()
+          );
+        } else {
+          author = repo.defaultSignature();
+        }
+
+        return repo.createCommit('HEAD', author, author, updateMessage, oid, [commit]);
+      })
+      .then(() => Git.Remote.lookup(repo, 'origin'))
+      .then(origin => {
+        return origin.push(
+          [`refs/heads/${branchName}:refs/heads/${branchName}`], {
+            callbacks: {
+              credentials: getCredentials
+            }
+          }
+        );
+      })
+      .then(() => {
+        return createPullRequest(branchName, `Update ${depName}`);
+      });
+  }
+
+  function createPullRequest(branchName, updateMessage) {
+    const head = `${branchName}`;
+    const options = {
+      method: 'POST',
+      json: true,
+      headers: {
+        Authorization: `token ${token}`
+      },
+      body: JSON.stringify({
+        title: updateMessage,
+        body: '',
+        head,
+        base: 'master'
+      })
+    };
+
+    return got(`https://api.github.com/repos/${repoName}/pulls`, options)
+      .then(
+        null,
+        err => {
+          let logError = true;
+
+          try {
+            if (err.response.body.errors.find(e => e.message.indexOf('A pull request already exists') === 0)) {
+              logError = false;
+            }
+          } catch (e) {
+          }
+
+          if (logError) {
+            console.log(err);
+          }
+        }
+      );
+  }
+
+  function readFile(commit, filename) {
+    return commit
+      .getEntry('package.json')
+      .then(entry => entry.getBlob())
+      .then(blob => String(blob));
+  }
+
+  function getCredentials(url, userName) {
+    // https://github.com/nodegit/nodegit/issues/1133#issuecomment-261779939
+    return Git.Cred.sshKeyNew(
+      userName,
+      sshPublicKeyPath,
+      sshPrivateKeyPath,
+      ''
+    );
+  }
+
+  function nativeCall(cmd) {
+    return require('child_process').execSync(cmd, { cwd: repoPath, stdio: [null, null, null] });
+  }
+}