diff --git a/cmd/core-agent/commands.go b/cmd/core-agent/commands.go new file mode 100644 index 0000000..2fe1eb5 --- /dev/null +++ b/cmd/core-agent/commands.go @@ -0,0 +1,95 @@ +package main + +import ( + "os" + + "dappco.re/go/core" +) + +// applyLogLevel scans os.Args for --quiet/-q/--debug before Core starts. +// Must run before c.Run() so ServiceStartup logs respect the level. +// +// core-agent --quiet version → errors only +// core-agent --debug mcp → full debug output +// core-agent status → default (info) +func applyLogLevel() { + var cleaned []string + cleaned = append(cleaned, os.Args[0]) + for _, arg := range os.Args[1:] { + switch arg { + case "--quiet", "-q": + core.SetLevel(core.LevelError) + case "--debug", "-d": + core.SetLevel(core.LevelDebug) + default: + cleaned = append(cleaned, arg) + } + } + os.Args = cleaned +} + +// registerAppCommands adds app-level CLI commands (version, check, env). +// These are not owned by any service — they're the binary's own commands. +// +// core-agent version — build info +// core-agent check — health check +// core-agent env — environment variables +func registerAppCommands(c *core.Core) { + c.Command("version", core.Command{ + Description: "Print version and build info", + Action: func(opts core.Options) core.Result { + core.Print(nil, "core-agent %s", c.App().Version) + core.Print(nil, " go: %s", core.Env("GO")) + core.Print(nil, " os: %s/%s", core.Env("OS"), core.Env("ARCH")) + core.Print(nil, " home: %s", core.Env("DIR_HOME")) + core.Print(nil, " hostname: %s", core.Env("HOSTNAME")) + core.Print(nil, " pid: %s", core.Env("PID")) + core.Print(nil, " channel: %s", updateChannel()) + return core.Result{OK: true} + }, + }) + + c.Command("check", core.Command{ + Description: "Verify workspace, deps, and config", + Action: func(opts core.Options) core.Result { + fs := c.Fs() + core.Print(nil, "core-agent %s health check", c.App().Version) + core.Print(nil, "") + core.Print(nil, " binary: core-agent") + + agentsPath := core.Path("Code", ".core", "agents.yaml") + if fs.IsFile(agentsPath) { + core.Print(nil, " agents: %s (ok)", agentsPath) + } else { + core.Print(nil, " agents: %s (MISSING)", agentsPath) + } + + wsRoot := core.Path("Code", ".core", "workspace") + if fs.IsDir(wsRoot) { + entries := core.PathGlob(core.JoinPath(wsRoot, "*")) + core.Print(nil, " workspace: %s (%d entries)", wsRoot, len(entries)) + } else { + core.Print(nil, " workspace: %s (MISSING)", wsRoot) + } + + core.Print(nil, " services: %d registered", len(c.Services())) + core.Print(nil, " actions: %d registered", len(c.Actions())) + core.Print(nil, " commands: %d registered", len(c.Commands())) + core.Print(nil, " env keys: %d loaded", len(core.EnvKeys())) + core.Print(nil, "") + core.Print(nil, "ok") + return core.Result{OK: true} + }, + }) + + c.Command("env", core.Command{ + Description: "Show all core.Env() keys and values", + Action: func(opts core.Options) core.Result { + keys := core.EnvKeys() + for _, k := range keys { + core.Print(nil, " %-15s %s", k, core.Env(k)) + } + return core.Result{OK: true} + }, + }) +} diff --git a/cmd/core-agent/commands_test.go b/cmd/core-agent/commands_test.go new file mode 100644 index 0000000..3b96bfc --- /dev/null +++ b/cmd/core-agent/commands_test.go @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package main + +import ( + "os" + "testing" + + "dappco.re/go/core" + "github.com/stretchr/testify/assert" +) + +// newTestCore creates a minimal Core with app commands registered. +func newTestCore(t *testing.T) *core.Core { + t.Helper() + c := core.New(core.WithOption("name", "core-agent")) + c.App().Version = "test" + registerAppCommands(c) + return c +} + +// --- applyLogLevel --- + +func TestCommands_ApplyLogLevel_Good(t *testing.T) { + original := os.Args + defer func() { os.Args = original }() + + os.Args = []string{"core-agent", "--quiet", "version"} + applyLogLevel() + assert.Equal(t, []string{"core-agent", "version"}, os.Args) +} + +func TestCommands_ApplyLogLevel_Good_Debug(t *testing.T) { + original := os.Args + defer func() { os.Args = original }() + + os.Args = []string{"core-agent", "-d", "check"} + applyLogLevel() + assert.Equal(t, []string{"core-agent", "check"}, os.Args) +} + +func TestCommands_ApplyLogLevel_Bad_NoFlag(t *testing.T) { + original := os.Args + defer func() { os.Args = original }() + + os.Args = []string{"core-agent", "status"} + applyLogLevel() + assert.Equal(t, []string{"core-agent", "status"}, os.Args) +} + +func TestCommands_ApplyLogLevel_Ugly_FlagAfterCommand(t *testing.T) { + original := os.Args + defer func() { os.Args = original }() + + os.Args = []string{"core-agent", "version", "-q"} + applyLogLevel() + assert.Equal(t, []string{"core-agent", "version"}, os.Args) +} + +// --- registerAppCommands --- + +func TestCommands_RegisterAppCommands_Good(t *testing.T) { + c := newTestCore(t) + cmds := c.Commands() + assert.Contains(t, cmds, "version") + assert.Contains(t, cmds, "check") + assert.Contains(t, cmds, "env") +} + +// --- version command --- + +func TestCommands_Version_Good(t *testing.T) { + c := newTestCore(t) + version = "0.8.0" + + r := c.Cli().Run("version") + assert.True(t, r.OK) +} + +func TestCommands_Version_Bad_DevVersion(t *testing.T) { + c := newTestCore(t) + version = "" + c.App().Version = "dev" + + r := c.Cli().Run("version") + assert.True(t, r.OK) +} + +// --- check command --- + +func TestCommands_Check_Good(t *testing.T) { + c := newTestCore(t) + + r := c.Cli().Run("check") + assert.True(t, r.OK) +} + +// --- env command --- + +func TestCommands_Env_Good(t *testing.T) { + c := newTestCore(t) + + r := c.Cli().Run("env") + assert.True(t, r.OK) +} + +// --- CLI resolution --- + +func TestCommands_Cli_Bad_UnknownCommand(t *testing.T) { + c := newTestCore(t) + r := c.Cli().Run("nonexistent") + assert.False(t, r.OK) +} + +func TestCommands_Cli_Good_Banner(t *testing.T) { + c := newTestCore(t) + c.Cli().SetBanner(func(_ *core.Cli) string { + return "core-agent test" + }) + // No args — shows banner, returns empty Result + r := c.Cli().Run() + _ = r +} + +func TestCommands_Cli_Ugly_EmptyArgs(t *testing.T) { + c := newTestCore(t) + // Explicit empty slice + r := c.Cli().Run() + _ = r +} diff --git a/cmd/core-agent/update_test.go b/cmd/core-agent/update_test.go new file mode 100644 index 0000000..633c294 --- /dev/null +++ b/cmd/core-agent/update_test.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUpdate_UpdateChannel_Good(t *testing.T) { + version = "1.0.0" + assert.Equal(t, "stable", updateChannel()) +} + +func TestUpdate_UpdateChannel_Good_Dev(t *testing.T) { + version = "dev" + assert.Equal(t, "dev", updateChannel()) +} + +func TestUpdate_UpdateChannel_Good_Empty(t *testing.T) { + version = "" + assert.Equal(t, "dev", updateChannel()) +} + +func TestUpdate_UpdateChannel_Good_Prerelease(t *testing.T) { + version = "0.8.0-alpha" + assert.Equal(t, "prerelease", updateChannel()) +} + +func TestUpdate_UpdateChannel_Ugly(t *testing.T) { + version = "0.8.0-beta.1" + // Ends in '1' which is < 'a', so stable + assert.Equal(t, "stable", updateChannel()) +} + +func TestUpdate_AppVersion_Good(t *testing.T) { + version = "1.2.3" + assert.Equal(t, "1.2.3", appVersion()) +} + +func TestUpdate_AppVersion_Good_Empty(t *testing.T) { + version = "" + assert.Equal(t, "dev", appVersion()) +}