Setting new goals is easy, achieving them is hard.

I have used a variety of tools over the years to help me achieve my goals. I have used a combination of Google Docs, Google Calendar, Google Keep, Loop Habit Tracker, Trello, and good ol’ alarm clocks to schedule tasks and remind myself to get things done. Whether these goals were personal or professional, small or big, they would usually find their way into one of these tools with the hope that it would hold me accountable.

When it came to my major goals (e.g. “do real good in school”, “get a job”), these tools were more organizational than motivational. If a goal is truly important to me, I don’t struggle to find motivation. Conversely, I have set many goals that sit in a grey area of motivation. These goals are much more difficult for me to achieve because they are deemed “not-so-important” or “not completely necessary”, even though they are enjoyable and fulfilling activities. Unfortunately, their lack of priority means that they are often side-lined by major goals or leisurely activities.

Examples of these “not-so-important” goals include:

  • Read 12 books this year
  • Go for a run 3 times per week
  • Meditate for 10 minutes every day

If you have set goals like these in the past, you will know how easy it is to avoid them. These goals were particularly easy to ignore because the tools I was using were simple reminder engines. Reminders alone are useful, but only if you are motivated and organized at the very moment they arrive. If you are not, they have no real power. It is extremely easy to swipe away a push notification, especially when you are not in the mood for it. So for the last 6 months, I have been experimenting with a tool that has radically changed my mindset around these types of goals. That tool is Beeminder.

“It’s reminders with a sting!”

The simplified premise of Beeminder is as follows:

  1. Set a goal
  2. Create a commitment contract (e.g. “I will run 3 times per week”)
  3. Input your data (manually or automatically via app integrations)
  4. Stay on track or Beeminder charges your credit card

In my experience, when I introduce Beeminder to people for the first time they usually think one of two things:

  • “Wow, that sounds great!”
  • “Wow, that app would take all of my money.”

Fair enough. Beeminder is not for everyone. For me, Beeminder provided the missing piece of the puzzle for my lower priority goals. Knowing that I could lose money from neglecting my goals forced me to decide whether being lazy in the moment was truly worth it or if I should just do what I said I was going to do.

So I wholeheartedly endorse Beeminder for those who think this kind of motivational scheme would work for them. It’s worked great for me, particularly with keeping up with my reading and exercise goals through lockdown. I have lost money ($25 USD at time of writing) for forgetting to read or not finding the time to exercise, but using Beeminder has been completely worth it.

Programming goals

Now, because I’m a very sane person, I’ve taken to Beeminding other goals since my initial success with reading and exercising. Staying up-to-date with new technologies and programming skills is important for my career. So Beeminding my programming activity was a natural next step.

Beeminder provides an official integration with GitHub, called gitminder, that left me slightly disappointed. The integration only supports data tracking in the following scenarios:

  • Commits in a single repository
  • Issues closed in a single repository
  • Commits across your entire account
  • Issues closed across your entire account

Having only these scenarios to choose from, Beeminder users are prevented from defining complex programming goals. For example, the configurations above are not flexible enough for a “Learn Python” or “Contribute to Open Source” goal. These goals could span multiple repositories, but would likely not include every repository in my account. Conversely, what if I was learning python AND contributing to open source in a single repository? That scenario would require a single repository connected to multiple goals, which is not possible through gitminder. Additionally, programming activity is not just restricted to commits and closed issues. There are many other events on GitHub (e.g. pull requests, opened issues, publishing releases, deployment), that could be used to contribute towards Beeminder goals.

So instead of giving in and manually tracking my programming goals, I built a tool that addresses all of the above concerns.

multigitminder

multigitminder is a GitHub Action that helps users capture their programming activity as data for their Beeminder goals. This tool allows users to connect any number of GitHub repositories to any number of Beeminder goals based on any combination of events supported by GitHub Actions.

How it works

If you are not familiar with GitHub Actions, it as an automation framework built directly into GitHub. It allows you to automate software development tasks that you would otherwise do manually on GitHub. It is primarily used to help software developers maintain and organize their projects. The great thing about GitHub Actions is that it can be used to automate any task, not just maintenance or organizational tasks.

So without getting into anymore detail on GitHub Actions, the important thing to know is that multigitminder is a GitHub Action that automatically captures and sends data to Beeminder based on your specifications.

After installation, GitHub Actions will run multigitminder every time your chosen event (e.g. commit, closed issue, pull request) occurs in or to your repository. The action uses a simple python script (via pyminder) to push datapoints to Beeminder’s API.

You can install multigitminder on any repository you own by:

  • Creating a .yml workflow file in a .github/workflows/ directory
  • Specifying your goal parameters in the workflow file
  • Storing your Beeminder username and authorization token as secrets in your repo

A basic workflow file using multigitminder would look like this:

name: multigitminder
on:
  push:
    branches: [ main ]

jobs:
  multigitminder:
    runs-on: ubuntu-latest
    name: multigitminder
    steps:
      - name: multigitminder
        uses: HaydenMacDonald/multigitminder@v1.0.0
        with:
          USERNAME: ${{ secrets.BEEMINDER_USERNAME }}
          AUTH_TOKEN: ${{ secrets.BEEMINDER_AUTH_TOKEN }}
          GOAL: YOUR_GOAL_NAME_HERE

 
After adding that workflow file, you can add your secrets to GitHub by going to their repository Settings > Secrets > New Repository Secret. You can retrieve your Beeminder Auth Token from the Apps & API tab in the Account Settings page.

After this is done, you will have a basic implementation of multigitminder that triggers on pushes to the main branch of your repository. From here, you can change your workflow file to suit your programming goal(s).

Basic Workflows

Perhaps you want data points to be submitted with every push and closed issue:

name: multigitminder-push-issue-closed
on:
  push:
    branches: [ main ]
  issues:
    types: [ closed ]

jobs:
  multigitminder:
    runs-on: ubuntu-latest
    name: multigitminder
    steps:
      - name: multigitminder
        uses: HaydenMacDonald/multigitminder@v1.0.0
        with:
          USERNAME: ${{ secrets.BEEMINDER_USERNAME }}
          AUTH_TOKEN: ${{ secrets.BEEMINDER_AUTH_TOKEN }}
          GOAL: YOUR_GOAL_NAME_HERE

 
Or when pull requests are opened or closed:

name: multigitminder-pull-request
on:
  pull_request:
    types: [ opened, closed ]

jobs:
  multigitminder:
    runs-on: ubuntu-latest
    name: multigitminder
    steps:
      - name: multigitminder
        uses: HaydenMacDonald/multigitminder@v1.0.0
        id: multigitminder
        with:
          USERNAME: ${{ secrets.BEEMINDER_USERNAME }}
          AUTH_TOKEN: ${{ secrets.BEEMINDER_AUTH_TOKEN }}
          GOAL: YOUR_GOAL_NAME_HERE

 
Again, multigitminder can be configured to trigger on any combination of events supported by GitHub Actions. So feel free to experiment with what works best for your Beeminder goals.

Additional parameterization

Specific Commits

multigitminder can also be configured with a conditional statement, so that only specific commits trigger the workflow.

name: multigitminder-specific-commits
on:
  push:
    branches: [ main ]

jobs:
  multigitminder:
    if: "contains(github.event.head_commit.message, '[multigitminder]')" ## THIS LINE HERE
    runs-on: ubuntu-latest
    name: multigitminder
    steps:
      - name: multigitminder
        uses: HaydenMacDonald/multigitminder@v1.0.0
        with:
          USERNAME: ${{ secrets.BEEMINDER_USERNAME }}
          AUTH_TOKEN: ${{ secrets.BEEMINDER_AUTH_TOKEN }}
          GOAL: YOUR_GOAL_NAME_HERE

 
In this example, only commits with ‘multigitminder’ in the commit message will trigger the workflow.

Commit Messages as Comments

Additionally, you can use the commit message as the comment for each data point so that you can cross-reference your commits across GitHub and Beeminder.

name: multigitminder-commit-message-comment
on:
  push:
    branches: [ main ]

jobs:
  multigitminder:
    runs-on: ubuntu-latest
    name: multigitminder
    steps:
      - name: multigitminder
        uses: HaydenMacDonald/multigitminder@v1.0.0
        with:
          USERNAME: ${{ secrets.BEEMINDER_USERNAME }}
          AUTH_TOKEN: ${{ secrets.BEEMINDER_AUTH_TOKEN }}
          GOAL: YOUR_GOAL_NAME_HERE
          COMMENT: ${{ github.event.head_commit.message }} ## THIS LINE HERE

 

Language-based Goals

You can configure multigitminder to trigger based on the languages in your repository, with the help of fabasoad/linguist-action. This configuration ensures that multigitminder only triggers if at least one of your target languages are found in the repository code.

name: multigitminder-linguist
on:
  push:
    branches: [ main ]

jobs:
  multigitminder:
    runs-on: ubuntu-latest
    name: multigitminder
    steps:
      # Checkout
      - name: Checkout
        uses: actions/checkout@v2
      # linguist
      - name: Linguist Action
        uses: fabasoad/linguist-action@v1.0.2
        id: linguist
        with:
          path: './'
          percentage: true
      # multigitminder
      - name: multigitminder
        uses: HaydenMacDonald/multigitminder@v1.0.0
        id: multigitminder
        with:
          USERNAME: ${{ secrets.BEEMINDER_USERNAME }}
          AUTH_TOKEN: ${{ secrets.BEEMINDER_AUTH_TOKEN }}
          GOAL: YOUR_GOAL_NAME_HERE
          TARGET_LANGS: YOUR_TARGET_LANGUAGES_HERE ## e.g. "['python', 'javascript']"
          REPO_LANGS: ${{ steps.linguist.outputs.data }}

 

One repository to multiple goals

Lastly, what if you want activity in a repository to contribute to multiple Beeminder goals? You can achieve this by creating a workflow file for each goal, changing the input parameters accordingly. For example, I want closed issues in my repository to contribute towards a ‘maintenance’ Beeminder goal…

name: multigitminder-issue-closed
on:
  issues:
    types: [ closed ]

jobs:
  multigitminder:
    runs-on: ubuntu-latest
    name: multigitminder
    steps:
      - name: multigitminder
        uses: HaydenMacDonald/multigitminder@v1.0.0
        with:
          USERNAME: ${{ secrets.BEEMINDER_USERNAME }}
          AUTH_TOKEN: ${{ secrets.BEEMINDER_AUTH_TOKEN }}
          GOAL: maintenance

 
and pushes to the main and development branches to contribute towards a ‘write-python’ goal (provided python is found in the repo)…

name: multigitminder-linguist
on:
  push:
    branches: [ main, dev ]

jobs:
  multigitminder:
    runs-on: ubuntu-latest
    name: multigitminder
    steps:
      # Checkout
      - name: Checkout
        uses: actions/checkout@v2
      # linguist
      - name: Linguist Action
        uses: fabasoad/linguist-action@v1.0.2
        id: linguist
        with:
          path: './'
          percentage: true
      # multigitminder
      - name: multigitminder
        uses: HaydenMacDonald/multigitminder@v1.0.0
        id: multigitminder
        with:
          USERNAME: ${{ secrets.BEEMINDER_USERNAME }}
          AUTH_TOKEN: ${{ secrets.BEEMINDER_AUTH_TOKEN }}
          GOAL: write-python
          TARGET_LANGS: python
          REPO_LANGS: ${{ steps.linguist.outputs.data }}

 
With both of these workflow files in my ./github/workflows directory, they will trigger independently of one another, according to their specifications.

Conclusion

If this project interests you, please experiment with multigitminder! Check out the action on GitHub Marketplace or its home repository for more information.

I hope this tools opens the doors for modular programming goals in Beeminder. At least until the official GitHub integration becomes more flexible 😄

Help

If you find a bug or have an idea on how to improve multigitminder, please submit an issue on GitHub or send me an email at hmd[at]needleinthehay.ca.