command

package
v0.37.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: Apache-2.0 Imports: 55 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ConfigWizardCmd = &cli.Command{
	Name:  "config-wizard",
	Usage: "Run the web-based config wizard",
	Description: `Start a local web UI that generates a knot.toml in an embedded TOML editor.

The wizard always starts. When no config exists yet, the editor's Write button
saves straight to disk; when a config already exists, Write is disabled and the
generated text is shown for manual copy / merge.

Listens on 127.0.0.1 by default; bind a different address with --listen. Use
--config to target a non-default output path.`,
	MaxArgs: cli.NoArgs,
	Flags: []cli.Flag{
		&cli.IntFlag{
			Name:         "port",
			Usage:        "TCP port to serve the wizard web UI on.",
			DefaultValue: 8080,
		},
		&cli.StringFlag{
			Name:         "listen",
			Usage:        "Address to bind the wizard web UI to. Defaults to loopback.",
			DefaultValue: "127.0.0.1",
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		addr := fmt.Sprintf("%s:%d", cmd.GetString("listen"), cmd.GetInt("port"))

		sigCtx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
		defer stop()

		return configwizard.Serve(sigCtx, addr, cmd.GetString("config"))
	},
}
View Source
var ConnectCmd = &cli.Command{
	Name:        "connect",
	Usage:       "Connect to server",
	Description: "Authenticate the client with a remote server and save the server address and access key.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "server",
			Usage:    "The server to connect to",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:  "use-web-auth",
			Usage: "If given then authorization will be done via the web interface.",
		},
		&cli.BoolFlag{
			Name:         "tls-skip-verify",
			Usage:        "Skip TLS verification when talking to server.",
			ConfigPath:   []string{"tls.skip_verify"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TLS_SKIP_VERIFY"},
			DefaultValue: true,
			Global:       true,
		},
		&cli.StringFlag{
			Name:    "username",
			Aliases: []string{"u"},
			Usage:   "Username to use for authentication.",
		},
		&cli.StringFlag{
			Name:         "alias",
			Aliases:      []string{"a"},
			Usage:        "The server alias to use to identify the connection.",
			DefaultValue: "default",
		},
	},
	Commands: []*cli.Command{
		connectcmd.ConnectListCmd,
		connectcmd.ConnectDeleteCmd,
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		var token string

		server := cmd.GetStringArg("server")

		if !strings.HasPrefix(server, "http://") && !strings.HasPrefix(server, "https://") {
			server = "https://" + server
		}

		fmt.Println("Connecting to server: ", server)

		u, err := url.Parse(server)
		if err != nil {
			return fmt.Errorf("invalid server address %q: %w", server, err)
		}

		hostname, err := os.Hostname()
		if err != nil {
			return fmt.Errorf("couldn't get this computer's host name to name the token: %w", err)
		}

		hostname = "knot client " + hostname

		client, err := apiclient.NewClient(
			server,
			"",
			cmd.GetBool("tls-skip-verify"),
		)
		if err != nil {
			return fmt.Errorf("couldn't create the API client for %s: %w", server, err)
		}

		totp, _, err := client.UsingTOTP(context.Background())
		if err != nil {
			return fmt.Errorf("couldn't connect to %s: %w", server, err)
		}

		if totp || cmd.GetBool("use-web-auth") {
			u.Path = "/api-tokens/create/" + url.PathEscape(hostname)
			err = util.OpenBrowser(u.String())
			if err != nil {
				return fmt.Errorf("couldn't open %s in a browser (%w); create an API token in the web interface and run connect again", u.String(), err)
			}
			fmt.Print("Enter token: ")
			_, err = fmt.Scanln(&token)
			if err != nil {
				return fmt.Errorf("couldn't read the token: %w", err)
			}

			client.SetAuthToken(token)
			if err := requireCompatibleServer(client); err != nil {
				return err
			}
		} else {
			username := cmd.GetString("username")
			var password []byte

			if username == "" {
				fmt.Print("Enter email: ")
				_, err = fmt.Scanln(&username)
				if err != nil {
					return fmt.Errorf("couldn't read the email address: %w", err)
				}
			}

			fmt.Print("Enter password: ")
			password, err = term.ReadPassword(int(syscall.Stdin))
			if err != nil {
				return fmt.Errorf("couldn't read the password: %w", err)
			}
			fmt.Println()

			if username == "" || string(password) == "" {
				return fmt.Errorf("an email address and password are required")
			}

			response, _, err := client.Login(context.Background(), username, string(password), "")
			if err != nil {

				if he := apiclient.AsHTTPError(err); he != nil && apiclient.IsUnauthorized(err) {
					return fmt.Errorf("couldn't sign in to %s as %s: %s", server, username, he.Message())
				}
				return fmt.Errorf("couldn't sign in to %s as %s: %w", server, username, err)
			}
			if response == nil || response.Token == "" {
				return fmt.Errorf("couldn't sign in to %s as %s: the server returned no session", server, username)
			}

			client.UseSessionCookie(true).SetAuthToken(response.Token)

			if err := requireCompatibleServer(client); err != nil {
				return err
			}

			token, _, err = client.CreateToken(context.Background(), hostname, nil)
			if err != nil {
				return fmt.Errorf("couldn't create an API token on %s: %w", server, err)
			}
			if token == "" {
				return fmt.Errorf("couldn't create an API token on %s: the server returned an empty token", server)
			}
		}

		alias := cmd.GetString("alias")
		if err := config.SaveConnection(alias, server, token, cmd); err != nil {
			return fmt.Errorf("couldn't save the connection %q: %w", alias, err)
		}

		fmt.Println("Successfully connected to server:", server)
		return nil
	},
}
View Source
var GenkeyCmd = &cli.Command{
	Name:        "genkey",
	Usage:       "Generate Encryption Key",
	Description: "Generate an encryption key for encrypting stored variables and cluster communications.",
	MaxArgs:     cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		key := crypt.CreateKey()
		fmt.Println("Encryption Key:", key)
		fmt.Println("")
		return nil
	},
}
View Source
var LegalCmd = &cli.Command{
	Name:        "legal",
	Usage:       "Show legal information",
	Description: "Output all the legal notices.",
	MaxArgs:     cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		legal.ShowLicenses()
		return nil
	},
}
View Source
var PingCmd = &cli.Command{
	Name:        "ping",
	Usage:       "Ping the server",
	Description: "Ping the server and display the health and version number.",
	MaxArgs:     cli.NoArgs,
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:    "server",
			Aliases: []string{"s"},
			Usage:   "The address of the remote server to ping.",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_SERVER"},
		},
		&cli.StringFlag{
			Name:    "token",
			Aliases: []string{"t"},
			Usage:   "The token to use for authentication.",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TOKEN"},
		},
		&cli.StringFlag{
			Name:         "alias",
			Aliases:      []string{"a"},
			Usage:        "The server alias to use.",
			DefaultValue: "default",
		},
		&cli.BoolFlag{
			Name:         "tls-skip-verify",
			Usage:        "Skip TLS verification when talking to server.",
			ConfigPath:   []string{"tls.skip_verify"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TLS_SKIP_VERIFY"},
			DefaultValue: true,
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		alias := cmd.GetString("alias")

		cfg := config.GetServerAddr(alias, cmd)
		fmt.Println("Pinging server: ", cfg.HttpServer)

		client, err := apiclient.NewClient(
			cfg.HttpServer,
			cfg.ApiToken,
			cmd.GetBool("tls-skip-verify"),
		)
		if err != nil {
			return fmt.Errorf("couldn't create the API client: %w", err)
		}

		version, err := client.Ping(ctx)
		if err != nil {
			return fmt.Errorf("couldn't ping the server: %w", err)
		}

		fmt.Println("\nServer is healthy")
		fmt.Println("Version: ", version.Version)
		fmt.Println("Zone: ", version.Zone)

		return nil
	},
}
View Source
var ScaffoldCmd = &cli.Command{
	Name:        "scaffold",
	Usage:       "Generate configuration files",
	Description: "Generates example configuration files for use with knot.",
	MaxArgs:     cli.NoArgs,
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:       "server",
			Usage:      "Generate a server configuration file",
			ConfigPath: []string{"scaffold.server"},
		},
		&cli.BoolFlag{
			Name:       "client",
			Usage:      "Generate a client configuration file",
			ConfigPath: []string{"scaffold.client"},
		},
		&cli.BoolFlag{
			Name:       "agent",
			Usage:      "Generate an agent configuration file",
			ConfigPath: []string{"scaffold.agent"},
		},
		&cli.BoolFlag{
			Name:       "nomad",
			Usage:      "Generate a nomad job file",
			ConfigPath: []string{"scaffold.nomad"},
		},
		&cli.BoolFlag{
			Name:       "system-prompt",
			Usage:      "Generate the internal system prompt",
			ConfigPath: []string{"scaffold.system_prompt"},
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		any := false

		if cmd.GetBool("server") {
			fmt.Println(scaffold.ServerScaffold)
			any = true
		}
		if cmd.GetBool("client") {
			fmt.Println(scaffold.ClientScaffold)
			any = true
		}
		if cmd.GetBool("agent") {
			fmt.Println(scaffold.AgentScaffold)
			any = true
		}
		if cmd.GetBool("nomad") {
			fmt.Println(scaffold.NomadScaffold)
			any = true
		}
		if cmd.GetBool("system-prompt") {
			fmt.Println(scaffold.GetSystemPromptScaffold())
			any = true
		}

		if !any {
			cmd.ShowHelp()
		}
		return nil
	},
}
View Source
var ServerCmd = &cli.Command{
	Name:        "server",
	Usage:       "Start the knot server",
	Description: "Start the knot server and listen for incoming connections.",
	MaxArgs:     cli.NoArgs,
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:         "listen",
			Aliases:      []string{"l"},
			Usage:        "The address to listen on.",
			ConfigPath:   []string{"server.listen"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_LISTEN"},
			DefaultValue: ":3000",
		},
		&cli.StringFlag{
			Name:         "listen-agent",
			Usage:        "The address to listen on for agent connections.",
			ConfigPath:   []string{"server.listen_agent"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_LISTEN_AGENT"},
			DefaultValue: "127.0.0.1:3010",
		},
		&cli.StringFlag{
			Name:         "listen-tunnel",
			Usage:        "The address to listen on for tunnel connections.",
			ConfigPath:   []string{"server.listen_tunnel"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_LISTEN_TUNNEL"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "url",
			Aliases:      []string{"u"},
			Usage:        "The URL to use for the server.",
			ConfigPath:   []string{"server.url"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_URL"},
			DefaultValue: "http://127.0.0.1:3000",
		},
		&cli.StringFlag{
			Name:         "tunnel-server",
			Usage:        "The URL for the tunnel client to connect to the individual server.",
			ConfigPath:   []string{"server.tunnel_server"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TUNNEL_SERVER"},
			DefaultValue: "",
		},
		&cli.BoolFlag{
			Name:         "terminal-webgl",
			Usage:        "Enable WebGL terminal renderer.",
			ConfigPath:   []string{"server.terminal.webgl"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_WEBGL"},
			DefaultValue: true,
		},
		&cli.StringFlag{
			Name:         "download-path",
			Usage:        "The path to serve download files from if set.",
			ConfigPath:   []string{"server.download_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_DOWNLOAD_PATH"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "wildcard-domain",
			Usage:        "The wildcard domain to use for proxying to spaces.",
			ConfigPath:   []string{"server.wildcard_domain"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_WILDCARD_DOMAIN"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "encrypt",
			Usage:        "The encryption key to use for encrypting stored variables.",
			ConfigPath:   []string{"server.encrypt"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_ENCRYPT"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "agent-endpoint",
			Usage:        "The address agents should use to talk to the server.",
			ConfigPath:   []string{"server.agent_endpoint"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AGENT_ENDPOINT"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:       "zone",
			Usage:      "The zone of the server.",
			ConfigPath: []string{"server.zone"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_ZONE"},
		},
		&cli.StringFlag{
			Name:       "hostname",
			Usage:      "The hostname to advertise to other servers (defaults to system hostname).",
			ConfigPath: []string{"server.hostname"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_HOSTNAME"},
		},
		&cli.StringFlag{
			Name:         "html-path",
			Usage:        "The optional path to the html files to serve.",
			ConfigPath:   []string{"server.html_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_HTML_PATH"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "template-path",
			Usage:        "The optional path to the template files to serve.",
			ConfigPath:   []string{"server.template_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TEMPLATE_PATH"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "agent-path",
			Usage:        "The optional path to the agent files to serve.",
			ConfigPath:   []string{"server.agent_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AGENT_PATH"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "package-path",
			Usage:        "The optional path to the scriptling packages to serve.",
			ConfigPath:   []string{"server.package_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_PACKAGE_PATH"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "timezone",
			Usage:        "The timezone to use for the server.",
			ConfigPath:   []string{"server.timezone"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TIMEZONE"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "tunnel-domain",
			Usage:        "The domain to use for tunnel connections.",
			ConfigPath:   []string{"server.tunnel_domain"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TUNNEL_DOMAIN"},
			DefaultValue: "",
		},
		&cli.IntFlag{
			Name:         "audit-retention",
			Usage:        "The number of days to keep audit logs.",
			ConfigPath:   []string{"server.audit_retention", "server.audit.retention"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AUDIT_RETENTION"},
			DefaultValue: 90,
		},
		&cli.StringFlag{
			Name:         "audit-routing",
			Usage:        "Audit log routing: internal, external, or both.",
			ConfigPath:   []string{"server.audit_routing", "server.audit.routing"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AUDIT_ROUTING"},
			DefaultValue: "internal",
		},
		&cli.BoolFlag{
			Name:         "audit-file-operations",
			Usage:        "Audit space file read, write and copy operations (path and byte count only, never content). Off by default — noisy for local development.",
			ConfigPath:   []string{"server.audit_file_operations", "server.audit.file_operations"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AUDIT_FILE_OPERATIONS"},
			DefaultValue: false,
		},
		&cli.BoolFlag{
			Name:         "audit-space-sessions",
			Usage:        "Audit interactive space session opens (web terminal, SSH, VS Code tunnel). Off by default — noisy for local development.",
			ConfigPath:   []string{"server.audit_space_sessions", "server.audit.space_sessions"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AUDIT_SPACE_SESSIONS"},
			DefaultValue: false,
		},
		&cli.StringFlag{
			Name:         "audit-stream",
			Usage:        "Stream label used when routing audit logs to the external log driver.",
			ConfigPath:   []string{"server.audit_stream", "server.audit.stream"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AUDIT_STREAM"},
			DefaultValue: "knot_audit",
		},
		&cli.IntFlag{
			Name:         "mcp-tool-timeout",
			Usage:        "The maximum execution time in seconds for MCP tool calls (allows for LLM operations with tool calling).",
			ConfigPath:   []string{"server.mcp_tool_timeout"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MCP_TOOL_TIMEOUT"},
			DefaultValue: 180,
		},
		&cli.StringSliceFlag{
			Name:       "script-fs-allowed-paths",
			Usage:      "Comma-separated list of directory paths server-side scripts (MCP tools, event sinks) may access via the fs library. The fs library is not registered on the server unless this is set.",
			ConfigPath: []string{"server.script_fs_allowed_paths"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_SCRIPT_FS_ALLOWED_PATHS"},
		},
		&cli.StringFlag{
			Name:       "script-net-policy",
			Usage:      "Path to a scriptling network policy TOML file (same schema as the scriptling CLI's --network-policy) restricting outbound access for server-side scripts (MCP tools, event sinks) via requests, wait_for, scriptling.ai and scriptling.mcp — https_only, allow_ip_literals, allow_loopback, allow_private_ips, allow_hosts, deny_hosts, allow_cidrs, deny_cidrs, dns_servers, client_timeout. Unset leaves outbound network access unrestricted.",
			ConfigPath: []string{"server.script_net_policy"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_SCRIPT_NET_POLICY"},
		},
		&cli.BoolFlag{
			Name:         "disable-space-create",
			Usage:        "Disable the ability to create spaces.",
			ConfigPath:   []string{"server.disable_space_create"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_DISABLE_SPACE_CREATE"},
			DefaultValue: false,
		},
		&cli.BoolFlag{
			Name:         "auth-ip-rate-limiting",
			Usage:        "Enable IP rate limiting of authentication.",
			ConfigPath:   []string{"server.auth_ip_rate_limiting"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AUTH_IP_RATE_LIMITING"},
			DefaultValue: true,
		},
		&cli.IntFlag{
			Name:         "auth-rate-limit-attempts",
			Usage:        "Failed login attempts per IP/email before auth is blocked.",
			ConfigPath:   []string{"server.auth_rate_limit_attempts"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AUTH_RATE_LIMIT_ATTEMPTS"},
			DefaultValue: 10,
		},
		&cli.IntFlag{
			Name:         "auth-rate-limit-window",
			Usage:        "Window in seconds over which failed login attempts are counted.",
			ConfigPath:   []string{"server.auth_rate_limit_window"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AUTH_RATE_LIMIT_WINDOW"},
			DefaultValue: 60,
		},
		&cli.IntFlag{
			Name:         "auth-rate-limit-block",
			Usage:        "How long in seconds auth stays blocked after the failed login limit trips.",
			ConfigPath:   []string{"server.auth_rate_limit_block"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_AUTH_RATE_LIMIT_BLOCK"},
			DefaultValue: 300,
		},
		&cli.StringFlag{
			Name:         "public-files-path",
			Usage:        "The path to the a directory to serve as /public-files.",
			ConfigPath:   []string{"server.public_files_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_PUBLIC_FILES_PATH"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "private-files-path",
			Usage:        "The path to the a directory to serve as /private-files.",
			ConfigPath:   []string{"server.private_files_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_PRIVATE_FILES_PATH"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "files-path",
			Usage:        "The directory for replicated file storage (buckets and the knot file commands; Knot Pro adds an S3 endpoint). File storage is disabled when not set.",
			ConfigPath:   []string{"server.files.path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_FILES_PATH"},
			DefaultValue: "",
		},
		&cli.IntFlag{
			Name:         "files-default-quota-mb",
			Usage:        "File storage quota in MB for users whose own and group limits are all 0 (0 = unlimited).",
			ConfigPath:   []string{"server.files.default_quota_mb"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_FILES_DEFAULT_QUOTA_MB"},
			DefaultValue: 0,
		},
		&cli.IntFlag{
			Name:         "files-default-max-buckets",
			Usage:        "Maximum number of file storage buckets a user can own when their own and group limits are all 0 (0 = unlimited).",
			ConfigPath:   []string{"server.files.default_max_buckets"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_FILES_DEFAULT_MAX_BUCKETS"},
			DefaultValue: 0,
		},
		&cli.StringFlag{
			Name:         "mcp-tools-path",
			Usage:        "Path to mcp-tools directory (overrides embedded tools).",
			ConfigPath:   []string{"server.mcp_tools_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MCP_TOOLS_PATH"},
			DefaultValue: "",
		},
		&cli.StringSliceFlag{
			Name:       "mcp-disable-builtin-tools",
			Usage:      "Comma-separated list of built-in tool names to disable.",
			ConfigPath: []string{"server.mcp.disable_builtin_tools"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_MCP_DISABLE_BUILTIN_TOOLS"},
		},
		&cli.StringFlag{
			Name:         "plugins-path",
			Usage:        "Path to a directory of knot plugins (single .py files or folders with main.py).",
			ConfigPath:   []string{"server.plugins_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_PLUGINS_PATH"},
			DefaultValue: "",
		},

		&cli.BoolFlag{
			Name:         "hide-support-links",
			Usage:        "Hide the support links in the UI.",
			ConfigPath:   []string{"server.ui.hide_support_links"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_HIDE_SUPPORT_LINKS"},
			DefaultValue: false,
		},
		&cli.BoolFlag{
			Name:         "hide-api-tokens",
			Usage:        "Hide the API tokens menu item in the UI.",
			ConfigPath:   []string{"server.ui.hide_api_tokens"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_HIDE_API_TOKENS"},
			DefaultValue: false,
		},
		&cli.BoolFlag{
			Name:         "enable-gravatar",
			Usage:        "Enable Gravatar support in the UI.",
			ConfigPath:   []string{"server.ui.enable_gravatar"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_ENABLE_GRAVATAR"},
			DefaultValue: true,
		},
		&cli.StringSliceFlag{
			Name:       "icons",
			Usage:      "File defining icons for use with templates and spaces, can be given multiple times.",
			ConfigPath: []string{"server.ui.icons"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_ICONS"},
		},
		&cli.BoolFlag{
			Name:         "enable-builtin-icons",
			Usage:        "Enable the use of the built-in icons.",
			ConfigPath:   []string{"server.ui.enable_builtin_icons"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_ENABLE_BUILTIN_ICONS"},
			DefaultValue: true,
		},
		&cli.StringFlag{
			Name:         "logo-url",
			Usage:        "The URL to the logo to use in the UI.",
			ConfigPath:   []string{"server.ui.logo_url"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_LOGO_URL"},
			DefaultValue: "",
		},
		&cli.BoolFlag{
			Name:         "logo-invert",
			Usage:        "Invert the logo colors in the UI for dark mode.",
			ConfigPath:   []string{"server.ui.logo_invert"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_LOGO_INVERT"},
			DefaultValue: false,
		},

		&cli.StringFlag{
			Name:         "cluster-key",
			Usage:        "The shared cluster key.",
			ConfigPath:   []string{"server.cluster.key"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_KEY"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "cluster-advertise-addr",
			Usage:        "The address to advertise to other servers.",
			ConfigPath:   []string{"server.cluster.advertise_addr"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_ADVERTISE_ADDR"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "cluster-bind-addr",
			Usage:        "The address to bind to for cluster communication.",
			ConfigPath:   []string{"server.cluster.bind_addr"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_BIND_ADDR"},
			DefaultValue: "",
		},
		&cli.StringSliceFlag{
			Name:       "cluster-peer",
			Usage:      "The addresses of the other servers in the cluster, can be given multiple times.",
			ConfigPath: []string{"server.cluster.peers"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_PEERS"},
		},
		&cli.BoolFlag{
			Name:         "allow-leaf-nodes",
			Usage:        "Allow leaf nodes to connect to the cluster.",
			ConfigPath:   []string{"server.cluster.allow_leaf_nodes"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_ALLOW_LEAF_NODES"},
			DefaultValue: true,
		},
		&cli.IntFlag{
			Name:         "min-cluster-size",
			Usage:        "Minimum nodes required for leader election quorum; set to the majority of the smallest zone (2 for production, 1 for single-machine testing).",
			ConfigPath:   []string{"server.cluster.min_cluster_size"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_MIN_CLUSTER_SIZE"},
			DefaultValue: 1,
		},
		&cli.BoolFlag{
			Name:         "cluster-compression",
			Usage:        "Enable compression for cluster communication.",
			ConfigPath:   []string{"server.cluster.compression"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_COMPRESSION"},
			DefaultValue: true,
		},
		&cli.BoolFlag{
			Name:       "cluster-tcp-only",
			Usage:      "Disable UDP and use TCP only for cluster communication.",
			ConfigPath: []string{"server.cluster.tcp_only"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_CLUSTER_TCP_ONLY"},
		},

		&cli.StringFlag{
			Name:         "origin-server",
			Usage:        "The address of the origin server.",
			ConfigPath:   []string{"server.origin.server"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_ORIGIN_SERVER"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "origin-token",
			Usage:        "The token to use for the origin server.",
			ConfigPath:   []string{"server.origin.token"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_ORIGIN_TOKEN"},
			DefaultValue: "",
		},

		&cli.BoolFlag{
			Name:         "enable-totp",
			Usage:        "Enable TOTP for users.",
			ConfigPath:   []string{"server.totp.enabled"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_ENABLE_TOTP"},
			DefaultValue: false,
		},
		&cli.IntFlag{
			Name:         "totp-window",
			Usage:        "The number of time steps (30 seconds) to check for TOTP codes.",
			ConfigPath:   []string{"server.totp.window"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TOTP_WINDOW"},
			DefaultValue: 1,
		},
		&cli.StringFlag{
			Name:         "totp-issuer",
			Usage:        "The issuer to use for TOTP codes.",
			ConfigPath:   []string{"server.totp.issuer"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TOTP_ISSUER"},
			DefaultValue: "Knot",
		},

		&cli.StringFlag{
			Name:         "cert-file",
			Usage:        "The file with the PEM encoded certificate to use for the server.",
			ConfigPath:   []string{"server.tls.cert_file"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CERT_FILE"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "key-file",
			Usage:        "The file with the PEM encoded key to use for the server.",
			ConfigPath:   []string{"server.tls.key_file"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_KEY_FILE"},
			DefaultValue: "",
		},
		&cli.BoolFlag{
			Name:         "use-tls",
			Usage:        "Enable TLS.",
			ConfigPath:   []string{"server.tls.use_tls"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_USE_TLS"},
			DefaultValue: true,
		},
		&cli.BoolFlag{
			Name:         "tls-skip-verify",
			Usage:        "Skip TLS verification when talking to agents.",
			ConfigPath:   []string{"tls.skip_verify"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TLS_SKIP_VERIFY"},
			DefaultValue: true,
		},

		&cli.StringFlag{
			Name:         "nomad-addr",
			Usage:        "The address of the Nomad server.",
			ConfigPath:   []string{"server.nomad.addr"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_NOMAD_ADDR"},
			DefaultValue: "http://127.0.0.1:4646",
		},
		&cli.StringFlag{
			Name:         "nomad-token",
			Usage:        "The token to use for Nomad API requests.",
			ConfigPath:   []string{"server.nomad.token"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_NOMAD_TOKEN"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "nomad-dc",
			Usage:        "The Nomad datacenter to expose as ${{ .nomad.dc }} in templates and to pre-fill in new Nomad jobs. Defaults to the NOMAD_DC environment variable (set automatically when knot runs as a Nomad job).",
			ConfigPath:   []string{"server.nomad.dc"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_NOMAD_DC"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "nomad-region",
			Usage:        "The Nomad region to expose as ${{ .nomad.region }} in templates and to pre-fill in new Nomad jobs. Defaults to the NOMAD_REGION environment variable (set automatically when knot runs as a Nomad job).",
			ConfigPath:   []string{"server.nomad.region"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_NOMAD_REGION"},
			DefaultValue: "",
		},

		&cli.StringFlag{
			Name:         "kvm-images-path",
			Usage:        "Working storage for KVM spaces: per-space disk overlays and cloud-init seeds, one directory per space. The base-image cache and cloud image library default to subdirectories of it unless pinned by their own flags. Must be writable by knot and readable by the qemu user libvirt runs VMs as.",
			ConfigPath:   []string{"server.kvm.images_path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_KVM_IMAGES_PATH"},
			DefaultValue: "/var/lib/libvirt/images/knot",
		},
		&cli.StringSliceFlag{
			Name:         "kvm-resolvers",
			Usage:        "DNS servers handed to bridged KVM virtual machines via cloud-init.",
			ConfigPath:   []string{"server.kvm.resolvers"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_KVM_RESOLVERS"},
			DefaultValue: []string{"1.1.1.1", "1.0.0.1"},
		},
		&cli.StringFlag{
			Name:       "kvm-cloud-image-path",
			Usage:      "Directory holding the node's cloud images. A bare image name in a KVM spec (e.g. ubuntu-24.04) resolves to <dir>/<name>.qcow2. Empty follows the images path (<kvm-images-path>/cloud-images).",
			ConfigPath: []string{"server.kvm.cloud_image_path"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_KVM_CLOUD_IMAGE_PATH"},
		},
		&cli.StringFlag{
			Name:       "kvm-base-image-path",
			Usage:      "Cache directory for base images downloaded from URLs in KVM specs; each file is named after the URL's last path segment and reused by later spaces. Empty follows the images path (<kvm-images-path>/base).",
			ConfigPath: []string{"server.kvm.base_image_path"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_KVM_BASE_IMAGE_PATH"},
		},

		&cli.BoolFlag{
			Name:         "mysql-enabled",
			Usage:        "Enable MySQL database backend.",
			ConfigPath:   []string{"server.mysql.enabled"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MYSQL_ENABLED"},
			DefaultValue: false,
		},
		&cli.StringFlag{
			Name:         "mysql-host",
			Usage:        "The MySQL host to connect to.",
			ConfigPath:   []string{"server.mysql.host"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MYSQL_HOST"},
			DefaultValue: "localhost",
		},
		&cli.IntFlag{
			Name:         "mysql-port",
			Usage:        "The MySQL port to connect to.",
			ConfigPath:   []string{"server.mysql.port"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MYSQL_PORT"},
			DefaultValue: 3306,
		},
		&cli.StringFlag{
			Name:         "mysql-user",
			Usage:        "The MySQL user to connect as.",
			ConfigPath:   []string{"server.mysql.user"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MYSQL_USER"},
			DefaultValue: "root",
		},
		&cli.StringFlag{
			Name:         "mysql-password",
			Usage:        "The MySQL password to use.",
			ConfigPath:   []string{"server.mysql.password"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MYSQL_PASSWORD"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "mysql-database",
			Usage:        "The MySQL database to use.",
			ConfigPath:   []string{"server.mysql.database"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MYSQL_DATABASE"},
			DefaultValue: "knot",
		},
		&cli.IntFlag{
			Name:         "mysql-connection-max-idle",
			Usage:        "The maximum number of idle connections in the connection pool.",
			ConfigPath:   []string{"server.mysql.connection_max_idle"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MYSQL_CONNECTION_MAX_IDLE"},
			DefaultValue: 10,
		},
		&cli.IntFlag{
			Name:         "mysql-connection-max-open",
			Usage:        "The maximum number of open connections to the database.",
			ConfigPath:   []string{"server.mysql.connection_max_open"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MYSQL_CONNECTION_MAX_OPEN"},
			DefaultValue: 100,
		},
		&cli.IntFlag{
			Name:         "mysql-connection-max-lifetime",
			Usage:        "The maximum amount of time in minutes a connection may be reused.",
			ConfigPath:   []string{"server.mysql.connection_max_lifetime"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MYSQL_CONNECTION_MAX_LIFETIME"},
			DefaultValue: 5,
		},

		&cli.BoolFlag{
			Name:         "postgres-enabled",
			Usage:        "Enable PostgreSQL database backend.",
			ConfigPath:   []string{"server.postgres.enabled"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_ENABLED"},
			DefaultValue: false,
		},
		&cli.StringFlag{
			Name:         "postgres-host",
			Usage:        "The PostgreSQL host to connect to.",
			ConfigPath:   []string{"server.postgres.host"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_HOST"},
			DefaultValue: "localhost",
		},
		&cli.IntFlag{
			Name:         "postgres-port",
			Usage:        "The PostgreSQL port to connect to.",
			ConfigPath:   []string{"server.postgres.port"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_PORT"},
			DefaultValue: 5432,
		},
		&cli.StringFlag{
			Name:         "postgres-user",
			Usage:        "The PostgreSQL user to connect as.",
			ConfigPath:   []string{"server.postgres.user"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_USER"},
			DefaultValue: "postgres",
		},
		&cli.StringFlag{
			Name:         "postgres-password",
			Usage:        "The PostgreSQL password to use.",
			ConfigPath:   []string{"server.postgres.password"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_PASSWORD"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "postgres-database",
			Usage:        "The PostgreSQL database to use.",
			ConfigPath:   []string{"server.postgres.database"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_DATABASE"},
			DefaultValue: "knot",
		},
		&cli.StringFlag{
			Name:         "postgres-sslmode",
			Usage:        "The PostgreSQL SSL mode: disable, allow, prefer, require, verify-ca or verify-full.",
			ConfigPath:   []string{"server.postgres.sslmode"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_SSLMODE"},
			DefaultValue: "prefer",
		},
		&cli.IntFlag{
			Name:         "postgres-connection-max-idle",
			Usage:        "The maximum number of idle connections in the connection pool.",
			ConfigPath:   []string{"server.postgres.connection_max_idle"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_CONNECTION_MAX_IDLE"},
			DefaultValue: 10,
		},
		&cli.IntFlag{
			Name:         "postgres-connection-max-open",
			Usage:        "The maximum number of open connections to the database.",
			ConfigPath:   []string{"server.postgres.connection_max_open"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_CONNECTION_MAX_OPEN"},
			DefaultValue: 100,
		},
		&cli.IntFlag{
			Name:         "postgres-connection-max-lifetime",
			Usage:        "The maximum amount of time in minutes a connection may be reused.",
			ConfigPath:   []string{"server.postgres.connection_max_lifetime"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_POSTGRES_CONNECTION_MAX_LIFETIME"},
			DefaultValue: 5,
		},

		&cli.BoolFlag{
			Name:         "badgerdb-enabled",
			Usage:        "Enable BadgerDB database backend.",
			ConfigPath:   []string{"server.badgerdb.enabled"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_BADGERDB_ENABLED"},
			DefaultValue: false,
		},
		&cli.StringFlag{
			Name:         "badgerdb-path",
			Usage:        "The path to the BadgerDB database.",
			ConfigPath:   []string{"server.badgerdb.path"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_BADGERDB_PATH"},
			DefaultValue: "./badger",
		},

		&cli.BoolFlag{
			Name:         "redis-enabled",
			Usage:        "Enable Redis database backend.",
			ConfigPath:   []string{"server.redis.enabled"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_REDIS_ENABLED"},
			DefaultValue: false,
		},
		&cli.StringSliceFlag{
			Name:         "redis-hosts",
			Usage:        "The redis server(s), can be specified multiple times.",
			ConfigPath:   []string{"server.redis.hosts"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_REDIS_HOSTS"},
			DefaultValue: []string{"localhost:6379"},
		},
		&cli.StringFlag{
			Name:         "redis-password",
			Usage:        "The password to use for the redis server.",
			ConfigPath:   []string{"server.redis.password"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_REDIS_PASSWORD"},
			DefaultValue: "",
		},
		&cli.IntFlag{
			Name:         "redis-db",
			Usage:        "The redis database to use.",
			ConfigPath:   []string{"server.redis.db"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_REDIS_DB"},
			DefaultValue: 0,
		},
		&cli.StringFlag{
			Name:         "redis-master-name",
			Usage:        "The name of the master to use for failover clients.",
			ConfigPath:   []string{"server.redis.master_name"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_REDIS_MASTER_NAME"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "redis-key-prefix",
			Usage:        "The prefix to use for all keys in the redis database.",
			ConfigPath:   []string{"server.redis.key_prefix"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_REDIS_KEY_PREFIX"},
			DefaultValue: "",
		},

		&cli.StringFlag{
			Name:         "docker-host",
			Usage:        "The Docker host to connect to.",
			ConfigPath:   []string{"server.docker.host"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_DOCKER_HOST"},
			DefaultValue: "unix:///var/run/docker.sock",
		},

		&cli.StringFlag{
			Name:         "podman-host",
			Usage:        "The Podman host to connect to.",
			ConfigPath:   []string{"server.podman.host"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_PODMAN_HOST"},
			DefaultValue: "unix:///var/run/podman.sock",
		},

		&cli.StringSliceFlag{
			Name:         "enabled-backends",
			Usage:        "Backends templates may use: manual, docker, podman, apple, nomad, kvm. Empty means all are offered (manual included). Container backends are auto-detected in the listed order.",
			ConfigPath:   []string{"server.enabled_backends"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_ENABLED_BACKENDS"},
			DefaultValue: []string{},
		},

		&cli.StringFlag{
			Name:         "base-image-registry",
			Usage:        "Default registry prefix for base images referenced by the template spec wizard. Exposed to specs as ${{ .server.base_image_registry }}.",
			ConfigPath:   []string{"server.base_image.registry_url"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_BASE_IMAGE_REGISTRY"},
			DefaultValue: "registry-1.docker.io/paularlott",
		},
		&cli.StringFlag{
			Name:       "base-images-manifest",
			Usage:      "Path to a TOML manifest of base images for the template spec wizard. Defaults to the bundled manifest.",
			ConfigPath: []string{"server.base_image.manifest"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BASE_IMAGES_MANIFEST"},
		},
		&cli.StringFlag{
			Name:       "base-image-registry-user",
			Usage:      "Username for the base image registry. Exposed to specs as ${{ .server.base_image_registry_user }}.",
			ConfigPath: []string{"server.base_image.registry_user"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BASE_IMAGE_REGISTRY_USER"},
		},
		&cli.StringFlag{
			Name:       "base-image-registry-password",
			Usage:      "Password for the base image registry. Exposed to specs as ${{ .server.base_image_registry_password }}.",
			ConfigPath: []string{"server.base_image.registry_password"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BASE_IMAGE_REGISTRY_PASSWORD"},
		},
		&cli.BoolFlag{
			Name:         "base-images-update-enabled",
			Usage:        "Master gate for fetching the base image manifest from --base-images-update-url. When set together with --base-images-update-url, the server fetches once on startup (background) and the admin refresh command is permitted; otherwise no remote fetch happens and `knot admin refresh-base-images` will fail. With --base-images-manifest set, the file is the baseline and a fetched copy overlays it only when newer.",
			ConfigPath:   []string{"server.base_image.update_enabled"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_BASE_IMAGES_UPDATE_ENABLED"},
			DefaultValue: false,
		},
		&cli.StringFlag{
			Name:       "base-images-update-url",
			Usage:      "URL to fetch the base image manifest from. No default: a fetch only happens when this is set and --base-images-update-enabled is on. For example, point it at https://raw.githubusercontent.com/paularlott/knot/main/internal/specwizard/manifest.toml to track the catalog shipped with knot releases.",
			ConfigPath: []string{"server.base_image.update_url"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_BASE_IMAGES_UPDATE_URL"},
		},

		&cli.BoolFlag{
			Name:         "mcp-enabled",
			Usage:        "Enable MCP (Model Context Protocol) server functionality.",
			ConfigPath:   []string{"server.mcp.enabled"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_MCP_ENABLED"},
			DefaultValue: false,
		},

		&cli.BoolFlag{
			Name:         "chat-enabled",
			Usage:        "Enable AI chat functionality.",
			ConfigPath:   []string{"server.chat.enabled"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_ENABLED"},
			DefaultValue: false,
		},
		&cli.BoolFlag{
			Name:         "chat-openai-endpoints",
			Usage:        "Enable OpenAI-compatible endpoints for external clients.",
			ConfigPath:   []string{"server.chat.openai_endpoints"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_OPENAI_ENDPOINTS"},
			DefaultValue: false,
		},
		&cli.StringFlag{
			Name:         "chat-provider",
			Usage:        "LLM provider for chat (openai, claude, gemini, ollama, mistral, zai, grok); base_url defaults to the provider's API. Ignored when chat-type is set.",
			ConfigPath:   []string{"server.chat.provider"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_PROVIDER"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "chat-api-key",
			Usage:        "API key for chat functionality.",
			ConfigPath:   []string{"server.chat.api_key"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_API_KEY"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "chat-base-url",
			Usage:        "Base URL for chat functionality.",
			ConfigPath:   []string{"server.chat.base_url"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_BASE_URL"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "chat-type",
			Usage:        "AI API protocol (openai, anthropic, google, ollama, xai) for the endpoint at chat-base-url. Takes precedence over chat-provider.",
			ConfigPath:   []string{"server.chat.type"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_TYPE"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "chat-openai-api-key",
			Usage:        "Deprecated: use chat-api-key instead.",
			ConfigPath:   []string{"server.chat.openai_api_key"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_OPENAI_API_KEY"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "chat-openai-base-url",
			Usage:        "Deprecated: use chat-base-url instead.",
			ConfigPath:   []string{"server.chat.openai_base_url"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_OPENAI_BASE_URL"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "chat-model",
			Usage:        "OpenAI model to use for chat.",
			ConfigPath:   []string{"server.chat.model"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_MODEL"},
			DefaultValue: "qwen2.5-coder:14b",
		},
		&cli.IntFlag{
			Name:         "chat-max-tokens",
			Usage:        "Maximum tokens for chat responses.",
			ConfigPath:   []string{"server.chat.max_tokens"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_MAX_TOKENS"},
			DefaultValue: 0,
		},
		&cli.Float32Flag{
			Name:       "chat-temperature",
			Usage:      "Temperature for chat responses (unset uses the provider's default).",
			ConfigPath: []string{"server.chat.temperature"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_CHAT_TEMPERATURE"},
		},
		&cli.StringFlag{
			Name:         "chat-system-prompt-file",
			Usage:        "Optional file path for system prompt. If not provided, uses default embedded prompt.",
			ConfigPath:   []string{"server.chat.system_prompt_file"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_SYSTEM_PROMPT_FILE"},
			DefaultValue: "",
		},
		&cli.StringFlag{
			Name:         "chat-reasoning-effort",
			Usage:        "Reasoning effort level for chat responses (low, medium, high).",
			ConfigPath:   []string{"server.chat.reasoning_effort"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_REASONING_EFFORT"},
			DefaultValue: "",
			ValidateFlag: func(c *cli.Command) error {
				value := c.GetString("chat-reasoning-effort")
				if value != "" && value != "none" && value != "low" && value != "medium" && value != "high" {
					return fmt.Errorf("--chat-reasoning-effort must be one of: none, low, medium, high (got %q)", value)
				}
				return nil
			},
		},
		&cli.StringFlag{
			Name:         "chat-ui-style",
			Usage:        "UI style for assistant button (icon or avatar).",
			ConfigPath:   []string{"server.chat.ui_style"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_CHAT_UI_STYLE"},
			DefaultValue: "icon",
			ValidateFlag: func(c *cli.Command) error {
				value := c.GetString("chat-ui-style")
				if value != "" && value != "icon" && value != "avatar" {
					return fmt.Errorf("--chat-ui-style must be one of: icon, avatar (got %q)", value)
				}
				return nil
			},
		},

		&cli.BoolFlag{
			Name:         "dns-enabled",
			Usage:        "Enable DNS server.",
			ConfigPath:   []string{"server.dns.enabled"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_DNS_ENABLED"},
			DefaultValue: false,
		},
		&cli.StringFlag{
			Name:         "dns-listen",
			Usage:        "The address and port to listen on for DNS queries.",
			ConfigPath:   []string{"server.dns.listen"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_DNS_LISTEN"},
			DefaultValue: ":3053",
		},
		&cli.StringSliceFlag{
			Name:       "dns-records",
			Usage:      "The DNS records to add, can be specified multiple times.",
			ConfigPath: []string{"server.dns.records"},
			EnvVars:    []string{config.CONFIG_ENV_PREFIX + "_DNS_RECORDS"},
		},
		&cli.IntFlag{
			Name:         "dns-default-ttl",
			Usage:        "Default TTL for records if a TTL isn't explicitly set.",
			ConfigPath:   []string{"server.dns.default_ttl"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_DNS_DEFAULT_TTL"},
			DefaultValue: 300,
		},
	},
	Commands: []*cli.Command{
		ConfigWizardCmd,
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		return RunServer(cmd, nil)
	},
}

Functions

func RunServer added in v0.33.0

func RunServer(cmd *cli.Command, quit <-chan struct{}) error

RunServer runs the knot server, blocking until a termination signal is received or the quit channel is closed (used by desktop mode to stop the server from the tray). A nil quit channel is valid and blocks forever.

func ServerFlags added in v0.33.0

func ServerFlags() []cli.Flag

ServerFlags returns the flags accepted by the server command. They are also attached to the root command so bare `knot` (desktop mode) accepts them.

Types

This section is empty.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL