diff --git a/README.md b/README.md index 23d45c06..58d59144 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,26 @@ Core is an **opinionated Web3 desktop application framework** providing: **Mental model:** A secure, encrypted workspace manager where each "workspace" is a cryptographically isolated environment. The framework handles windows, menus, trays, config, and i18n. -## Quick Start +## CLI Quick Start + +```bash +# 1. Install Core +go install github.com/host-uk/core/cmd/core@latest + +# 2. Verify environment +core doctor + +# 3. Run tests in any Go/PHP project +core go test # or core php test + +# 4. Build and preview release +core build +core ci +``` + +For more details, see the [User Guide](docs/user-guide.md). + +## Framework Quick Start (Go) ```go import core "github.com/host-uk/core" @@ -344,6 +363,24 @@ Implementations: `local/`, `sftp/`, `webdav/` --- +## Getting Help + +- **[User Guide](docs/user-guide.md)**: Detailed usage and concepts. +- **[FAQ](docs/faq.md)**: Frequently asked questions. +- **[Workflows](docs/workflows.md)**: Common task sequences. +- **[Troubleshooting](docs/troubleshooting.md)**: Solving common issues. +- **[Configuration](docs/configuration.md)**: Config file reference. + +```bash +# Check environment +core doctor + +# Command help +core --help +``` + +--- + ## For New Contributors 1. Run `task test` to verify all tests pass diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 00000000..54ba99c1 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,97 @@ +# Frequently Asked Questions (FAQ) + +Common questions and answers about the Core CLI and Framework. + +## General + +### What is Core? + +Core is a unified CLI and framework for building and managing Go, PHP, and Wails applications. It provides an opinionated set of tools for development, testing, building, and releasing projects within the host-uk ecosystem. + +### Is Core a CLI or a Framework? + +It is both. The Core Framework (`pkg/core`) is a library for building Go desktop applications with Wails. The Core CLI (`cmd/core`) is the tool you use to manage projects, run tests, build binaries, and handle multi-repository workspaces. + +--- + +## Installation + +### How do I install the Core CLI? + +The recommended way is via Go: + +```bash +go install github.com/host-uk/core/cmd/core@latest +``` + +Ensure your Go bin directory is in your PATH. See [Getting Started](getting-started.md) for more options. + +### I get "command not found: core" after installation. + +This usually means your Go bin directory is not in your system's PATH. Add it by adding this to your shell profile (`.bashrc`, `.zshrc`, etc.): + +```bash +export PATH="$PATH:$(go env GOPATH)/bin" +``` + +--- + +## Usage + +### Why does `core ci` not publish anything by default? + +Core is designed to be **safe by default**. `core ci` runs in dry-run mode to show you what would be published. To actually publish a release, you must use the `--we-are-go-for-launch` flag: + +```bash +core ci --we-are-go-for-launch +``` + +### How do I run tests for only one package? + +You can pass standard Go test flags to `core go test`: + +```bash +core go test ./pkg/my-package +``` + +### What is `core doctor` for? + +`core doctor` checks your development environment to ensure all required tools (Go, Git, Docker, etc.) are installed and correctly configured. It's the first thing you should run if something isn't working. + +--- + +## Configuration + +### Where is Core's configuration stored? + +- **Project-specific**: In the `.core/` directory within your project root. +- **Global**: In `~/.core/` or as defined by `CORE_CONFIG`. +- **Registry**: The `repos.yaml` file defines the multi-repo workspace. + +### How do I change the build targets? + +You can specify targets in `.core/release.yaml` or use the `--targets` flag with the `core build` command: + +```bash +core build --targets linux/amd64,darwin/arm64 +``` + +--- + +## Workspaces and Registry + +### What is a "workspace" in Core? + +In the context of the CLI, a workspace is a directory containing multiple repositories defined in a `repos.yaml` file. The `core dev` commands allow you to manage status, commits, and synchronization across all repositories in the workspace at once. + +### What is `repos.yaml`? + +`repos.yaml` is the "registry" for your workspace. It lists the repositories, their types (foundation, module, product), and their dependencies. Core uses this file to know which repositories to clone during `core setup`. + +--- + +## See Also + +- [Getting Started](getting-started.md) - Installation and first steps +- [User Guide](user-guide.md) - Detailed usage information +- [Troubleshooting](troubleshooting.md) - Solving common issues diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index c075f3a8..e3c892eb 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -293,6 +293,30 @@ go mod download --- +## AI and Agentic Issues + +### "ANTHROPIC_API_KEY not set" + +**Cause:** You're trying to use `core ai` or `core dev commit` (which uses Claude for messages) without an API key. + +**Fix:** + +```bash +export ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxx +``` + +### "failed to connect to Agentic API" + +**Cause:** Network issues or incorrect `AGENTIC_BASE_URL`. + +**Fix:** + +1. Check your internet connection +2. If using a custom endpoint, verify `AGENTIC_BASE_URL` +3. Ensure you are authenticated if required: `export AGENTIC_TOKEN=xxxx` + +--- + ## Getting More Help ### Enable Verbose Output diff --git a/docs/user-guide.md b/docs/user-guide.md new file mode 100644 index 00000000..3820d9aa --- /dev/null +++ b/docs/user-guide.md @@ -0,0 +1,100 @@ +# User Guide + +This guide provides a comprehensive overview of how to use the Core CLI to manage your development workflow. + +## Key Concepts + +### Projects +A Project is a single repository containing code (Go, PHP, or Wails). Core helps you test, build, and release these projects using a consistent set of commands. + +### Workspaces +A Workspace is a collection of related projects. Core is designed to work across multiple repositories, allowing you to perform actions (like checking status or committing changes) on all of them at once. + +### Registry (`repos.yaml`) +The Registry is a configuration file that defines the repositories in your workspace. It includes information about where they are located on GitHub, their dependencies, and their purpose. + +--- + +## Daily Workflow + +### Working with a Single Project + +For a typical day-to-day development on a single project: + +1. **Verify your environment**: + ```bash + core doctor + ``` +2. **Run tests while you work**: + ```bash + core go test + ``` +3. **Keep code clean**: + ```bash + core go fmt --fix + core go lint + ``` +4. **Build and preview**: + ```bash + core build + ``` + +### Working with Multiple Repositories + +If you are working across many repositories in a workspace: + +1. **Check status of all repos**: + ```bash + core dev work --status + ``` +2. **Sync all changes**: + ```bash + core dev pull --all + ``` +3. **Commit and push everything**: + ```bash + core dev work + ``` + +--- + +## Building and Releasing + +Core separates the building of artifacts from the releasing of those artifacts. + +### 1. Build +The `core build` command detects your project type and builds binaries for your configured targets. Artifacts are placed in the `dist/` directory. + +### 2. Preview Release +Use `core ci` to see a summary of what would be included in a release (changelog, artifacts, etc.). This is a dry-run by default. + +### 3. Publish Release +When you are ready to publish to GitHub: +```bash +core ci --we-are-go-for-launch +``` + +--- + +## PHP and Laravel Development + +Core provides a unified development server for Laravel projects that orchestrates several services: + +```bash +core php dev +``` +This starts FrankenPHP, Vite, Horizon, Reverb, and Redis as configured in your `.core/php.yaml`. + +--- + +## Common Workflows + +For detailed examples of common end-to-end workflows, see the [Workflows](workflows.md) page. + +--- + +## Getting More Help + +- Use the `--help` flag with any command: `core build --help` +- Check the [FAQ](faq.md) for common questions. +- If you run into trouble, see the [Troubleshooting Guide](troubleshooting.md). diff --git a/internal/cmd/unifi/cmd_clients.go b/internal/cmd/unifi/cmd_clients.go index 69188ae9..3f453d7d 100644 --- a/internal/cmd/unifi/cmd_clients.go +++ b/internal/cmd/unifi/cmd_clients.go @@ -39,7 +39,7 @@ func runClients() error { return log.E("unifi.clients", "conflicting flags", errors.New("--wired and --wireless cannot both be set")) } - client, err := uf.NewFromConfig("", "", "", "") + client, err := uf.NewFromConfig("", "", "", "", nil) if err != nil { return log.E("unifi.clients", "failed to initialise client", err) } diff --git a/internal/cmd/unifi/cmd_config.go b/internal/cmd/unifi/cmd_config.go index ab00e1bf..762068b7 100644 --- a/internal/cmd/unifi/cmd_config.go +++ b/internal/cmd/unifi/cmd_config.go @@ -9,11 +9,12 @@ import ( // Config command flags. var ( - configURL string - configUser string - configPass string - configAPIKey string - configTest bool + configURL string + configUser string + configPass string + configAPIKey string + configInsecure bool + configTest bool ) // addConfigCommand adds the 'config' subcommand for UniFi connection setup. @@ -23,7 +24,7 @@ func addConfigCommand(parent *cli.Command) { Short: "Configure UniFi connection", Long: "Set the UniFi controller URL and credentials, or test the current connection.", RunE: func(cmd *cli.Command, args []string) error { - return runConfig() + return runConfig(cmd) }, } @@ -31,15 +32,21 @@ func addConfigCommand(parent *cli.Command) { cmd.Flags().StringVar(&configUser, "user", "", "UniFi username") cmd.Flags().StringVar(&configPass, "pass", "", "UniFi password") cmd.Flags().StringVar(&configAPIKey, "apikey", "", "UniFi API key") + cmd.Flags().BoolVar(&configInsecure, "insecure", false, "Allow insecure TLS connections (e.g. self-signed certs)") cmd.Flags().BoolVar(&configTest, "test", false, "Test the current connection") parent.AddCommand(cmd) } -func runConfig() error { +func runConfig(cmd *cli.Command) error { + var insecure *bool + if cmd.Flags().Changed("insecure") { + insecure = &configInsecure + } + // If setting values, save them first - if configURL != "" || configUser != "" || configPass != "" || configAPIKey != "" { - if err := uf.SaveConfig(configURL, configUser, configPass, configAPIKey); err != nil { + if configURL != "" || configUser != "" || configPass != "" || configAPIKey != "" || insecure != nil { + if err := uf.SaveConfig(configURL, configUser, configPass, configAPIKey, insecure); err != nil { return err } @@ -55,11 +62,14 @@ func runConfig() error { if configAPIKey != "" { cli.Success("UniFi API key saved") } + if insecure != nil { + cli.Success(fmt.Sprintf("UniFi insecure mode set to %v", *insecure)) + } } // If testing, verify the connection if configTest { - return runConfigTest() + return runConfigTest(cmd) } // If no flags, show current config @@ -71,7 +81,7 @@ func runConfig() error { } func showConfig() error { - url, user, pass, apikey, err := uf.ResolveConfig("", "", "", "") + url, user, pass, apikey, insecure, err := uf.ResolveConfig("", "", "", "", nil) if err != nil { return err } @@ -101,13 +111,20 @@ func showConfig() error { cli.Print(" %s %s\n", dimStyle.Render("API Key:"), warningStyle.Render("not set")) } + cli.Print(" %s %s\n", dimStyle.Render("Insecure:"), valueStyle.Render(fmt.Sprintf("%v", insecure))) + cli.Blank() return nil } -func runConfigTest() error { - client, err := uf.NewFromConfig(configURL, configUser, configPass, configAPIKey) +func runConfigTest(cmd *cli.Command) error { + var insecure *bool + if cmd.Flags().Changed("insecure") { + insecure = &configInsecure + } + + client, err := uf.NewFromConfig(configURL, configUser, configPass, configAPIKey, insecure) if err != nil { return err } diff --git a/internal/cmd/unifi/cmd_devices.go b/internal/cmd/unifi/cmd_devices.go index 9cbbbe4d..2f810c81 100644 --- a/internal/cmd/unifi/cmd_devices.go +++ b/internal/cmd/unifi/cmd_devices.go @@ -32,7 +32,7 @@ func addDevicesCommand(parent *cli.Command) { } func runDevices() error { - client, err := uf.NewFromConfig("", "", "", "") + client, err := uf.NewFromConfig("", "", "", "", nil) if err != nil { return log.E("unifi.devices", "failed to initialise client", err) } diff --git a/internal/cmd/unifi/cmd_networks.go b/internal/cmd/unifi/cmd_networks.go index 67fc2c4f..9196fc94 100644 --- a/internal/cmd/unifi/cmd_networks.go +++ b/internal/cmd/unifi/cmd_networks.go @@ -30,7 +30,7 @@ func addNetworksCommand(parent *cli.Command) { } func runNetworks() error { - client, err := uf.NewFromConfig("", "", "", "") + client, err := uf.NewFromConfig("", "", "", "", nil) if err != nil { return log.E("unifi.networks", "failed to initialise client", err) } diff --git a/internal/cmd/unifi/cmd_routes.go b/internal/cmd/unifi/cmd_routes.go index e217c800..a6895a77 100644 --- a/internal/cmd/unifi/cmd_routes.go +++ b/internal/cmd/unifi/cmd_routes.go @@ -32,7 +32,7 @@ func addRoutesCommand(parent *cli.Command) { } func runRoutes() error { - client, err := uf.NewFromConfig("", "", "", "") + client, err := uf.NewFromConfig("", "", "", "", nil) if err != nil { return log.E("unifi.routes", "failed to initialise client", err) } diff --git a/internal/cmd/unifi/cmd_sites.go b/internal/cmd/unifi/cmd_sites.go index b55df2d5..b7eace47 100644 --- a/internal/cmd/unifi/cmd_sites.go +++ b/internal/cmd/unifi/cmd_sites.go @@ -21,7 +21,7 @@ func addSitesCommand(parent *cli.Command) { } func runSites() error { - client, err := uf.NewFromConfig("", "", "", "") + client, err := uf.NewFromConfig("", "", "", "", nil) if err != nil { return log.E("unifi.sites", "failed to initialise client", err) } diff --git a/mkdocs.yml b/mkdocs.yml index e1ac966a..1f88fd14 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,6 +43,26 @@ markdown_extensions: nav: - Home: index.md + - User Documentation: + - User Guide: user-guide.md + - FAQ: faq.md + - Troubleshooting: troubleshooting.md + - Workflows: workflows.md + - CLI Reference: + - Overview: cmd/index.md + - AI: cmd/ai/index.md + - Build: cmd/build/index.md + - CI: cmd/ci/index.md + - Dev: cmd/dev/index.md + - Go: cmd/go/index.md + - PHP: cmd/php/index.md + - SDK: cmd/sdk/index.md + - Setup: cmd/setup/index.md + - Doctor: cmd/doctor/index.md + - Test: cmd/test/index.md + - VM: cmd/vm/index.md + - Pkg: cmd/pkg/index.md + - Docs: cmd/docs/index.md - Getting Started: - Installation: getting-started/installation.md - Quick Start: getting-started/quickstart.md @@ -77,3 +97,14 @@ nav: - API Reference: - Core: api/core.md - Display: api/display.md + - Development: + - Package Standards: pkg/PACKAGE_STANDARDS.md + - Internationalization: + - Overview: pkg/i18n/README.md + - Grammar: pkg/i18n/GRAMMAR.md + - Extending: pkg/i18n/EXTENDING.md + - Claude Skill: skill/index.md + - Reference: + - Configuration: configuration.md + - Migration: migration.md + - Glossary: glossary.md diff --git a/pkg/unifi/client.go b/pkg/unifi/client.go index 0a6c61fe..13b15d34 100644 --- a/pkg/unifi/client.go +++ b/pkg/unifi/client.go @@ -16,8 +16,8 @@ type Client struct { } // New creates a new UniFi API client for the given controller URL and credentials. -// TLS verification is disabled by default (self-signed certs on home lab controllers). -func New(url, user, pass, apikey string) (*Client, error) { +// TLS verification can be disabled via the insecure parameter (useful for self-signed certs on home lab controllers). +func New(url, user, pass, apikey string, insecure bool) (*Client, error) { cfg := &uf.Config{ URL: url, User: user, @@ -25,11 +25,11 @@ func New(url, user, pass, apikey string) (*Client, error) { APIKey: apikey, } - // Skip TLS verification for self-signed certs + // Skip TLS verification if requested (e.g. for self-signed certs) httpClient := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, //nolint:gosec + InsecureSkipVerify: insecure, MinVersion: tls.VersionTLS12, }, }, diff --git a/pkg/unifi/config.go b/pkg/unifi/config.go index bab65987..0eac2d2f 100644 --- a/pkg/unifi/config.go +++ b/pkg/unifi/config.go @@ -24,6 +24,8 @@ const ( ConfigKeyPass = "unifi.pass" // ConfigKeyAPIKey is the config key for the UniFi API key. ConfigKeyAPIKey = "unifi.apikey" + // ConfigKeyInsecure is the config key for allowing insecure TLS connections. + ConfigKeyInsecure = "unifi.insecure" // DefaultURL is the default UniFi controller URL. DefaultURL = "https://10.69.1.1" @@ -32,10 +34,10 @@ const ( // NewFromConfig creates a UniFi client using the standard config resolution: // // 1. ~/.core/config.yaml keys: unifi.url, unifi.user, unifi.pass, unifi.apikey -// 2. UNIFI_URL + UNIFI_USER + UNIFI_PASS + UNIFI_APIKEY environment variables (override config file) +// 2. UNIFI_URL + UNIFI_USER + UNIFI_PASS + UNIFI_APIKEY + UNIFI_INSECURE environment variables (override config file) // 3. Provided flag overrides (highest priority; pass empty to skip) -func NewFromConfig(flagURL, flagUser, flagPass, flagAPIKey string) (*Client, error) { - url, user, pass, apikey, err := ResolveConfig(flagURL, flagUser, flagPass, flagAPIKey) +func NewFromConfig(flagURL, flagUser, flagPass, flagAPIKey string, flagInsecure *bool) (*Client, error) { + url, user, pass, apikey, insecure, err := ResolveConfig(flagURL, flagUser, flagPass, flagAPIKey, flagInsecure) if err != nil { return nil, err } @@ -44,12 +46,12 @@ func NewFromConfig(flagURL, flagUser, flagPass, flagAPIKey string) (*Client, err return nil, log.E("unifi.NewFromConfig", "no credentials configured (set UNIFI_USER/UNIFI_PASS or UNIFI_APIKEY, or run: core unifi config)", nil) } - return New(url, user, pass, apikey) + return New(url, user, pass, apikey, insecure) } // ResolveConfig resolves the UniFi URL and credentials from all config sources. // Flag values take highest priority, then env vars, then config file. -func ResolveConfig(flagURL, flagUser, flagPass, flagAPIKey string) (url, user, pass, apikey string, err error) { +func ResolveConfig(flagURL, flagUser, flagPass, flagAPIKey string, flagInsecure *bool) (url, user, pass, apikey string, insecure bool, err error) { // Start with config file values cfg, cfgErr := config.New() if cfgErr == nil { @@ -57,6 +59,7 @@ func ResolveConfig(flagURL, flagUser, flagPass, flagAPIKey string) (url, user, p _ = cfg.Get(ConfigKeyUser, &user) _ = cfg.Get(ConfigKeyPass, &pass) _ = cfg.Get(ConfigKeyAPIKey, &apikey) + _ = cfg.Get(ConfigKeyInsecure, &insecure) } // Overlay environment variables @@ -72,6 +75,9 @@ func ResolveConfig(flagURL, flagUser, flagPass, flagAPIKey string) (url, user, p if envAPIKey := os.Getenv("UNIFI_APIKEY"); envAPIKey != "" { apikey = envAPIKey } + if envInsecure := os.Getenv("UNIFI_INSECURE"); envInsecure != "" { + insecure = envInsecure == "true" || envInsecure == "1" + } // Overlay flag values (highest priority) if flagURL != "" { @@ -86,17 +92,20 @@ func ResolveConfig(flagURL, flagUser, flagPass, flagAPIKey string) (url, user, p if flagAPIKey != "" { apikey = flagAPIKey } + if flagInsecure != nil { + insecure = *flagInsecure + } // Default URL if nothing configured if url == "" { url = DefaultURL } - return url, user, pass, apikey, nil + return url, user, pass, apikey, insecure, nil } // SaveConfig persists the UniFi URL and/or credentials to the config file. -func SaveConfig(url, user, pass, apikey string) error { +func SaveConfig(url, user, pass, apikey string, insecure *bool) error { cfg, err := config.New() if err != nil { return log.E("unifi.SaveConfig", "failed to load config", err) @@ -126,5 +135,11 @@ func SaveConfig(url, user, pass, apikey string) error { } } + if insecure != nil { + if err := cfg.Set(ConfigKeyInsecure, *insecure); err != nil { + return log.E("unifi.SaveConfig", "failed to save insecure flag", err) + } + } + return nil }