diff --git a/bin/bugz b/bin/bugz
index 39229ac..afaa4f8 100755
--- a/bin/bugz
+++ b/bin/bugz
@@ -4,79 +4,28 @@ import locale
 import sys
 import traceback
 
-from optparse import OptionParser
-
-from bugz import cli, __version__
 from bugz.cli import BugzError, PrettyBugz
+from bugz.clparser import make_parser, get_kwds
 
 def main():
-	if len(sys.argv) < 2:
-		PrettyBugz.help()
-		sys.exit(-1)
-
-	cmd = sys.argv[1]
-
-	parser = OptionParser(version = '%%prog %s' % __version__)
-
-	global_opts = PrettyBugz.options
-	for x in global_opts.values():
-		parser.add_option(x)
-
-	if cmd == '--help' or cmd == '-h' or cmd == 'help':
-		PrettyBugz.help()
-		sys.exit(0)
-	elif cmd == '--version' :
-		parser.print_version()
-		sys.exit(0)
-
-	cmd_func = getattr(PrettyBugz, cmd, None)
-	if cmd_func is None:
-		print '!! Error: Unable to recognise command: %s' % cmd
-		print
-		PrettyBugz.help()
-		sys.exit(-1)
-
-	cmd_options = getattr(cmd_func, "options", {})
-	cmd_args = getattr(cmd_func, "args", "[options]")
-	parser.set_description(cmd_func.__doc__)
-	parser.set_usage('%%prog %s %s' % (cmd, cmd_args))
-
-	for x in cmd_options.values():
-		parser.add_option(x)
+	parser = make_parser()
 
 	# parse options
-	opts, args = parser.parse_args(sys.argv[2:])
-
-	# separate bugz options and cmd options
-	bugz_kwds = {}
-	for name, opt in global_opts.items():
-		try:
-			bugz_kwds[name] = getattr(opts, name)
-		except AttributeError:
-			pass
-
-	cmd_kwds = {}
-	for name, opt in cmd_options.items():
-		try:
-			cmd_kwds[name] = getattr(opts, name)
-		except AttributeError:
-			pass
+	args = parser.parse_args()
+	(bugz_kwds, cmd_kwds) = get_kwds(args)
 
 	try:
 		bugz = PrettyBugz(**bugz_kwds)
-		getattr(bugz, cmd)(*args, **cmd_kwds)
+		args.func(bugz, **cmd_kwds)
 
 	except BugzError, e:
 		print ' ! Error: %s' % e
-		print
-		parser.print_help()
 		sys.exit(-1)
 
 	except TypeError, e:
 		print ' ! Error: Incorrect number of arguments supplied'
 		print
 		traceback.print_exc()
-		parser.print_help()
 		sys.exit(-1)
 
 	except RuntimeError, e:
diff --git a/bugz/bugzilla.py b/bugz/bugzilla.py
index 7e1a2a9..2134de6 100644
--- a/bugz/bugzilla.py
+++ b/bugz/bugzilla.py
@@ -7,6 +7,7 @@ import locale
 import mimetypes
 import os
 import re
+import sys
 
 from cookielib import LWPCookieJar, CookieJar
 from cStringIO import StringIO
@@ -84,6 +85,8 @@ def get_content_type(filename):
 #
 # Override the behaviour of elementtree and allow us to
 # force the encoding to utf-8
+# Not needed in Python 2.7, since ElementTree.XMLTreeBuilder uses the forced
+# encoding.
 #
 
 class ForcedEncodingXMLTreeBuilder(ElementTree.XMLTreeBuilder):
@@ -235,7 +238,7 @@ class Bugz:
 			base64string = base64.encodestring('%s:%s' % (self.httpuser, self.httppassword))[:-1]
 			req.add_header("Authorization", "Basic %s" % base64string)
 		resp = self.opener.open(req)
-		re_request_login = re.compile(r'<title>.*Log in to Bugzilla</title>')
+		re_request_login = re.compile(r'<title>.*Log in to .*</title>')
 		if not re_request_login.search(resp.read()):
 			self.log('Already logged in.')
 			self.authenticated = True
@@ -255,6 +258,8 @@ class Bugz:
 		qparams = config.params['auth'].copy()
 		qparams['Bugzilla_login'] = self.user
 		qparams['Bugzilla_password'] = self.password
+		if not self.forget:
+			qparams['Bugzilla_remember'] = 'on'
 
 		req_url = urljoin(self.base, config.urls['auth'])
 		req = Request(req_url, urlencode(qparams), config.headers)
@@ -285,7 +290,7 @@ class Bugz:
 				self.log('Unknown field: ' + field)
 				columns.append(field)
 		for row in rows[1:]:
-			if row[0].find("Missing Search") != -1:
+			if "Missing Search" in row[0]:
 				self.log('Bugzilla error (Missing search found)')
 				return None
 			fields = {}
@@ -439,9 +444,25 @@ class Bugz:
 			req.add_header("Authorization", "Basic %s" % base64string)
 		resp = self.opener.open(req)
 
-		fd = StringIO(resp.read())
+		data = resp.read()
+		# Get rid of control characters.
+		data = re.sub('[\x00-\x08\x0e-\x1f\x0b\x0c]', '', data)
+		fd = StringIO(data)
+
 		# workaround for ill-defined XML templates in bugzilla 2.20.2
-		parser = ForcedEncodingXMLTreeBuilder(encoding = 'utf-8')
+		(major_version, minor_version) = \
+		    (sys.version_info[0], sys.version_info[1])
+		if major_version > 2 or \
+			    (major_version == 2 and minor_version >= 7):
+			# If this is 2.7 or greater, then XMLTreeBuilder
+			# does what we want.
+			parser_class = ElementTree.XMLParser
+		else:
+			# Running under Python 2.6, so we need to use our
+			# subclass of XMLTreeBuilder instead.
+			parser_class = ForcedEncodingXMLTreeBuilder
+		parser = parser_class(encoding = 'utf-8')
+
 		etree = ElementTree.parse(fd, parser)
 		bug = etree.find('.//bug')
 		if bug and bug.attrib.has_key('error'):
@@ -517,7 +538,7 @@ class Bugz:
 		FIELDS = ('bug_file_loc', 'bug_severity', 'short_desc', 'bug_status',
 				'status_whiteboard', 'keywords',
 				'op_sys', 'priority', 'version', 'target_milestone',
-				'assigned_to', 'rep_platform', 'product', 'component')
+				'assigned_to', 'rep_platform', 'product', 'component', 'token')
 
 		FIELDS_MULTI = ('blocked', 'dependson')
 
@@ -637,6 +658,11 @@ class Bugz:
 
 		try:
 			resp = self.opener.open(req)
+			re_error = re.compile(r'id="error_msg".*>([^<]+)<')
+			error = re_error.search(resp.read())
+			if error:
+				print error.group(1)
+				return []
 			return modified
 		except:
 			return []
@@ -753,7 +779,7 @@ class Bugz:
 		return 0
 
 	def attach(self, bugid, title, description, filename,
-			content_type = 'text/plain'):
+			content_type = 'text/plain', ispatch = False):
 		"""Attach a file to a bug.
 
 		@param bugid: bug id
@@ -777,7 +803,11 @@ class Bugz:
 		qparams['bugid'] = bugid
 		qparams['description'] = title
 		qparams['comment'] = description
-		qparams['contenttypeentry'] = content_type
+		if ispatch:
+			qparams['ispatch'] = '1'
+			qparams['contenttypeentry'] = 'text/plain'
+		else:
+			qparams['contenttypeentry'] = content_type
 
 		filedata = [('data', filename, open(filename).read())]
 		content_type, body = encode_multipart_formdata(qparams.items(),
diff --git a/bugz/cli.py b/bugz/cli.py
index e97da1b..8f25450 100644
--- a/bugz/cli.py
+++ b/bugz/cli.py
@@ -8,8 +8,6 @@ import sys
 import tempfile
 import textwrap
 
-from optparse import OptionParser
-from optparse import make_option
 from urlparse import urljoin
 
 try:
@@ -122,43 +120,7 @@ def block_edit(comment, comment_from = ''):
 class BugzError(Exception):
 	pass
 
-#
-# ugly optparse callbacks (really need to integrate this somehow)
-#
-
-def modify_opt_fixed(opt, opt_str, val, parser):
-	parser.values.status = 'RESOLVED'
-	parser.values.resolution = 'FIXED'
-
-def modify_opt_invalid(opt, opt_str, val, parser):
-	parser.values.status = 'RESOLVED'
-	parser.values.resolution = 'INVALID'
-
 class PrettyBugz(Bugz):
-	options = {
-		'base': make_option('-b', '--base', type='string',
-							default = 'https://bugs.gentoo.org/',
-							help = 'Base URL of Bugzilla'),
-		'user': make_option('-u', '--user', type='string',
-							help = 'Username for commands requiring authentication'),
-		'password': make_option('-p', '--password', type='string',
-							help = 'Password for commands requiring authentication'),
-		'httpuser': make_option('-H', '--httpuser', type='string',
-							help = 'Username for basic http auth'),
-		'httppassword': make_option('-P', '--httppassword', type='string',
-							help = 'Password for basic http auth'),
-		'forget': make_option('-f', '--forget', action='store_true',
-							help = 'Forget login after execution'),
-		'columns': make_option('--columns', type='int', default = 0,
-							help = 'Maximum number of columns output should use'),
-		'encoding': make_option('--encoding',
-							help = 'Output encoding (default: utf-8).'),
-		'skip_auth': make_option('--skip-auth', action='store_true',
-							default = False, help = 'Skip Authentication.'),
-		'quiet': make_option('-q', '--quiet', action='store_true',
-							default = False, help = 'Quiet mode'),
-	}
-
 	def __init__(self, base, user = None, password =None, forget = False,
 			columns = 0, encoding = '', skip_auth = False,
 			quiet = False, httpuser = None, httppassword = None ):
@@ -195,10 +157,11 @@ class PrettyBugz(Bugz):
 	def get_input(self, prompt):
 		return raw_input(prompt)
 
-	def search(self, *args, **kwds):
+	def search(self, **kwds):
 		"""Performs a search on the bugzilla database with the keywords given on the title (or the body if specified).
 		"""
-		search_term = ' '.join(args).strip()
+		search_term = ' '.join(kwds['terms']).strip()
+		del kwds['terms']
 		show_status = kwds['show_status']
 		del kwds['show_status']
 		show_url = kwds['show_url']
@@ -232,41 +195,6 @@ class PrettyBugz(Bugz):
 
 		self.listbugs(result, show_url, show_status)
 
-	search.args = "<search term> [options..]"
-	search.options = {
-		'order': make_option('-o', '--order', type='choice',
-								choices = config.choices['order'].keys(),
-								default = 'number'),
-		'assigned_to': make_option('-a', '--assigned-to',
-								help = 'email the bug is assigned to'),
-		'reporter': make_option('-r', '--reporter',
-								help = 'email the bug was reported by'),
-		'cc': make_option('--cc',help = 'Restrict by CC email address'),
-		'commenter': make_option('--commenter',help = 'email that commented the bug'),
-		'status': make_option('-s', '--status', action='append',
-								help = 'Bug status (for multiple choices,'
-								'use --status=NEW --status=ASSIGNED) or --status=all for all statuses'),
-		'severity': make_option('--severity', action='append',
-								choices = config.choices['severity'],
-								help = 'Restrict by severity.'),
-		'priority': make_option('--priority', action='append',
-								choices = config.choices['priority'].values(),
-								help = 'Restrict by priority (1 or more)'),
-		'comments': make_option('-c', '--comments',  action='store_true',
-								help = 'Search comments instead of title'),
-		'product': make_option('--product', action='append',
-								help = 'Restrict by product (1 or more)'),
-		'component': make_option('-C', '--component', action='append',
-								help = 'Restrict by component (1 or more)'),
-		'keywords': make_option('-k', '--keywords', help = 'Bug keywords'),
-		'whiteboard': make_option('-w', '--whiteboard',
-								help = 'Status whiteboard'),
-		'show_status': make_option('--show-status', help='show status of bugs',
-								action = 'store_true', default = False),
-		'show_url': make_option('--show-url', help='Show bug id as a url.',
-								action = 'store_true', default = False),
-	}
-
 	def namedcmd(self, command, show_status=False, show_url=False):
 		"""Run a command stored in Bugzilla by name."""
 		log_msg = 'Running namedcmd \'%s\''%command
@@ -280,14 +208,6 @@ class PrettyBugz(Bugz):
 
 		self.listbugs(result, show_url, show_status)
 
-	namedcmd.args = "<command name>"
-	namedcmd.options = {
-		'show_status': make_option('--show-status', help='show status of bugs',
-								action = 'store_true', default = False),
-		'show_url': make_option('--show-url', help='Show bug id as a url.',
-								action = 'store_true', default = False),
-	}
-
 	def get(self, bugid, comments = True, attachments = True):
 		""" Fetch bug details given the bug id """
 		self.log('Getting bug %s ..' % bugid)
@@ -384,13 +304,6 @@ class PrettyBugz(Bugz):
 				i += 1
 			print
 
-	get.args = "<bug_id> [options..]"
-	get.options = {
-		'comments': make_option("-n", "--no-comments", dest = 'comments',
-								action="store_false", default = True,
-								help = 'Do not show comments'),
-	}
-
 	def post(self, product = None, component = None,
 			title = None, description = None, assigned_to = None,
 			cc = None, url = None, keywords = None,
@@ -579,42 +492,6 @@ class PrettyBugz(Bugz):
 		else:
 			raise RuntimeError('Failed to submit bug')
 
-	post.args = "[options]"
-	post.options = {
-		'product': make_option('--product', help = 'Product'),
-		'component': make_option('--component', help = 'Component'),
-		'prodversion': make_option('--prodversion',
-									help = 'Version of the product'),
-		'title': make_option('-t', '--title', help = 'Title of bug'),
-		'description': make_option('-d', '--description',
-									help = 'Description of the bug'),
-		'description_from': make_option('-F' , '--description-from',
-									help = 'Description from contents of'
-									' file'),
-		'append_command': make_option('--append-command',
-									help = 'Append the output of a command to the description.'),
-		'assigned_to': make_option('-a', '--assigned-to',
-									help = 'Assign bug to someone other than '
-									'the default assignee'),
-		'cc': make_option('--cc', help = 'Add a list of emails to CC list'),
-		'url': make_option('-U', '--url',
-									help = 'URL associated with the bug'),
-		'dependson': make_option('--depends-on', dest='dependson', help = 'Add a list of bug dependencies'),
-		'blocked': make_option('--blocked', help = 'Add a list of blocker bugs'),
-		'keywords': make_option('-k', '--keywords', help = 'List of bugzilla keywords'),
-		'batch': make_option('--batch', action="store_true",
-									help = 'do not prompt for any values'),
-		'default_confirm': make_option('--default-confirm',
-									choices = ['y','Y','n','N'],
-									default = 'y',
-									help = 'default answer to confirmation question (y/n)'),
-		'priority': make_option('--priority',
-								choices=config.choices['priority'].values()),
-		'severity': make_option('-S', '--severity',
-								choices=config.choices['severity']),
-	}
-
-
 	def modify(self, bugid, **kwds):
 		"""Modify an existing bug (eg. adding a comment or changing resolution.)"""
 		if 'comment_from' in kwds:
@@ -637,6 +514,15 @@ class PrettyBugz(Bugz):
 				kwds['comment'] = block_edit('Enter comment:')
 			del kwds['comment_editor']
 
+		if kwds['fixed']:
+			kwds['STATUS'] = 'CLOSED'
+			kwds['resolution'] = 'FIXED'
+		del kwds['fixed']
+
+		if kwds['invalid']:
+			kwds['STATUS'] = 'CLOSED'
+			kwds['resolution'] = 'INVALID'
+		del kwds['invalid']
 		result = Bugz.modify(self, bugid, **kwds)
 		if not result:
 			raise RuntimeError('Failed to modify bug')
@@ -645,53 +531,6 @@ class PrettyBugz(Bugz):
 			for field, value in result:
 				self.log('  %-12s: %s' % (field, value))
 
-
-	modify.args = "<bug_id> [options..]"
-	modify.options = {
-		'title': make_option('-t', '--title', help = 'Set title of bug'),
-		'comment_from': make_option('-F', '--comment-from',
-									help = 'Add comment from file.  If -C is also specified, the editor will be opened with this file as its contents.'),
-		'comment_editor': make_option('-C', '--comment-editor',
-									action='store_true', default = False,
-									help = 'Add comment via default editor'),
-		'comment': make_option('-c', '--comment', help = 'Add comment to bug'),
-		'url': make_option('-U', '--url', help = 'Set URL field of bug'),
-		'status': make_option('-s', '--status',
-									choices=config.choices['status'].values(),
-									help = 'Set new status of bug (eg. RESOLVED)'),
-		'resolution': make_option('-r', '--resolution',
-									choices=config.choices['resolution'].values(),
-									help = 'Set new resolution (only if status = RESOLVED)'),
-		'assigned_to': make_option('-a', '--assigned-to'),
-		'duplicate': make_option('-d', '--duplicate', type='int', default=0),
-		'priority': make_option('--priority',
-								choices=config.choices['priority'].values()),
-		'severity': make_option('-S', '--severity',
-								choices=config.choices['severity']),
-		'fixed': make_option('--fixed', action='callback',
-								callback = modify_opt_fixed,
-								help = "Mark bug as RESOLVED, FIXED"),
-		'invalid': make_option('--invalid', action='callback',
-								callback = modify_opt_invalid,
-								help = "Mark bug as RESOLVED, INVALID"),
-		'add_cc': make_option('--add-cc', action = 'append',
-								help = 'Add an email to the CC list'),
-		'remove_cc': make_option('--remove-cc', action = 'append',
-								help = 'Remove an email from the CC list'),
-		'add_dependson': make_option('--add-dependson', action = 'append',
-								help = 'Add a bug to the depends list'),
-		'remove_dependson': make_option('--remove-dependson', action = 'append',
-								help = 'Remove a bug from the depends list'),
-		'add_blocked': make_option('--add-blocked', action = 'append',
-								help = 'Add a bug to the blocked list'),
-		'remove_blocked': make_option('--remove-blocked', action = 'append',
-								help = 'Remove a bug from the blocked list'),
-		'whiteboard': make_option('-w', '--whiteboard',
-								help = 'Set Status whiteboard'),
-		'keywords': make_option('-k', '--keywords',
-								help = 'Set bug keywords'),
-		}
-
 	def attachment(self, attachid, view = False):
 		""" Download or view an attachment given the id."""
 		self.log('Getting attachment %s' % attachid)
@@ -713,30 +552,14 @@ class PrettyBugz(Bugz):
 
 			open(safe_filename, 'wb').write(result['fd'].read())
 
-	attachment.args = "<attachid> [-v]"
-	attachment.options = {
-		'view': make_option('-v', '--view', action="store_true",
-							default = False,
-							help = "Print attachment rather than save")
-	}
-
-	def attach(self, bugid, filename, content_type = 'text/plain', description = None):
+	def attach(self, bugid, filename, content_type = 'text/plain', patch = False, description = None):
 		""" Attach a file to a bug given a filename. """
 		if not os.path.exists(filename):
 			raise BugzError('File not found: %s' % filename)
 		if not description:
 			description = block_edit('Enter description (optional)')
 		result = Bugz.attach(self, bugid, filename, description, filename,
-				content_type)
-
-	attach.args = "<bugid> <filename> [-c=<mimetype>] [-d=<description>]"
-	attach.options = {
-		'content_type': make_option('-c', '--content-type',
-									default='text/plain',
-									help = 'Mimetype of the file (default: text/plain)'),
-		'description': make_option('-d', '--description',
-									help = 'A description of the attachment.')
-	}
+				content_type, patch)
 
 	def listbugs(self, buglist, show_url=False, show_status=False):
 		for row in buglist:
@@ -760,27 +583,3 @@ class PrettyBugz(Bugz):
 				print line[:self.columns]
 
 		self.log("%i bug(s) found." % len(buglist))
-
-	@classmethod
-	def help(self):
-		print 'Usage: bugz <subcommand> [parameter(s)] [options..]'
-		print
-		print 'Subcommands:'
-		print '  search      Search for bugs in bugzilla'
-		print '  get         Get a bug from bugzilla'
-		print '  attachment  Get an attachment from bugzilla'
-		print '  post        Post a new bug into bugzilla'
-		print '  modify      Modify a bug (eg. post a comment)'
-		print '  attach      Attach file to a bug'
-		print '  namedcmd    Run a stored search,'
-		print
-		print 'Examples:'
-		print '  bugz get 12345'
-		print '  bugz search python --assigned-to liquidx@gentoo.org'
-		print '  bugz attachment 5000 --view'
-		print '  bugz attach 140574 python-2.4.3.ebuild'
-		print '  bugz modify 140574 -c "Me too"'
-		print '  bugz namedcmd "Amd64 stable"'
-		print
-		print 'For more information on subcommands, run:'
-		print '  bugz <subcommand> --help'
diff --git a/bugz/clparser.py b/bugz/clparser.py
new file mode 100644
index 0000000..f9bdca9
--- /dev/null
+++ b/bugz/clparser.py
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+
+import argparse
+
+from bugz import __version__
+from bugz.cli import PrettyBugz
+from bugz.config import config
+
+def make_attach_parser(subparsers):
+	attach_parser = subparsers.add_parser('attach',
+		help = 'attach file to a bug')
+	attach_parser.add_argument('bugid',
+		help = 'the ID of the bug where the file should be attached')
+	attach_parser.add_argument('filename',
+		help = 'the name of the file to attach')
+	attach_parser.add_argument('-c', '--content-type',
+		default='text/plain',
+		help = 'mimetype of the file (default: text/plain)')
+	attach_parser.add_argument('-d', '--description',
+		help = 'a description of the attachment.')
+	attach_parser.add_argument('-p', '--patch',
+		action='store_true',
+	help = 'attachment is a patch')
+	attach_parser.set_defaults(func = PrettyBugz.attach)
+
+def make_attachment_parser(subparsers):
+	attachment_parser = subparsers.add_parser('attachment',
+		help = 'get an attachment from bugzilla')
+	attachment_parser.add_argument('attachid',
+		help = 'the ID of the attachment')
+	attachment_parser.add_argument('-v', '--view',
+		action="store_true",
+		default = False,
+		help = 'print attachment rather than save')
+	attachment_parser.set_defaults(func = PrettyBugz.attachment)
+
+def make_get_parser(subparsers):
+	get_parser = subparsers.add_parser('get',
+		help = 'get a bug from bugzilla')
+	get_parser.add_argument('bugid',
+		help = 'the ID of the bug to retrieve.')
+	get_parser.add_argument("-a", "--no-attachments",
+		action="store_false",
+		default = True,
+		help = 'do not show attachments',
+		dest = 'attachments')
+	get_parser.add_argument("-n", "--no-comments",
+		action="store_false",
+		default = True,
+		help = 'do not show comments',
+		dest = 'comments')
+	get_parser.set_defaults(func = PrettyBugz.get)
+
+def make_modify_parser(subparsers):
+	modify_parser = subparsers.add_parser('modify',
+		help = 'modify a bug (eg. post a comment)')
+	modify_parser.add_argument('bugid',
+		help = 'the ID of the bug to modify')
+	modify_parser.add_argument('-a', '--assigned-to',
+		help = 'change assignee for this bug')
+	modify_parser.add_argument('-C', '--comment-editor',
+		action='store_true',
+		help = 'add comment via default editor')
+	modify_parser.add_argument('-F', '--comment-from',
+		help = 'add comment from file.  If -C is also specified, the editor will be opened with this file as its contents.')
+	modify_parser.add_argument('-c', '--comment',
+		help = 'add comment from command line')
+	modify_parser.add_argument('-d', '--duplicate',
+		type = int,
+		default = 0,
+		help = 'this bug is a duplicate')
+	modify_parser.add_argument('-k', '--keywords',
+		help = 'set bug keywords'),
+	modify_parser.add_argument('--priority',
+		choices=config.choices['priority'].values(),
+		help = 'change the priority for this bug')
+	modify_parser.add_argument('-r', '--resolution',
+		choices=config.choices['resolution'].values(),
+		help = 'set new resolution (only if status = RESOLVED)')
+	modify_parser.add_argument('-s', '--status',
+		choices=config.choices['status'].values(),
+		help = 'set new status of bug (eg. RESOLVED)')
+	modify_parser.add_argument('-S', '--severity',
+		choices=config.choices['severity'],
+		help = 'set severity for this bug')
+	modify_parser.add_argument('-t', '--title',
+		help = 'set title of bug')
+	modify_parser.add_argument('-U', '--url',
+		help = 'set URL field of bug')
+	modify_parser.add_argument('-w', '--whiteboard',
+		help = 'set Status whiteboard'),
+	modify_parser.add_argument('--add-cc',
+		action = 'append',
+		help = 'add an email to the CC list')
+	modify_parser.add_argument('--remove-cc',
+		action = 'append',
+		help = 'remove an email from the CC list')
+	modify_parser.add_argument('--add-dependson',
+		action = 'append',
+		help = 'add a bug to the depends list')
+	modify_parser.add_argument('--remove-dependson',
+		action = 'append',
+		help = 'remove a bug from the depends list')
+	modify_parser.add_argument('--add-blocked',
+		action = 'append',
+		help = 'add a bug to the blocked list')
+	modify_parser.add_argument('--remove-blocked',
+		action = 'append',
+		help = 'remove a bug from the blocked list')
+	modify_parser.add_argument('--fixed',
+		action='store_true',
+		help = 'mark bug as RESOLVED, FIXED')
+	modify_parser.add_argument('--invalid',
+		action='store_true',
+		help = 'mark bug as RESOLVED, INVALID')
+	modify_parser.set_defaults(func = PrettyBugz.modify)
+
+def make_namedcmd_parser(subparsers):
+	namedcmd_parser = subparsers.add_parser('namedcmd',
+		help = 'run a stored search')
+	namedcmd_parser.add_argument('command',
+		help = 'the name of the stored search')
+	namedcmd_parser.add_argument('--show-status',
+		action = 'store_true',
+		help = 'show status of bugs')
+	namedcmd_parser.add_argument('--show-url',
+		action = 'store_true',
+		help = 'show bug id as a url')
+	namedcmd_parser.set_defaults(func = PrettyBugz.namedcmd)
+
+def make_post_parser(subparsers):
+	post_parser = subparsers.add_parser('post',
+		help = 'post a new bug into bugzilla')
+	post_parser.add_argument('--product',
+		help = 'product')
+	post_parser.add_argument('--component',
+		help = 'component')
+	post_parser.add_argument('--prodversion',
+		help = 'version of the product')
+	post_parser.add_argument('-t', '--title',
+		help = 'title of bug')
+	post_parser.add_argument('-d', '--description',
+		help = 'description of the bug')
+	post_parser.add_argument('-F' , '--description-from',
+		help = 'description from contents of file')
+	post_parser.add_argument('--append-command',
+		help = 'append the output of a command to the description')
+	post_parser.add_argument('-a', '--assigned-to',
+		help = 'assign bug to someone other than the default assignee')
+	post_parser.add_argument('--cc',
+		help = 'add a list of emails to CC list')
+	post_parser.add_argument('-U', '--url',
+		help = 'URL associated with the bug')
+	post_parser.add_argument('--depends-on',
+		help = 'add a list of bug dependencies',
+		dest='dependson')
+	post_parser.add_argument('--blocked',
+		help = 'add a list of blocker bugs')
+	post_parser.add_argument('-k', '--keywords',
+		help = 'list of bugzilla keywords')
+	post_parser.add_argument('--batch',
+		action="store_true",
+		help = 'do not prompt for any values')
+	post_parser.add_argument('--default-confirm',
+		choices = ['y','Y','n','N'],
+		default = 'y',
+		help = 'default answer to confirmation question')
+	post_parser.add_argument('--priority',
+		choices=config.choices['priority'].values(),
+		help = 'set priority for the new bug')
+	post_parser.add_argument('-S', '--severity',
+		choices=config.choices['severity'],
+		help = 'set the severity for the new bug')
+	post_parser.set_defaults(func = PrettyBugz.post)
+
+def make_search_parser(subparsers):
+	search_parser = subparsers.add_parser('search',
+		help = 'search for bugs in bugzilla')
+	search_parser.add_argument('terms',
+		nargs='*',
+		help = 'strings to search for in title or body')
+	search_parser.add_argument('-o', '--order',
+		choices = config.choices['order'].keys(),
+		default = 'number',
+		help = 'display bugs in this order')
+	search_parser.add_argument('-a', '--assigned-to',
+		help = 'email the bug is assigned to')
+	search_parser.add_argument('-r', '--reporter',
+		help = 'email the bug was reported by')
+	search_parser.add_argument('--cc',
+		help = 'restrict by CC email address')
+	search_parser.add_argument('--commenter',
+		help = 'email that commented the bug')
+	search_parser.add_argument('-s', '--status',
+		action='append',
+		help = 'restrict by status (one or more, use all for all statuses)')
+	search_parser.add_argument('--severity',
+		action='append',
+		choices = config.choices['severity'],
+		help = 'restrict by severity (one or more)')
+	search_parser.add_argument('--priority',
+		action='append',
+		choices = config.choices['priority'].values(),
+		help = 'restrict by priority (one or more)')
+	search_parser.add_argument('-c', '--comments',
+		action='store_true',
+		default=None,
+		help = 'search comments instead of title')
+	search_parser.add_argument('--product',
+		action='append',
+		help = 'restrict by product (one or more)')
+	search_parser.add_argument('-C', '--component',
+		action='append',
+		help = 'restrict by component (1 or more)')
+	search_parser.add_argument('-k', '--keywords',
+		help = 'restrict by keywords')
+	search_parser.add_argument('-w', '--whiteboard',
+		help = 'status whiteboard')
+	search_parser.add_argument('--show-status',
+		action = 'store_true',
+		help='show status of bugs')
+	search_parser.add_argument('--show-url',
+		action = 'store_true',
+		help='show bug id as a url.')
+	search_parser.set_defaults(func = PrettyBugz.search)
+
+def make_parser():
+	parser = argparse.ArgumentParser(
+		epilog = 'use -h after a sub-command for sub-command specific help')
+	parser.add_argument('-b', '--base',
+		default = 'https://bugs.gentoo.org/',
+		help = 'base URL of Bugzilla')
+	parser.add_argument('-u', '--user',
+		help = 'username for commands requiring authentication')
+	parser.add_argument('-p', '--password',
+		help = 'password for commands requiring authentication')
+	parser.add_argument('-H', '--httpuser',
+		help = 'username for basic http auth')
+	parser.add_argument('-P', '--httppassword',
+		help = 'password for basic http auth')
+	parser.add_argument('-f', '--forget',
+		action='store_true',
+		help = 'forget login after execution')
+	parser.add_argument('-q', '--quiet',
+		action='store_true',
+		default = False,
+		help = 'quiet mode')
+	parser.add_argument('--columns', 
+		type = int,
+		default = 0,
+		help = 'maximum number of columns output should use')
+	parser.add_argument('--encoding',
+		help = 'output encoding (default: utf-8).')
+	parser.add_argument('--skip-auth',
+		action='store_true',
+		default = False,
+		help = 'skip Authentication.')
+	parser.add_argument('--version',
+		action='version',
+		help='show program version and exit',
+		version='%(prog)s ' + __version__)
+	subparsers = parser.add_subparsers(help = 'help for sub-commands')
+	make_attach_parser(subparsers)
+	make_attachment_parser(subparsers)
+	make_get_parser(subparsers)
+	make_modify_parser(subparsers)
+	make_namedcmd_parser(subparsers)
+	make_post_parser(subparsers)
+	make_search_parser(subparsers)
+	return parser
+
+def get_kwds(args):
+	bugz = {}
+	cmd = {}
+	global_attrs = ['user', 'password', 'httpuser', 'httppassword', 'forget',
+		'base', 'columns', 'encoding', 'quiet', 'skip_auth']
+	for attr in dir(args):
+		if attr[0] != '_' and attr != 'func':
+			if attr in global_attrs:
+				bugz[attr] = getattr(args,attr)
+			else:
+				cmd[attr] = getattr(args,attr)
+	return bugz, cmd
diff --git a/bugz/config.py b/bugz/config.py
index 4083e48..824c2ad 100644
--- a/bugz/config.py
+++ b/bugz/config.py
@@ -54,6 +54,7 @@ class BugzConfig:
 
 		'attach_post': {
 		'action': 'insert',
+		'ispatch': '',
 		'contenttypemethod': 'manual',
 		'bugid': '',
 		'description': '',
diff --git a/contrib/zsh-completion b/contrib/zsh-completion
index ee4bef6..c88ebff 100644
--- a/contrib/zsh-completion
+++ b/contrib/zsh-completion
@@ -14,7 +14,7 @@ _bugz() {
     '(-P --httppassword)'{-P,--httppassword}'[basic http auth password (if required)]:password: '
     '(-f --forget)'{-f,--forget}'[do not remember authentication]'
     '--columns[number of columns to use when displaying output]:number: '
-    '(--skip-auth)'{--skip-auth}'[do not authenticate]'
+    '--skip-auth[do not authenticate]'
     '(-q --quiet)'{-q,--quiet}'[do not display status messages]'
   )
   _bugz_commands=(
