Command Line Utility Belt
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
README.md
__init__.py
apps.py
apps_sample.conf
builtins.py
commander.sh
commander_sample.py
conf_handler.py
convert_apps.py
convert_sites.py
engine.py
gui.py
helpers.py
mac.py
macros.py
macros_sample.conf
open_task.py
sites.py
sites_sample.conf

README.md

Commander: Command Line Utility for Hackers and Poets

Commander is a task-automation utility. The basic idea is:

  • You write functions to do useful things in python
  • You decorate them with the @command decorator commander provides
  • You can now trigger your functions from the command line by name
  • You don't need to write boilerplate code and wrapper scripts
  • You can directly integrate your python commander functions into your interactive shell (!)

Commander designed to run in one of several modes:

  1. As a standalone interactive REPL where you directly type commands
  2. Integrated into your existing shell. Your commander functions are available directly in your shell as if they were shell functions.
  3. As a build tool similar to Make, Rake, or Shovel

What can I do with it?

Your commander functions can do just about anything since they are written in python. Out of the box, commander provides helpers to make the following things trivial.

  • Opening one or more browser URLs (bookmarks)
  • quick web based searches
  • Opening applications
  • running programs and scripts
  • miscellaneous system automation
  • Integrate with Keyboard Maestro on OS X for automation that is challenging from python such as manipulating windows, simulating mouse clicks, and so on

Here are some ideas to seed your mind with commander functions you may want to write.

  • Tweet something
  • Search for something on google, wikipedia, youtube, or any site or search engine
  • quickly check weather, stocks, sports scores, etc from the command line
  • quickly append notes to a journal file with a timestamp
  • add an item to your to-do list
  • see the last few git commit messages in a particular repository
  • perform an advanced renaming operation on all files under the current directory
  • perform a smart search/replace across a set of files
  • whatever else you feel would be useful to do from a command prompt
  • Anything that works better in python than as a shell script

Installation

Commander works well as a git submodule under your dotfiles repository. Set it up like this.

cd ~/dotfiles
git submodule add https://github.com/focusaurus/commander.git
cp -a commander/commander_sample.py commander.py

Then edit your commander.py script. Read the samples there to get the hang of it. Once you see how it works, feel free to delete the sample command functions defined there.

Running Commander in single-command mode

By default, commander will start up, interpret the command given on the command line, then exit. This mode is good for:

  1. built tool automation a la Make/Rake/Shovel

  2. invoking commander from a non-interactive shell script

  3. Learning commander

  4. Developing commander functions

    ./commander.py kablammo

This would run your custom-defined "kablammo" function then exit.

#Running an interactive REPL

Commander can also run as a continuous interactive read-eval-print-loop.

./commander.py --repl
> kablammo
>

This would start the interactive prompt, run your custom-defined kablammo function, then prompt for the next command. Type CTRL-D, "quit", or "q" to quit and return back to your shell.

This mode is good for:

  1. Keeping available in an easily-accessible shell tab
  2. Typing commands without worrying about shell quoting and globbing rules

Making the REPL better with rlwrap

The external "readline wrap" rlwrap utility is highly recommended to add more sophisticated command line editing, history, etc to commander.

  • apt-get install rlwrap on Ubuntu/Debian
  • brew install rlwrap on OS X with Homebrew
  • yum install rlwrap on RPM+yum based linux distros (untested)

Then launch your repl like this:

rlwrap ./commander.py --repl

You can now use the up arrow or CTRL-p to go back in your command history, etc.

Automatic reloading

In repl mode, commander will watch the mtime on the code and configuration files and automatically reload itself when they change. If you add a new command to commander.py while a repl is already running, you can immediately use it in the repl. Same thing applies to sites.conf. You can use the commander.engine.addReloader function to watch additional files to trigger reloads.

Integrating commander into your regular shell

(Or: Complete Shell Nirvana)

Here's the mode that will allow you to seamlessly integrate commander commands into your normal interactive bash or zsh. Once you get this set up, my hope is you'll take advantage of the power of python and write more and more of your sophisticated functionality as a commander function instead of a shell function. In this mode you get the best of both worlds of your shell and python.

  • All the normal shell features you love
  • Ability to define simple shell alias and functions in the shell as always
  • Ability to use environment variables (with tab completion, too)
  • Awesome tab completion
  • Awesome command editing
  • Awesome command history and history manipulation
  • filename globbing and pattern expantion
  • Commander integration
    • Specifically your commander commands get all that good stuff, too: tab completion of environment variables, history searching, shell globbing, etc

Source the commander.sh script from your shell's configuration file (~/.bashrc or ~/.zshrc) like this:

[ -f ~/dotfiles/commander/commander.sh ] && source ~/dotfiles/commander/commander.sh

Now you can turn on commander shell integration by typing commander on

So what does this mean? It means now in your shell when you type a command your shell will look for that command as:

  • a shell built-in command
  • an alias or function
  • an executable file or script on your PATH
  • a commander function

To turn off commander integration with your shell type commander off

To start commander in repl mode, type commander repl. This is good for a dedicated commander window and avoids having to deal with shell quoting and globbing rules.

For now, it is recommended that a new python process be started each time. This makes it easy to do interactive things like prompting for a password securely using the getpass module and so forth. However, commander can also read its input from a file via the --in command line argument. Thus it is possible for the shell to create a fifo, start a single long-running commander python process, and then feed it commands one at a time via the fifo. This avoids any startup/shutdown overhead and makes it easier for your commands to keep state in memory across multiple commands. However, it is not possible to interact with the tty in this mode, so you can't prompt the user for input (at least I haven't found a clean way yet). On most modern systems, the python startup is fast enough to be negligible, so single-command mode is probably sufficient. However, it's on my todo list to figure out a good way to do a background process IPC version.

To start commander shell integration in fifo mode, type commander fifo. Turn it off with commander off

Note that if shell filename globbing is sometimes screwing with your commander arguments when you don't want it to, prefix your command with noglob to disable shell globbing for that single command.

noglob mycommand_that_needs_a_regex  f?art

More info on command_not_found_handler

command_not_found_handler function is a hook that zsh or bash (bash calls it command_not_found_handle though) will call whenever you type in something that doesn't match a target operation (a shell builtin command, an alias, a function, a binary in your PATH, etc). Ever seen an Ubuntu box tell you something like "The program 'cowsay' is currently not installed. The program 'cowsay can be found in the following packages...". That's the command-not-found package using this hook to make that happen.

Built-in support for opening URLs and Applications

commander has built-in support for opening stuff.

  • First, define a task by running the built-in open task
  • You will be prompted for a required task name
    • This can be a simple name or several aliases delimited by a comma
    • Examples: espn or sports,espn
  • You will be prompted for an optional application name
    • Leave this blank to just open URLs with the default application (default browser for web URLs, Preview.app for images on the filesystem, text editor for code on the filesystem, Finder for directories, etc)
    • Example: "Google Chrome", "Textedit", "HipChat"
  • You will be prompted for arguments
    • These are space-delimited and can be URLs or other program arguments
    • For example: https://forecast.io http://www.weather.com/ would open both of those URLs in a browser
  • Your data file open.json will be updated to hold this command
  • There is also an open_local command which stores data in open-local.json. I use this for work-related things I need to keep out of my public github dotfiles repository due to NDAs and proprietary info.
  • after the open command completes the new task is immediately available for use

Built-in support for opening web sites

Deprecated!

As of commander 2.0 this functionality has been combined into the open command.

Commander includes a sites module which gives you shortcuts to launch or search web sites from the command line. Your mapping of command trigger to URL is stored in a sites.conf configuration file. To set up the sites module:

  • Copy sites_sample.conf to sites.conf and edit it
    • cp commander/sites_sample.conf commander/sites.conf
    • $EDITOR sites.conf
  • read sites_sample.conf for documentation on its syntax

Now you can be hacking away in your shell, type gh (for example) and have https://github.com open in a new browser tab. Passing in search terms via the query string is also supported by including a %s in the URL where the search terms go.

To add a new site to the configuration file, use the site commander command, so just type "site zefrank http://www.zefrank.com/" and it will be added to the configuration file and immediately available.

Note that sites.conf supports aliases as the site keywords as well as multiple URLs opened in browser tabs. This is handy for projects. For example, if you have a project "zippio" and you always need your docs, development server, art repo, and github repo open in browser tabs, you can make a site called "zippio" that will open all of those pages in tabs.

Built-in support for OS X Applications

Deprecated!

As of commander 2.0 this functionality has been combined into the open command.

Similar to the sites module there is an apps module which gives you the ability to define commander commands to launch Applications on OS X. To set up the apps module:

  • Copy apps_sample.conf to apps.conf and edit it
    • cp commander/apps_sample.conf commander/apps.conf
    • $EDITOR apps.conf
  • read apps_sample.conf for documentation on its syntax

#Built-in support for Keyboard Maestro Macros

Same pattern as above for sites and apps, just called "macros".

Built in commands

  • quit (also CTRL-D or just "q")
    • quits commander.py
  • help (also "?")
    • Print a basic help message and a list of loaded commands

Supported Environments

  • Python 2.7 (probably 2.6 will also work)
  • zsh or bash (version 4 or newer)
  • OS X (10.10-10.6, probably the rest of them)
  • Linux

Using Commander over the network

It is pretty straightforward to use the basic unix utilities of fifos and netcat to make commander work across the network. Note this is entirely cleartext and insecure. Also, netcat will only handle one connection at a time.

  • Create a fifo for commander to use
    • mkfifo ~/.commander.fifo
  • Start commander reading commands from that fifo
    • ./commander.py --in ~/.commander.fifo --out ~/.commander.fifo --repl
  • Start a local netcat daemon, sending its output to commander's fifo
    • nc -k -l 1234 > ~/.commander.fifo
  • On a remote machine, use netcat as your commander shell
    • nc host.where.you.started.commander.and.netcat.example 1234
  • Type your commander commands into that nc prompt and they will execute on the remote machine

Keyboard Maestro

I wrote this as a complement to the excellent Keyboard Maestro. I use Keyboard Maestro for lots of UI automation including assinging my most-used apps to the function keys on my macbook and text abbreviations. I highly recommend it. However, Keyboard Maestro does not seem to have the notion of triggering actions by global keystroke followed by typing a keyword/phrase then enter. This is more along the lines of other automation utilities like Spotlight or LaunchBar. You may also want to check out Alfred or Quicksilver. However, I've never been into "adaptive" automation and I like the simplicity and concreteness of knowing I'm typing into a dumb static script that is going to do the same thing every time until I change the code to do something else. That said, those commercial apps have bells and whistles out the wazoo, so if you need iCal integration graphical previews of your PDF files, have a look at them.

My Workflow

I map Command-Space to a Keyboard Maestro macro that actives Terminal.app, positions the window at the top left corner, and resizes it to a small narrow strip. In this window I keep the commander.py prompt running in the repl. Thus to access any functionality I want from commander.py, I type Command-Space followed by the command such as "unmount", which is a little script that unmounts all my external disks so I can pack up my macbook. I can do this globally no matter where my keyboard focus is at the moment, and the UI is instantaneous.

License

Commander is licensed under the MIT License

Copyright (c) 2011 Peter Lyons LLC

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.