   config: (config) ->
     JSON.stringify config, null, "    "
 
-commands =
-
-  install: () ->
-    components = allComponents()
-    componentsDirName = join testDirectory(), "static/components"
-    provide componentsDirName
-    for component in components
-      src = join(component.path, sourceDirectory(component.path))
-      name = componentName(null, component.json)
-      installTo componentsDirName, argv.link, src, name
-    if isComponent()
-      installTo componentsDirName, argv.link, 'src', componentName()
-
-  test: () ->
-    port = process.env.PORT || 1335
-    host = process.env.HOST || "localhost"
-    components = allComponents()
-    staticDirName = join testDirectory(), "static"
-    return listTests(discoverTests(staticDirName), host, port) if argv.listtests
-    commands.install()
-    tests = discoverTests staticDirName
-    extraHtml = getTestTemplate() + "\n" + stringBundlesAsRequirejsModule()
-    testServer
-      .createServer(staticDirName, getConfig(), extraHtml, getTestMiddleware())
-      .listen(port, host)
-    console.log "serving at http://#{host}:#{port}"
-    console.log "available tests:"
-    listTests tests, host, port
-
-  template: (cmd, args...) ->
-    templateCommands[cmd] getConfig()
-
-  build: () ->
-    commands.install()
-
-    staticDirName = join testDirectory(), "static"
-    provide argv.staticpath
-    wrench.copyDirSyncRecursive staticDirName, argv.staticpath
-
-    config = getConfig()
-    templateObj = _.reduce(templateCommands, ((templateObj, cmd, name) ->
-      templateObj[name] = templateCommands[name](config)
-      return templateObj
-    ), {})
-    provide argv.templatepath
-    contextjsonname = join(argv.templatepath, argv.contextjsonname)
-    write contextjsonname, JSON.stringify(templateObj)
+exports.commands = commands =
+  install:
+    description: """
+    install component dependencies
+    """
+    action: () ->
+      components = allComponents()
+      componentsDirName = join testDirectory(), "static/components"
+      provide componentsDirName
+      for component in components
+        src = join(component.path, sourceDirectory(component.path))
+        name = componentName(null, component.json)
+        installTo componentsDirName, argv.link, src, name
+      if isComponent()
+        installTo componentsDirName, argv.link, 'src', componentName()
+
+  test:
+    description: """
+    start up a test server
+    """
+    action: () ->
+      port = process.env.PORT || 1335
+      host = process.env.HOST || "localhost"
+      components = allComponents()
+      staticDirName = join testDirectory(), "static"
+      if argv.listtests
+        return listTests(discoverTests(staticDirName), host, port)
+      commands.action.install()
+      tests = discoverTests staticDirName
+      extraHtml = getTestTemplate() + "\n" + stringBundlesAsRequirejsModule()
+      testServer
+        .createServer(staticDirName, getConfig(), extraHtml, getTestMiddleware())
+        .listen(port, host)
+      console.log "serving at http://#{host}:#{port}"
+      console.log "available tests:"
+      listTests tests, host, port
+
+  template:
+    description: """
+    output html snippets for things like the require.js path, can be any of:
+    #{(' - ' + cmd + '\n' for cmd of templateCommands).join("")}
+    """.replace(/\n$/, '')
+    action: (cmd, args...) ->
+      templateCommands[cmd] getConfig()
+
+  build:
+    description: """
+    the build command does two things:
+     - if the [staticpath] is given, it will run the `component install`
+       command and then copy the installation directory to [staticpath]
+     - if the [templatepath] is given, it will output a json file named
+       [contextjsonname] to that path containing the keyed info from the
+       `component template` command
+    """
+    action: () ->
+      if argv.staticpath
+        provide argv.staticpath
+
+        commands.action.install()
+
+        staticDirName = join testDirectory(), "static"
+        wrench.copyDirSyncRecursive staticDirName, argv.staticpath
+
+      if argv.templatepath
+        config = getConfig()
+        templateObj = _.reduce(templateCommands, ((templateObj, cmd, name) ->
+          templateObj[name] = templateCommands[name](config)
+          return templateObj
+        ), {})
+        provide argv.templatepath
+        contextjsonname = join(argv.templatepath, argv.contextjsonname)
+        write contextjsonname, JSON.stringify(templateObj)
 
 
 commandNames = _.reduce(commands, ((memo, v, k) ->

  memo
), {})

exports.run = () ->
  argv = require("optimist")
    .usage(usage)

    .boolean(["l", "link"]).alias("l", "link")
    .describe("l", "install components as symlinks (useful for development)")

    .boolean("listtests")
    .describe("listtests", "when running the 'test' command, just list tests")

    .string(["t", "templatepath"]).alias("t", "templatepath")
    .describe("t", "when running 'build', command, specify the templatepath")

    .string(["j", "contextjsonname"]).alias("j", "contextjsonname")
    .default("contextjsonname", "csi-context.json")
    .describe("j", "when running 'build', command, specify the name of the context json")

    .string(["s", "staticpath"]).alias("s", "staticpath")
    .describe("s", "when running 'build', command, specify the staticpath")

    .string(["b", "baseurl"]).alias("b", "baseurl")
    .default("baseurl", "/static")
    .describe("b", "when running 'build', command, specify the baseurl")

    .check((argv) ->
      if argv._[0] is 'build'
        for arg in ['templatepath', 'staticpath']
          if not argv[arg]
            throw Error "'#{arg}' option must be defined for 'build' command"
    )

    .alias("h", "help")

    .argv


yaml = require "js-yaml"
testServer = require "./server"

read = (fname, encoding="utf8") ->
  fs.readFileSync(fname, encoding)
write = (fname, content, encoding="utf8") ->
  fs.writeFileSync(fname, content, encoding)
exists = path.existsSync
join = path.join
pathSep = path.normalize("/")
argv = null
usage = null

pkgJson = (dir = ".") ->
  JSON.parse read(join(dir, "package.json"))
