diff --git a/cmd/ai/ai.go b/cmd/ai/ai.go index 1679312e..b7beabfa 100644 --- a/cmd/ai/ai.go +++ b/cmd/ai/ai.go @@ -3,35 +3,35 @@ package ai import ( - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/spf13/cobra" ) // Style aliases from shared package var ( - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - dimStyle = shared.DimStyle - truncate = shared.Truncate - formatAge = shared.FormatAge + successStyle = cli.SuccessStyle + errorStyle = cli.ErrorStyle + dimStyle = cli.DimStyle + truncate = cli.Truncate + formatAge = cli.FormatAge ) // Task priority/status styles from shared var ( - taskPriorityHighStyle = shared.PriorityHighStyle - taskPriorityMediumStyle = shared.PriorityMediumStyle - taskPriorityLowStyle = shared.PriorityLowStyle - taskStatusPendingStyle = shared.StatusPendingStyle - taskStatusInProgressStyle = shared.StatusRunningStyle - taskStatusCompletedStyle = shared.StatusSuccessStyle - taskStatusBlockedStyle = shared.StatusErrorStyle + taskPriorityHighStyle = cli.PriorityHighStyle + taskPriorityMediumStyle = cli.PriorityMediumStyle + taskPriorityLowStyle = cli.PriorityLowStyle + taskStatusPendingStyle = cli.StatusPendingStyle + taskStatusInProgressStyle = cli.StatusRunningStyle + taskStatusCompletedStyle = cli.StatusSuccessStyle + taskStatusBlockedStyle = cli.StatusErrorStyle ) // Task-specific styles (aliases to shared where possible) var ( - taskIDStyle = shared.TitleStyle // Bold + blue - taskTitleStyle = shared.ValueStyle // Light gray - taskLabelStyle = shared.AccentLabelStyle // Violet for labels + taskIDStyle = cli.TitleStyle // Bold + blue + taskTitleStyle = cli.ValueStyle // Light gray + taskLabelStyle = cli.AccentLabelStyle // Violet for labels ) // AddAgenticCommands adds the agentic task management commands to the ai command. diff --git a/cmd/build/build.go b/cmd/build/build.go index 143581d8..50586852 100644 --- a/cmd/build/build.go +++ b/cmd/build/build.go @@ -4,18 +4,18 @@ package build import ( "embed" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style aliases from shared package var ( - buildHeaderStyle = shared.TitleStyle - buildTargetStyle = shared.ValueStyle - buildSuccessStyle = shared.SuccessStyle - buildErrorStyle = shared.ErrorStyle - buildDimStyle = shared.DimStyle + buildHeaderStyle = cli.TitleStyle + buildTargetStyle = cli.ValueStyle + buildSuccessStyle = cli.SuccessStyle + buildErrorStyle = cli.ErrorStyle + buildDimStyle = cli.DimStyle ) //go:embed all:tmpl/gui diff --git a/cmd/ci/ci_release.go b/cmd/ci/ci_release.go index 8f45cb11..12721931 100644 --- a/cmd/ci/ci_release.go +++ b/cmd/ci/ci_release.go @@ -2,18 +2,18 @@ package ci import ( - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style aliases from shared var ( - releaseHeaderStyle = shared.RepoNameStyle - releaseSuccessStyle = shared.SuccessStyle - releaseErrorStyle = shared.ErrorStyle - releaseDimStyle = shared.DimStyle - releaseValueStyle = shared.ValueStyle + releaseHeaderStyle = cli.RepoNameStyle + releaseSuccessStyle = cli.SuccessStyle + releaseErrorStyle = cli.ErrorStyle + releaseDimStyle = cli.DimStyle + releaseValueStyle = cli.ValueStyle ) // Flag variables for ci command diff --git a/cmd/core.go b/cmd/core.go index 8173773d..fc67cc78 100644 --- a/cmd/core.go +++ b/cmd/core.go @@ -19,17 +19,17 @@ package cmd import ( "os" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/spf13/cobra" ) // Terminal styles using Tailwind colour palette (from shared package). var ( // coreStyle is used for primary headings and the CLI name. - coreStyle = shared.RepoNameStyle + coreStyle = cli.RepoNameStyle // linkStyle is used for URLs and clickable references. - linkStyle = shared.LinkStyle + linkStyle = cli.LinkStyle ) // rootCmd is the base command for the CLI. diff --git a/cmd/dev/dev.go b/cmd/dev/dev.go index bae8b480..da23bcc0 100644 --- a/cmd/dev/dev.go +++ b/cmd/dev/dev.go @@ -29,27 +29,27 @@ package dev import ( - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style aliases from shared package var ( - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - warningStyle = shared.WarningStyle - dimStyle = shared.DimStyle - valueStyle = shared.ValueStyle - headerStyle = shared.HeaderStyle - repoNameStyle = shared.RepoNameStyle + successStyle = cli.SuccessStyle + errorStyle = cli.ErrorStyle + warningStyle = cli.WarningStyle + dimStyle = cli.DimStyle + valueStyle = cli.ValueStyle + headerStyle = cli.HeaderStyle + repoNameStyle = cli.RepoNameStyle ) // Table styles for status display (extends shared styles with cell padding) var ( - dirtyStyle = shared.GitDirtyStyle.Padding(0, 1) - aheadStyle = shared.GitAheadStyle.Padding(0, 1) - cleanStyle = shared.GitCleanStyle.Padding(0, 1) + dirtyStyle = cli.GitDirtyStyle.Padding(0, 1) + aheadStyle = cli.GitAheadStyle.Padding(0, 1) + cleanStyle = cli.GitCleanStyle.Padding(0, 1) ) // AddCommands registers the 'dev' command and all subcommands. diff --git a/cmd/dev/dev_ci.go b/cmd/dev/dev_ci.go index e9db2b24..22009132 100644 --- a/cmd/dev/dev_ci.go +++ b/cmd/dev/dev_ci.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" @@ -16,10 +16,10 @@ import ( // CI-specific styles (aliases to shared) var ( - ciSuccessStyle = shared.SuccessStyle - ciFailureStyle = shared.ErrorStyle - ciPendingStyle = shared.StatusWarningStyle - ciSkippedStyle = shared.DimStyle + ciSuccessStyle = cli.SuccessStyle + ciFailureStyle = cli.ErrorStyle + ciPendingStyle = cli.StatusWarningStyle + ciSkippedStyle = cli.DimStyle ) // WorkflowRun represents a GitHub Actions workflow run @@ -246,10 +246,10 @@ func printWorkflowRun(run WorkflowRun) { } // Workflow name (truncated) - workflowName := shared.Truncate(run.Name, 20) + workflowName := cli.Truncate(run.Name, 20) // Age - age := shared.FormatAge(run.UpdatedAt) + age := cli.FormatAge(run.UpdatedAt) fmt.Printf(" %s %-18s %-22s %s\n", status, diff --git a/cmd/dev/dev_commit.go b/cmd/dev/dev_commit.go index 7037f9c2..aa67aa92 100644 --- a/cmd/dev/dev_commit.go +++ b/cmd/dev/dev_commit.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/git" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" @@ -128,7 +128,7 @@ func runCommit(registryPath string, all bool) error { // Confirm unless --all if !all { fmt.Println() - if !shared.Confirm(i18n.T("cmd.dev.confirm_claude_commit")) { + if !cli.Confirm(i18n.T("cmd.dev.confirm_claude_commit")) { fmt.Println(i18n.T("cli.aborted")) return nil } @@ -207,7 +207,7 @@ func runCommitSingleRepo(ctx context.Context, repoPath string, all bool) error { // Confirm unless --all if !all { fmt.Println() - if !shared.Confirm(i18n.T("cmd.dev.confirm_claude_commit")) { + if !cli.Confirm(i18n.T("cmd.dev.confirm_claude_commit")) { fmt.Println(i18n.T("cli.aborted")) return nil } diff --git a/cmd/dev/dev_health.go b/cmd/dev/dev_health.go index 7a7fe255..d10e62ae 100644 --- a/cmd/dev/dev_health.go +++ b/cmd/dev/dev_health.go @@ -6,7 +6,7 @@ import ( "os" "sort" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/git" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" @@ -144,36 +144,36 @@ func runHealth(registryPath string, verbose bool) error { func printHealthSummary(total int, dirty, ahead, behind, errors []string) { parts := []string{ - shared.StatusPart(total, i18n.T("cmd.dev.health.repos"), shared.ValueStyle), + cli.StatusPart(total, i18n.T("cmd.dev.health.repos"), cli.ValueStyle), } // Dirty status if len(dirty) > 0 { - parts = append(parts, shared.StatusPart(len(dirty), i18n.T("cmd.dev.health.dirty"), shared.WarningStyle)) + parts = append(parts, cli.StatusPart(len(dirty), i18n.T("cmd.dev.health.dirty"), cli.WarningStyle)) } else { - parts = append(parts, shared.StatusText(i18n.T("cmd.dev.status.clean"), shared.SuccessStyle)) + parts = append(parts, cli.StatusText(i18n.T("cmd.dev.status.clean"), cli.SuccessStyle)) } // Push status if len(ahead) > 0 { - parts = append(parts, shared.StatusPart(len(ahead), i18n.T("cmd.dev.health.to_push"), shared.ValueStyle)) + parts = append(parts, cli.StatusPart(len(ahead), i18n.T("cmd.dev.health.to_push"), cli.ValueStyle)) } else { - parts = append(parts, shared.StatusText(i18n.T("cmd.dev.health.synced"), shared.SuccessStyle)) + parts = append(parts, cli.StatusText(i18n.T("cmd.dev.health.synced"), cli.SuccessStyle)) } // Pull status if len(behind) > 0 { - parts = append(parts, shared.StatusPart(len(behind), i18n.T("cmd.dev.health.to_pull"), shared.WarningStyle)) + parts = append(parts, cli.StatusPart(len(behind), i18n.T("cmd.dev.health.to_pull"), cli.WarningStyle)) } else { - parts = append(parts, shared.StatusText(i18n.T("cmd.dev.health.up_to_date"), shared.SuccessStyle)) + parts = append(parts, cli.StatusText(i18n.T("cmd.dev.health.up_to_date"), cli.SuccessStyle)) } // Errors (only if any) if len(errors) > 0 { - parts = append(parts, shared.StatusPart(len(errors), i18n.T("cmd.dev.health.errors"), shared.ErrorStyle)) + parts = append(parts, cli.StatusPart(len(errors), i18n.T("cmd.dev.health.errors"), cli.ErrorStyle)) } - fmt.Println(shared.StatusLine(parts...)) + fmt.Println(cli.StatusLine(parts...)) } func formatRepoList(reposList []string) string { diff --git a/cmd/dev/dev_impact.go b/cmd/dev/dev_impact.go index 9754688c..1c495aae 100644 --- a/cmd/dev/dev_impact.go +++ b/cmd/dev/dev_impact.go @@ -4,7 +4,7 @@ import ( "fmt" "sort" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" @@ -12,9 +12,9 @@ import ( // Impact-specific styles (aliases to shared) var ( - impactDirectStyle = shared.ErrorStyle - impactIndirectStyle = shared.StatusWarningStyle - impactSafeStyle = shared.StatusSuccessStyle + impactDirectStyle = cli.ErrorStyle + impactIndirectStyle = cli.StatusWarningStyle + impactSafeStyle = cli.StatusSuccessStyle ) // Impact command flags @@ -112,7 +112,7 @@ func runImpact(registryPath string, repoName string) error { r, _ := reg.Get(d) desc := "" if r != nil && r.Description != "" { - desc = dimStyle.Render(" - " + shared.Truncate(r.Description, 40)) + desc = dimStyle.Render(" - " + cli.Truncate(r.Description, 40)) } fmt.Printf(" %s%s\n", d, desc) } @@ -129,7 +129,7 @@ func runImpact(registryPath string, repoName string) error { r, _ := reg.Get(d) desc := "" if r != nil && r.Description != "" { - desc = dimStyle.Render(" - " + shared.Truncate(r.Description, 40)) + desc = dimStyle.Render(" - " + cli.Truncate(r.Description, 40)) } fmt.Printf(" %s%s\n", d, desc) } diff --git a/cmd/dev/dev_issues.go b/cmd/dev/dev_issues.go index dc65bbc4..41f05a87 100644 --- a/cmd/dev/dev_issues.go +++ b/cmd/dev/dev_issues.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" @@ -17,12 +17,12 @@ import ( // Issue-specific styles (aliases to shared) var ( - issueRepoStyle = shared.DimStyle - issueNumberStyle = shared.TitleStyle - issueTitleStyle = shared.ValueStyle - issueLabelStyle = shared.WarningStyle - issueAssigneeStyle = shared.StatusSuccessStyle - issueAgeStyle = shared.DimStyle + issueRepoStyle = cli.DimStyle + issueNumberStyle = cli.TitleStyle + issueTitleStyle = cli.ValueStyle + issueLabelStyle = cli.WarningStyle + issueAssigneeStyle = cli.StatusSuccessStyle + issueAgeStyle = cli.DimStyle ) // GitHubIssue represents a GitHub issue from the API. @@ -201,7 +201,7 @@ func printIssue(issue GitHubIssue) { // #42 [core-bio] Fix avatar upload num := issueNumberStyle.Render(fmt.Sprintf("#%d", issue.Number)) repo := issueRepoStyle.Render(fmt.Sprintf("[%s]", issue.RepoName)) - title := issueTitleStyle.Render(shared.Truncate(issue.Title, 60)) + title := issueTitleStyle.Render(cli.Truncate(issue.Title, 60)) line := fmt.Sprintf(" %s %s %s", num, repo, title) @@ -224,7 +224,7 @@ func printIssue(issue GitHubIssue) { } // Add age - age := shared.FormatAge(issue.CreatedAt) + age := cli.FormatAge(issue.CreatedAt) line += " " + issueAgeStyle.Render(age) fmt.Println(line) diff --git a/cmd/dev/dev_push.go b/cmd/dev/dev_push.go index e262bee4..7abdde70 100644 --- a/cmd/dev/dev_push.go +++ b/cmd/dev/dev_push.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/git" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" @@ -122,7 +122,7 @@ func runPush(registryPath string, force bool) error { // Confirm unless --force if !force { fmt.Println() - if !shared.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": totalCommits, "Repos": len(aheadRepos)})) { + if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": totalCommits, "Repos": len(aheadRepos)})) { fmt.Println(i18n.T("cli.aborted")) return nil } @@ -161,7 +161,7 @@ func runPush(registryPath string, force bool) error { if len(divergedRepos) > 0 { fmt.Println() fmt.Printf("%s\n", i18n.T("cmd.dev.push.diverged_help")) - if shared.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) { + if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) { fmt.Println() for _, r := range divergedRepos { fmt.Printf(" %s %s...\n", dimStyle.Render("↓"), r.Name) @@ -226,7 +226,7 @@ func runPushSingleRepo(ctx context.Context, repoPath string, force bool) error { } fmt.Println() fmt.Println() - if shared.Confirm(i18n.T("cmd.dev.push.uncommitted_changes_commit")) { + if cli.Confirm(i18n.T("cmd.dev.push.uncommitted_changes_commit")) { fmt.Println() // Use edit-enabled commit if only untracked files (may need .gitignore fix) var err error @@ -260,7 +260,7 @@ func runPushSingleRepo(ctx context.Context, repoPath string, force bool) error { // Confirm unless --force if !force { fmt.Println() - if !shared.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": s.Ahead, "Repos": 1})) { + if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": s.Ahead, "Repos": 1})) { fmt.Println(i18n.T("cli.aborted")) return nil } @@ -275,7 +275,7 @@ func runPushSingleRepo(ctx context.Context, repoPath string, force bool) error { fmt.Printf(" %s %s: %s\n", warningStyle.Render("!"), repoName, i18n.T("cmd.dev.push.diverged")) fmt.Println() fmt.Printf("%s\n", i18n.T("cmd.dev.push.diverged_help")) - if shared.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) { + if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) { fmt.Println() fmt.Printf(" %s %s...\n", dimStyle.Render("↓"), repoName) if pullErr := git.Pull(ctx, repoPath); pullErr != nil { diff --git a/cmd/dev/dev_reviews.go b/cmd/dev/dev_reviews.go index 1edae9d5..262f1d72 100644 --- a/cmd/dev/dev_reviews.go +++ b/cmd/dev/dev_reviews.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" @@ -17,13 +17,13 @@ import ( // PR-specific styles (aliases to shared) var ( - prNumberStyle = shared.PrNumberStyle - prTitleStyle = shared.ValueStyle - prAuthorStyle = shared.InfoStyle - prApprovedStyle = shared.SuccessStyle - prChangesStyle = shared.WarningStyle - prPendingStyle = shared.DimStyle - prDraftStyle = shared.DimStyle + prNumberStyle = cli.PrNumberStyle + prTitleStyle = cli.ValueStyle + prAuthorStyle = cli.InfoStyle + prApprovedStyle = cli.SuccessStyle + prChangesStyle = cli.WarningStyle + prPendingStyle = cli.DimStyle + prDraftStyle = cli.DimStyle ) // GitHubPR represents a GitHub pull request. @@ -234,7 +234,7 @@ func printPR(pr GitHubPR) { // #12 [core-php] Webhook validation num := prNumberStyle.Render(fmt.Sprintf("#%d", pr.Number)) repo := issueRepoStyle.Render(fmt.Sprintf("[%s]", pr.RepoName)) - title := prTitleStyle.Render(shared.Truncate(pr.Title, 50)) + title := prTitleStyle.Render(cli.Truncate(pr.Title, 50)) author := prAuthorStyle.Render("@" + pr.Author.Login) // Review status @@ -254,7 +254,7 @@ func printPR(pr GitHubPR) { draft = prDraftStyle.Render(" " + i18n.T("cmd.dev.reviews.draft")) } - age := shared.FormatAge(pr.CreatedAt) + age := cli.FormatAge(pr.CreatedAt) fmt.Printf(" %s %s %s%s %s %s %s\n", num, repo, title, draft, author, status, issueAgeStyle.Render(age)) } diff --git a/cmd/dev/dev_work.go b/cmd/dev/dev_work.go index d2fc68ce..d35976a5 100644 --- a/cmd/dev/dev_work.go +++ b/cmd/dev/dev_work.go @@ -8,7 +8,7 @@ import ( "sort" "strings" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/agentic" "github.com/host-uk/core/pkg/git" "github.com/host-uk/core/pkg/i18n" @@ -109,7 +109,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { // Auto-commit dirty repos if requested if autoCommit && len(dirtyRepos) > 0 { fmt.Println() - fmt.Printf("%s\n", shared.TitleStyle.Render(i18n.T("cmd.dev.commit.committing"))) + fmt.Printf("%s\n", cli.TitleStyle.Render(i18n.T("cmd.dev.commit.committing"))) fmt.Println() for _, s := range dirtyRepos { @@ -168,7 +168,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { } fmt.Println() - if !shared.Confirm(i18n.T("cmd.dev.push.confirm")) { + if !cli.Confirm(i18n.T("cmd.dev.push.confirm")) { fmt.Println(i18n.T("cli.aborted")) return nil } @@ -203,7 +203,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { if len(divergedRepos) > 0 { fmt.Println() fmt.Printf("%s\n", i18n.T("cmd.dev.push.diverged_help")) - if shared.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) { + if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) { fmt.Println() for _, s := range divergedRepos { fmt.Printf(" %s %s...\n", dimStyle.Render("↓"), s.Name) @@ -244,11 +244,11 @@ func printStatusTable(statuses []git.RepoStatus) { // Print header with fixed-width formatting fmt.Printf("%-*s %8s %9s %6s %5s\n", nameWidth, - shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_repo")), - shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_modified")), - shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_untracked")), - shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_staged")), - shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_ahead")), + cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_repo")), + cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_modified")), + cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_untracked")), + cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_staged")), + cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_ahead")), ) // Print separator diff --git a/cmd/docs/docs.go b/cmd/docs/docs.go index 851d4c04..2dc58e22 100644 --- a/cmd/docs/docs.go +++ b/cmd/docs/docs.go @@ -2,22 +2,22 @@ package docs import ( - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style and utility aliases from shared var ( - repoNameStyle = shared.RepoNameStyle - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - dimStyle = shared.DimStyle - headerStyle = shared.HeaderStyle - confirm = shared.Confirm - docsFoundStyle = shared.SuccessStyle - docsMissingStyle = shared.DimStyle - docsFileStyle = shared.InfoStyle + repoNameStyle = cli.RepoNameStyle + successStyle = cli.SuccessStyle + errorStyle = cli.ErrorStyle + dimStyle = cli.DimStyle + headerStyle = cli.HeaderStyle + confirm = cli.Confirm + docsFoundStyle = cli.SuccessStyle + docsMissingStyle = cli.DimStyle + docsFileStyle = cli.InfoStyle ) var docsCmd = &cobra.Command{ diff --git a/cmd/docs/list.go b/cmd/docs/list.go index 935054a9..df859066 100644 --- a/cmd/docs/list.go +++ b/cmd/docs/list.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -44,11 +44,11 @@ func runDocsList(registryPath string) error { for _, repo := range reg.List() { info := scanRepoDocs(repo) - readme := shared.CheckMark(info.Readme != "") - claude := shared.CheckMark(info.ClaudeMd != "") - changelog := shared.CheckMark(info.Changelog != "") + readme := cli.CheckMark(info.Readme != "") + claude := cli.CheckMark(info.ClaudeMd != "") + changelog := cli.CheckMark(info.Changelog != "") - docsDir := shared.CheckMark(false) + docsDir := cli.CheckMark(false) if len(info.DocsFiles) > 0 { docsDir = docsFoundStyle.Render(i18n.T("cmd.docs.list.files_count", map[string]interface{}{"Count": len(info.DocsFiles)})) } @@ -70,7 +70,7 @@ func runDocsList(registryPath string) error { fmt.Println() fmt.Printf("%s %s\n", - shared.Label(i18n.T("cmd.docs.list.coverage_label")), + cli.Label(i18n.T("cmd.docs.list.coverage_label")), i18n.T("cmd.docs.list.coverage_summary", map[string]interface{}{"WithDocs": withDocs, "WithoutDocs": withoutDocs}), ) diff --git a/cmd/doctor/doctor.go b/cmd/doctor/doctor.go index 4dc96eb5..c2532959 100644 --- a/cmd/doctor/doctor.go +++ b/cmd/doctor/doctor.go @@ -4,16 +4,16 @@ package doctor import ( "fmt" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style aliases from shared var ( - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - dimStyle = shared.DimStyle + successStyle = cli.SuccessStyle + errorStyle = cli.ErrorStyle + dimStyle = cli.DimStyle ) // Flag variable for doctor command @@ -44,13 +44,13 @@ func runDoctor(verbose bool) error { ok, version := runCheck(c) if ok { if verbose { - fmt.Println(shared.CheckResult(true, c.name, version)) + fmt.Println(cli.CheckResult(true, c.name, version)) } else { - fmt.Println(shared.CheckResult(true, c.name, "")) + fmt.Println(cli.CheckResult(true, c.name, "")) } passed++ } else { - fmt.Printf(" %s %s - %s\n", errorStyle.Render(shared.SymbolCross), c.name, c.description) + fmt.Printf(" %s %s - %s\n", errorStyle.Render(cli.SymbolCross), c.name, c.description) failed++ } } @@ -61,13 +61,13 @@ func runDoctor(verbose bool) error { ok, version := runCheck(c) if ok { if verbose { - fmt.Println(shared.CheckResult(true, c.name, version)) + fmt.Println(cli.CheckResult(true, c.name, version)) } else { - fmt.Println(shared.CheckResult(true, c.name, "")) + fmt.Println(cli.CheckResult(true, c.name, "")) } passed++ } else { - fmt.Printf(" %s %s - %s\n", dimStyle.Render(shared.SymbolSkip), c.name, dimStyle.Render(c.description)) + fmt.Printf(" %s %s - %s\n", dimStyle.Render(cli.SymbolSkip), c.name, dimStyle.Render(c.description)) optional++ } } @@ -75,16 +75,16 @@ func runDoctor(verbose bool) error { // Check GitHub access fmt.Printf("\n%s\n", i18n.T("cmd.doctor.github")) if checkGitHubSSH() { - fmt.Println(shared.CheckResult(true, i18n.T("cmd.doctor.ssh_found"), "")) + fmt.Println(cli.CheckResult(true, i18n.T("cmd.doctor.ssh_found"), "")) } else { - fmt.Printf(" %s %s\n", errorStyle.Render(shared.SymbolCross), i18n.T("cmd.doctor.ssh_missing")) + fmt.Printf(" %s %s\n", errorStyle.Render(cli.SymbolCross), i18n.T("cmd.doctor.ssh_missing")) failed++ } if checkGitHubCLI() { - fmt.Println(shared.CheckResult(true, i18n.T("cmd.doctor.cli_auth"), "")) + fmt.Println(cli.CheckResult(true, i18n.T("cmd.doctor.cli_auth"), "")) } else { - fmt.Printf(" %s %s\n", errorStyle.Render(shared.SymbolCross), i18n.T("cmd.doctor.cli_auth_missing")) + fmt.Printf(" %s %s\n", errorStyle.Render(cli.SymbolCross), i18n.T("cmd.doctor.cli_auth_missing")) failed++ } @@ -95,12 +95,12 @@ func runDoctor(verbose bool) error { // Summary fmt.Println() if failed > 0 { - fmt.Println(shared.Error(i18n.T("cmd.doctor.issues", map[string]interface{}{"Count": failed}))) + fmt.Println(cli.Error(i18n.T("cmd.doctor.issues", map[string]interface{}{"Count": failed}))) fmt.Printf("\n%s\n", i18n.T("cmd.doctor.install_missing")) printInstallInstructions() return fmt.Errorf("%s", i18n.T("cmd.doctor.issues_error", map[string]interface{}{"Count": failed})) } - fmt.Println(shared.Success(i18n.T("cmd.doctor.ready"))) + fmt.Println(cli.Success(i18n.T("cmd.doctor.ready"))) return nil } diff --git a/cmd/go/go.go b/cmd/go/go.go index 6c683128..d3362809 100644 --- a/cmd/go/go.go +++ b/cmd/go/go.go @@ -4,16 +4,16 @@ package gocmd import ( - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style aliases for shared styles var ( - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - dimStyle = shared.DimStyle + successStyle = cli.SuccessStyle + errorStyle = cli.ErrorStyle + dimStyle = cli.DimStyle ) // AddGoCommands adds Go development commands. diff --git a/cmd/go/go_test_cmd.go b/cmd/go/go_test_cmd.go index 8d89b99b..801fba75 100644 --- a/cmd/go/go_test_cmd.go +++ b/cmd/go/go_test_cmd.go @@ -8,7 +8,7 @@ import ( "regexp" "strings" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -119,7 +119,7 @@ func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose boo } if cov > 0 { - fmt.Printf("\n %s %s\n", shared.ProgressLabel(i18n.T("cmd.go.test.coverage")), shared.FormatCoverage(cov)) + fmt.Printf("\n %s %s\n", cli.ProgressLabel(i18n.T("cmd.go.test.coverage")), cli.FormatCoverage(cov)) } if err == nil { @@ -241,7 +241,7 @@ func addGoCovCommand(parent *cobra.Command) { // Print coverage summary fmt.Println() - fmt.Printf(" %s %s\n", shared.ProgressLabel(i18n.T("label.total")), shared.FormatCoverage(totalCov)) + fmt.Printf(" %s %s\n", cli.ProgressLabel(i18n.T("label.total")), cli.FormatCoverage(totalCov)) // Generate HTML if requested if covHTML || covOpen { diff --git a/cmd/php/php.go b/cmd/php/php.go index a016c4dc..5326543b 100644 --- a/cmd/php/php.go +++ b/cmd/php/php.go @@ -3,49 +3,49 @@ package php import ( "github.com/charmbracelet/lipgloss" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style aliases from shared var ( - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - dimStyle = shared.DimStyle - linkStyle = shared.LinkStyle + successStyle = cli.SuccessStyle + errorStyle = cli.ErrorStyle + dimStyle = cli.DimStyle + linkStyle = cli.LinkStyle ) // Service colors for log output (domain-specific, keep local) var ( - phpFrankenPHPStyle = lipgloss.NewStyle().Foreground(shared.ColourIndigo500) - phpViteStyle = lipgloss.NewStyle().Foreground(shared.ColourYellow500) - phpHorizonStyle = lipgloss.NewStyle().Foreground(shared.ColourOrange500) - phpReverbStyle = lipgloss.NewStyle().Foreground(shared.ColourViolet500) - phpRedisStyle = lipgloss.NewStyle().Foreground(shared.ColourRed500) + phpFrankenPHPStyle = lipgloss.NewStyle().Foreground(cli.ColourIndigo500) + phpViteStyle = lipgloss.NewStyle().Foreground(cli.ColourYellow500) + phpHorizonStyle = lipgloss.NewStyle().Foreground(cli.ColourOrange500) + phpReverbStyle = lipgloss.NewStyle().Foreground(cli.ColourViolet500) + phpRedisStyle = lipgloss.NewStyle().Foreground(cli.ColourRed500) ) // Status styles (from shared) var ( - phpStatusRunning = shared.SuccessStyle - phpStatusStopped = shared.StatusPendingStyle - phpStatusError = shared.ErrorStyle + phpStatusRunning = cli.SuccessStyle + phpStatusStopped = cli.StatusPendingStyle + phpStatusError = cli.ErrorStyle ) // QA command styles (from shared) var ( - phpQAPassedStyle = shared.SuccessStyle - phpQAFailedStyle = shared.ErrorStyle - phpQAWarningStyle = shared.WarningStyle - phpQAStageStyle = shared.StageStyle + phpQAPassedStyle = cli.SuccessStyle + phpQAFailedStyle = cli.ErrorStyle + phpQAWarningStyle = cli.WarningStyle + phpQAStageStyle = cli.StageStyle ) // Security severity styles (from shared) var ( - phpSecurityCriticalStyle = shared.SeverityCriticalStyle - phpSecurityHighStyle = shared.SeverityHighStyle - phpSecurityMediumStyle = shared.SeverityMediumStyle - phpSecurityLowStyle = shared.SeverityLowStyle + phpSecurityCriticalStyle = cli.SeverityCriticalStyle + phpSecurityHighStyle = cli.SeverityHighStyle + phpSecurityMediumStyle = cli.SeverityMediumStyle + phpSecurityLowStyle = cli.SeverityLowStyle ) // AddPHPCommands adds PHP/Laravel development commands. diff --git a/cmd/php/php_deploy.go b/cmd/php/php_deploy.go index 2715fc97..09b41fa5 100644 --- a/cmd/php/php_deploy.go +++ b/cmd/php/php_deploy.go @@ -6,7 +6,7 @@ import ( "os" "time" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" phppkg "github.com/host-uk/core/pkg/php" "github.com/spf13/cobra" @@ -14,9 +14,9 @@ import ( // Deploy command styles (aliases to shared) var ( - phpDeployStyle = shared.DeploySuccessStyle - phpDeployPendingStyle = shared.StatusWarningStyle - phpDeployFailedStyle = shared.StatusErrorStyle + phpDeployStyle = cli.DeploySuccessStyle + phpDeployPendingStyle = cli.StatusWarningStyle + phpDeployFailedStyle = cli.StatusErrorStyle ) func addPHPDeployCommands(parent *cobra.Command) { diff --git a/cmd/pkg/pkg.go b/cmd/pkg/pkg.go index 6bbe9a8d..76ca31cd 100644 --- a/cmd/pkg/pkg.go +++ b/cmd/pkg/pkg.go @@ -2,19 +2,19 @@ package pkg import ( - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style and utility aliases var ( - repoNameStyle = shared.RepoNameStyle - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - dimStyle = shared.DimStyle - ghAuthenticated = shared.GhAuthenticated - gitClone = shared.GitClone + repoNameStyle = cli.RepoNameStyle + successStyle = cli.SuccessStyle + errorStyle = cli.ErrorStyle + dimStyle = cli.DimStyle + ghAuthenticated = cli.GhAuthenticated + gitClone = cli.GitClone ) // AddPkgCommands adds the 'pkg' command and subcommands for package management. diff --git a/cmd/sdk/sdk.go b/cmd/sdk/sdk.go index e4224568..e5bb59da 100644 --- a/cmd/sdk/sdk.go +++ b/cmd/sdk/sdk.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" sdkpkg "github.com/host-uk/core/pkg/sdk" "github.com/spf13/cobra" @@ -13,10 +13,10 @@ import ( // SDK styles (aliases to shared) var ( - sdkHeaderStyle = shared.TitleStyle - sdkSuccessStyle = shared.SuccessStyle - sdkErrorStyle = shared.ErrorStyle - sdkDimStyle = shared.DimStyle + sdkHeaderStyle = cli.TitleStyle + sdkSuccessStyle = cli.SuccessStyle + sdkErrorStyle = cli.ErrorStyle + sdkDimStyle = cli.DimStyle ) var sdkCmd = &cobra.Command{ diff --git a/cmd/setup/setup.go b/cmd/setup/setup.go index 2c158aec..df643e4b 100644 --- a/cmd/setup/setup.go +++ b/cmd/setup/setup.go @@ -2,17 +2,17 @@ package setup import ( - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style aliases from shared package var ( - repoNameStyle = shared.RepoNameStyle - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - dimStyle = shared.DimStyle + repoNameStyle = cli.RepoNameStyle + successStyle = cli.SuccessStyle + errorStyle = cli.ErrorStyle + dimStyle = cli.DimStyle ) // Default organization and devops repo for bootstrap diff --git a/cmd/setup/setup_registry.go b/cmd/setup/setup_registry.go index 9a624473..5ca43f37 100644 --- a/cmd/setup/setup_registry.go +++ b/cmd/setup/setup_registry.go @@ -13,7 +13,7 @@ import ( "path/filepath" "strings" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" ) @@ -216,7 +216,7 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP // gitClone clones a repository using gh CLI or git. func gitClone(ctx context.Context, org, repo, path string) error { // Try gh clone first with HTTPS (works without SSH keys) - if shared.GhAuthenticated() { + if cli.GhAuthenticated() { // Use HTTPS URL directly to bypass git_protocol config httpsURL := fmt.Sprintf("https://github.com/%s/%s.git", org, repo) cmd := exec.CommandContext(ctx, "gh", "repo", "clone", httpsURL, path) diff --git a/cmd/setup/setup_wizard.go b/cmd/setup/setup_wizard.go index 3b5a7106..eba09204 100644 --- a/cmd/setup/setup_wizard.go +++ b/cmd/setup/setup_wizard.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/charmbracelet/huh" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "golang.org/x/term" @@ -157,7 +157,7 @@ func runPackageWizard(reg *repos.Registry, preselectedTypes []string) ([]string, var selected []string // Header styling - headerStyle := shared.TitleStyle.MarginBottom(1) + headerStyle := cli.TitleStyle.MarginBottom(1) fmt.Println(headerStyle.Render(i18n.T("cmd.setup.wizard.package_selection"))) fmt.Println(i18n.T("cmd.setup.wizard.selection_hint")) diff --git a/cmd/test/test.go b/cmd/test/test.go index bc5a82e4..0d04ee8b 100644 --- a/cmd/test/test.go +++ b/cmd/test/test.go @@ -4,21 +4,21 @@ package testcmd import ( - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style aliases from shared var ( - testHeaderStyle = shared.RepoNameStyle - testPassStyle = shared.SuccessStyle - testFailStyle = shared.ErrorStyle - testSkipStyle = shared.WarningStyle - testDimStyle = shared.DimStyle - testCovHighStyle = shared.CoverageHighStyle - testCovMedStyle = shared.CoverageMedStyle - testCovLowStyle = shared.CoverageLowStyle + testHeaderStyle = cli.RepoNameStyle + testPassStyle = cli.SuccessStyle + testFailStyle = cli.ErrorStyle + testSkipStyle = cli.WarningStyle + testDimStyle = cli.DimStyle + testCovHighStyle = cli.CoverageHighStyle + testCovMedStyle = cli.CoverageMedStyle + testCovLowStyle = cli.CoverageLowStyle ) // Flag variables for test command diff --git a/cmd/test/test_output.go b/cmd/test/test_output.go index 6fb9e899..abe15eda 100644 --- a/cmd/test/test_output.go +++ b/cmd/test/test_output.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" ) @@ -153,7 +153,7 @@ func printCoverageSummary(results testResults) { } func formatCoverage(cov float64) string { - return shared.FormatCoverage(cov) + return cli.FormatCoverage(cov) } func shortenPackageName(name string) string { diff --git a/cmd/vm/vm.go b/cmd/vm/vm.go index 33eba40f..9ab83bb2 100644 --- a/cmd/vm/vm.go +++ b/cmd/vm/vm.go @@ -3,23 +3,23 @@ package vm import ( "github.com/charmbracelet/lipgloss" - "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) // Style aliases from shared var ( - repoNameStyle = shared.RepoNameStyle - successStyle = shared.SuccessStyle - errorStyle = shared.ErrorStyle - dimStyle = shared.DimStyle + repoNameStyle = cli.RepoNameStyle + successStyle = cli.SuccessStyle + errorStyle = cli.ErrorStyle + dimStyle = cli.DimStyle ) // VM-specific styles var ( - varStyle = lipgloss.NewStyle().Foreground(shared.ColourAmber500) - defaultStyle = lipgloss.NewStyle().Foreground(shared.ColourGray500).Italic(true) + varStyle = lipgloss.NewStyle().Foreground(cli.ColourAmber500) + defaultStyle = lipgloss.NewStyle().Foreground(cli.ColourGray500).Italic(true) ) // AddVMCommands adds container-related commands under 'vm' to the CLI. diff --git a/pkg/cli/runtime.go b/pkg/cli/runtime.go new file mode 100644 index 00000000..b86e2bac --- /dev/null +++ b/pkg/cli/runtime.go @@ -0,0 +1,198 @@ +// Package cli provides the CLI runtime and utilities. +// +// The CLI uses the Core framework for its own runtime, providing: +// - Global singleton access via cli.App() +// - Output service for styled terminal printing +// - Signal handling for graceful shutdown +// - Worker bundle spawning for commands +package cli + +import ( + "context" + "fmt" + "os" + "os/signal" + "sync" + "syscall" + + "github.com/host-uk/core/pkg/framework" +) + +var ( + instance *Runtime + once sync.Once +) + +// Runtime is the CLI's Core runtime. +type Runtime struct { + Core *framework.Core + ctx context.Context + cancel context.CancelFunc +} + +// RuntimeOptions configures the CLI runtime. +type RuntimeOptions struct { + // AppName is the CLI application name (used in output) + AppName string + // Version is the CLI version string + Version string +} + +// Init initialises the global CLI runtime. +// Call this once at startup (typically in main.go). +func Init(opts RuntimeOptions) error { + var initErr error + once.Do(func() { + ctx, cancel := context.WithCancel(context.Background()) + + core, err := framework.New( + framework.WithService(NewOutputService(OutputServiceOptions{ + AppName: opts.AppName, + })), + framework.WithService(NewSignalService(SignalServiceOptions{ + Cancel: cancel, + })), + framework.WithServiceLock(), + ) + if err != nil { + initErr = err + cancel() + return + } + + instance = &Runtime{ + Core: core, + ctx: ctx, + cancel: cancel, + } + + // Start services + if err := core.ServiceStartup(ctx, nil); err != nil { + initErr = err + return + } + }) + return initErr +} + +// App returns the global CLI runtime. +// Panics if Init() hasn't been called. +func App() *Runtime { + if instance == nil { + panic("cli.App() called before cli.Init()") + } + return instance +} + +// Context returns the CLI's root context. +// This context is cancelled on shutdown signals. +func (r *Runtime) Context() context.Context { + return r.ctx +} + +// Shutdown gracefully shuts down the CLI runtime. +func (r *Runtime) Shutdown() { + r.cancel() + r.Core.ServiceShutdown(r.ctx) +} + +// Output returns the output service for styled printing. +func (r *Runtime) Output() *OutputService { + return framework.MustServiceFor[*OutputService](r.Core, "output") +} + +// --- Output Service --- + +// OutputServiceOptions configures the output service. +type OutputServiceOptions struct { + AppName string +} + +// OutputService provides styled terminal output. +type OutputService struct { + *framework.ServiceRuntime[OutputServiceOptions] +} + +// NewOutputService creates an output service factory. +func NewOutputService(opts OutputServiceOptions) func(*framework.Core) (any, error) { + return func(c *framework.Core) (any, error) { + return &OutputService{ + ServiceRuntime: framework.NewServiceRuntime(c, opts), + }, nil + } +} + +// Success prints a success message with checkmark. +func (s *OutputService) Success(msg string) { + fmt.Println(SuccessStyle.Render(SymbolCheck + " " + msg)) +} + +// Error prints an error message with cross. +func (s *OutputService) Error(msg string) { + fmt.Println(ErrorStyle.Render(SymbolCross + " " + msg)) +} + +// Warning prints a warning message. +func (s *OutputService) Warning(msg string) { + fmt.Println(WarningStyle.Render(SymbolWarning + " " + msg)) +} + +// Info prints an info message. +func (s *OutputService) Info(msg string) { + fmt.Println(InfoStyle.Render(SymbolInfo + " " + msg)) +} + +// Title prints a title/header. +func (s *OutputService) Title(msg string) { + fmt.Println(TitleStyle.Render(msg)) +} + +// Dim prints dimmed/subtle text. +func (s *OutputService) Dim(msg string) { + fmt.Println(DimStyle.Render(msg)) +} + +// --- Signal Service --- + +// SignalServiceOptions configures the signal service. +type SignalServiceOptions struct { + Cancel context.CancelFunc +} + +// SignalService handles OS signals for graceful shutdown. +type SignalService struct { + *framework.ServiceRuntime[SignalServiceOptions] + sigChan chan os.Signal +} + +// NewSignalService creates a signal service factory. +func NewSignalService(opts SignalServiceOptions) func(*framework.Core) (any, error) { + return func(c *framework.Core) (any, error) { + return &SignalService{ + ServiceRuntime: framework.NewServiceRuntime(c, opts), + sigChan: make(chan os.Signal, 1), + }, nil + } +} + +// OnStartup starts listening for signals. +func (s *SignalService) OnStartup(ctx context.Context) error { + signal.Notify(s.sigChan, syscall.SIGINT, syscall.SIGTERM) + + go func() { + select { + case <-s.sigChan: + s.Opts().Cancel() + case <-ctx.Done(): + } + }() + + return nil +} + +// OnShutdown stops listening for signals. +func (s *SignalService) OnShutdown(ctx context.Context) error { + signal.Stop(s.sigChan) + close(s.sigChan) + return nil +} diff --git a/cmd/shared/styles.go b/pkg/cli/styles.go similarity index 99% rename from cmd/shared/styles.go rename to pkg/cli/styles.go index a804c00d..b22156d1 100644 --- a/cmd/shared/styles.go +++ b/pkg/cli/styles.go @@ -1,11 +1,11 @@ -// Package shared provides common utilities and styles for CLI commands. +// Package cli provides common utilities and styles for CLI commands. // // This package contains: // - Terminal styling using lipgloss with Tailwind colours // - Unicode symbols for consistent visual indicators // - Helper functions for common output patterns // - Git and GitHub CLI utilities -package shared +package cli import ( "fmt" diff --git a/cmd/shared/utils.go b/pkg/cli/utils.go similarity index 99% rename from cmd/shared/utils.go rename to pkg/cli/utils.go index 0dee4330..ee107c2d 100644 --- a/cmd/shared/utils.go +++ b/pkg/cli/utils.go @@ -1,4 +1,4 @@ -package shared +package cli import ( "context"