diff --git a/funkwhale_cli/cli.py b/funkwhale_cli/cli.py
index 39eb281b976dac361955c5074ca3b31d38c90e44..cc58483886f6bf3363937ed8b2a427bb83523bd9 100644
--- a/funkwhale_cli/cli.py
+++ b/funkwhale_cli/cli.py
@@ -352,7 +352,9 @@ async def info(ctx, raw):
 @cli.group()
 @click.pass_context
 def libraries(ctx):
-    pass
+    """
+    Manage libraries
+    """
 
 
 def get_url_param(url, name):
@@ -398,12 +400,16 @@ def get_ls_command(
     ordering=True,
     with_id=False,
     name="ls",
+    doc="",
+    id_metavar="ID",
 ):
 
     available_fields = sorted(
         set(output_conf["labels"]) | set(output.FIELDS["*"].keys())
     )
-    id_decorator = click.argument("id") if with_id else noop_decorator
+    id_decorator = (
+        click.argument("id", metavar=id_metavar) if with_id else noop_decorator
+    )
     page_decorator = (
         click.option("--page", "-p", type=click.INT, default=1)
         if pagination
@@ -426,7 +432,6 @@ def get_ls_command(
         click.option("--filter", "-f", multiple=True) if filter else noop_decorator
     )
 
-    @group.command(name)
     @id_decorator
     @click.argument("query", nargs=-1)
     @RAW_DECORATOR
@@ -533,10 +538,13 @@ def get_ls_command(
                     )
                 )
 
-    return ls
+    ls.__doc__ = doc
+    return group.command(name)(ls)
 
 
-def get_show_command(group, url_template, output_conf, name="show", force_id=None):
+def get_show_command(
+    group, url_template, output_conf, name="show", force_id=None, doc=""
+):
 
     available_fields = sorted(
         set(output_conf["labels"]) | set(output.FIELDS["*"].keys())
@@ -553,7 +561,6 @@ def get_show_command(group, url_template, output_conf, name="show", force_id=Non
     else:
         id_decorator = click.argument("id")
 
-    @group.command(name)
     @id_decorator
     @RAW_DECORATOR
     @click.option(
@@ -589,16 +596,19 @@ def get_show_command(group, url_template, output_conf, name="show", force_id=Non
                 )
             )
 
-    return show
+    show.__doc__ = ""
+    return group.command(name)(show)
 
 
 def get_delete_command(
     group,
     url_template,
     confirm="Do you want to delete {} objects? This action is irreversible.",
+    doc='Delect the given objects',
+    name="rm",
+    id_metavar='ID'
 ):
-    @group.command("rm")
-    @click.argument("id", nargs=-1)
+    @click.argument("id", nargs=-1, metavar=id_metavar)
     @RAW_DECORATOR
     @click.option("--no-input", is_flag=True)
     @click.pass_context
@@ -618,7 +628,8 @@ def get_delete_command(
 
         click.echo("{} Objects deleted!".format(len(id)))
 
-    return delete
+    delete.__doc__ = doc
+    return group.command(name)(delete)
 
 
 libraries_ls = get_ls_command(
@@ -1037,12 +1048,15 @@ favorites_tracks_ls = get_ls_command(  # noqa
 @cli.group()
 @click.pass_context
 def playlists(ctx):
-    pass
+    """
+    Manage playlists
+    """
 
 
 playlists_ls = get_ls_command(
     playlists,
     "api/v1/playlists/",
+    doc="List available playlists",
     output_conf={
         "labels": [
             "ID",
@@ -1056,7 +1070,9 @@ playlists_ls = get_ls_command(
         "type": "PLAYLIST",
     },
 )
-playlists_rm = get_delete_command(playlists, "api/v1/playlists/{}/")
+playlists_rm = get_delete_command(
+    playlists, "api/v1/playlists/{}/", id_metavar="PLAYLIST_ID", doc='Delete the given playlists'
+)
 
 
 @playlists.command("create")
@@ -1071,6 +1087,9 @@ playlists_rm = get_delete_command(playlists, "api/v1/playlists/{}/")
 @click.pass_context
 @async_command
 async def playlists_create(ctx, raw, name, visibility):
+    """
+    Create a new playlist
+    """
     async with ctx.obj["remote"]:
         result = await ctx.obj["remote"].request(
             "post", "api/v1/playlists/", data={"name": name, "visibility": visibility}
@@ -1089,17 +1108,23 @@ async def playlists_create(ctx, raw, name, visibility):
 
 
 @playlists.command("tracks-add")
-@click.argument("id")
-@click.argument("track", nargs=-1)
+@click.argument("id", metavar="PLAYLIST_ID")
+@click.argument("track", nargs=-1, metavar="[TRACK_ID]…")
 @click.option(
     "--no-duplicates",
     is_flag=True,
     default=False,
-    help="Prevent insertion of tracks that already exist in the playlist",
+    help="Prevent insertion of tracks that already exist in the playlist. An error will be raised in this case.",
 )
 @click.pass_context
 @async_command
 async def playlists_tracks_add(ctx, id, track, no_duplicates):
+    """
+    Insert one or more tracks in a playlist
+    """
+    if not track:
+        return click.echo("No track id provided")
+
     async with ctx.obj["remote"]:
         async with ctx.obj["remote"].request(
             "post",
@@ -1122,6 +1147,8 @@ playlists_tracks = get_ls_command(
         "id_field": "ID",
         "type": "PLAYLIST_TRACK",
     },
+    id_metavar="PLAYLIST_ID",
+    doc="List the tracks included in a playlist"
 )