···1111## Features
12121313- **Shows up like a real app.** Lands in your application menu with its real name and icon.
1414+- **Installs from a file or a URL.** Point it at a local AppImage or paste a download link — appherder fetches and installs it.
1415- **Uninstalls cleanly.** Remove an app and its launcher and icon go with it. No leftovers.
1516- **Upgrades replace instead of piling up.** appherder names an app by what's inside it, not the download's filename, so a newer version of `Foo` just replaces the old one.
1617- **Checks for updates.** Reads the update info baked into each AppImage and fetches the latest from GitHub, GitLab, zsync, or a static URL. `appherder upgrade --check` shows what's new; `appherder upgrade` downloads and installs it.
···5051## Usage
51525253```bash
5353-appherder install ~/Downloads/Foo-x86_64.AppImage # install one
5454+appherder install ~/Downloads/Foo-x86_64.AppImage # install from a file
5555+appherder install https://example.com/Foo.AppImage # or download and install
5456appherder uninstall foo # remove one
5557appherder list # see what's installed
5658appherder sync # match your apps to what's in ~/AppImages
+21-5
cmd/appherder/cli.go
···33import (
44 "fmt"
55 "io"
66+ "net/url"
6778 "github.com/alyraffauf/appherder/internal/appherder"
89 "github.com/spf13/cobra"
···33343435func newInstallCommand(a appherder.App) *cobra.Command {
3536 return &cobra.Command{
3636- Use: "install APPIMAGE",
3737- Short: "Install an AppImage",
3838- Long: "Copies an AppImage into ~/AppImages and creates a launcher for it with the app's\nreal name and icon. You can delete the original download afterward.",
3939- Args: cobra.ExactArgs(1),
3737+ Use: "install APPIMAGE|URL",
3838+ Short: "Install an AppImage from a file or URL",
3939+ Long: "Copies an AppImage into ~/AppImages and creates a launcher for it with the app's\n" +
4040+ "real name and icon. You can delete the original download afterward.\n\n" +
4141+ "Accepts a local file path or an HTTP/HTTPS URL to download directly.",
4242+ Args: cobra.ExactArgs(1),
4043 RunE: func(cmd *cobra.Command, args []string) error {
4141- name, err := a.Install(args[0])
4444+ arg := args[0]
4545+ var name string
4646+ var err error
4747+ if isURL(arg) {
4848+ fmt.Fprintf(cmd.OutOrStdout(), "downloading %s...\n", arg)
4949+ name, err = a.InstallFromURL(cmd.Context(), arg)
5050+ } else {
5151+ name, err = a.Install(arg)
5252+ }
4253 if err != nil {
4354 return err
4455 }
···4657 return nil
4758 },
4859 }
6060+}
6161+6262+func isURL(s string) bool {
6363+ parsed, err := url.Parse(s)
6464+ return err == nil && (parsed.Scheme == "http" || parsed.Scheme == "https") && parsed.Host != ""
4965}
50665167func newUninstallCommand(a appherder.App) *cobra.Command {