Word Count Script requested

Hey guys,

I currently use Beeminder’s draft integration to beemind daily word count. I’d like to be able to write in plain text files on my computer so I don’t need to copy paste from Draft all the time.

Does anyone have a short shell script that I can run in Hazel on a designated writing folder that’ll send the word count to Beeminder?

1 Like

I use Hazel to merge my TagTime logs and update the relevant Beeminder goals. (screenshots in the linked forum post!)

That’s a good idea, having Hazel upload word counts. One thing to be mildly careful of is that our Draft integration gives you credit for editing (i.e. deleting) words. The easy shell script version is only going to report the current word count on existing files…

For updating Beeminder, you’ll want to grab a copy of @bkam’s bmndr script or my forked version. Then you can run something like this to update mygoal with the wordcount and a comment of the filename:

bmndr mygoal $(cat "$1" | wc -w) "$1"

There’s obviously a relationship between your Hazel setup and how you configure your Beeminder goal. What did you have in mind?

For those playing along at home, Hazel is an awesome and flexible Mac automation utility. Rules watch for changes in files or folders, and trigger a wide variety of actions, including arbitrary scripts.


I actually only care about total word count, not words edited.

My goal is be able to just automatically run word count on a file whenever I add that file to my writing folder, and then send that total to update Beeminder.


Excellent. In that case, the above will do the trick.

Even with a dedicated count-these-files writing folder, you’ll probably want to build some safety measures into your Hazel file-matching rules, like

  • not if modified in the last X minutes
  • only if modified in the last day

If you create an ordinary do more Beeminder goal, then a script similar to this should work fine:

bmndr mygoal $(cat "$1" | wc -w) "$1"

If you keep modifying the files in place, Hazel will keep adding new datapoints to your goal, each with the current total word count, which is almost certainly not what you want. If you only ever copy across writing that is ‘done’, it shouldn’t be an issue. If that’s where you keep your work-in-progress, you’ll have to come up with a counting scheme that you’re happy with.

Premium subscribers could do something fancy with the aggregation settings, but there’s no single straightforward generic answer.

Let me know how it goes.


You could maybe use an odometer goal and wordcount all the files in the directory every time.

1 Like

Indeed. That’s why the goal and the hazel need to match the workflow.

An odometer goal has an equal & opposite problem. Can’t delete or move files out…

1 Like

I get the issue of command not found: bmdr when I try to run this script
in the terminal. I have the correct api keys, so I’m wondering why my shell
does not recognize the command.

Alok Singh

Hi Alok! I’m answering this assuming that you’re new to using Terminal and command line. Apologies if that’s not related to this particular confusion. You could do worse than buying the relevant take control guide as an introduction to the command line. But here are a couple ideas that might get you unstuck:

Loosely speaking, any script needs to be a) executable and b) located by the thing that’s calling it.

The first can be fixed by running this in the directory that you’ve saved the bmndr script

chmod a+x bmndr

For the second, you could either move the script to somewhere on your ‘path’, or explicitly tell Hazel where you’ve put it, as in something like:

/Users/philip/bmndr mygoal $(cat "$1" | wc -w) "$1"

Or to test from the terminal itself, try:


I’m not new to the terminal.
I already ran chmod to no avail. Running ./bmndr gives the following output.

File "./bmndr", line 60, in <module> data = loads(urlopen('https://www.beeminder.com/api/v1/users/me/goals.json?auth_token=%s&filter=%s' % (auth_token,burner)).read().decode('utf-8')) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 87, in urlopen return opener.open(url) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 208, in open return getattr(self, name)(url) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 451, in open_https return self.http_error(url, fp, errcode, errmsg, headers) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 372, in http_error result = method(url, fp, errcode, errmsg, headers) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 683, in http_error_401 errcode, errmsg, headers) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib.py", line 381, in http_error_default raise IOError, ('http error', errcode, errmsg, headers) IOError: ('http error', 401, 'Unauthorized', <httplib.HTTPMessage instance at 0x10e411d88>)

That looks nothing like ‘command not found’… :slight_smile:

(Re-reading your initial error report, it says ‘bmdr’ instead of ‘bmndr’, which is a typo in one place or another.)

Line 60 of my bmndr script contains the error “Missing or malformed configuration file.”, which is highly suggestive of what might have gone wrong (though I’m a python ignoramus, so can’t begin to guess why that message doesn’t appear in your stack trace).

I seem to recall that one of the differences between my fork and lydgate’s original has to do with the format of the config file, because I use the same file from perl and other scripts.

1 Like

The last line shows the problem you need to fix:

I’m guessing your auth token is not set correctly.
Did you copy the auth token from Beeminder into the correct place in your config file?