main.go

135 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
package main

import (
	"fmt"
	"os"
	"runtime"
	"strings"

	"congo.gg/cmd/commands"
)

var version = "dev"

const usage = `Congo — Go framework CLI

Usage:
  congo <command> [arguments]

Commands:
  init <name>    Create a new project
  new <name>     Add an app to existing project
  dev            Run development server
  build          Build for production
  launch         Deploy infrastructure from infra.json
  status         Show deployment status
  logs           Stream logs from deployed services
  destroy        Remove a deployed server
  connect        SSH into a deployed server
  claude         Launch Claude Code with framework context
  source         Extract Congo source code
  version        Print version

Run 'congo <command> --help' for command-specific usage.
`

func main() {
	if len(os.Args) < 2 {
		fmt.Print(usage)
		os.Exit(1)
	}

	switch os.Args[1] {
	case "init":
		commands.Init()
	case "new":
		commands.New()
	case "dev":
		commands.Dev()
	case "build":
		commands.Build()
	case "launch":
		commands.Launch()
	case "status":
		commands.Status()
	case "logs":
		commands.Logs()
	case "destroy":
		commands.Destroy()
	case "connect":
		commands.Connect()
	case "claude":
		commands.Claude()
	case "source":
		commands.Source()
	case "version", "--version", "-v":
		fmt.Printf("congo %s (%s/%s, go%s)\n", version, runtime.GOOS, runtime.GOARCH, runtime.Version()[2:])
	case "help", "--help", "-h":
		if len(os.Args) > 2 && os.Args[2] != "help" && os.Args[2] != "--help" && os.Args[2] != "-h" {
			// Forward "congo help <cmd>" → "congo <cmd> --help"
			os.Args = []string{os.Args[0], os.Args[2], "--help"}
			main()
			return
		}
		fmt.Print(usage)
	default:
		fmt.Fprintf(os.Stderr, "unknown command %q", os.Args[1])
		if suggestion := closestCommand(os.Args[1]); suggestion != "" {
			fmt.Fprintf(os.Stderr, " — did you mean %q?", suggestion)
		}
		fmt.Fprintf(os.Stderr, "\n\nRun 'congo help' for usage.\n")
		os.Exit(1)
	}
}

var allCommands = []string{
	"init", "new", "dev", "build", "launch", "status",
	"logs", "destroy", "connect", "claude", "source", "version",
}

// closestCommand returns the command with the smallest edit distance
// to input, if it's close enough to be a likely typo.
func closestCommand(input string) string {
	input = strings.ToLower(input)
	best, bestDist := "", len(input)+1

	for _, cmd := range allCommands {
		// Check prefix match first (e.g., "la" → "launch").
		if strings.HasPrefix(cmd, input) {
			return cmd
		}

		d := editDistance(input, cmd)
		if d < bestDist {
			best, bestDist = cmd, d
		}
	}

	// Only suggest if edit distance is at most 2.
	if bestDist <= 2 {
		return best
	}
	return ""
}

func editDistance(a, b string) int {
	la, lb := len(a), len(b)
	dp := make([]int, lb+1)
	for j := range dp {
		dp[j] = j
	}
	for i := 1; i <= la; i++ {
		prev := dp[0]
		dp[0] = i
		for j := 1; j <= lb; j++ {
			tmp := dp[j]
			if a[i-1] == b[j-1] {
				dp[j] = prev
			} else {
				dp[j] = 1 + min(prev, dp[j-1], dp[j])
			}
			prev = tmp
		}
	}
	return dp[lb]
}