Rely on loopback bind plus Host/Origin checks; open http://127.0.0.1:8787/ without a query token after SSH tunnel. Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
2.6 KiB
Go
116 lines
2.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"chat-agent-system/internal/engineeringdashboard"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
cmd := os.Args[1]
|
|
args := os.Args[2:]
|
|
switch cmd {
|
|
case "serve":
|
|
if err := runServe(args); err != nil {
|
|
fmt.Fprintf(os.Stderr, "serve: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
case "export":
|
|
if err := runExport(args); err != nil {
|
|
fmt.Fprintf(os.Stderr, "export: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
case "help", "-h", "--help":
|
|
usage()
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown command %q\n", cmd)
|
|
usage()
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintf(os.Stderr, `engineering-dashboard — local read-only personal engineering control center
|
|
|
|
Usage:
|
|
engineering-dashboard serve [--repo PATH] [--addr 127.0.0.1:PORT]
|
|
engineering-dashboard export [--repo PATH] [--output FILE]
|
|
|
|
Does not write docs, operate git/docker, deploy, or control Herdr.
|
|
`)
|
|
}
|
|
|
|
func parseFlags(args []string) (repo, addr, output string) {
|
|
addr = "127.0.0.1:8787"
|
|
output = "engineering-dashboard-snapshot.html"
|
|
for i := 0; i < len(args); i++ {
|
|
switch args[i] {
|
|
case "--repo":
|
|
i++
|
|
if i < len(args) {
|
|
repo = args[i]
|
|
}
|
|
case "--addr":
|
|
i++
|
|
if i < len(args) {
|
|
addr = args[i]
|
|
}
|
|
case "--output":
|
|
i++
|
|
if i < len(args) {
|
|
output = args[i]
|
|
}
|
|
}
|
|
}
|
|
return repo, addr, output
|
|
}
|
|
|
|
func runServe(args []string) error {
|
|
repo, addr, _ := parseFlags(args)
|
|
root, err := engineeringdashboard.ResolveRepoRoot(repo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !strings.HasPrefix(addr, "127.0.0.1:") && !strings.HasPrefix(addr, "localhost:") && !strings.HasPrefix(addr, "[::1]:") {
|
|
return fmt.Errorf("addr must be loopback (got %s)", addr)
|
|
}
|
|
c := engineeringdashboard.NewCollector(root, engineeringdashboard.ExecRunner{})
|
|
ctx := context.Background()
|
|
if _, err := c.Refresh(ctx); err != nil {
|
|
fmt.Fprintf(os.Stderr, "warning: initial refresh: %v\n", err)
|
|
}
|
|
srv, err := engineeringdashboard.NewServer(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("engineering-dashboard listening on http://%s/\n", addr)
|
|
fmt.Printf("repo=%s\n", root)
|
|
return engineeringdashboard.ListenAndServe(addr, srv.Handler())
|
|
}
|
|
|
|
func runExport(args []string) error {
|
|
repo, _, output := parseFlags(args)
|
|
root, err := engineeringdashboard.ResolveRepoRoot(repo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !filepath.IsAbs(output) {
|
|
wd, _ := os.Getwd()
|
|
output = filepath.Join(wd, output)
|
|
}
|
|
c := engineeringdashboard.NewCollector(root, engineeringdashboard.ExecRunner{})
|
|
snap, err := c.Refresh(context.Background())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return engineeringdashboard.ExportHTML(*snap, output)
|
|
}
|