feat(cli): implement setup wizard, fix ai examples, ci dry-run flag
Setup command now has three modes:
- Registry mode: interactive wizard to select packages
- Bootstrap mode: clones core-devops first, then wizard
- Repo setup mode: generates .core/{build,release,test}.yaml
Changes:
- setup: add interactive package selection with charmbracelet/huh
- setup: detect project type (go/php/node/wails) and generate configs
- setup: auto-detect GitHub repo from git remote
- ai: fix command examples (core dev -> core ai)
- ci: rename flag to --we-are-go-for-launch
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
996fae70f4
commit
9565122fdc
9 changed files with 944 additions and 147 deletions
|
|
@ -67,22 +67,22 @@ var (
|
||||||
|
|
||||||
// AddAgenticCommands adds the agentic task management commands to the dev command.
|
// AddAgenticCommands adds the agentic task management commands to the dev command.
|
||||||
func AddAgenticCommands(parent *clir.Command) {
|
func AddAgenticCommands(parent *clir.Command) {
|
||||||
// core dev tasks - list available tasks
|
// core ai tasks - list available tasks
|
||||||
addTasksCommand(parent)
|
addTasksCommand(parent)
|
||||||
|
|
||||||
// core dev task <id> - show task details and claim
|
// core ai task <id> - show task details and claim
|
||||||
addTaskCommand(parent)
|
addTaskCommand(parent)
|
||||||
|
|
||||||
// core dev task:update <id> - update task
|
// core ai task:update <id> - update task
|
||||||
addTaskUpdateCommand(parent)
|
addTaskUpdateCommand(parent)
|
||||||
|
|
||||||
// core dev task:complete <id> - mark task complete
|
// core ai task:complete <id> - mark task complete
|
||||||
addTaskCompleteCommand(parent)
|
addTaskCompleteCommand(parent)
|
||||||
|
|
||||||
// core dev task:commit <id> - auto-commit with task reference
|
// core ai task:commit <id> - auto-commit with task reference
|
||||||
addTaskCommitCommand(parent)
|
addTaskCommitCommand(parent)
|
||||||
|
|
||||||
// core dev task:pr <id> - create PR for task
|
// core ai task:pr <id> - create PR for task
|
||||||
addTaskPRCommand(parent)
|
addTaskPRCommand(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,9 +100,9 @@ func addTasksCommand(parent *clir.Command) {
|
||||||
" 2. .env file in current directory\n" +
|
" 2. .env file in current directory\n" +
|
||||||
" 3. ~/.core/agentic.yaml\n\n" +
|
" 3. ~/.core/agentic.yaml\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core dev tasks\n" +
|
" core ai tasks\n" +
|
||||||
" core dev tasks --status pending --priority high\n" +
|
" core ai tasks --status pending --priority high\n" +
|
||||||
" core dev tasks --labels bug,urgent")
|
" core ai tasks --labels bug,urgent")
|
||||||
|
|
||||||
cmd.StringFlag("status", "Filter by status (pending, in_progress, completed, blocked)", &status)
|
cmd.StringFlag("status", "Filter by status (pending, in_progress, completed, blocked)", &status)
|
||||||
cmd.StringFlag("priority", "Filter by priority (critical, high, medium, low)", &priority)
|
cmd.StringFlag("priority", "Filter by priority (critical, high, medium, low)", &priority)
|
||||||
|
|
@ -163,10 +163,10 @@ func addTaskCommand(parent *clir.Command) {
|
||||||
cmd := parent.NewSubCommand("task", "Show task details or auto-select a task")
|
cmd := parent.NewSubCommand("task", "Show task details or auto-select a task")
|
||||||
cmd.LongDescription("Shows details of a specific task or auto-selects the highest priority task.\n\n" +
|
cmd.LongDescription("Shows details of a specific task or auto-selects the highest priority task.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core dev task abc123 # Show task details\n" +
|
" core ai task abc123 # Show task details\n" +
|
||||||
" core dev task abc123 --claim # Show and claim the task\n" +
|
" core ai task abc123 --claim # Show and claim the task\n" +
|
||||||
" core dev task abc123 --context # Show task with gathered context\n" +
|
" core ai task abc123 --context # Show task with gathered context\n" +
|
||||||
" core dev task --auto # Auto-select highest priority pending task")
|
" core ai task --auto # Auto-select highest priority pending task")
|
||||||
|
|
||||||
cmd.BoolFlag("auto", "Auto-select highest priority pending task", &autoSelect)
|
cmd.BoolFlag("auto", "Auto-select highest priority pending task", &autoSelect)
|
||||||
cmd.BoolFlag("claim", "Claim the task after showing details", &claim)
|
cmd.BoolFlag("claim", "Claim the task after showing details", &claim)
|
||||||
|
|
@ -275,8 +275,8 @@ func addTaskUpdateCommand(parent *clir.Command) {
|
||||||
cmd := parent.NewSubCommand("task:update", "Update task status or progress")
|
cmd := parent.NewSubCommand("task:update", "Update task status or progress")
|
||||||
cmd.LongDescription("Updates a task's status, progress, or adds notes.\n\n" +
|
cmd.LongDescription("Updates a task's status, progress, or adds notes.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core dev task:update abc123 --status in_progress\n" +
|
" core ai task:update abc123 --status in_progress\n" +
|
||||||
" core dev task:update abc123 --progress 50 --notes 'Halfway done'")
|
" core ai task:update abc123 --progress 50 --notes 'Halfway done'")
|
||||||
|
|
||||||
cmd.StringFlag("status", "New status (pending, in_progress, completed, blocked)", &status)
|
cmd.StringFlag("status", "New status (pending, in_progress, completed, blocked)", &status)
|
||||||
cmd.IntFlag("progress", "Progress percentage (0-100)", &progress)
|
cmd.IntFlag("progress", "Progress percentage (0-100)", &progress)
|
||||||
|
|
@ -336,8 +336,8 @@ func addTaskCompleteCommand(parent *clir.Command) {
|
||||||
cmd := parent.NewSubCommand("task:complete", "Mark a task as completed")
|
cmd := parent.NewSubCommand("task:complete", "Mark a task as completed")
|
||||||
cmd.LongDescription("Marks a task as completed with optional output and artifacts.\n\n" +
|
cmd.LongDescription("Marks a task as completed with optional output and artifacts.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core dev task:complete abc123 --output 'Feature implemented'\n" +
|
" core ai task:complete abc123 --output 'Feature implemented'\n" +
|
||||||
" core dev task:complete abc123 --failed --error 'Build failed'")
|
" core ai task:complete abc123 --failed --error 'Build failed'")
|
||||||
|
|
||||||
cmd.StringFlag("output", "Summary of the completed work", &output)
|
cmd.StringFlag("output", "Summary of the completed work", &output)
|
||||||
cmd.BoolFlag("failed", "Mark the task as failed", &failed)
|
cmd.BoolFlag("failed", "Mark the task as failed", &failed)
|
||||||
|
|
@ -407,7 +407,7 @@ func printTaskList(tasks []agentic.Task) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf("%s\n", dimStyle.Render("Use 'core dev task <id>' to view details"))
|
fmt.Printf("%s\n", dimStyle.Render("Use 'core ai task <id>' to view details"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func printTaskDetails(task *agentic.Task) {
|
func printTaskDetails(task *agentic.Task) {
|
||||||
|
|
@ -492,9 +492,9 @@ func addTaskCommitCommand(parent *clir.Command) {
|
||||||
" Task: #123\n" +
|
" Task: #123\n" +
|
||||||
" Co-Authored-By: Claude <noreply@anthropic.com>\n\n" +
|
" Co-Authored-By: Claude <noreply@anthropic.com>\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core dev task:commit abc123 --message 'add user authentication'\n" +
|
" core ai task:commit abc123 --message 'add user authentication'\n" +
|
||||||
" core dev task:commit abc123 -m 'fix login bug' --scope auth\n" +
|
" core ai task:commit abc123 -m 'fix login bug' --scope auth\n" +
|
||||||
" core dev task:commit abc123 -m 'update docs' --push")
|
" core ai task:commit abc123 -m 'update docs' --push")
|
||||||
|
|
||||||
cmd.StringFlag("message", "Commit message (without task reference)", &message)
|
cmd.StringFlag("message", "Commit message (without task reference)", &message)
|
||||||
cmd.StringFlag("m", "Commit message (short form)", &message)
|
cmd.StringFlag("m", "Commit message (short form)", &message)
|
||||||
|
|
@ -593,10 +593,10 @@ func addTaskPRCommand(parent *clir.Command) {
|
||||||
cmd.LongDescription("Creates a GitHub pull request linked to a task.\n\n" +
|
cmd.LongDescription("Creates a GitHub pull request linked to a task.\n\n" +
|
||||||
"Requires the GitHub CLI (gh) to be installed and authenticated.\n\n" +
|
"Requires the GitHub CLI (gh) to be installed and authenticated.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core dev task:pr abc123\n" +
|
" core ai task:pr abc123\n" +
|
||||||
" core dev task:pr abc123 --title 'Add authentication feature'\n" +
|
" core ai task:pr abc123 --title 'Add authentication feature'\n" +
|
||||||
" core dev task:pr abc123 --draft --labels 'enhancement,needs-review'\n" +
|
" core ai task:pr abc123 --draft --labels 'enhancement,needs-review'\n" +
|
||||||
" core dev task:pr abc123 --base develop")
|
" core ai task:pr abc123 --base develop")
|
||||||
|
|
||||||
cmd.StringFlag("title", "PR title (defaults to task title)", &title)
|
cmd.StringFlag("title", "PR title (defaults to task title)", &title)
|
||||||
cmd.BoolFlag("draft", "Create as draft PR", &draft)
|
cmd.BoolFlag("draft", "Create as draft PR", &draft)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func AddCIReleaseCommand(app *clir.Cli) {
|
||||||
releaseCmd := app.NewSubCommand("ci", "Publish releases (dry-run by default)")
|
releaseCmd := app.NewSubCommand("ci", "Publish releases (dry-run by default)")
|
||||||
releaseCmd.LongDescription("Publishes pre-built artifacts from dist/ to configured targets.\n" +
|
releaseCmd.LongDescription("Publishes pre-built artifacts from dist/ to configured targets.\n" +
|
||||||
"Run 'core build' first to create artifacts.\n\n" +
|
"Run 'core build' first to create artifacts.\n\n" +
|
||||||
"SAFE BY DEFAULT: Runs in dry-run mode unless --were-go-for-launch is specified.\n\n" +
|
"SAFE BY DEFAULT: Runs in dry-run mode unless --we-are-go-for-launch is specified.\n\n" +
|
||||||
"Configuration: .core/release.yaml")
|
"Configuration: .core/release.yaml")
|
||||||
|
|
||||||
// Flags for the main release command
|
// Flags for the main release command
|
||||||
|
|
@ -49,7 +49,7 @@ func AddCIReleaseCommand(app *clir.Cli) {
|
||||||
var draft bool
|
var draft bool
|
||||||
var prerelease bool
|
var prerelease bool
|
||||||
|
|
||||||
releaseCmd.BoolFlag("were-go-for-launch", "Actually publish (default is dry-run for safety)", &goForLaunch)
|
releaseCmd.BoolFlag("we-are-go-for-launch", "Actually publish (default is dry-run for safety)", &goForLaunch)
|
||||||
releaseCmd.StringFlag("version", "Version to release (e.g., v1.2.3)", &version)
|
releaseCmd.StringFlag("version", "Version to release (e.g., v1.2.3)", &version)
|
||||||
releaseCmd.BoolFlag("draft", "Create release as a draft", &draft)
|
releaseCmd.BoolFlag("draft", "Create release as a draft", &draft)
|
||||||
releaseCmd.BoolFlag("prerelease", "Mark release as a prerelease", &prerelease)
|
releaseCmd.BoolFlag("prerelease", "Mark release as a prerelease", &prerelease)
|
||||||
|
|
@ -122,7 +122,7 @@ func runCIPublish(dryRun bool, version string, draft, prerelease bool) error {
|
||||||
// Print header
|
// Print header
|
||||||
fmt.Printf("%s Publishing release\n", releaseHeaderStyle.Render("CI:"))
|
fmt.Printf("%s Publishing release\n", releaseHeaderStyle.Render("CI:"))
|
||||||
if dryRun {
|
if dryRun {
|
||||||
fmt.Printf(" %s\n", releaseDimStyle.Render("(dry-run) use --were-go-for-launch to publish"))
|
fmt.Printf(" %s\n", releaseDimStyle.Render("(dry-run) use --we-are-go-for-launch to publish"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s\n", releaseSuccessStyle.Render("🚀 GO FOR LAUNCH"))
|
fmt.Printf(" %s\n", releaseSuccessStyle.Render("🚀 GO FOR LAUNCH"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,24 @@
|
||||||
// Package setup provides workspace initialisation commands.
|
// Package setup provides workspace bootstrap and package cloning commands.
|
||||||
//
|
//
|
||||||
// Clones all repositories defined in repos.yaml into the workspace.
|
// Two modes of operation:
|
||||||
// Skips repos that already exist. Supports filtering by type.
|
//
|
||||||
|
// REGISTRY MODE (repos.yaml exists):
|
||||||
|
// - Clones all repositories defined in repos.yaml into packages/
|
||||||
|
// - Skips repos that already exist
|
||||||
|
// - Supports filtering by type with --only
|
||||||
|
//
|
||||||
|
// BOOTSTRAP MODE (no repos.yaml):
|
||||||
|
// - Clones core-devops to set up the workspace foundation
|
||||||
|
// - Presents an interactive wizard to select packages (unless --all)
|
||||||
|
// - Clones selected packages
|
||||||
//
|
//
|
||||||
// Flags:
|
// Flags:
|
||||||
// - --registry: Path to repos.yaml (auto-detected if not specified)
|
// - --registry: Path to repos.yaml (auto-detected if not specified)
|
||||||
// - --only: Filter by repo type (foundation, module, product)
|
// - --only: Filter by repo type (foundation, module, product)
|
||||||
// - --dry-run: Preview what would be cloned
|
// - --dry-run: Preview what would be cloned
|
||||||
|
// - --all: Skip wizard, clone all packages (non-interactive)
|
||||||
|
// - --name: Project directory name for bootstrap mode
|
||||||
|
// - --build: Run build after cloning
|
||||||
//
|
//
|
||||||
// Uses gh CLI with HTTPS when authenticated, falls back to SSH.
|
// Uses gh CLI with HTTPS when authenticated, falls back to SSH.
|
||||||
package setup
|
package setup
|
||||||
|
|
|
||||||
|
|
@ -22,48 +22,178 @@ var (
|
||||||
dimStyle = shared.DimStyle
|
dimStyle = shared.DimStyle
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Default organization and devops repo for bootstrap
|
||||||
|
const (
|
||||||
|
defaultOrg = "host-uk"
|
||||||
|
devopsRepo = "core-devops"
|
||||||
|
devopsReposYaml = "repos.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
// AddSetupCommand adds the 'setup' command to the given parent command.
|
// AddSetupCommand adds the 'setup' command to the given parent command.
|
||||||
func AddSetupCommand(parent *clir.Cli) {
|
func AddSetupCommand(parent *clir.Cli) {
|
||||||
var registryPath string
|
var registryPath string
|
||||||
var only string
|
var only string
|
||||||
var dryRun bool
|
var dryRun bool
|
||||||
|
var all bool
|
||||||
|
var name string
|
||||||
|
var build bool
|
||||||
|
|
||||||
setupCmd := parent.NewSubCommand("setup", "Clone all repos from registry")
|
setupCmd := parent.NewSubCommand("setup", "Bootstrap workspace or clone packages from registry")
|
||||||
setupCmd.LongDescription("Clones all repositories defined in repos.yaml into packages/.\n" +
|
setupCmd.LongDescription("Sets up a development workspace.\n\n" +
|
||||||
"Skips repos that already exist. Use --only to filter by type.")
|
"REGISTRY MODE (repos.yaml exists):\n" +
|
||||||
|
" Clones all repositories defined in repos.yaml into packages/.\n" +
|
||||||
|
" Skips repos that already exist. Use --only to filter by type.\n\n" +
|
||||||
|
"BOOTSTRAP MODE (no repos.yaml):\n" +
|
||||||
|
" 1. Clones core-devops to set up the workspace\n" +
|
||||||
|
" 2. Presents an interactive wizard to select packages\n" +
|
||||||
|
" 3. Clones selected packages\n\n" +
|
||||||
|
"Use --all to skip the wizard and clone everything.")
|
||||||
|
|
||||||
setupCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
setupCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
||||||
setupCmd.StringFlag("only", "Only clone repos of these types (comma-separated: foundation,module,product)", &only)
|
setupCmd.StringFlag("only", "Only clone repos of these types (comma-separated: foundation,module,product)", &only)
|
||||||
setupCmd.BoolFlag("dry-run", "Show what would be cloned without cloning", &dryRun)
|
setupCmd.BoolFlag("dry-run", "Show what would be cloned without cloning", &dryRun)
|
||||||
|
setupCmd.BoolFlag("all", "Skip wizard, clone all packages (non-interactive)", &all)
|
||||||
|
setupCmd.StringFlag("name", "Project directory name for bootstrap mode", &name)
|
||||||
|
setupCmd.BoolFlag("build", "Run build after cloning", &build)
|
||||||
|
|
||||||
setupCmd.Action(func() error {
|
setupCmd.Action(func() error {
|
||||||
return runSetup(registryPath, only, dryRun)
|
return runSetupOrchestrator(registryPath, only, dryRun, all, name, build)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSetup(registryPath, only string, dryRun bool) error {
|
// runSetupOrchestrator decides between registry mode and bootstrap mode.
|
||||||
|
func runSetupOrchestrator(registryPath, only string, dryRun, all bool, projectName string, runBuild bool) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Find registry
|
// Try to find an existing registry
|
||||||
var reg *repos.Registry
|
var foundRegistry string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
foundRegistry = registryPath
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
foundRegistry, err = repos.FindRegistry()
|
||||||
if err != nil {
|
}
|
||||||
return fmt.Errorf("no repos.yaml found - run this from a workspace directory")
|
|
||||||
|
// If registry exists, use registry mode
|
||||||
|
if err == nil && foundRegistry != "" {
|
||||||
|
return runRegistrySetup(ctx, foundRegistry, only, dryRun, all, runBuild)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No registry found - enter bootstrap mode
|
||||||
|
return runBootstrap(ctx, only, dryRun, all, projectName, runBuild)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runBootstrap handles the case where no repos.yaml exists.
|
||||||
|
func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectName string, runBuild bool) error {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s Bootstrap mode (no repos.yaml found)\n", dimStyle.Render(">>"))
|
||||||
|
|
||||||
|
var targetDir string
|
||||||
|
|
||||||
|
// Check if current directory is empty
|
||||||
|
empty, err := isDirEmpty(cwd)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if empty {
|
||||||
|
// Clone into current directory
|
||||||
|
targetDir = cwd
|
||||||
|
fmt.Printf("%s Cloning into current directory\n", dimStyle.Render(">>"))
|
||||||
|
} else {
|
||||||
|
// Directory has content - check if it's a git repo root
|
||||||
|
isRepo := isGitRepoRoot(cwd)
|
||||||
|
|
||||||
|
if isRepo && isTerminal() && !all {
|
||||||
|
// Offer choice: setup working directory or create package
|
||||||
|
choice, err := promptSetupChoice()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get choice: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if choice == "setup" {
|
||||||
|
// Setup this working directory with .core/ config
|
||||||
|
return runRepoSetup(cwd, dryRun)
|
||||||
|
}
|
||||||
|
// Otherwise continue to "create package" flow
|
||||||
}
|
}
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
|
||||||
if err != nil {
|
// Create package flow - need a project name
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
if projectName == "" {
|
||||||
|
if !isTerminal() || all {
|
||||||
|
projectName = defaultOrg
|
||||||
|
} else {
|
||||||
|
projectName, err = promptProjectName(defaultOrg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get project name: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetDir = filepath.Join(cwd, projectName)
|
||||||
|
fmt.Printf("%s Creating project directory: %s\n", dimStyle.Render(">>"), projectName)
|
||||||
|
|
||||||
|
if !dryRun {
|
||||||
|
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone core-devops first
|
||||||
|
devopsPath := filepath.Join(targetDir, devopsRepo)
|
||||||
|
if _, err := os.Stat(filepath.Join(devopsPath, ".git")); os.IsNotExist(err) {
|
||||||
|
fmt.Printf("%s Cloning %s...\n", dimStyle.Render(">>"), devopsRepo)
|
||||||
|
|
||||||
|
if !dryRun {
|
||||||
|
if err := gitClone(ctx, defaultOrg, devopsRepo, devopsPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to clone %s: %w", devopsRepo, err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s %s cloned\n", successStyle.Render(">>"), devopsRepo)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Would clone %s/%s to %s\n", defaultOrg, devopsRepo, devopsPath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s %s already exists\n", dimStyle.Render(">>"), devopsRepo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the repos.yaml from core-devops
|
||||||
|
registryPath := filepath.Join(devopsPath, devopsReposYaml)
|
||||||
|
|
||||||
|
if dryRun {
|
||||||
|
fmt.Printf("\n%s Would load registry from %s and present package wizard\n", dimStyle.Render(">>"), registryPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reg, err := repos.LoadRegistry(registryPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load registry from %s: %w", devopsRepo, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override base path to target directory
|
||||||
|
reg.BasePath = targetDir
|
||||||
|
|
||||||
|
// Now run the regular setup with the loaded registry
|
||||||
|
return runRegistrySetupWithReg(ctx, reg, registryPath, only, dryRun, all, runBuild)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runRegistrySetup loads a registry from path and runs setup.
|
||||||
|
func runRegistrySetup(ctx context.Context, registryPath, only string, dryRun, all, runBuild bool) error {
|
||||||
|
reg, err := repos.LoadRegistry(registryPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load registry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return runRegistrySetupWithReg(ctx, reg, registryPath, only, dryRun, all, runBuild)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runRegistrySetupWithReg runs setup with an already-loaded registry.
|
||||||
|
func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryPath, only string, dryRun, all, runBuild bool) error {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render("Registry:"), registryPath)
|
fmt.Printf("%s %s\n", dimStyle.Render("Registry:"), registryPath)
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render("Org:"), reg.Org)
|
fmt.Printf("%s %s\n", dimStyle.Render("Org:"), reg.Org)
|
||||||
|
|
||||||
|
|
@ -85,11 +215,10 @@ func runSetup(registryPath, only string, dryRun bool) error {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render("Target:"), basePath)
|
fmt.Printf("%s %s\n", dimStyle.Render("Target:"), basePath)
|
||||||
|
|
||||||
// Parse type filter
|
// Parse type filter
|
||||||
var typeFilter map[string]bool
|
var typeFilter []string
|
||||||
if only != "" {
|
if only != "" {
|
||||||
typeFilter = make(map[string]bool)
|
|
||||||
for _, t := range strings.Split(only, ",") {
|
for _, t := range strings.Split(only, ",") {
|
||||||
typeFilter[strings.TrimSpace(t)] = true
|
typeFilter = append(typeFilter, strings.TrimSpace(t))
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render("Filter:"), only)
|
fmt.Printf("%s %s\n", dimStyle.Render("Filter:"), only)
|
||||||
}
|
}
|
||||||
|
|
@ -101,32 +230,73 @@ func runSetup(registryPath, only string, dryRun bool) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get repos to clone
|
// Get all available repos
|
||||||
allRepos := reg.List()
|
allRepos := reg.List()
|
||||||
|
|
||||||
|
// Determine which repos to clone
|
||||||
var toClone []*repos.Repo
|
var toClone []*repos.Repo
|
||||||
var skipped, exists int
|
var skipped, exists int
|
||||||
|
|
||||||
for _, repo := range allRepos {
|
// Use wizard in interactive mode, unless --all specified
|
||||||
// Skip if type filter doesn't match
|
useWizard := isTerminal() && !all && !dryRun
|
||||||
if typeFilter != nil && !typeFilter[repo.Type] {
|
|
||||||
skipped++
|
if useWizard {
|
||||||
continue
|
selected, err := runPackageWizard(reg, typeFilter)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("wizard error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if clone: false
|
// Build set of selected repos
|
||||||
if repo.Clone != nil && !*repo.Clone {
|
selectedSet := make(map[string]bool)
|
||||||
skipped++
|
for _, name := range selected {
|
||||||
continue
|
selectedSet[name] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if already exists
|
// Filter repos based on selection
|
||||||
repoPath := filepath.Join(basePath, repo.Name)
|
for _, repo := range allRepos {
|
||||||
if _, err := os.Stat(filepath.Join(repoPath, ".git")); err == nil {
|
if !selectedSet[repo.Name] {
|
||||||
exists++
|
skipped++
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already exists
|
||||||
|
repoPath := filepath.Join(basePath, repo.Name)
|
||||||
|
if _, err := os.Stat(filepath.Join(repoPath, ".git")); err == nil {
|
||||||
|
exists++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
toClone = append(toClone, repo)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Non-interactive: filter by type
|
||||||
|
typeFilterSet := make(map[string]bool)
|
||||||
|
for _, t := range typeFilter {
|
||||||
|
typeFilterSet[t] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
toClone = append(toClone, repo)
|
for _, repo := range allRepos {
|
||||||
|
// Skip if type filter doesn't match (when filter is specified)
|
||||||
|
if len(typeFilterSet) > 0 && !typeFilterSet[repo.Type] {
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if clone: false
|
||||||
|
if repo.Clone != nil && !*repo.Clone {
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already exists
|
||||||
|
repoPath := filepath.Join(basePath, repo.Name)
|
||||||
|
if _, err := os.Stat(filepath.Join(repoPath, ".git")); err == nil {
|
||||||
|
exists++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
toClone = append(toClone, repo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
|
|
@ -146,6 +316,18 @@ func runSetup(registryPath, only string, dryRun bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Confirm in interactive mode
|
||||||
|
if useWizard {
|
||||||
|
confirmed, err := confirmClone(len(toClone), basePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !confirmed {
|
||||||
|
fmt.Println("Cancelled.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clone repos
|
// Clone repos
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
var succeeded, failed int
|
var succeeded, failed int
|
||||||
|
|
@ -157,10 +339,10 @@ func runSetup(registryPath, only string, dryRun bool) error {
|
||||||
|
|
||||||
err := gitClone(ctx, reg.Org, repo.Name, repoPath)
|
err := gitClone(ctx, reg.Org, repo.Name, repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s\n", errorStyle.Render("✗ "+err.Error()))
|
fmt.Printf("%s\n", errorStyle.Render("x "+err.Error()))
|
||||||
failed++
|
failed++
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s\n", successStyle.Render("✓"))
|
fmt.Printf("%s\n", successStyle.Render("done"))
|
||||||
succeeded++
|
succeeded++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -176,9 +358,322 @@ func runSetup(registryPath, only string, dryRun bool) error {
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
|
// Run build if requested
|
||||||
|
if runBuild && succeeded > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("%s Running build...\n", dimStyle.Render(">>"))
|
||||||
|
buildCmd := exec.Command("core", "build")
|
||||||
|
buildCmd.Dir = basePath
|
||||||
|
buildCmd.Stdout = os.Stdout
|
||||||
|
buildCmd.Stderr = os.Stderr
|
||||||
|
if err := buildCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("build failed: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isGitRepoRoot returns true if the directory is a git repository root.
|
||||||
|
func isGitRepoRoot(path string) bool {
|
||||||
|
_, err := os.Stat(filepath.Join(path, ".git"))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runRepoSetup sets up the current repository with .core/ configuration.
|
||||||
|
func runRepoSetup(repoPath string, dryRun bool) error {
|
||||||
|
fmt.Printf("%s Setting up repository: %s\n", dimStyle.Render(">>"), repoPath)
|
||||||
|
|
||||||
|
// Detect project type
|
||||||
|
projectType := detectProjectType(repoPath)
|
||||||
|
fmt.Printf("%s Detected project type: %s\n", dimStyle.Render(">>"), projectType)
|
||||||
|
|
||||||
|
// Create .core directory
|
||||||
|
coreDir := filepath.Join(repoPath, ".core")
|
||||||
|
if !dryRun {
|
||||||
|
if err := os.MkdirAll(coreDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create .core directory: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate configs based on project type
|
||||||
|
name := filepath.Base(repoPath)
|
||||||
|
configs := map[string]string{
|
||||||
|
"build.yaml": generateBuildConfig(repoPath, projectType),
|
||||||
|
"release.yaml": generateReleaseConfig(name, projectType),
|
||||||
|
"test.yaml": generateTestConfig(projectType),
|
||||||
|
}
|
||||||
|
|
||||||
|
if dryRun {
|
||||||
|
fmt.Printf("\n%s Would create:\n", dimStyle.Render(">>"))
|
||||||
|
for filename, content := range configs {
|
||||||
|
fmt.Printf("\n %s:\n", filepath.Join(coreDir, filename))
|
||||||
|
// Indent content for display
|
||||||
|
for _, line := range strings.Split(content, "\n") {
|
||||||
|
fmt.Printf(" %s\n", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for filename, content := range configs {
|
||||||
|
configPath := filepath.Join(coreDir, filename)
|
||||||
|
if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s Created %s\n", successStyle.Render(">>"), configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectProjectType identifies the project type from files present.
|
||||||
|
func detectProjectType(path string) string {
|
||||||
|
// Check in priority order
|
||||||
|
if _, err := os.Stat(filepath.Join(path, "wails.json")); err == nil {
|
||||||
|
return "wails"
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(path, "go.mod")); err == nil {
|
||||||
|
return "go"
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(path, "composer.json")); err == nil {
|
||||||
|
return "php"
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(path, "package.json")); err == nil {
|
||||||
|
return "node"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateBuildConfig creates a build.yaml configuration based on project type.
|
||||||
|
func generateBuildConfig(path, projectType string) string {
|
||||||
|
name := filepath.Base(path)
|
||||||
|
|
||||||
|
switch projectType {
|
||||||
|
case "go", "wails":
|
||||||
|
return fmt.Sprintf(`version: 1
|
||||||
|
project:
|
||||||
|
name: %s
|
||||||
|
description: Go application
|
||||||
|
main: ./cmd/%s
|
||||||
|
binary: %s
|
||||||
|
build:
|
||||||
|
cgo: false
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
ldflags:
|
||||||
|
- -s
|
||||||
|
- -w
|
||||||
|
targets:
|
||||||
|
- os: linux
|
||||||
|
arch: amd64
|
||||||
|
- os: linux
|
||||||
|
arch: arm64
|
||||||
|
- os: darwin
|
||||||
|
arch: amd64
|
||||||
|
- os: darwin
|
||||||
|
arch: arm64
|
||||||
|
- os: windows
|
||||||
|
arch: amd64
|
||||||
|
`, name, name, name)
|
||||||
|
|
||||||
|
case "php":
|
||||||
|
return fmt.Sprintf(`version: 1
|
||||||
|
project:
|
||||||
|
name: %s
|
||||||
|
description: PHP application
|
||||||
|
type: php
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: %s
|
||||||
|
`, name, name)
|
||||||
|
|
||||||
|
case "node":
|
||||||
|
return fmt.Sprintf(`version: 1
|
||||||
|
project:
|
||||||
|
name: %s
|
||||||
|
description: Node.js application
|
||||||
|
type: node
|
||||||
|
build:
|
||||||
|
script: npm run build
|
||||||
|
output: dist
|
||||||
|
`, name)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf(`version: 1
|
||||||
|
project:
|
||||||
|
name: %s
|
||||||
|
description: Application
|
||||||
|
`, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateReleaseConfig creates a release.yaml configuration.
|
||||||
|
func generateReleaseConfig(name, projectType string) string {
|
||||||
|
// Try to detect GitHub repo from git remote
|
||||||
|
repo := detectGitHubRepo()
|
||||||
|
if repo == "" {
|
||||||
|
repo = "owner/" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
base := fmt.Sprintf(`version: 1
|
||||||
|
project:
|
||||||
|
name: %s
|
||||||
|
repository: %s
|
||||||
|
`, name, repo)
|
||||||
|
|
||||||
|
switch projectType {
|
||||||
|
case "go", "wails":
|
||||||
|
return base + `
|
||||||
|
changelog:
|
||||||
|
include:
|
||||||
|
- feat
|
||||||
|
- fix
|
||||||
|
- perf
|
||||||
|
- refactor
|
||||||
|
exclude:
|
||||||
|
- chore
|
||||||
|
- docs
|
||||||
|
- style
|
||||||
|
- test
|
||||||
|
|
||||||
|
publishers:
|
||||||
|
- type: github
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
`
|
||||||
|
case "php":
|
||||||
|
return base + `
|
||||||
|
changelog:
|
||||||
|
include:
|
||||||
|
- feat
|
||||||
|
- fix
|
||||||
|
- perf
|
||||||
|
|
||||||
|
publishers:
|
||||||
|
- type: github
|
||||||
|
draft: false
|
||||||
|
`
|
||||||
|
default:
|
||||||
|
return base + `
|
||||||
|
changelog:
|
||||||
|
include:
|
||||||
|
- feat
|
||||||
|
- fix
|
||||||
|
|
||||||
|
publishers:
|
||||||
|
- type: github
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateTestConfig creates a test.yaml configuration.
|
||||||
|
func generateTestConfig(projectType string) string {
|
||||||
|
switch projectType {
|
||||||
|
case "go", "wails":
|
||||||
|
return `version: 1
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- name: unit
|
||||||
|
run: go test ./...
|
||||||
|
- name: coverage
|
||||||
|
run: go test -coverprofile=coverage.out ./...
|
||||||
|
- name: race
|
||||||
|
run: go test -race ./...
|
||||||
|
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
`
|
||||||
|
case "php":
|
||||||
|
return `version: 1
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- name: unit
|
||||||
|
run: vendor/bin/pest --parallel
|
||||||
|
- name: types
|
||||||
|
run: vendor/bin/phpstan analyse
|
||||||
|
- name: lint
|
||||||
|
run: vendor/bin/pint --test
|
||||||
|
|
||||||
|
env:
|
||||||
|
APP_ENV: testing
|
||||||
|
DB_CONNECTION: sqlite
|
||||||
|
`
|
||||||
|
case "node":
|
||||||
|
return `version: 1
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- name: unit
|
||||||
|
run: npm test
|
||||||
|
- name: lint
|
||||||
|
run: npm run lint
|
||||||
|
- name: typecheck
|
||||||
|
run: npm run typecheck
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODE_ENV: test
|
||||||
|
`
|
||||||
|
default:
|
||||||
|
return `version: 1
|
||||||
|
|
||||||
|
commands:
|
||||||
|
- name: test
|
||||||
|
run: echo "No tests configured"
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectGitHubRepo tries to extract owner/repo from git remote.
|
||||||
|
func detectGitHubRepo() string {
|
||||||
|
cmd := exec.Command("git", "remote", "get-url", "origin")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
url := strings.TrimSpace(string(output))
|
||||||
|
|
||||||
|
// Handle SSH format: git@github.com:owner/repo.git
|
||||||
|
if strings.HasPrefix(url, "git@github.com:") {
|
||||||
|
repo := strings.TrimPrefix(url, "git@github.com:")
|
||||||
|
repo = strings.TrimSuffix(repo, ".git")
|
||||||
|
return repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle HTTPS format: https://github.com/owner/repo.git
|
||||||
|
if strings.Contains(url, "github.com/") {
|
||||||
|
parts := strings.Split(url, "github.com/")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
repo := strings.TrimSuffix(parts[1], ".git")
|
||||||
|
return repo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDirEmpty returns true if the directory is empty or contains only hidden files.
|
||||||
|
func isDirEmpty(path string) (bool, error) {
|
||||||
|
entries, err := os.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
name := e.Name()
|
||||||
|
// Ignore common hidden/metadata files
|
||||||
|
if name == ".DS_Store" || name == ".git" || name == ".gitignore" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Any other non-hidden file means directory is not empty
|
||||||
|
if !strings.HasPrefix(name, ".") {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func gitClone(ctx context.Context, org, repo, path string) error {
|
func gitClone(ctx context.Context, org, repo, path string) error {
|
||||||
// Try gh clone first with HTTPS (works without SSH keys)
|
// Try gh clone first with HTTPS (works without SSH keys)
|
||||||
if ghAuthenticated() {
|
if ghAuthenticated() {
|
||||||
|
|
|
||||||
221
cmd/setup/setup_wizard.go
Normal file
221
cmd/setup/setup_wizard.go
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
// setup_wizard.go implements the interactive package selection wizard.
|
||||||
|
//
|
||||||
|
// Uses charmbracelet/huh for a rich terminal UI with multi-select checkboxes.
|
||||||
|
// Falls back to non-interactive mode when not in a TTY or --all is specified.
|
||||||
|
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/host-uk/core/pkg/repos"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// wizardTheme returns a Dracula-inspired theme matching our CLI styling.
|
||||||
|
func wizardTheme() *huh.Theme {
|
||||||
|
t := huh.ThemeDracula()
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTerminal returns true if stdin is a terminal.
|
||||||
|
func isTerminal() bool {
|
||||||
|
return term.IsTerminal(int(os.Stdin.Fd()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptSetupChoice asks the user whether to setup the working directory or create a package.
|
||||||
|
func promptSetupChoice() (string, error) {
|
||||||
|
var choice string
|
||||||
|
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewSelect[string]().
|
||||||
|
Title("This directory is a git repository").
|
||||||
|
Description("What would you like to do?").
|
||||||
|
Options(
|
||||||
|
huh.NewOption("Setup Working Directory", "setup").Selected(true),
|
||||||
|
huh.NewOption("Create Package (clone repos into subdirectory)", "package"),
|
||||||
|
).
|
||||||
|
Value(&choice),
|
||||||
|
),
|
||||||
|
).WithTheme(wizardTheme())
|
||||||
|
|
||||||
|
if err := form.Run(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return choice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptProjectName asks the user for a project directory name.
|
||||||
|
func promptProjectName(defaultName string) (string, error) {
|
||||||
|
var name string
|
||||||
|
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewInput().
|
||||||
|
Title("Project directory name").
|
||||||
|
Description("Enter the name for your new workspace directory").
|
||||||
|
Placeholder(defaultName).
|
||||||
|
Value(&name),
|
||||||
|
),
|
||||||
|
).WithTheme(wizardTheme())
|
||||||
|
|
||||||
|
if err := form.Run(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return defaultName, nil
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// groupPackagesByType organizes repos by their type for display.
|
||||||
|
func groupPackagesByType(reposList []*repos.Repo) map[string][]*repos.Repo {
|
||||||
|
groups := make(map[string][]*repos.Repo)
|
||||||
|
|
||||||
|
for _, repo := range reposList {
|
||||||
|
repoType := repo.Type
|
||||||
|
if repoType == "" {
|
||||||
|
repoType = "other"
|
||||||
|
}
|
||||||
|
groups[repoType] = append(groups[repoType], repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort within each group
|
||||||
|
for _, group := range groups {
|
||||||
|
sort.Slice(group, func(i, j int) bool {
|
||||||
|
return group[i].Name < group[j].Name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
// packageOption represents a selectable package in the wizard.
|
||||||
|
type packageOption struct {
|
||||||
|
repo *repos.Repo
|
||||||
|
selected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// runPackageWizard presents an interactive multi-select UI for package selection.
|
||||||
|
// Returns the list of selected repo names.
|
||||||
|
func runPackageWizard(reg *repos.Registry, preselectedTypes []string) ([]string, error) {
|
||||||
|
allRepos := reg.List()
|
||||||
|
|
||||||
|
// Build preselection set
|
||||||
|
preselect := make(map[string]bool)
|
||||||
|
for _, t := range preselectedTypes {
|
||||||
|
preselect[strings.TrimSpace(t)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group repos by type for organized display
|
||||||
|
groups := groupPackagesByType(allRepos)
|
||||||
|
|
||||||
|
// Build options with preselection
|
||||||
|
var options []huh.Option[string]
|
||||||
|
typeOrder := []string{"foundation", "module", "product", "template", "other"}
|
||||||
|
|
||||||
|
for _, typeKey := range typeOrder {
|
||||||
|
group, ok := groups[typeKey]
|
||||||
|
if !ok || len(group) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add type header as a visual separator (empty option)
|
||||||
|
typeLabel := strings.ToUpper(typeKey)
|
||||||
|
options = append(options, huh.NewOption[string](
|
||||||
|
fmt.Sprintf("── %s ──", typeLabel),
|
||||||
|
"",
|
||||||
|
).Selected(false))
|
||||||
|
|
||||||
|
for _, repo := range group {
|
||||||
|
// Skip if clone: false
|
||||||
|
if repo.Clone != nil && !*repo.Clone {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
label := repo.Name
|
||||||
|
if repo.Description != "" {
|
||||||
|
label = fmt.Sprintf("%s - %s", repo.Name, truncateDesc(repo.Description, 40))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preselect based on type filter or select all if no filter
|
||||||
|
selected := len(preselect) == 0 || preselect[repo.Type]
|
||||||
|
|
||||||
|
options = append(options, huh.NewOption[string](label, repo.Name).Selected(selected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected []string
|
||||||
|
|
||||||
|
// Header styling
|
||||||
|
headerStyle := lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Foreground(lipgloss.Color("#3b82f6")).
|
||||||
|
MarginBottom(1)
|
||||||
|
|
||||||
|
fmt.Println(headerStyle.Render("Package Selection"))
|
||||||
|
fmt.Println("Use space to select/deselect, enter to confirm")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewMultiSelect[string]().
|
||||||
|
Title("Select packages to clone").
|
||||||
|
Options(options...).
|
||||||
|
Value(&selected).
|
||||||
|
Filterable(true).
|
||||||
|
Height(20),
|
||||||
|
),
|
||||||
|
).WithTheme(wizardTheme())
|
||||||
|
|
||||||
|
if err := form.Run(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out empty values (type headers)
|
||||||
|
var result []string
|
||||||
|
for _, name := range selected {
|
||||||
|
if name != "" {
|
||||||
|
result = append(result, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirmClone asks for confirmation before cloning.
|
||||||
|
func confirmClone(count int, target string) (bool, error) {
|
||||||
|
var confirmed bool
|
||||||
|
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewConfirm().
|
||||||
|
Title(fmt.Sprintf("Clone %d packages to %s?", count, target)).
|
||||||
|
Affirmative("Yes, clone").
|
||||||
|
Negative("Cancel").
|
||||||
|
Value(&confirmed),
|
||||||
|
),
|
||||||
|
).WithTheme(wizardTheme())
|
||||||
|
|
||||||
|
if err := form.Run(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return confirmed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncateDesc truncates a description to max length with ellipsis.
|
||||||
|
func truncateDesc(s string, max int) string {
|
||||||
|
if len(s) <= max {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[:max-3] + "..."
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Publish releases to GitHub, Docker, npm, Homebrew, and more.
|
Publish releases to GitHub, Docker, npm, Homebrew, and more.
|
||||||
|
|
||||||
**Safety:** Dry-run by default. Use `--were-go-for-launch` to actually publish.
|
**Safety:** Dry-run by default. Use `--we-are-go-for-launch` to actually publish.
|
||||||
|
|
||||||
## Subcommands
|
## Subcommands
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ core ci [flags]
|
||||||
|
|
||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `--were-go-for-launch` | Actually publish (default is dry-run) |
|
| `--we-are-go-for-launch` | Actually publish (default is dry-run) |
|
||||||
| `--version` | Override version |
|
| `--version` | Override version |
|
||||||
| `--draft` | Create as draft release |
|
| `--draft` | Create as draft release |
|
||||||
| `--prerelease` | Mark as prerelease |
|
| `--prerelease` | Mark as prerelease |
|
||||||
|
|
@ -34,13 +34,13 @@ core ci [flags]
|
||||||
core ci
|
core ci
|
||||||
|
|
||||||
# Actually publish
|
# Actually publish
|
||||||
core ci --were-go-for-launch
|
core ci --we-are-go-for-launch
|
||||||
|
|
||||||
# Publish as draft
|
# Publish as draft
|
||||||
core ci --were-go-for-launch --draft
|
core ci --we-are-go-for-launch --draft
|
||||||
|
|
||||||
# Publish as prerelease
|
# Publish as prerelease
|
||||||
core ci --were-go-for-launch --prerelease
|
core ci --we-are-go-for-launch --prerelease
|
||||||
```
|
```
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
@ -56,7 +56,7 @@ core build sdk
|
||||||
core ci
|
core ci
|
||||||
|
|
||||||
# Step 3: Publish (explicit flag required)
|
# Step 3: Publish (explicit flag required)
|
||||||
core ci --were-go-for-launch
|
core ci --we-are-go-for-launch
|
||||||
```
|
```
|
||||||
|
|
||||||
## Publishers
|
## Publishers
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ The `setup` command operates in three modes:
|
||||||
|
|
||||||
1. **Registry mode** - When `repos.yaml` exists nearby, clones repositories into packages/
|
1. **Registry mode** - When `repos.yaml` exists nearby, clones repositories into packages/
|
||||||
2. **Bootstrap mode** - When no registry exists, clones `core-devops` first, then presents an interactive wizard to select packages
|
2. **Bootstrap mode** - When no registry exists, clones `core-devops` first, then presents an interactive wizard to select packages
|
||||||
3. **Repo setup mode** - When run in a git repository root, offers to create `.core/build.yaml` configuration
|
3. **Repo setup mode** - When run in a git repo root, offers to create `.core/build.yaml` configuration
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
@ -21,15 +21,15 @@ core setup [flags]
|
||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `--registry` | Path to repos.yaml (auto-detected if not specified) |
|
| `--registry` | Path to repos.yaml (auto-detected if not specified) |
|
||||||
| `--dry-run` | Show what would happen without making changes |
|
| `--dry-run` | Show what would be cloned without cloning |
|
||||||
| `--only` | Only clone repos of these types (comma-separated: foundation,module,product) |
|
| `--only` | Only clone repos of these types (comma-separated: foundation,module,product) |
|
||||||
| `--all` | Skip wizard, clone all packages (non-interactive) |
|
| `--all` | Skip wizard, clone all packages (non-interactive) |
|
||||||
| `--name` | Project directory name for bootstrap mode |
|
| `--name` | Project directory name for bootstrap mode |
|
||||||
| `--build` | Run build after cloning |
|
| `--build` | Run build after cloning |
|
||||||
|
|
||||||
## Modes
|
---
|
||||||
|
|
||||||
### Registry Mode
|
## Registry Mode
|
||||||
|
|
||||||
When `repos.yaml` is found nearby (current directory or parents), setup clones all defined repositories:
|
When `repos.yaml` is found nearby (current directory or parents), setup clones all defined repositories:
|
||||||
|
|
||||||
|
|
@ -42,9 +42,16 @@ core setup --dry-run
|
||||||
|
|
||||||
# Only clone foundation packages
|
# Only clone foundation packages
|
||||||
core setup --only foundation
|
core setup --only foundation
|
||||||
|
|
||||||
|
# Multiple types
|
||||||
|
core setup --only foundation,module
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bootstrap Mode
|
In registry mode with a TTY, an interactive wizard allows you to select which packages to clone. Use `--all` to skip the wizard and clone everything.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bootstrap Mode
|
||||||
|
|
||||||
When no `repos.yaml` exists, setup enters bootstrap mode:
|
When no `repos.yaml` exists, setup enters bootstrap mode:
|
||||||
|
|
||||||
|
|
@ -62,105 +69,104 @@ core setup --all --name ci-test
|
||||||
```
|
```
|
||||||
|
|
||||||
Bootstrap mode:
|
Bootstrap mode:
|
||||||
1. Clones `core-devops` (contains `repos.yaml`)
|
1. Detects if current directory is empty
|
||||||
2. Shows interactive package selection wizard
|
2. If not empty, prompts for project name (or uses `--name`)
|
||||||
3. Clones selected packages
|
3. Clones `core-devops` (contains `repos.yaml`)
|
||||||
|
4. Loads the registry from core-devops
|
||||||
|
5. Shows interactive package selection wizard (unless `--all`)
|
||||||
|
6. Clones selected packages
|
||||||
|
7. Optionally runs build (with `--build`)
|
||||||
|
|
||||||
### Repo Setup Mode
|
---
|
||||||
|
|
||||||
When run in a git repository root, offers to set up the repo with `.core/` configuration:
|
## Repo Setup Mode
|
||||||
|
|
||||||
|
When run in a git repository root (without `repos.yaml`), setup offers two choices:
|
||||||
|
|
||||||
|
1. **Setup Working Directory** - Creates `.core/build.yaml` based on detected project type
|
||||||
|
2. **Create Package** - Creates a subdirectory and clones packages there
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# In a git repo without .core/
|
|
||||||
cd ~/Code/my-go-project
|
cd ~/Code/my-go-project
|
||||||
core setup
|
core setup
|
||||||
|
|
||||||
# Choose "Setup this repo" when prompted
|
# Output:
|
||||||
# Creates .core/build.yaml based on detected project type
|
# >> This directory is a git repository
|
||||||
|
# > Setup Working Directory
|
||||||
|
# Create Package (clone repos into subdirectory)
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported project types:
|
Choosing "Setup Working Directory" detects the project type and generates configuration:
|
||||||
- **Go** - Detected via `go.mod`
|
|
||||||
- **Wails** - Detected via `wails.json`
|
| Detected File | Project Type |
|
||||||
- **Node.js** - Detected via `package.json`
|
|---------------|--------------|
|
||||||
- **PHP** - Detected via `composer.json`
|
| `wails.json` | Wails |
|
||||||
|
| `go.mod` | Go |
|
||||||
|
| `composer.json` | PHP |
|
||||||
|
| `package.json` | Node.js |
|
||||||
|
|
||||||
|
Creates three config files in `.core/`:
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `build.yaml` | Build targets, flags, output settings |
|
||||||
|
| `release.yaml` | Changelog format, GitHub release config |
|
||||||
|
| `test.yaml` | Test commands, environment variables |
|
||||||
|
|
||||||
|
Also auto-detects GitHub repo from git remote for release config.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interactive Wizard
|
||||||
|
|
||||||
|
When running in a terminal (TTY), the setup command presents an interactive multi-select wizard:
|
||||||
|
|
||||||
|
- Packages are grouped by type (foundation, module, product, template)
|
||||||
|
- Use arrow keys to navigate
|
||||||
|
- Press space to select/deselect packages
|
||||||
|
- Type to filter the list
|
||||||
|
- Press enter to confirm selection
|
||||||
|
|
||||||
|
The wizard is skipped when:
|
||||||
|
- `--all` flag is specified
|
||||||
|
- Not running in a TTY (e.g., CI pipelines)
|
||||||
|
- `--dry-run` is specified
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Clone from Registry
|
### Clone from Registry
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone all repos
|
# Clone all repos (interactive wizard)
|
||||||
core setup
|
core setup
|
||||||
|
|
||||||
|
# Clone all repos (non-interactive)
|
||||||
|
core setup --all
|
||||||
|
|
||||||
# Preview without cloning
|
# Preview without cloning
|
||||||
core setup --dry-run
|
core setup --dry-run
|
||||||
|
|
||||||
# Only foundation packages
|
# Only foundation packages
|
||||||
core setup --only foundation
|
core setup --only foundation
|
||||||
|
|
||||||
# Multiple types
|
|
||||||
core setup --only foundation,module
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bootstrap New Workspace
|
### Bootstrap New Workspace
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Interactive bootstrap
|
# Interactive bootstrap in empty directory
|
||||||
mkdir workspace && cd workspace
|
mkdir workspace && cd workspace
|
||||||
core setup
|
core setup
|
||||||
|
|
||||||
# Non-interactive with all packages
|
# Non-interactive with all packages
|
||||||
core setup --all --name my-project
|
core setup --all --name my-project
|
||||||
|
|
||||||
# Bootstrap in current directory
|
# Bootstrap and run build
|
||||||
core setup --name .
|
core setup --all --name my-project --build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setup Single Repository
|
---
|
||||||
|
|
||||||
```bash
|
|
||||||
# In a Go project
|
|
||||||
cd my-go-project
|
|
||||||
core setup --dry-run
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
# → Setting up repository configuration
|
|
||||||
# ✓ Detected project type: go
|
|
||||||
# → Would create:
|
|
||||||
# /path/to/my-go-project/.core/build.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Generated Configuration
|
|
||||||
|
|
||||||
When setting up a repository, `core setup` generates `.core/build.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: 1
|
|
||||||
project:
|
|
||||||
name: my-project
|
|
||||||
description: Go application
|
|
||||||
main: ./cmd/my-project
|
|
||||||
binary: my-project
|
|
||||||
build:
|
|
||||||
cgo: false
|
|
||||||
flags:
|
|
||||||
- -trimpath
|
|
||||||
ldflags:
|
|
||||||
- -s
|
|
||||||
- -w
|
|
||||||
targets:
|
|
||||||
- os: linux
|
|
||||||
arch: amd64
|
|
||||||
- os: linux
|
|
||||||
arch: arm64
|
|
||||||
- os: darwin
|
|
||||||
arch: amd64
|
|
||||||
- os: darwin
|
|
||||||
arch: arm64
|
|
||||||
- os: windows
|
|
||||||
arch: amd64
|
|
||||||
```
|
|
||||||
|
|
||||||
## Registry Format
|
## Registry Format
|
||||||
|
|
||||||
|
|
@ -183,6 +189,8 @@ repos:
|
||||||
description: Link-in-bio product
|
description: Link-in-bio product
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Finding Registry
|
## Finding Registry
|
||||||
|
|
||||||
Core looks for `repos.yaml` in:
|
Core looks for `repos.yaml` in:
|
||||||
|
|
@ -192,6 +200,8 @@ Core looks for `repos.yaml` in:
|
||||||
3. `~/Code/host-uk/repos.yaml`
|
3. `~/Code/host-uk/repos.yaml`
|
||||||
4. `~/.config/core/repos.yaml`
|
4. `~/.config/core/repos.yaml`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## After Setup
|
## After Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -208,8 +218,10 @@ core build
|
||||||
core test
|
core test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [work command](../dev/work/) - Multi-repo operations
|
- [dev work](../dev/work/) - Multi-repo operations
|
||||||
- [build command](../build/) - Build projects
|
- [build](../build/) - Build projects
|
||||||
- [doctor command](../doctor/) - Check environment
|
- [doctor](../doctor/) - Check environment
|
||||||
|
|
|
||||||
14
go.mod
14
go.mod
|
|
@ -4,6 +4,7 @@ go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Snider/Borg v0.1.0
|
github.com/Snider/Borg v0.1.0
|
||||||
|
github.com/charmbracelet/huh v0.8.0
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/getkin/kin-openapi v0.133.0
|
github.com/getkin/kin-openapi v0.133.0
|
||||||
github.com/leaanthony/clir v1.7.0
|
github.com/leaanthony/clir v1.7.0
|
||||||
|
|
@ -17,6 +18,7 @@ require (
|
||||||
golang.org/x/mod v0.31.0
|
golang.org/x/mod v0.31.0
|
||||||
golang.org/x/net v0.49.0
|
golang.org/x/net v0.49.0
|
||||||
golang.org/x/oauth2 v0.34.0
|
golang.org/x/oauth2 v0.34.0
|
||||||
|
golang.org/x/term v0.39.0
|
||||||
golang.org/x/text v0.33.0
|
golang.org/x/text v0.33.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
@ -28,15 +30,22 @@ require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||||
github.com/TwiN/go-color v1.4.1 // indirect
|
github.com/TwiN/go-color v1.4.1 // indirect
|
||||||
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/catppuccin/go v0.3.0 // indirect
|
||||||
|
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.6 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.16.3 // indirect
|
github.com/go-git/go-git/v5 v5.16.3 // indirect
|
||||||
|
|
@ -51,8 +60,12 @@ require (
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||||
|
|
@ -75,6 +88,7 @@ require (
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||||
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.40.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
43
go.sum
43
go.sum
|
|
@ -4,6 +4,8 @@ cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||||
|
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
|
@ -17,31 +19,61 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||||
|
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||||
|
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||||
|
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||||
|
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
|
||||||
|
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||||
|
github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY=
|
||||||
|
github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
||||||
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
|
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
|
||||||
|
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
|
||||||
|
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
||||||
|
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
||||||
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||||
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||||
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
||||||
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||||
|
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||||
|
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
|
||||||
|
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
|
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
||||||
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
|
|
@ -101,14 +133,22 @@ github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
||||||
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
|
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
|
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
|
||||||
github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
|
github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
github.com/oasdiff/oasdiff v1.11.8 h1:3LalSR0yYVM5sAYNInlIG4TVckLCJBkgjcnst2GKWVg=
|
github.com/oasdiff/oasdiff v1.11.8 h1:3LalSR0yYVM5sAYNInlIG4TVckLCJBkgjcnst2GKWVg=
|
||||||
|
|
@ -190,6 +230,8 @@ golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -197,6 +239,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue