diff --git a/doc/footer b/doc/footer
new file mode 100644
index 0000000000000000000000000000000000000000..505654ab7b32158c1bb022e91b6a5b4b52ce3034
--- /dev/null
+++ b/doc/footer
@@ -0,0 +1,40 @@
+.SH "AUTHOR AND AVAILABILITY"
+Lektor is a karaoke player for POSIX compliant systems (i.e. not MS-Windows)
+writen initially in C. Some may call it Lektor mk 7. It was writen by Hubert
+\'Taiite' HIRTZ, Maël 'Kubat' MARTIN and Louis 'Elliu' GOYARD. These people
+also helped: Loïc 'Sting' ALLÈGRE and Etienne 'Pelle' BRATEAU.
+.PP
+The up\-to\-date source code is available via Git from Gitlab\&. See
+\fBhttps://git\&.iiens\&.net/martin2018/lektor\fP. The source code is
+provided under the \fIISC\fP licence.
+.fi
+.SH "RELATED SITES AND THE LEKTOR FAQ"
+Just see one of the dev from lektor or a bakateux if you have some questions
+about lektor.
+.PP
+.PD 0
+.TP
+\fBhttps://kurisu.iiens.net\fP  The depo site for the karaoke base
+.TP
+\fBhttps://manga.iiens.net\fP   The home website for the Bakaclub
+.PD
+.SH "FILES"
+.PD 0
+.TP
+\fB$HOME/.config/lektor/lektor.ini\fP                   general config file for the user
+.TP
+\fB/home/kara/\fP                                       default prefix for the bakabase
+.TP
+\fB/home/kara/kara.db\fP                                default sqlite3 base for the bakabase
+.PD
+.SH "SEE ALSO"
+\fIlektor\fP(1),
+\fIlektord\fP(1),
+\fIlktadm\fP(1),
+\fIlkt\fP(1),
+\fImpc\fP(1),
+\fImpd\fP(1),
+\fImeson\fP(1)
+.PP
+\fBIEEE Standard for information Technology \-
+Portable Operating System Interface (POSIX)\fP
diff --git a/doc/header b/doc/header
new file mode 100644
index 0000000000000000000000000000000000000000..04e76afaac69806019422f6b6b7aec0e478d3c17
--- /dev/null
+++ b/doc/header
@@ -0,0 +1 @@
+.TH "___PAGE___" "1" "___DATE___" "LEKTOR MK 7"
diff --git a/doc/lektor.1 b/doc/lektor.1
new file mode 100644
index 0000000000000000000000000000000000000000..aa5f9cdaa5734ce3d24237493f3ad0d9cc7b2336
--- /dev/null
+++ b/doc/lektor.1
@@ -0,0 +1,83 @@
+.SH "NAME"
+lektor \- the lektor karaoke player
+.SH "OVERVIEW"
+Lektor is a collection of pragram, a daemon a client and an administrator
+used to play karaoker from the bakabase.
+.PP
+You may be interested in other related programs like:
+.PP
+.PD 0
+.TP
+\fIlektord\fP       The lektor daemon
+.TP
+\fIlktadm\fP        Administration of lektor from a separate executable
+.TP
+\fIlkt\fP           A standard client for lektord
+.PD
+.SH "DESCRIPTION"
+lektor is a replacement of old bash scripts present on \fISakura\fP (now
+\fIShinSakura\fP). It allows interaction from the network with a \fIMPD\fP
+compatible protocol even if functionnalities may differ.
+.PP
+Here is a list of functionnalities offered by lektor:
+.PP
+.PD 0
+.TP
+.PD
+\fBBase synchronisation\fP
+The entire base can be syncrhonised from \fIKurisu\fP.
+.TP
+\fBManipulation over the network\fP
+Lektord can be piloted over the network with \fBlkt\fP.
+.TP
+\fBPlaylists and stickers\fP
+Reading and searching through the base can be facilitated by creating
+\fIplaylist\fP and using \fIstickers\fP (\fIMPD\fP version of tags).
+.PP
+.SH "SUPPORTED URIS"
+When searching the database, the following URIs are supported by lektor. Those
+URIs are hidden, you should not use them, but here is some documentation about
+them.
+.PP
+.PD 0
+.TP
+.PD
+\(bu
+\fBid://${arg}\fP
+the \fBarg\fP should be the id of the kara in the sqlite3 database.
+.TP
+\(bu
+\fBfs://${arg}\fP
+the \fBarg\fP should be a part of the path to the kara.
+.TP
+\(bu
+\fBlang://${arg}\fP / \fBlanguage://${arg}\fP
+the \fBarg\fP should be the language of the kara.
+.TP
+\(bu
+\fBtype://${arg}\fP
+the \fBarg\fP should be the type of the kara.
+.TP
+\(bu
+\fBcat://${arg}\fP / \fBcategory://${arg}\fP
+the \fBarg\fP should be the category of the kara.
+.TP
+\(bu
+\fBauthor://${arg}\fP
+the \fBarg\fP should be the author of the kara
+.TP
+\(bu
+\fBquery://${arg}\fP
+the \fBarg\fP should be a sqlite3 query. The formating string on which the query
+is applied is like \fB"${cat}/${source} - ${type}${song_num?} - ${title}"\fP.
+You usually want to do this.
+.PP
+.SH "SUPPORTED LANGUAGES"
+The supported languages in lektor are: \fBjp\fP, \fBfr\fP, \fBsp\fP, \fBen\fP,
+\fBlatin\fP, \fBit\fP, \fBru\fP, \fBmulti\fP and \fBundefined\fP.
+.SH "SUPPORTED TYPES"
+The supported types in lektor are: \fBOP\fP, \fBED\fP, \fBIS\fP, \fBAMV\fP,
+\fBVOCA\fP, \fBMV\fP, \fBPV\fP and \fBLIVE\fP.
+.SH "SUPPORTED CATEGORIES"
+The supported categories in lektor are: \fBvo\fP, \fBva\fP, \fBcdg\fP,
+\fBamv\fP, \fBvocaloid\fP and \fBautres\fP.
diff --git a/doc/lektord.1 b/doc/lektord.1
index 66245cf2900548f5b488318b667df9bdbb787b76..5db48c628aee3f3345ca6d8521d5b17e4b89ae93 100644
--- a/doc/lektord.1
+++ b/doc/lektord.1
@@ -1,4 +1,3 @@
-.TH "LEKTORD" "1" "February 12, 2020" "lektord 0\&.1\&.0"
 .SH "NAME"
 lektord \- the lektor daemon
 .SH "OVERVIEW"
@@ -18,26 +17,6 @@ You may be interested in other related programs like:
 Lektor allows one to interactivelly and remotly interact with the player. The
 lektor daemon is mpd compatible, even if some functionnalities may differ.
 You can almost use \fBmpc\fP with lektord.
-.SH "AUTHOR"
-Lektor is a karaoke player for POSIX compliant systems (i.e. not MS-Windows)
-writen initially in C. Some may call it Lektor mk 7. It was writen by Hubert
-\'Taiite' HIRTZ, Maël 'Kubat' MARTIN and Louis 'Elliu' GOYARD.
-.SH "AVAILABILITY"
-I don't know what to do in this section.
-.PP
-The up\-to\-date source code is available via Git from Gitlab\&. See
-\fBhttps://git\&.iiens\&.net/martin2018/lektor\fP
-.fi
-.SH "THE LEKTOR FAQ"
-Just see one of the dev from lektor or a bakateux.
-.SH "RELATED SITES"
-.PP
-.PD 0
-.TP
-\fBhttps://kurisu.iiens.net\fP  The depo site for the karaoke base
-.TP
-\fBhttps://manga.iiens.net\fP   The home website for the Bakaclub
-.PD
 .SH "INSTALLATION"
 You may install lektor from source like this:
 .sp
@@ -82,67 +61,3 @@ by meson during the install), you should just use:
 .PP
 You should edit the configuration file to set correct path to \fB.so\fP file,
 those files are modules for the window of lektord.
-.SH "SUPPORTED URIS"
-When searching the database, the following URIs are supported by lektor. Those
-URIs are hidden, you should not use them, but here is some documentation about
-them.
-.PP
-.PD 0
-.TP
-.PD
-\(bu
-\fBid://${arg}\fP
-the \fBarg\fP should be the id of the kara in the sqlite3 database.
-.TP
-\(bu
-\fBfs://${arg}\fP
-the \fBarg\fP should be a part of the path to the kara.
-.TP
-\(bu
-\fBlang://${arg}\fP / \fBlanguage://${arg}\fP
-the \fBarg\fP should be the language of the kara.
-.TP
-\(bu
-\fBtype://${arg}\fP
-the \fBarg\fP should be the type of the kara.
-.TP
-\(bu
-\fBcat://${arg}\fP / \fBcategory://${arg}\fP
-the \fBarg\fP should be the category of the kara.
-.TP
-\(bu
-\fBauthor://${arg}\fP
-the \fBarg\fP should be the author of the kara
-.TP
-\(bu
-\fBquery://${arg}\fP
-the \fBarg\fP should be a sqlite3 query. The formating string on which the query
-is applied is like \fB"${cat}/${source} - ${type}${song_num?} - ${title}"\fP.
-You usually want to do this.
-.PP
-.SH "SUPPORTED LANGUAGES"
-The supported languages in lektor are: \fBjp\fP, \fBfr\fP, \fBsp\fP, \fBen\fP,
-\fBlatin\fP, \fBit\fP, \fBru\fP, \fBmulti\fP and \fBundefined\fP.
-.SH "SUPPORTED TYPES"
-The supported types in lektor are: \fBOP\fP, \fBED\fP, \fBIS\fP, \fBAMV\fP,
-\fBVOCA\fP, \fBMV\fP, \fBPV\fP and \fBLIVE\fP.
-.SH "SUPPORTED CATEGORIES"
-The supported categories in lektor are: \fBvo\fP, \fBva\fP, \fBcdg\fP,
-\fBamv\fP, \fBvocaloid\fP and \fBautres\fP.
-.SH "FILES"
-.PD 0
-.TP
-\fB$HOME/.config/lektor/lektor.ini\fP                   general config file for the user
-.TP
-\fB/home/kara/\fP                                       default prefix for the bakabase
-.TP
-\fB/home/kara/kara.db\fP                                default sqlite3 base for the bakabase
-.PD
-.SH "SEE ALSO"
-\fIlktadm\fP(1),
-\fIlkt\fP(1),
-\fImpc\fP(1),
-\fImpd\fP(1)
-.PP
-\fBIEEE Standard for information Technology \-
-Portable Operating System Interface (POSIX)\fP
diff --git a/doc/lkt.1 b/doc/lkt.1
new file mode 100644
index 0000000000000000000000000000000000000000..153952886d9b2be3df10003ebaae9e221a8f783f
--- /dev/null
+++ b/doc/lkt.1
@@ -0,0 +1,224 @@
+.SH "NAME"
+lkt \- the lektor command line client
+
+.SH "OVERVIEW"
+\fBlkt\fP is the \fBlektord\fP client, much like \fBmpc\fP but with
+more functionnalities like \fIplaylists\fP and \fIstickers\fP management.
+.PP
+Commands can be used with the minimal name that permit them to be
+distinguished from others commands. Thus, it is possible to use the
+string \fIadm\fP as an alias for \fIadmin\fP. This is like cisco switch
+command line interface.
+.PP
+You may be interested in other related programs like:
+.PP
+.PD 0
+.TP
+\fIlektord\fP       The lektor daemon
+.TP
+\fIlktadm\fP        Administration of lektor from a separate executable
+.PD
+
+.SH "COMMANDS"
+Here are the \fBlkt\fP commands:
+
+.PP
+\fIBASE-COMMANDS\fP
+.PP
+.PD 0
+.TP
+.PD
+\fBcurrent\fP
+Prints informations about the currently playing kara. Can be used to
+display the current kara in a status bar like \fBxmobar\fP or in the
+\fBi3 panel\fP
+.TP
+\fBplay\fP [index]
+Toggle play/pause state. If the playback is stopped, start at a possibly
+specified index
+.TP
+\fBnext\fP
+Play next kara in the queue
+.TP
+\fBprevious\fP
+Play the previous kara in the queue
+.TP
+\fBshuffle\fP
+Shuffle the queue. If the state was stoppped, play from the first kara
+in the queue. If lektor was already playing a kara, it will play it
+from the begening. The current kara will be placed in first position
+in the queue.
+.TP
+\fBstatus\fP
+Prints information about the state of lektor and the currently playing kara
+.TP
+\fBstop\fP
+Stop the playback and reading the queue, the state is now \fIstopped\fP.
+.PP
+\fIPLAYLIST-COMMANDS\fP
+.PP
+.PD 0
+.TP
+.PD
+\fBplt create\fP <plt-name>
+Creates a playlist, do nothing if it was already present
+.TP
+\fBplt destroy\fP <plt-name>
+Delete a playlist with all its content, do nothing if the playlist didn't exists
+.TP
+\fBplt delete\fP <plt-name> <query>
+Deletes karas from a playlist with a valid \fIquery\fP
+.TP
+\fBplt add\fP <plt-name> <query>
+Adds karas to a playlist with a valid \fIquery\fP
+.PP
+
+\fIQUEUE-COMMANDS\fP
+.PP
+.PD 0
+.TP
+.PD
+\fBqueue\fP [count]
+Prints the names and ids of the next karas in the queue
+.TP
+\fBqueue pos\fP <pos | from:to>
+Prints the names and ids of karas in the queue. Karas can be designated by
+their position or with a range
+.TP
+\fBqueue pop\fP
+Delete the currently playing kara from the queue and pass to the next one.
+This can work only if the currently playong kara is not the last
+.TP
+\fBqueue add\fP <query>
+Add karas to the queue at the end of it with a valid query
+.TP
+\fBqueue seek\fP <id>
+Goto to the kara with the specified id in the queue
+.TP
+\fBqueue delete\fP <id>
+Delete karas from the playlist with their id. You can't delete the currently
+playing kara, for that use the \fBpop\fP queue command
+.TP
+\fBqueue clear\fP
+Clear the queue and set the state to \fIstopped\fP
+.TP
+\fBqueue crop\fP
+Crop the queue, delete every kara from it appart from the currently
+playing one
+.PP
+
+\fISEARCH-COMMANDS\fP
+.PP
+.PD 0
+.TP
+.PD
+\fBsearch get\fP <query>
+Search and prints the kara that correspond to the query in the database
+.TP
+\fBsearch add\fP <query>
+Search, prints and add to the queue the karas that match the query
+.TP
+\fBsearch insert\fP <query>
+Search, prints and insert into the queue the karas that match the query
+.TP
+\fBsearch plt\fP <plt-name> <query>
+Search, prints and add to an existing playlist the karas that match
+the query
+.TP
+\fBsearch count\fP <query>
+Search and prints the number of karas that match the query
+.TP
+\fBsearch queue\fP <query>
+Search in the queue and prints the karas that match the query
+.PP
+
+\fIADMIN-COMMANDS\fP
+.PP
+.PD 0
+.TP
+.PD
+\fBadmin ping\fP
+Pings the lektord daemon, prints \fIOK\fP only if the ping succeeded
+.TP
+\fBadmin kill\fP
+Kill the lektord daemon
+.TP
+\fBadmin restart\fP
+Try to restart the lektord daemon
+.TP
+\fBadmin rescan\fP
+Rescan karas from the filesystem. New karas that are not in the database
+will be added to it. Don't synchronize from the repo
+.TP
+\fBadmin update\fP
+Update the base from the \fIKurisu\fP repo. Don't scan for new files in
+the filesystem
+.PP
+
+.SH "OPTIONS"
+Options can be passed to \fBlkt\fP before specifying the command in a
+\fIoption=value\fP format. This is done this way to allow one to make
+an alias of the \fBlkt\fP command.
+.PP
+The possible options are the following:
+.PP
+.PD 0
+.TP
+.PD
+\fBhost\fP
+The hostname or the IP of the machine where the \fBlektord\fP daemon
+is running
+.TP
+\fBport\fP
+The port on which the \fBlektord\fP daemon is listening
+.TP
+\fBpwd\fP
+The password to use for commands that require authentification. This is the
+case of most of the \fIadmin\fP commands
+.PP
+
+.SH "QUERIES"
+Queries are a way of listing karas in the database. They are composed of a
+type and the next of the line is the SQL regex that the kara must verify.
+In SQL regexes, the wildcard is the "%" character and the jocker the
+character "_". Queries are case insensitive.
+.PP
+Valid types for a query are the following: \fIid\fP, \fIlanguage\fP, or
+\fIlang\fP, \fItype\fP, \fIcat\fP or \fIcategory\fP, \fIauthor\fP, \fIquery\fP,
+\fIsource\fP and \fItitle\fP.
+.PP
+For the type \fItype\fP, the valid values are the following: \fIOP\fP,
+\fIED\fP, \fIIS\fP, \fIAMV\fP, \fIVOCA\fP, \fIMV\fP, \fIPV\fP
+and \fILIVE\fP.
+.PP
+For the type \fIlanguage\fP or \fIlang\fP, the valid values are the following:
+\fIjp\fP, \fIfr\fP, \fIsp\fP, \fIen\fP, \fIlatin\fP, \fIit\fP, \fIru\fP,
+\fImulti\fP and \fIundefined\fP.
+.PP
+For the \fIcat\fP or \fIcategory\fP type, the valid values are the following:
+\fIvo\fP, \fIva\fP, \fIcdg\fP, \fIamv\fP, \fIvocaloid\fP and \fIautres\fP.
+.PP
+Here are some examples of queries:
+.PP
+.PD 0
+.TP
+.PD
+\fBtype OP\fP
+Select karas that are openings
+.TP
+\fBauthor k%\fP
+Select karas where the author begin with the character "k"
+.PP
+
+.SH "EXAMPLES"
+Valid invocations of the \fBlkt\fP command are the following:
+.PP
+.PD 0
+.TP
+.PD
+\fBlkt host=sakura port=6601 pwd=toto admin restart\fP
+Restart the lektord daemon on the \fIsakura\fP PC. This daemon is listening on
+the port \fI6601\fP and the password of the admin user is \fItoto\fP
+.TP
+\fBlkt q a author krocoh\fP
+Add kara that Krocoh has done to the queue
diff --git a/doc/lktadm.1 b/doc/lktadm.1
new file mode 100644
index 0000000000000000000000000000000000000000..f7309e8b9180698700a7f169fd75555b76c59b03
--- /dev/null
+++ b/doc/lktadm.1
@@ -0,0 +1,64 @@
+.SH "NAME"
+lktadm \- the lektor administration tool
+.SH "OVERVIEW"
+Commands can be used with the minimal name that permit them to be
+distinguished from others commands. Thus, it is possible to use the
+string \fIdow\fP as an alias for \fIdownload\fP. This is like cisco switch
+command line interface.
+.PP
+You may be interested in other related programs like:
+.PP
+.PD 0
+.TP
+\fIlektord\fP       The lektor daemon
+.TP
+\fIlkt\fP           A standard client for lektord
+.PD
+
+.SH "COMMANDS"
+Here are the \fBlkt\fP commands:
+
+.PP
+\fIBASE-COMMANDS\fP
+.PP
+.PD 0
+.TP
+.PD
+\fBget\fP <id>
+Get and display the metadata of a kara by its id. You must have access to
+the \fIKurisu\fP repo
+.TP
+\fBdownload\fP <id> <filepath>
+Download a kara from kurisu and save it to the specified path
+.TP
+\fBcat\fP <filepath>
+Display the metadata of a kara specified by the its path
+.TP
+\fBconf\fP
+Prints the default configuration file to stdout
+
+.PP
+\fIINITIALISATION-COMMANDS\fP
+.PP
+.PD 0
+.TP
+.PD
+\fBinit database\fP
+Create the empty database with the correct SQL schema. You need a valid
+configuration file to do that
+.TP
+\fBinit populate\fP
+Populate the database with kara on the filesystem. You need a valid
+configuration file and a valid database for that
+.TP
+\fBinit metadata\fP
+Write the correct metadata to kara in the filesystem. For that you
+need a valid configuration file and a specific organisation of the files.
+The best way of doing it is using the \fIadmin update\fP command with
+\fBlkt\fP. This command is present but depracted
+.TP
+\fBinit file\fP
+Write correct metadata to a kara in the filesystem. You need a specific
+file organisation for that command to work. It is depracted but present
+for debuging purpose
+.PP
diff --git a/inc/lektor/cmd.h b/inc/lektor/cmd.h
index de18772650d1e335c7fdfc9e1aa937a4ee2e555e..7861e0e6b45e86ea10a910fc8b10ce6527107bf8 100644
--- a/inc/lektor/cmd.h
+++ b/inc/lektor/cmd.h
@@ -16,12 +16,12 @@ typedef void (*lkt_cmd_callback)(struct lkt_cmd_args *);
 struct lkt_cmd_opt {
     const char *name;
     lkt_cmd_callback call;
-    const char *help;
-    struct lkt_cmd_opt *sub;
 };
 
 /* Parse the command line with the list of options. No parameter may be NULL.
    Does not return. */
-noreturn void lkt_cmd_parse(struct lkt_cmd_opt *opts, int argc, const char **argv, void (*help)(void));
+noreturn void lkt_cmd_parse(struct lkt_cmd_opt *opts, int argc, const char **argv);
 
-void lkt_cmd_help(struct lkt_cmd_opt *opts, const char *name);
+/* Must be setted for the lkt_cmd_parse function to display the correct help
+   in case of errors. */
+extern const char *executable_name;
diff --git a/meson.build b/meson.build
index 0e421b7bb22354e6a359dacc415542c66d8aa7b6..3a06d3244e75a7e7d510f9cf1145b1ffa5bae427 100644
--- a/meson.build
+++ b/meson.build
@@ -9,7 +9,6 @@ project( 'lektor'
                           , 'strip=true'
                           , 'debug=true'
                           , 'buildtype=debug'
-                          , 'b_sanitize=undefined'
                           ]
        )
 
@@ -84,16 +83,21 @@ mthread_deps = [ declare_dependency( link_with: static_library( 'mthread'
                                    , include_directories: includes
                                    , dependencies: [ common_deps ] ) ]
 
-initsql_deps = [ declare_dependency( link_with: static_library( 'initsql'
-                                                              , [ custom_target('init.c'
-                                                                               , output: 'sql_init.c'
-                                                                               , input: 'scripts/init.sql'
-                                                                               , command: [ find_program('xxd'), '-i', '@INPUT@', '@OUTPUT@']) ] ) ) ]
+generated_deps = [ declare_dependency( link_with: static_library( 'generated'
+                                                                , [ custom_target( 'sqlinit'
+                                                                                 , output: 'sqlinit.c'
+                                                                                 , input: 'scripts/init.sql'
+                                                                                 , command: [ find_program('xxd'), '-i', '@INPUT@', '@OUTPUT@' ] ) ]
+                                                                , [ custom_target( 'manpath'
+                                                                                 , output: 'manpath.c'
+                                                                                 , input: 'scripts/getmanpath.sh'
+                                                                                 , command: [ find_program('scripts/getmanpath.sh'), '@OUTPUT@' ] ) ]
+                                                                ) ) ]
 
 lib = static_library( 'lektor'
                     , files(core_sources)
                     , include_directories: includes
-                    , dependencies: [ core_deps, libdl, common_deps, mthread_deps, initsql_deps ] )
+                    , dependencies: [ core_deps, libdl, common_deps, mthread_deps, generated_deps ] )
 
 bin_deps = [ declare_dependency( link_with: lib, include_directories: includes) ]
 
@@ -114,6 +118,7 @@ lktadm = executable( 'lktadm'
 lkt = executable( 'lkt'
                 , files('src/main/lkt.c', 'src/cmd.c', 'src/common.c')
                 , include_directories: includes
+                , dependencies: [ generated_deps ]
                 , install: true )
 
 # X11 window module
@@ -138,6 +143,27 @@ if dep_sdl.found() and dep_mpv.found()
                                , install_dir: 'lib/lektor' )
 endif
 
+# Man pages
+man_sh     = find_program('scripts/man.sh')
+man_header = run_command('realpath', 'doc/header').stdout().strip()
+man_footer = run_command('realpath', 'doc/footer').stdout().strip()
+man_files  = [ [ 'lektord', '1' ]
+             , [ 'lektor',  '1' ]
+             , [ 'lktadm',  '1' ]
+             , [ 'lkt',     '1' ]
+             ]
+
+foreach man: man_files
+  man_name    = man.get(0)
+  man_section = man.get(1, '1')
+  custom_target( '@0@.@1@'.format(man_name, man_section)
+               , input: 'doc/@0@.@1@'.format(man_name, man_section)
+               , output: '@0@.@1@'.format(man_name, man_section)
+               , command: [ man_sh, '@INPUT@', '@OUTPUT@', man_header, man_footer ]
+               , depend_files: [ man_header, man_footer ]
+               , install: true
+               , install_dir: join_paths(get_option('mandir'), 'man@0@'.format(man_section)) )
+endforeach
+
 # Install
-install_man('doc/lektord.1')
 meson.add_install_script('scripts/install.sh')
diff --git a/scripts/getmanpath.sh b/scripts/getmanpath.sh
new file mode 100644
index 0000000000000000000000000000000000000000..84a088cea05c9aa6c413fb4700f32ecc11574b2e
--- /dev/null
+++ b/scripts/getmanpath.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# $1: output file
+[ $# -ne 1 ] && exit 1
+echo "const char *man_executable_path = \"$(which man)\";" > "$1"
diff --git a/scripts/man.sh b/scripts/man.sh
new file mode 100755
index 0000000000000000000000000000000000000000..50bff11fac7f998288ee5861e293afc1d174c3a5
--- /dev/null
+++ b/scripts/man.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# $0: script name
+# $1: input
+# $2: output
+# $3: header
+# $4: footer
+[ $# -ne 4 ] && exit 1
+[ "$1" == "$2" ] && exit 2
+
+# Global variables
+HEADER="$3"
+FOOTER="$4"
+INPUT="$1"
+OUTPUT="$2"
+
+# Functions
+function the_day() {
+    # $1: file
+    # $2: what to replace with the date
+    local DATE=$(date +'%B %d, %Y')
+    sed -i "s/$2/$DATE/g" "$1"
+}
+
+function the_page() {
+    # $1: file
+    # $2: what to replace with the date
+    FILE=$(basename "$1")
+    FILE="${FILE%.*}"
+    FILE="${FILE^^}"
+    sed -i "s/$2/$FILE/g" "$1"
+}
+
+function mk_manpage() {
+    # $1: input
+    # $2: output
+    cat $HEADER > $2
+    cat $1 >> $2
+    cat $FOOTER >> $2
+    the_day "$2" '___DATE___'
+    the_page "$2" '___PAGE___'
+}
+
+mk_manpage "$INPUT" "$OUTPUT"
+exit 0
diff --git a/src/cmd.c b/src/cmd.c
index ae46c8b299db796e69d335b267d9ffdfa827b2db..4a38bddccedf921471fde4fc149ccc5fc8f28a01 100644
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -9,15 +9,29 @@
 #include <stdio.h>
 #include <unistd.h>
 
+const char *executable_name = NULL;
+extern const char *man_executable_path;
+
+noreturn void
+help__()
+{
+    if (!executable_name)
+        exit(EXIT_FAILURE);
+    const char *const args[] = { man_executable_path, executable_name };
+    execv(args[0], (char *const *) args);
+    exit(EXIT_FAILURE);
+}
+
 noreturn void
-lkt_cmd_parse(struct lkt_cmd_opt *opts, int argc, const char **argv, void (*help)(void))
+lkt_cmd_parse(struct lkt_cmd_opt *opts, int argc, const char **argv)
 {
     int count = 0, is_ok, offset = 0;
     struct lkt_cmd_opt *it = opts;
     lkt_cmd_callback call[2] = { NULL, NULL };
 
-    if (argc == 0 || *argv == NULL)
-        goto no_args;
+    if (argc == 0 || *argv == NULL || strcasecmp(argv[0], "help") ||
+        strcasecmp(argv[0], "--help") || strcasecmp(argv[0], "-help"))
+        goto help;
 
     /* Find the command */
     while (it && it->name) {
@@ -56,46 +70,10 @@ not_found:
     LOG_ERROR_SCT("COMMAND", "Command '%s' could not be found", argv[0]);
     exit(EXIT_FAILURE);
 
-no_args:
-    lkt_cmd_help(opts, NULL);
-    if (help)
-        help();
-    exit(EXIT_SUCCESS);
+help:
+    help__();
 
 not_exclusive:
     LOG_ERROR_SCT("COMMAND", "Failed to determine option, '%s' not exclusive", argv[0]);
     exit(EXIT_FAILURE);
 }
-
-void
-lkt_cmd_help(struct lkt_cmd_opt *opts, const char *name)
-{
-    struct lkt_cmd_opt *it = opts;
-    int offset = 0, max_len = 1;
-
-    /* Get the maximan argument len */
-    while (it && it->name) {
-        int len = strlen(it->name);
-        max_len = MAX(max_len, len);
-        it = opts + (++offset);
-    }
-
-    /* Print the options */
-    it = opts;
-    offset = 0;
-    printf("COMMAND %s:\n", name);
-    while (it && it->name) {
-        printf("\t%*s  %s\n", max_len, it->name, it->help);
-        it = opts + (++offset);
-    }
-
-    /* Prints the sub commands help */
-    it = opts;
-    offset = 0;
-    write(1, "\n", sizeof(char));
-    while (it && it->name) {
-        if (it->sub)
-            lkt_cmd_help(it->sub, it->name);
-        it = opts + (++offset);
-    }
-}
diff --git a/src/config.c b/src/config.c
index e9348887180bb8790c50fc7664f06de2dd608e80..cfc6e2b3eaef579628ad42f7a251ba7be3880aeb 100644
--- a/src/config.c
+++ b/src/config.c
@@ -15,7 +15,7 @@
 #include <sys/stat.h>
 #include <limits.h>
 
-static inline int
+int
 load_so(const char *const mod_path, const char *const mod_init, void *mod)
 {
     /* An init function takes two arguments: the module pointer and a void*
diff --git a/src/database/playlist.c b/src/database/playlist.c
index 0dbb4ac59036762122a1fa11bf38f90e1b430624..2079ec836c5b8433595b031cee830a1ec7b0f2c6 100644
--- a/src/database/playlist.c
+++ b/src/database/playlist.c
@@ -99,7 +99,7 @@ database_plt_export(volatile sqlite3 *db, const char *name)
         "( kara_id INTEGER PRIMARY KEY NOT NULL CHECK(kara_id > 0) );";
     char SQL_STMT[LKT_MAX_SQLITE_STATEMENT];
     int code, ret = false;
-    sqlite3_stmt *stmt;
+    sqlite3_stmt *stmt = NULL;
 
     if (is_sql_str_invalid(name)) {
         LOG_ERROR("The playlist name '%s' is invalid", name);
diff --git a/src/database/queue.c b/src/database/queue.c
index 7adbac37caf29a97d7c426ef2ca4af91bf2c7cc2..8cf4fae3838e6d8f98d1414cbce037c8ff9caa15 100644
--- a/src/database/queue.c
+++ b/src/database/queue.c
@@ -643,7 +643,7 @@ bool
 database_queue_seekid(volatile sqlite3 *db, int id, int *out_pos)
 {
     static const char *SQL_STMT = "SELECT position FROM queue_ WHERE kara_id = ? LIMIT 1";
-    int ret;
+    int ret = 0;
     sqlite3_stmt *stmt;
 
     SQLITE_PREPARE(db, stmt, SQL_STMT, error);
diff --git a/src/main/lkt.c b/src/main/lkt.c
index 6ed06b2230d591b1e0892b0d3641df14e0efbe82..8ea952bd2556dd90061d75f69a3ad4465eed3293 100644
--- a/src/main/lkt.c
+++ b/src/main/lkt.c
@@ -28,32 +28,6 @@
 #define STR_MATCH(str1, str2)       (! strcasecmp(str1, str2))
 #define STR_NMATCH(str1, str2, n)   (! strncasecmp(str1, str2, n))
 
-/* The help. */
-
-noreturn void
-help(void)
-{
-    static const char *help_str =
-        "OPTIONS:\n"
-        "  host         named of the lektor's host, can be resolved\n"
-        "  port         port on which lektor is listening\n"
-        "\n"
-        "  options most be passed as one word (no spaced), such as the following:\n"
-        "  % lkt host=sakura port=6601 play\n"
-        "\n"
-        "QUERY:\n"
-        "  A query is passed in argument of a COMMAND and is composed of:\n"
-        "   - The first word must be the type\n"
-        "   - the rest is used for the sqlite regex\n"
-        "  Supported types are: title, [a]ny, source, [auth]or, [lang]uage, type, title\n"
-        "\n"
-        "RANGE:\n"
-        "  A range is specified like BEGIN:END which implies from BEGIN to END included\n"
-        "\n";
-    write(1, help_str, strlen(help_str));
-    exit(EXIT_SUCCESS);
-}
-
 static noreturn inline void
 fail(const char *message)
 {
@@ -974,40 +948,40 @@ search_queue__(struct lkt_cmd_args *args)
 /* Parsing stuff. */
 
 static struct lkt_cmd_opt options_queue[] = {
-    { .name = "pos",        .call = queue_pos__,    .help = "Display the content of the queue by a range or a position" },
-    { .name = "pop",        .call = queue_pop__,    .help = "Pop the current kara in the queue"                         },
-    { .name = "add",        .call = queue_add__,    .help = "Add karas to the queue by a query"                         },
-    { .name = "seek",       .call = queue_seek__,   .help = "Seek a kara in the queu by a query"                        },
-    { .name = "delete",     .call = queue_delete__, .help = "Delete a kara by its id in the queue"                      },
-    { .name = "clear",      .call = queue_clear__,  .help = "Clear the queue"                                           },
-    { .name = "crop",       .call = queue_crop__,   .help = "Crop the queue"                                            },
+    { .name = "pos",        .call = queue_pos__    },
+    { .name = "pop",        .call = queue_pop__    },
+    { .name = "add",        .call = queue_add__    },
+    { .name = "seek",       .call = queue_seek__   },
+    { .name = "delete",     .call = queue_delete__ },
+    { .name = "clear",      .call = queue_clear__  },
+    { .name = "crop",       .call = queue_crop__   },
     LKT_OPT_DEFAULT(queue_list__),
 };
 
 static struct lkt_cmd_opt options_plt[] = {
-    { .name = "add",        .call = plt_add__,      .help = "Add something to a playlist"               },
-    { .name = "delete",     .call = plt_delete__,   .help = "Delete karas from a playlist with a query" },
-    { .name = "destroy",    .call = plt_destroy__,  .help = "Delete a playlist"                         },
-    { .name = "create",     .call = plt_create__,   .help = "Create a playlist"                         },
+    { .name = "add",        .call = plt_add__     },
+    { .name = "delete",     .call = plt_delete__  },
+    { .name = "destroy",    .call = plt_destroy__ },
+    { .name = "create",     .call = plt_create__  },
     LKT_OPT_NULL,
 };
 
 static struct lkt_cmd_opt options_search[] = {
-    { .name = "get",        .call = search_get__,       .help = "Prints the results of the query"                                       },
-    { .name = "add",        .call = search_add__,       .help = "Prints and add the results to the queue"                               },
-    { .name = "insert",     .call = search_insert__,    .help = "Prints and inserts at the top of the queue the results of the query"   },
-    { .name = "plt",        .call = search_plt__,       .help = "Search inside a playlist and prints the results"                       },
-    { .name = "count",      .call = search_count__,     .help = "Count the number of karas matching the query and prints it"            },
-    { .name = "queue",      .call = search_queue__,     .help = "Search in the queue and prints the results of the query"               },
+    { .name = "get",        .call = search_get__    },
+    { .name = "add",        .call = search_add__    },
+    { .name = "insert",     .call = search_insert__ },
+    { .name = "plt",        .call = search_plt__    },
+    { .name = "count",      .call = search_count__  },
+    { .name = "queue",      .call = search_queue__  },
     LKT_OPT_NULL,
 };
 
 static struct lkt_cmd_opt options_admin[] = {
-    { .name = "ping",       .call = ping__,     .help = "Only pings the server"                                             },
-    { .name = "kill",       .call = kill__,     .help = "Kills the lektord server"                                          },
-    { .name = "restart",    .call = restart__,  .help = "Restarts the lektord server"                                       },
-    { .name = "rescan",     .call = rescan__,   .help = "Scan the filesystem and update the database with present files"    },
-    { .name = "update",     .call = update__,   .help = "Update the database with files present on Kurisu"                  },
+    { .name = "ping",       .call = ping__     },
+    { .name = "kill",       .call = kill__     },
+    { .name = "restart",    .call = restart__  },
+    { .name = "rescan",     .call = rescan__   },
+    { .name = "update",     .call = update__   },
     LKT_OPT_NULL,
 };
 
@@ -1017,7 +991,7 @@ admin__(struct lkt_cmd_args *args)
     if (args->argc == 0)
         fail("Invalid argument, you must specify a sub command for the admin command");
 
-    lkt_cmd_parse(options_admin, args->argc, args->argv, help);
+    lkt_cmd_parse(options_admin, args->argc, args->argv);
 }
 
 noreturn void
@@ -1026,7 +1000,7 @@ queue__(struct lkt_cmd_args *args)
     if (args->argc == 0)
         queue_list__(args);
 
-    lkt_cmd_parse(options_queue, args->argc, args->argv, help);
+    lkt_cmd_parse(options_queue, args->argc, args->argv);
 }
 
 noreturn void
@@ -1035,7 +1009,7 @@ search__(struct lkt_cmd_args *args)
     if (args->argc == 0)
         fail("Invalid argument, you must specify a sub command for the search command");
 
-    lkt_cmd_parse(options_search, args->argc, args->argv, help);
+    lkt_cmd_parse(options_search, args->argc, args->argv);
 }
 
 noreturn void
@@ -1044,21 +1018,21 @@ plt__(struct lkt_cmd_args *args)
     if (args->argc == 0)
         fail("Invalid argument, you must specify a sub command for the playlist command");
 
-    lkt_cmd_parse(options_plt, args->argc, args->argv, help);
+    lkt_cmd_parse(options_plt, args->argc, args->argv);
 }
 
 static struct lkt_cmd_opt options_[] = {
-    { .name = "current",  .call = current__, .help = "Get the current playing song"                                                 },
-    { .name = "play",     .call = play__,    .help = "Toggle play/pause, may starts at a specified index"                           },
-    { .name = "next",     .call = next__,    .help = "Play the next kara in the queue"                                              },
-    { .name = "previous", .call = prev__,    .help = "Play the previous kara in the queue"                                          },
-    { .name = "queue",    .call = queue__,   .help = "The queue sub-command",                               .sub = options_queue    },
-    { .name = "shuffle",  .call = shuffle__, .help = "Shuffle the queue"                                                            },
-    { .name = "status",   .call = status__,  .help = "Get the status of lektord"                                                    },
-    { .name = "stop",     .call = stop__,    .help = "Stop playing the queue, won't close lektord's window"                         },
-    { .name = "plt",      .call = plt__,     .help = "The playlist sub-command",                            .sub = options_plt      },
-    { .name = "search",   .call = search__,  .help = "The search sub-command",                              .sub = options_search   },
-    { .name = "admin",    .call = admin__,   .help = "Administration commands",                             .sub = options_admin    },
+    { .name = "current",  .call = current__  },
+    { .name = "play",     .call = play__     },
+    { .name = "next",     .call = next__     },
+    { .name = "previous", .call = prev__     },
+    { .name = "queue",    .call = queue__    },
+    { .name = "shuffle",  .call = shuffle__  },
+    { .name = "status",   .call = status__   },
+    { .name = "stop",     .call = stop__     },
+    { .name = "plt",      .call = plt__      },
+    { .name = "search",   .call = search__   },
+    { .name = "admin",    .call = admin__    },
     LKT_OPT_NULL,
 };
 
@@ -1109,6 +1083,7 @@ parse_args(args_t *args, int argc, const char **argv)
 int
 main(int argc, const char **argv)
 {
+    executable_name = "lkt";
     assert(NULL != setlocale(LC_ALL, "en_US.UTF-8"));   /* BECAUSE!         */
     assert(!signal(SIGPIPE, sigpipe__));                /* Argument checks. */
 
@@ -1124,5 +1099,5 @@ main(int argc, const char **argv)
     password = args.pwd;
 
     /* Communication with lektor. */
-    lkt_cmd_parse(options_, args.argc, args.argv, help);
+    lkt_cmd_parse(options_, args.argc, args.argv);
 }
diff --git a/src/main/lktadm.c b/src/main/lktadm.c
index 84a2de4a56232a28e5539cbbafdbd489ba5ac3ee..df2846b693e25559d8deb3eb7c803af1095b7455 100644
--- a/src/main/lktadm.c
+++ b/src/main/lktadm.c
@@ -229,30 +229,31 @@ download__(struct lkt_cmd_args *args)
 }
 
 static struct lkt_cmd_opt options_init[] = {
-    { .name = "database",   .call = init_database__,    .help = "Create an empty database"          },
-    { .name = "populate",   .call = init_populate__,    .help = "Populate database from kara on fs" },
-    { .name = "metadata",   .call = init_metadata__,    .help = "Set mdt for all kara in fs"        },
-    { .name = "file",       .call = init_file__,        .help = "Set mdt for a kara by its path"    },
+    { .name = "database",   .call = init_database__  },
+    { .name = "populate",   .call = init_populate__  },
+    { .name = "metadata",   .call = init_metadata__  },
+    { .name = "file",       .call = init_file__      },
     LKT_OPT_NULL,
 };
 
 noreturn void
 init__(struct lkt_cmd_args *args)
 {
-    lkt_cmd_parse(options_init, args->argc, args->argv, NULL);
+    lkt_cmd_parse(options_init, args->argc, args->argv);
 }
 
 static struct lkt_cmd_opt options[] = {
-    { .name = "init",       .call = init__,     .help = "The init sub-command",             .sub = options_init },
-    { .name = "get",        .call = get__,      .help = "Get the mdt of a kara by its id"                       },
-    { .name = "download",   .call = download__, .help = "Download a kara by its id"                             },
-    { .name = "cat",        .call = cat__,      .help = "Prints the mdt of a kara on the fs"                    },
-    { .name = "conf",       .call = conf__,     .help = "Prints out the default config"                         },
+    { .name = "init",       .call = init__     },
+    { .name = "get",        .call = get__      },
+    { .name = "download",   .call = download__ },
+    { .name = "cat",        .call = cat__      },
+    { .name = "conf",       .call = conf__     },
     LKT_OPT_NULL,
 };
 
 int
 main(int argc, const char **argv)
 {
-    lkt_cmd_parse(options, argc - 1, argv + 1, NULL);
+    executable_name = "lktadm";
+    lkt_cmd_parse(options, argc - 1, argv + 1);
 }
diff --git a/src/net/downloader.c b/src/net/downloader.c
index 76a9307a49ae7d002b7962c781e0b721d1a2f040..96abec51a631caf426e3a7b6593d4ccfa285a93b 100644
--- a/src/net/downloader.c
+++ b/src/net/downloader.c
@@ -28,7 +28,7 @@ struct file {
     int fd;
 };
 
-static size_t
+size_t
 write_mem__(char *data, size_t size, size_t nmem, void *user)
 {
     size_t realsize = size * nmem;
@@ -43,7 +43,7 @@ write_mem__(char *data, size_t size, size_t nmem, void *user)
     return realsize;
 }
 
-static size_t
+size_t
 write_disk__(char *data, size_t size, size_t nmem, void *user)
 {
     ssize_t realsize = size * nmem;
@@ -87,7 +87,8 @@ repo_free(struct lkt_repo *const repo)
     if (!curl_init)
         curl_global_cleanup();
 }
-static inline int
+
+int
 safe_json_get_string(struct json_object *jobj, const char *key, char *content, const size_t len)
 {
     const char *got;
@@ -100,7 +101,7 @@ safe_json_get_string(struct json_object *jobj, const char *key, char *content, c
     return 0;
 }
 
-static inline int
+int
 safe_json_get_int32(struct json_object *json, const char *key, int32_t *ret)
 {
     struct json_object *field;
@@ -111,7 +112,7 @@ safe_json_get_int32(struct json_object *json, const char *key, int32_t *ret)
     return 0;
 }
 
-static inline long
+long
 get_digit_number(long i)
 {
     int count = 0;
@@ -133,7 +134,7 @@ safe_json_get_long(struct json_object *json, const char *key, long *ret)
     return err;
 }
 
-static inline int
+int
 __json_sync(struct lkt_repo *const repo, struct json_object **json)
 {
     RETURN_UNLESS(json, "Invalid argument", 1);
@@ -245,7 +246,7 @@ err:
     return ret;
 }
 
-static inline int
+int
 __download_kara(const char *url, const char *path, int override)
 {
     CURL *curl_handle;
@@ -333,7 +334,7 @@ repo_download_id_sync(struct lkt_repo *const repo, const uint64_t id, const char
 
 /* Get all the kara, make them unavailable */
 
-static inline void
+void
 __handle_got_json(volatile sqlite3 *db, struct lkt_repo *repo, struct json_object *json)
 {
     size_t i, len = json_object_array_length(json);
@@ -419,7 +420,7 @@ err:
     free(kara);
 }
 
-static inline void *
+void *
 __repo_get_all_id_async(void *arg)
 {
     struct json_object *json;
@@ -434,7 +435,7 @@ __repo_get_all_id_async(void *arg)
     return NULL;
 }
 
-inline int
+int
 repo_update(struct lkt_repo *const repo)
 {
     return mthread_create(NULL, ATTR_DETACHED, __repo_get_all_id_async, repo);
diff --git a/src/net/listen.c b/src/net/listen.c
index 35aa74ed7eef616239504b80e3e1788f73e9f021..30f5e5789f7a32e17ff3dbde969b269ae9560e6f 100644
--- a/src/net/listen.c
+++ b/src/net/listen.c
@@ -147,7 +147,7 @@ command_list_begin(struct lkt_state *srv, size_t c, int list_ok)
 static int
 handle_simple_command(struct lkt_state *srv, size_t c, struct lkt_command cmd)
 {
-    int err, continuation = 0;
+    int err = 0, continuation = 0;
 
     switch (*lkt_client_get_mask(srv, c)) {
     case MPD_IDLE_NONE:
diff --git a/src/thread.c b/src/thread.c
index e4e3f1074945c1cae565f986c7d74efb1439bfc8..8df3d086884a4795827181d716db04b89aa6fef0 100644
--- a/src/thread.c
+++ b/src/thread.c
@@ -15,7 +15,7 @@ struct __args {
     struct poller_thread_arg *arg;
 };
 
-static void *
+void *
 __start(void *args__)
 {
     struct __args *args = (struct __args *) args__;
@@ -92,7 +92,7 @@ poller_join(struct poller_thread *th, void **ret)
     return sta;
 }
 
-static inline int
+int
 th_append(unsigned int *len, unsigned int *size, void ***cells,
           pthread_mutex_t *lock, void *ptr)
 {
@@ -121,7 +121,7 @@ end:
     return ret;
 }
 
-static inline int
+int
 th_pop(unsigned int *len, void **cells, pthread_mutex_t *lock, void **ptr)
 {
     int ret = 1;
@@ -139,7 +139,7 @@ end:
     return ret;
 }
 
-static inline int
+int
 th_head(unsigned int len, void **cells, pthread_mutex_t *lock, void **ptr)
 {
     int ret = 1;