211 lines
4.5 KiB
Go
211 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Flags holds all parsed CLI flags.
|
|
type Flags struct {
|
|
JSON bool
|
|
Domain string
|
|
IssueType string // --type
|
|
Status string
|
|
Prio string
|
|
App string
|
|
Pattern string
|
|
Risk string
|
|
}
|
|
|
|
func main() {
|
|
args := os.Args[1:]
|
|
|
|
if len(args) == 0 {
|
|
printUsage()
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Parse global flags and collect remaining args
|
|
var noun, verb string
|
|
var rest []string
|
|
flags := Flags{}
|
|
|
|
// First arg is noun, second is verb, rest are flags/args
|
|
pos := 0
|
|
for pos < len(args) {
|
|
arg := args[pos]
|
|
if strings.HasPrefix(arg, "--") {
|
|
flag, val, ok := parseFlag(args, pos)
|
|
pos += flag.consumed
|
|
if !ok {
|
|
fmt.Fprintf(os.Stderr, "unknown flag: %s\n", arg)
|
|
os.Exit(1)
|
|
}
|
|
applyFlag(&flags, flag.name, val)
|
|
} else {
|
|
// Positional
|
|
if noun == "" {
|
|
noun = arg
|
|
} else if verb == "" {
|
|
verb = arg
|
|
} else {
|
|
rest = append(rest, arg)
|
|
}
|
|
pos++
|
|
}
|
|
}
|
|
|
|
// Dispatch
|
|
switch noun {
|
|
case "issue":
|
|
if verb == "" {
|
|
fmt.Fprintln(os.Stderr, "usage: dev_console issue <list|show|status|board>")
|
|
os.Exit(1)
|
|
}
|
|
cmdIssue(append([]string{verb}, rest...), flags)
|
|
case "flow":
|
|
if verb == "" {
|
|
fmt.Fprintln(os.Stderr, "usage: dev_console flow <list|show|status>")
|
|
os.Exit(1)
|
|
}
|
|
cmdFlow(append([]string{verb}, rest...), flags)
|
|
case "work":
|
|
if verb == "" {
|
|
fmt.Fprintln(os.Stderr, "usage: dev_console work <today|dashboard>")
|
|
os.Exit(1)
|
|
}
|
|
cmdWork(append([]string{verb}, rest...), flags)
|
|
case "help", "--help", "-h":
|
|
printUsage()
|
|
os.Exit(0)
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "unknown command: %s\n", noun)
|
|
printUsage()
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
type parsedFlag struct {
|
|
name string
|
|
consumed int
|
|
}
|
|
|
|
func parseFlag(args []string, pos int) (parsedFlag, string, bool) {
|
|
arg := args[pos]
|
|
// Strip leading --
|
|
name := strings.TrimPrefix(arg, "--")
|
|
|
|
// Check for --key=value form
|
|
if idx := strings.Index(name, "="); idx != -1 {
|
|
key := name[:idx]
|
|
val := name[idx+1:]
|
|
return parsedFlag{name: key, consumed: 1}, val, true
|
|
}
|
|
|
|
// Boolean flags (no value)
|
|
switch name {
|
|
case "json":
|
|
return parsedFlag{name: name, consumed: 1}, "true", true
|
|
}
|
|
|
|
// Flags that take a value
|
|
switch name {
|
|
case "domain", "type", "status", "prio", "app", "pattern", "risk", "epic", "days":
|
|
if pos+1 < len(args) && !strings.HasPrefix(args[pos+1], "--") {
|
|
return parsedFlag{name: name, consumed: 2}, args[pos+1], true
|
|
}
|
|
return parsedFlag{name: name, consumed: 1}, "", true
|
|
}
|
|
|
|
return parsedFlag{consumed: 1}, "", false
|
|
}
|
|
|
|
func applyFlag(flags *Flags, name, val string) {
|
|
switch name {
|
|
case "json":
|
|
flags.JSON = true
|
|
case "domain":
|
|
flags.Domain = val
|
|
case "type":
|
|
flags.IssueType = val
|
|
case "status":
|
|
flags.Status = val
|
|
case "prio":
|
|
flags.Prio = val
|
|
case "app":
|
|
flags.App = val
|
|
case "pattern":
|
|
flags.Pattern = val
|
|
case "risk":
|
|
flags.Risk = val
|
|
}
|
|
}
|
|
|
|
func printUsage() {
|
|
fmt.Print(`dev_console — CLI unificado para issues + flows del registry
|
|
|
|
Usage:
|
|
dev_console issue list [--domain X] [--type Y] [--status Z] [--prio P] [--json]
|
|
dev_console issue show NNNN [--json]
|
|
dev_console issue status NNNN [--json]
|
|
dev_console issue board [--json]
|
|
|
|
dev_console flow list [--app X] [--pattern P] [--risk R] [--json]
|
|
dev_console flow show NNNN [--json]
|
|
dev_console flow status NNNN [--json]
|
|
|
|
dev_console work today [--json]
|
|
dev_console work dashboard
|
|
|
|
Stubs (v2):
|
|
issue dep|roadmap|tag|done|stale|create
|
|
flow create|dod|trace|user-test|run|chain|done
|
|
work weekly|search
|
|
|
|
Env:
|
|
FN_REGISTRY_ROOT Root of the fn_registry repo (auto-detected if not set)
|
|
`)
|
|
}
|
|
|
|
// mustRegistryRoot returns the registry root or exits.
|
|
func mustRegistryRoot() string {
|
|
root, err := findRegistryRoot()
|
|
if err != nil {
|
|
fatalf("cannot find registry root: %v\nSet FN_REGISTRY_ROOT env var or run from within fn_registry.", err)
|
|
}
|
|
return root
|
|
}
|
|
|
|
// findRegistryRoot tries FN_REGISTRY_ROOT, then walks up from cwd looking for registry.db.
|
|
func findRegistryRoot() (string, error) {
|
|
if r := os.Getenv("FN_REGISTRY_ROOT"); r != "" {
|
|
return r, nil
|
|
}
|
|
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
dir := cwd
|
|
for {
|
|
if _, err := os.Stat(filepath.Join(dir, "registry.db")); err == nil {
|
|
return dir, nil
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
break
|
|
}
|
|
dir = parent
|
|
}
|
|
return "", fmt.Errorf("registry.db not found walking up from %s", cwd)
|
|
}
|
|
|
|
// fatalf prints an error to stderr and exits with code 1.
|
|
func fatalf(format string, args ...any) {
|
|
fmt.Fprintf(os.Stderr, "error: "+format+"\n", args...)
|
|
os.Exit(1)
|
|
}
|