diff --git a/include/asterisk/cli.h b/include/asterisk/cli.h
index e2a6f818b03a0e5740d596835cef56a7f0832c51..050fe87d4138138dbe1da437b2ac9332a79a2899 100644
--- a/include/asterisk/cli.h
+++ b/include/asterisk/cli.h
@@ -44,9 +44,106 @@ void ast_cli(int fd, char *fmt, ...)
 
 #define AST_CLI_COMPLETE_EOF	"_EOF_"
 
-/*! \brief A command line entry */
+/*!
+   CLI commands are described by a struct ast_cli_entry that contains
+   all the components for their implementation.
+   In the "old-style" format, the record must contain:
+   - a NULL-terminated array of words constituting the command, e.g.
+	{ "set", "debug", "on", NULL },
+   - a summary string (short) and a usage string (longer);
+   - a handler which implements the command itself, invoked with
+     a file descriptor and argc/argv as typed by the user
+   - a 'generator' function which, given a partial string, can
+     generate legal completions for it.
+   An example is
+
+	int old_setdebug(int fd, int argc, char *argv[]);
+	char *dbg_complete(const char *line, const char *word, int pos, int n);
+
+	{ { "set", "debug", "on", NULL }, do_setdebug, "Enable debugging",
+	set_debug_usage, dbg_complete },
+
+   In the "new-style" format, all the above functionalities are implemented
+   by a single function, and the arguments tell which output is required.
+
+   NOTE: ideally, the new-style handler would have a different prototype,
+   i.e. something like
+	int new_setdebug(const struct ast_cli *e, int function,
+	    int fd, int argc, char *argv[],	// handler args
+	    int n, int pos, const char *line, const char *word // -complete args)
+   but at this moment we want to help the transition from old-style to new-style
+   functions so we keep the same interface and override some of the traditional
+   arguments.
+
+   To help the transition, a new-style entry has the same interface as the old one,
+   but it is declared as follows:
+
+	int new_setdebug(int fd, int argc, char *argv[]);
+
+	...
+	// this is how we create the entry to register
+	NEW_CLI(new_setdebug, "short description")
+	...
+
+   Called with the default arguments (argc > 0), the new_handler implements
+   the command as before.
+   A negative argc indicates one of the other functions, namely
+   generate the usage string, the full command, or implement the generator.
+   As a trick to extend the interface while being backward compatible,
+   argv[-1] points to a struct ast_cli_args, and, for the generator,
+   argv[0] is really a pointer to a struct ast_cli_args.
+   The return string is obtained by casting the result to char *
+
+   An example of new-style handler is the following
+
+\code
+static int test_new_cli(int fd, int argc, char *argv[])
+{
+        struct ast_cli_entry *e = (struct ast_cli_entry *)argv[-1];
+        struct ast_cli_args *a;
+	static char *choices = { "one", "two", "three", NULL };
+
+        switch(argc) {
+        case CLI_USAGE:
+                return (int)
+			"Usage: do this well <arg>\n"
+			"	typically multiline with body indented\n";
+
+        case CLI_CMD_STRING:
+                return (int)"do this well";
+
+        case CLI_GENERATE:
+                a = (struct ast_cli_args *)argv[0];
+                if (a->pos > e->args)
+                        return NULL;
+        	return ast_cli_complete(a->word, choices, a->n);
+
+        default:        
+                // we are guaranteed to be called with argc >= e->args;
+                if (argc > e->args + 1) // we accept one extra argument
+                        return RESULT_SHOWUSAGE;
+                ast_cli(fd, "done this well for %s\n", e->args[argc-1]);
+                return RESULT_SUCCESS;
+        }
+}
+
+\endcode
+ *
+ */
+
+/*! \brief calling arguments for new-style handlers */
+enum ast_cli_fn {
+	CLI_USAGE = -1,		/* return the usage string */
+	CLI_CMD_STRING = -2,	/* return the command string */
+	CLI_GENERATE = -3,	/* behave as 'generator', remap argv to struct ast_cli_args */
+};
+
+typedef int (*old_cli_fn)(int fd, int argc, char *argv[]);
+
+/*! \brief descriptor for a cli entry */
 struct ast_cli_entry {
-	char * const cmda[AST_MAX_CMD_LEN];
+	char * const cmda[AST_MAX_CMD_LEN];	/*!< words making up the command.
+		* set the first entry to NULL for a new-style entry. */
 	/*! Handler for the command (fd for output, # of args, argument list).
 	  Returns RESULT_SHOWUSAGE for improper arguments.
 	  argv[] has argc 'useful' entries, and an additional NULL entry
@@ -56,10 +153,8 @@ struct ast_cli_entry {
 	  that this memory is deallocated after the handler returns.
 	 */
 	int (*handler)(int fd, int argc, char *argv[]);
-	/*! Summary of the command (< 60 characters) */
-	const char *summary;
-	/*! Detailed usage information */
-	const char *usage;
+	const char *summary; /*!< Summary of the command (< 60 characters) */
+	const char *usage; /*!< Detailed usage information */
 	/*! Generate the n-th (starting from 0) possible completion
 	  for a given 'word' following 'line' in position 'pos'.
 	  'line' and 'word' must not be modified.
@@ -70,22 +165,34 @@ struct ast_cli_entry {
 	 */
 	char *(*generator)(const char *line, const char *word, int pos, int n);
 	struct ast_cli_entry *deprecate_cmd;
-	/*! For keeping track of usage */
-	int inuse;
-	struct module *module;	/*! module this belongs to */
+	int inuse; /*!< For keeping track of usage */
+	struct module *module;	/*!< module this belongs to */
 	char *_full_cmd;	/* built at load time from cmda[] */
 	/* This gets set in ast_cli_register()
 	  It then gets set to something different when the deprecated command
 	  is run for the first time (ie; after we warn the user that it's deprecated)
 	 */
+	int args;		/*!< number of non-null entries in cmda */
+	char *command;		/*!< command, non-null for new-style entries */
 	int deprecated;
 	char *_deprecated_by;	/* copied from the "parent" _full_cmd, on deprecated commands */
 	/*! For linking */
 	AST_LIST_ENTRY(ast_cli_entry) list;
 };
 
+#define NEW_CLI(fn, txt)	{ .handler = (old_cli_fn)fn, .summary = txt }
+
+/* argument for new-style CLI handler */
+struct ast_cli_args {
+	char fake[4];		/* a fake string, in the first position, for safety */
+	const char *line;	/* the current input line */
+	const char *word;	/* the word we want to complete */
+	int pos;		/* position of the word to complete */
+	int n;			/* the iteration count (n-th entry we generate) */
+};
+
 /*!
- * \brief Helper function to generate cli entries from a NULL-terminated array.
+ * Helper function to generate cli entries from a NULL-terminated array.
  * Returns the n-th matching entry from the array, or NULL if not found.
  * Can be used to implement generate() for static entries as below
  * (in this example we complete the word in position 2):
diff --git a/main/cli.c b/main/cli.c
index e3a5ffe897f5c301945e4cb941623860e0d698fa..5949085e015d60243bf32f619cafdcc02abe76ff 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -111,17 +111,6 @@ static char verbose_help[] =
 "       no messages should be displayed. Equivalent to -v[v[v...]]\n"
 "       on startup\n";
 
-static char debug_help[] = 
-"Usage: core set debug <level> [filename]\n"
-"       Sets level of core debug messages to be displayed.  0 means\n"
-"       no messages should be displayed.  Equivalent to -d[d[d...]]\n"
-"       on startup.  If filename is specified, debugging will be\n"
-"       limited to just that file.\n";
-
-static char nodebug_help[] = 
-"Usage: core set debug off\n"
-"       Turns off core debug messages.\n";
-
 static char logger_mute_help[] = 
 "Usage: logger mute\n"
 "       Disables logging output to the current console, making it possible to\n"
@@ -245,55 +234,61 @@ static int handle_verbose(int fd, int argc, char *argv[])
 
 static int handle_set_debug(int fd, int argc, char *argv[])
 {
+	struct ast_cli_entry *e = (struct ast_cli_entry *)argv[-1];
 	int oldval = option_debug;
 	int newlevel;
 	int atleast = 0;
 	char *filename = '\0';
-
-	/* 'core set debug <level>'
-	 * 'core set debug <level> <fn>'
-	 * 'core set debug atleast <level>'
-	 * 'core set debug atleast <level> <fn>'
+	static char *choices[] = { "off", "atleast", NULL };
+	struct ast_cli_args *a;
+
+	switch (argc) {
+	case CLI_CMD_STRING:
+		return (int)"core set debug";
+
+	case CLI_USAGE:
+		return (int)
+			"Usage: core set debug [atleast] <level> [filename]\n"
+			"       core set debug off\n"
+			"       Sets level of core debug messages to be displayed. 0 or 'off' means\n"
+			"       no messages should be displayed.  Equivalent to -d[d[d...]]\n"
+			"       on startup.  If filename is specified, debugging will be\n"
+			"       limited to just that file.\n";
+
+	case CLI_GENERATE:
+		a = (struct ast_cli_args *)argv[0];
+		if (a->pos > e->args)
+			return NULL;
+		return (int)ast_cli_complete(a->word, choices, a->n);
+	}
+	/* all the above return, so we proceed with the handler.
+	 * we are guaranteed to be called with argc >= e->args;
 	 */
-	if ((argc < 4) || (argc > 6))
+
+	if (argc < e->args + 1)
 		return RESULT_SHOWUSAGE;
 
-	if (!strcasecmp(argv[3], "atleast"))
+	if (argc == e->args + 1 && !strcasecmp(argv[e->args], "off")) {
+		newlevel = 0;
+		goto done;
+	}
+	if (!strcasecmp(argv[e->args], "atleast"))
 		atleast = 1;
+	if (argc > e->args + atleast + 2)
+		return RESULT_SHOWUSAGE;
+	if (sscanf(argv[e->args + atleast], "%d", &newlevel) != 1)
+		return RESULT_SHOWUSAGE;
 
-	if (!atleast) {
-		if (argc > 5)
-			return RESULT_SHOWUSAGE;
-
-		if (sscanf(argv[3], "%d", &newlevel) != 1)
-			return RESULT_SHOWUSAGE;
-
-		if (argc == 4) {
-			debug_filename[0] = '\0';
-		} else {
-			filename = argv[4];
-			ast_copy_string(debug_filename, filename, sizeof(debug_filename));
-		}
-
-		option_debug = newlevel;
+	if (argc == e->args + atleast + 1) {
+		debug_filename[0] = '\0';
 	} else {
-		if (argc < 5 || argc > 6)
-			return RESULT_SHOWUSAGE;
-
-		if (sscanf(argv[4], "%d", &newlevel) != 1)
-			return RESULT_SHOWUSAGE;
-
-		if (argc == 5) {
-			debug_filename[0] = '\0';
-		} else {
-			filename = argv[5];
-			ast_copy_string(debug_filename, filename, sizeof(debug_filename));
-		}
-
-		if (newlevel > option_debug)
-			option_debug = newlevel;
+		ast_copy_string(debug_filename, argv[e->args + atleast + 1], sizeof(debug_filename));
 	}
 
+done:
+	if (!atleast || newlevel > option_debug)
+		option_debug = newlevel;
+
 	if (oldval > 0 && option_debug == 0)
 		ast_cli(fd, "Core debug is now OFF\n");
 	else if (option_debug > 0) {
@@ -407,10 +402,6 @@ static int modlist_modentry(const char *module, const char *description, int use
 	return 0;
 }
 
-static char modlist_help[] =
-"Usage: module show [like keyword]\n"
-"       Shows Asterisk modules currently in use, and usage statistics.\n";
-
 static char uptime_help[] =
 "Usage: core show uptime [seconds]\n"
 "       Shows Asterisk uptime information.\n"
@@ -483,17 +474,39 @@ static int handle_showuptime(int fd, int argc, char *argv[])
 	return RESULT_SUCCESS;
 }
 
-/* core show modules [like keyword] */
 static int handle_modlist(int fd, int argc, char *argv[])
 {
-	char *like = "";
-	if (argc != 2 && argc != 4)
-		return RESULT_SHOWUSAGE;
-	else if (argc == 4) {
-		if (strcmp(argv[2],"like")) 
-			return RESULT_SHOWUSAGE;
-		like = argv[3];
+	struct ast_cli_entry *e = (struct ast_cli_entry *)argv[-1];
+	char *like;
+	struct ast_cli_args *a;
+
+	switch(argc) {
+	case CLI_CMD_STRING:
+		return (int)"module show";
+
+	case CLI_USAGE:
+		return (int)
+			"Usage: module show [like keyword]\n"
+			"       Shows Asterisk modules currently in use, and usage statistics.\n";
+
+	case CLI_GENERATE:
+		a = (struct ast_cli_args *)argv[0];
+		if (a->pos == e->args)
+			return (int)(a->n == 0 ? strdup("like") : NULL);
+		else if (a->pos == e->args+1 && strcasestr(a->line," like "))
+			return (int)ast_module_helper(a->line, a->word, a->pos, a->n, a->pos, 0);
+		else
+			return (int)NULL;
 	}
+	/* all the above return, so we proceed with the handler.
+	 * we are guaranteed to have argc >= e->args
+	 */
+	if (argc == e->args)
+		like = "";
+	else if (argc == e->args + 2 && !strcmp(argv[e->args],"like"))
+		like = argv[e->args + 1];
+	else
+		return RESULT_SHOWUSAGE;
 		
 	ast_mutex_lock(&climodentrylock);
 	climodentryfd = fd; /* global, protected by climodentrylock */
@@ -981,11 +994,6 @@ static char *complete_mod_3(const char *line, const char *word, int pos, int sta
 	return ast_module_helper(line, word, pos, state, 2, 1);
 }
 
-static char *complete_mod_4(const char *line, const char *word, int pos, int state)
-{
-	return ast_module_helper(line, word, pos, state, 3, 0);
-}
-
 static char *complete_fn(const char *line, const char *word, int pos, int state)
 {
 	char *c;
@@ -1129,13 +1137,7 @@ static struct ast_cli_entry cli_cli[] = {
 	handle_core_set_debug_channel, "Enable/disable debugging on a channel",
 	debugchan_help, complete_ch_5, &cli_debug_channel_deprecated },
 
-	{ { "core", "set", "debug", NULL },
-	handle_set_debug, "Set level of debug chattiness",
-	debug_help },
-
-	{ { "core", "set", "debug", "off", NULL },
-	handle_nodebug, "Turns off debug chattiness",
-	nodebug_help },
+	NEW_CLI(handle_set_debug, "Set level of debug chattiness"),
 
 	{ { "core", "set", "verbose", NULL },
 	handle_verbose, "Set level of verboseness",
@@ -1153,13 +1155,7 @@ static struct ast_cli_entry cli_cli[] = {
 	handle_logger_mute, "Toggle logging output to a console",
 	logger_mute_help },
 
-	{ { "module", "show", NULL },
-	handle_modlist, "List modules and info",
-	modlist_help },
-
-	{ { "module", "show", "like", NULL },
-	handle_modlist, "List modules and info",
-	modlist_help, complete_mod_4 },
+	NEW_CLI(handle_modlist, "List modules and info"),
 
 	{ { "module", "load", NULL },
 	handle_load, "Load a module by name",
@@ -1309,6 +1305,14 @@ static int __ast_cli_unregister(struct ast_cli_entry *e, struct ast_cli_entry *e
 		AST_LIST_REMOVE(&helpers, e, list);
 		AST_LIST_UNLOCK(&helpers);
 		free(e->_full_cmd);
+		e->_full_cmd = NULL;
+		if (e->command) {
+			/* this is a new-style entry. Reset fields and free memory. */
+			((char **)e->cmda)[0] = NULL;
+			free(e->command);
+			e->command = NULL;
+			e->usage = NULL;
+		}
 	}
 	return 0;
 }
@@ -1317,8 +1321,29 @@ static int __ast_cli_register(struct ast_cli_entry *e, struct ast_cli_entry *ed)
 {
 	struct ast_cli_entry *cur;
 	char fulle[80] ="";
-	int lf, ret = -1;
-	
+	int i, lf, ret = -1;
+
+	if (e->cmda[0] == NULL) {	/* new style entry, run the handler to init fields */
+		char *args[2] = { (char *)e, NULL };
+		char *s = (char *)(e->handler(-1, CLI_CMD_STRING, args+1));
+		char **dst = (char **)e->cmda;	/* need to cast as the entry is readonly */
+
+		s = ast_skip_blanks(s);
+		s = e->command = ast_strdup(s);
+		for (i=0; !ast_strlen_zero(s) && i < AST_MAX_CMD_LEN-1; i++) {
+			*dst++ = s;	/* store string */
+			s = ast_skip_nonblanks(s);
+			if (*s == '\0')	/* we are done */
+				break;
+			*s++ = '\0';
+			s = ast_skip_blanks(s);
+		}
+		*dst++ = NULL;
+		e->usage = (char *)(e->handler(-1, CLI_USAGE, args+1));
+	}
+	for (i = 0; e->cmda[i]; i++)
+		;
+	e->args = i;
 	ast_join(fulle, sizeof(fulle), e->cmda);
 	AST_LIST_LOCK(&helpers);
 	
@@ -1432,7 +1457,7 @@ static int help1(int fd, char *match[], int locked)
 			continue;
 		if (match && strncasecmp(matchstr, e->_full_cmd, len))
 			continue;
-		ast_cli(fd, "%25.25s  %s\n", e->_full_cmd, e->summary);
+		ast_cli(fd, "%25.25s  %s\n", e->_full_cmd, S_OR(e->summary, "<no description available>"));
 		found++;
 	}
 	AST_LIST_UNLOCK(&helpers);
@@ -1622,7 +1647,7 @@ static char *__ast_cli_generator(const char *text, const char *word, int state,
 	}
 	if (lock)
 		AST_LIST_LOCK(&helpers);
-	while ((e = cli_next(&i))) {
+	while ( (e = cli_next(&i)) ) {
 		int lc = strlen(e->_full_cmd);
 		if (e->_full_cmd[0] != '_' && lc > 0 && matchlen <= lc &&
 				!strncasecmp(matchstr, e->_full_cmd, matchlen)) {
@@ -1632,11 +1657,30 @@ static char *__ast_cli_generator(const char *text, const char *word, int state,
 				break;
 			}
 		} else if (!strncasecmp(matchstr, e->_full_cmd, lc) && matchstr[lc] < 33) {
-			/* We have a command in its entirity within us -- theoretically only one
-			   command can have this occur */
+			/* This entry is a prefix of the command string entered
+			 * (only one entry in the list should have this property).
+			 * Run the generator if one is available. In any case we are done.
+			 */
 			if (e->generator)
 				ret = e->generator(matchstr, word, argindex, state);
-			break;
+			else if (e->command) {	/* new style command */
+				/* prepare fake arguments for the generator.
+				 * argv[-1] is the cli entry we use,
+				 * argv[0] is a pointer to the generator arguments,
+				 *   with a fake string '-' at the beginning so we can
+				 *   dereference it as a string with no trouble,
+				 *   and then the usual NULL terminator.
+				 */
+				struct ast_cli_args a = {
+					.fake = "-",
+					.line = matchstr, .word = word,
+					.pos = argindex,
+					.n = state };
+				char *args[] = { (char *)e, (char *)&a, NULL };
+				ret = (char *)e->handler(-1, CLI_GENERATE, args + 1);
+			}
+			if (ret)
+				break;
 		}
 	}
 	if (lock)
@@ -1652,24 +1696,28 @@ char *ast_cli_generator(const char *text, const char *word, int state)
 
 int ast_cli_command(int fd, const char *s)
 {
-	char *argv[AST_MAX_ARGS];
+	char *args[AST_MAX_ARGS + 1];
 	struct ast_cli_entry *e;
 	int x;
 	char *dup;
 	int tws;
 	
-	if (!(dup = parse_args(s, &x, argv, sizeof(argv) / sizeof(argv[0]), &tws)))
+	if (!(dup = parse_args(s, &x, args + 1, AST_MAX_ARGS, &tws)))
 		return -1;
 
 	/* We need at least one entry, or ignore */
 	if (x > 0) {
 		AST_LIST_LOCK(&helpers);
-		e = find_cli(argv, 0);
+		e = find_cli(args + 1, 0);
 		if (e)
 			e->inuse++;
 		AST_LIST_UNLOCK(&helpers);
 		if (e) {
-			switch(e->handler(fd, x, argv)) {
+			/* within calling the handler, argv[-1] contains a pointer
+			 * to the cli entry, and the array is null-terminated
+			 */
+			args[0] = (char *)e;
+			switch(e->handler(fd, x, args + 1)) {
 			case RESULT_SHOWUSAGE:
 				if (e->usage)
 					ast_cli(fd, "%s", e->usage);
@@ -1686,7 +1734,7 @@ int ast_cli_command(int fd, const char *s)
 				break;
 			}
 		} else 
-			ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv));
+			ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(args + 1));
 		if (e)
 			ast_atomic_fetchadd_int(&e->inuse, -1);
 	}