diff --git a/cmd/root.go b/cmd/root.go
index bf72649c51f60d2af23b144cf2463af94e76d12f..6e4dd57fe1e341c6ba76c62988bfe9403f49043c 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -2,107 +2,39 @@ package cmd
 
 import (
 	"fmt"
-	"image/png"
-	"log"
-	"net/http"
 	"os"
-	"path"
-	"text/template"
 
-	"github.com/ehedenst/go-ipa/constant"
 	"github.com/joho/godotenv"
-	"github.com/phinexdaz/ipapk"
-	"github.com/skip2/go-qrcode"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 )
 
-type AppInfoWithURL struct {
-	*ipapk.AppInfo
-	URL string
-}
-
-var scheme string
-var file string
-var filePath string
-var appInfo *ipapk.AppInfo
-var manifestTemplate *template.Template
-
-func iconHandler(w http.ResponseWriter, req *http.Request) {
-	w.Header().Set("Content-Type", "image/png")
-	png.Encode(w, appInfo.Icon)
-}
-
-func qrcodeHandler(w http.ResponseWriter, req *http.Request) {
-	w.Header().Set("Content-Type", "image/png")
-	url := fmt.Sprintf("itms-services://?action=download-manifest&url=%s://%s/manifest.plist", scheme, req.Host)
-	image, _ := qrcode.Encode(url, qrcode.Medium, 256)
-	w.Write(image)
-}
-
-func manifestHandler(w http.ResponseWriter, req *http.Request) {
-	w.Header().Set("Content-Type", "text/xml")
-	appInfoWithURL := AppInfoWithURL{
-		AppInfo: appInfo,
-		URL:     fmt.Sprintf("%s://%s%s", scheme, req.Host, filePath),
-	}
-	manifestTemplate.Execute(w, appInfoWithURL)
-}
-
-func fileDownloadHandler(w http.ResponseWriter, req *http.Request) {
-	http.ServeFile(w, req, file)
-}
-
 var rootCmd = &cobra.Command{
 	Use:   "go-ipa",
 	Short: "Root Short",
 	Long:  `Root Long`,
-	Run: func(cmd *cobra.Command, args []string) {
-		var err error
-		appInfo, err = ipapk.NewAppParser(file)
-		filePath = fmt.Sprintf("/%s", path.Base(file))
-		http.HandleFunc("/icon.png", iconHandler)
-		http.HandleFunc("/qrcode.png", qrcodeHandler)
-		http.HandleFunc("/manifest.plist", manifestHandler)
-		http.HandleFunc(filePath, fileDownloadHandler)
-		addr := viper.GetString("ADDR")
-		certFile := viper.GetString("CERT")
-		if certFile != "" {
-			scheme = "https"
-			keyFile := viper.GetString("KEY")
-			err = http.ListenAndServeTLS(addr, certFile, keyFile, nil)
-		} else {
-			scheme = "http"
-			err = http.ListenAndServe(addr, nil)
-		}
-		if err != nil {
-			log.Fatal("ListenAndServe: ", err)
-		}
-	},
-}
-
-func Execute() {
-	if err := rootCmd.Execute(); err != nil {
-		fmt.Println(err)
-		os.Exit(-1)
-	}
 }
 
 func init() {
 	cobra.OnInitialize(initEnv)
-	rootCmd.PersistentFlags().StringP("addr", "a", ":9313", "addr")
-	rootCmd.PersistentFlags().StringP("cert", "c", "", "cert")
-	rootCmd.PersistentFlags().StringP("key", "k", "", "key")
-	rootCmd.Flags().StringVarP(&file, "file", "f", "", "file (required)")
-	rootCmd.MarkFlagRequired("file")
+	rootCmd.PersistentFlags().StringP("url", "b", "", "Set a base URL")
 	viper.SetEnvPrefix("IPA")
-	viper.BindPFlag("ADDR", rootCmd.PersistentFlags().Lookup("addr"))
-	viper.BindPFlag("CERT", rootCmd.PersistentFlags().Lookup("cert"))
-	viper.BindPFlag("KEY", rootCmd.PersistentFlags().Lookup("key"))
-	manifestTemplate, _ = template.New("manifest").Parse(constant.ManifestTemplate)
+	viper.BindPFlag("BASE_URL", rootCmd.PersistentFlags().Lookup("url"))
 }
 
 func initEnv() {
 	_ = godotenv.Load(".env")
 	viper.AutomaticEnv()
 }
+
+func Execute() {
+	defer func() {
+		if rec := recover(); rec != nil {
+			fmt.Println(rec)
+			os.Exit(-1)
+		}
+	}()
+	if err := rootCmd.Execute(); err != nil {
+		panic(err)
+	}
+}
diff --git a/cmd/share.go b/cmd/share.go
new file mode 100644
index 0000000000000000000000000000000000000000..756422b559ddd85723dec21d7859acc49e220175
--- /dev/null
+++ b/cmd/share.go
@@ -0,0 +1,144 @@
+package cmd
+
+import (
+	"errors"
+	"fmt"
+	"image/png"
+	"net/http"
+	"os"
+	"path"
+	"strings"
+	"text/template"
+
+	"github.com/ehedenst/go-ipa/constant"
+	"github.com/mdp/qrterminal"
+	"github.com/phinexdaz/ipapk"
+	"github.com/skip2/go-qrcode"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+)
+
+type ExtendedAppInfo struct {
+	*ipapk.AppInfo
+	IsIPA       bool
+	Filename    string
+	BaseURL     string
+	DownloadURL string
+}
+
+var useTLS bool
+var useHalfBlock bool
+
+var scheme string
+var file string
+var appInfo *ipapk.AppInfo
+
+func getExtendedAppInfo(host string) ExtendedAppInfo {
+
+	fileName := path.Base(file)
+	isIPA := strings.ToLower(path.Ext(fileName)) == ".ipa"
+	baseURL := fmt.Sprintf("%s://%s", scheme, host)
+	if viper.IsSet("BASE_URL") {
+		baseURL = viper.GetString("BASE_URL")
+	}
+
+	var downloadURL string
+	if isIPA {
+		downloadURL = fmt.Sprintf("itms-services://?action=download-manifest&url=%s/manifest.plist", baseURL)
+	} else {
+		downloadURL = fmt.Sprintf("%s/%s", baseURL, fileName)
+	}
+
+	return ExtendedAppInfo{
+		AppInfo:     appInfo,
+		Filename:    fileName,
+		IsIPA:       isIPA,
+		BaseURL:     baseURL,
+		DownloadURL: downloadURL,
+	}
+}
+
+func iconHandler(w http.ResponseWriter, req *http.Request) {
+	w.Header().Set("Content-Type", "image/png")
+	png.Encode(w, appInfo.Icon)
+}
+
+func qrcodeHandler(w http.ResponseWriter, req *http.Request) {
+	appInfo := getExtendedAppInfo(req.Host)
+	w.Header().Set("Content-Type", "image/png")
+	image, _ := qrcode.Encode(appInfo.DownloadURL, qrcode.Medium, 256)
+	w.Write(image)
+}
+
+func indexHandler(w http.ResponseWriter, req *http.Request) {
+	w.Header().Set("Content-Type", "text/html")
+	tmpl, _ := template.New("index").Parse(constant.IndexTemplate)
+	tmpl.Execute(w, getExtendedAppInfo(req.Host))
+}
+
+func manifestHandler(w http.ResponseWriter, req *http.Request) {
+	w.Header().Set("Content-Type", "text/xml")
+	tmpl, _ := template.New("manifest").Parse(constant.ManifestTemplate)
+	tmpl.Execute(w, getExtendedAppInfo(req.Host))
+}
+
+func fileDownloadHandler(w http.ResponseWriter, req *http.Request) {
+	http.ServeFile(w, req, file)
+}
+
+var shareCmd = &cobra.Command{
+	Use:   "share [flags] [file]",
+	Short: "share Short",
+	Long:  `share Long`,
+	Args: func(cmd *cobra.Command, args []string) error {
+		if len(args) < 1 {
+			return errors.New("requires a file argument")
+		}
+		// Todo: check if it is a valid file
+		return nil
+	},
+	RunE: func(cmd *cobra.Command, args []string) (err error) {
+
+		file = args[0]
+
+		http.HandleFunc("/qrcode.png", qrcodeHandler)
+		http.HandleFunc(fmt.Sprintf("/%s", path.Base(file)), fileDownloadHandler)
+
+		if appInfo, err = ipapk.NewAppParser(file); err == nil {
+			http.HandleFunc("/", indexHandler)
+			http.HandleFunc("/index.html", indexHandler)
+			http.HandleFunc("/icon.png", iconHandler)
+			http.HandleFunc("/manifest.plist", manifestHandler)
+		}
+
+		if viper.IsSet("BASE_URL") {
+			extAppInfo := getExtendedAppInfo("")
+			fmt.Println(extAppInfo.DownloadURL)
+			if useHalfBlock {
+				qrterminal.GenerateHalfBlock(extAppInfo.DownloadURL, qrterminal.L, os.Stdout)
+			} else {
+				qrterminal.Generate(extAppInfo.DownloadURL, qrterminal.L, os.Stdout)
+			}
+		}
+
+		addr := viper.GetString("ADDR")
+		if useTLS {
+			scheme = "https"
+			return http.ListenAndServeTLS(addr, viper.GetString("CERT"), viper.GetString("KEY"), nil)
+		}
+		scheme = "http"
+		return http.ListenAndServe(addr, nil)
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(shareCmd)
+	shareCmd.Flags().BoolVar(&useTLS, "ssl", true, "enable SSL/TLS")
+	shareCmd.Flags().BoolVar(&useHalfBlock, "half", false, "generate a smaller QR code in the terminal")
+	shareCmd.Flags().StringP("addr", "a", ":8080", "address and port on which the server will accept requests")
+	shareCmd.Flags().StringP("cert", "c", "", "path to SSL certificate")
+	shareCmd.Flags().StringP("key", "k", "", "path to SSL secret key")
+	viper.BindPFlag("ADDR", shareCmd.Flags().Lookup("addr"))
+	viper.BindPFlag("CERT", shareCmd.Flags().Lookup("cert"))
+	viper.BindPFlag("KEY", shareCmd.Flags().Lookup("key"))
+}
diff --git a/constant/templates.go b/constant/templates.go
index ea51f0f84ea8b382d24e4788c45f8cbd8cc784c7..42092b25b9fae4e34e2b095a7efd6c5b58bbfc54 100644
--- a/constant/templates.go
+++ b/constant/templates.go
@@ -1,5 +1,36 @@
 package constant
 
+var IndexTemplate = `
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1" />
+    <meta name="Description" content="Ad-Hoc distribution for {{.Filename}}" />
+
+    <title>{{.Name}}</title>
+
+    <link rel="icon" type="image/png" href="icon.png" />
+
+    <style>
+        h1 {color:red;}
+        p {color:blue;}
+    </style>
+
+  </head>
+
+  <body>
+    <img src="icon.png">
+    <img src="qrcode.png">
+    <p><a href="{{.DownloadURL}}">{{.Name}}</a></p>
+    <p>{{.BundleId}}</p>
+    <p>{{.Version}}</p>
+    <p>{{.Build}}</p>
+    <p>{{.Size}}</p>
+  </body>
+</html>
+`
+
 var ManifestTemplate = `<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
@@ -13,7 +44,7 @@ var ManifestTemplate = `<?xml version="1.0" encoding="UTF-8"?>
           <key>kind</key>
           <string>software-package</string>
           <key>url</key>
-          <string>{{.URL}}</string>
+          <string>{{.BaseURL}}/{{.Filename}}</string>
         </dict>
       </array>
       <key>metadata</key>