feat(cli): add the prx command-line interface

This commit is contained in:
ahp
2026-10-15 14:29:19 +03:30
parent 1e9b0e9aac
commit 940c47030f
7 changed files with 1073 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: MIT
package cli
import (
"errors"
"fmt"
"os/exec"
"github.com/spf13/cobra"
"prx/internal/config"
"prx/internal/engine"
"prx/internal/proto"
"prx/internal/version"
)
func (a *app) newLogsCmd() *cobra.Command {
var follow bool
cmd := &cobra.Command{
Use: "logs",
Short: "Show proxy engine journal logs",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if _, err := exec.LookPath("journalctl"); err != nil {
return errors.New("journalctl not found; install systemd to use 'prx logs'")
}
cfg, err := a.loadConfig()
if err != nil {
return err
}
if cfg.Engine.ServiceName == "" {
return errors.New("engine.service_name is not configured; cannot read logs")
}
args := []string{"-u", cfg.Engine.ServiceName, "--no-pager"}
if follow {
args = append(args, "-f")
} else {
args = append(args, "-n", "100")
}
c := exec.CommandContext(cmd.Context(), "journalctl", args...)
c.Stdout = a.out
c.Stderr = a.errOut
if err := c.Run(); err != nil {
return fmt.Errorf("journalctl: %w (you may need to be in the systemd-journal group)", err)
}
return nil
},
}
cmd.Flags().BoolVarP(&follow, "follow", "f", false, "follow log output")
return cmd
}
func (a *app) newVersionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Print version information",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
fmt.Fprintf(a.out, "prx %s (commit %s, built %s)\n", version.Version, version.Commit, version.Date)
return nil
},
}
}
func (a *app) newInitCmd() *cobra.Command {
var (
force bool
withEngine bool
engineConfig string
)
cmd := &cobra.Command{
Use: "init",
Short: "Write a default configuration file",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if config.Exists(a.configPath) && !force {
fmt.Fprintf(a.out, "config already exists at %s (use --force to overwrite)\n", a.configPath)
} else {
if err := config.Save(a.configPath, config.Default()); err != nil {
return err
}
fmt.Fprintf(a.out, "wrote default config to %s\n", a.configPath)
}
if !withEngine {
return nil
}
cfg, err := a.loadConfig()
if err != nil {
return err
}
path := engineConfig
if path == "" {
path = cfg.Engine.ConfigPath
}
body, err := engine.Template(cfg)
if err != nil {
return err
}
return a.dispatch(cmd.Context(), cfg, proto.ActionInitEngine, func(p *proto.Plan) {
p.EngineConfig = path
p.EngineConfigBody = body
})
},
}
cmd.Flags().BoolVar(&force, "force", false, "overwrite an existing config file")
cmd.Flags().BoolVar(&withEngine, "engine-config", false, "also generate a baseline engine configuration")
cmd.Flags().StringVar(&engineConfig, "engine-config-path", "", "engine config path (defaults to engine.config_path)")
return cmd
}