feat(hyperguild): tier subcommand
Adds the tier subcommand to the hyperguild CLI. Reuses internal/tier.Detect verbatim, sources probe URLs from ANTHROPIC_PROBE_URL (default https://api.anthropic.com) and LITELLM_BASE_URL (no default — empty triggers airplane). Human-readable output by default; --json emits the same Info struct as the supervisor's tier MCP returns. Tests cover all three tier states via httptest fakes.
This commit is contained in:
@@ -25,7 +25,7 @@ func notYet(ctx context.Context, args []string, stdin io.Reader, stdout, stderr
|
|||||||
|
|
||||||
func subcommands() map[string]subcommand {
|
func subcommands() map[string]subcommand {
|
||||||
return map[string]subcommand{
|
return map[string]subcommand{
|
||||||
"tier": notYet,
|
"tier": runTier,
|
||||||
"brain": notYet,
|
"brain": notYet,
|
||||||
"mode": notYet,
|
"mode": notYet,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ func TestDispatch_UnknownSubcommand_ReturnsTwo(t *testing.T) {
|
|||||||
|
|
||||||
func TestDispatch_KnownSubcommand_RoutesAndReturnsExitCode(t *testing.T) {
|
func TestDispatch_KnownSubcommand_RoutesAndReturnsExitCode(t *testing.T) {
|
||||||
var out, errBuf bytes.Buffer
|
var out, errBuf bytes.Buffer
|
||||||
code := dispatch(context.Background(), []string{"tier"}, strings.NewReader(""), &out, &errBuf)
|
code := dispatch(context.Background(), []string{"brain"}, strings.NewReader(""), &out, &errBuf)
|
||||||
// At Task 1 time, tier returns the not-implemented error → exit 1.
|
// At this point, brain still returns the not-implemented error → exit 1.
|
||||||
assert.Equal(t, 1, code)
|
assert.Equal(t, 1, code)
|
||||||
assert.Contains(t, errBuf.String(), "not implemented")
|
assert.Contains(t, errBuf.String(), "not implemented")
|
||||||
}
|
}
|
||||||
|
|||||||
42
cmd/hyperguild/tier.go
Normal file
42
cmd/hyperguild/tier.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mathiasbq/supervisor/internal/tier"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultAnthropicProbe = "https://api.anthropic.com"
|
||||||
|
|
||||||
|
func runTier(ctx context.Context, args []string, _ io.Reader, stdout, stderr io.Writer) error {
|
||||||
|
fs := flag.NewFlagSet("tier", flag.ContinueOnError)
|
||||||
|
fs.SetOutput(stderr)
|
||||||
|
asJSON := fs.Bool("json", false, "output JSON instead of human-readable")
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return fmt.Errorf("parse flags: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
anthropicURL := os.Getenv("ANTHROPIC_PROBE_URL")
|
||||||
|
if anthropicURL == "" {
|
||||||
|
anthropicURL = defaultAnthropicProbe
|
||||||
|
}
|
||||||
|
liteLLMURL := os.Getenv("LITELLM_BASE_URL") // empty → tier falls through to airplane
|
||||||
|
|
||||||
|
info := tier.Detect(ctx, anthropicURL, liteLLMURL)
|
||||||
|
|
||||||
|
if *asJSON {
|
||||||
|
enc := json.NewEncoder(stdout)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
if err := enc.Encode(info); err != nil {
|
||||||
|
return fmt.Errorf("encode json: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Fprintf(stdout, "tier %d (%s) managed_agents=%t\n", int(info.Tier), info.Label, info.ManagedAgents) //nolint:errcheck
|
||||||
|
return nil
|
||||||
|
}
|
||||||
77
cmd/hyperguild/tier_test.go
Normal file
77
cmd/hyperguild/tier_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func okServer(t *testing.T) *httptest.Server {
|
||||||
|
t.Helper()
|
||||||
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunTier_Full_Human(t *testing.T) {
|
||||||
|
anthropic := okServer(t)
|
||||||
|
defer anthropic.Close()
|
||||||
|
litellm := okServer(t)
|
||||||
|
defer litellm.Close()
|
||||||
|
|
||||||
|
t.Setenv("ANTHROPIC_PROBE_URL", anthropic.URL)
|
||||||
|
t.Setenv("LITELLM_BASE_URL", litellm.URL)
|
||||||
|
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
err := runTier(context.Background(), []string{}, strings.NewReader(""), &out, &errBuf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, out.String(), "tier 1")
|
||||||
|
assert.Contains(t, out.String(), "full-online")
|
||||||
|
assert.Contains(t, out.String(), "managed_agents=true")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunTier_LANOnly_JSON(t *testing.T) {
|
||||||
|
litellm := okServer(t)
|
||||||
|
defer litellm.Close()
|
||||||
|
|
||||||
|
t.Setenv("ANTHROPIC_PROBE_URL", "http://127.0.0.1:1") // unreachable
|
||||||
|
t.Setenv("LITELLM_BASE_URL", litellm.URL)
|
||||||
|
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
err := runTier(context.Background(), []string{"--json"}, strings.NewReader(""), &out, &errBuf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var got struct {
|
||||||
|
Tier int `json:"tier"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
ManagedAgents bool `json:"managed_agents"`
|
||||||
|
}
|
||||||
|
require.NoError(t, json.Unmarshal(out.Bytes(), &got))
|
||||||
|
assert.Equal(t, 2, got.Tier)
|
||||||
|
assert.Equal(t, "lan-only", got.Label)
|
||||||
|
assert.False(t, got.ManagedAgents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunTier_Airplane_NoLiteLLMBaseURL(t *testing.T) {
|
||||||
|
t.Setenv("ANTHROPIC_PROBE_URL", "http://127.0.0.1:1")
|
||||||
|
t.Setenv("LITELLM_BASE_URL", "")
|
||||||
|
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
err := runTier(context.Background(), []string{}, strings.NewReader(""), &out, &errBuf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, out.String(), "tier 3")
|
||||||
|
assert.Contains(t, out.String(), "airplane")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunTier_UnknownFlag_ReturnsError(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
err := runTier(context.Background(), []string{"--bogus"}, strings.NewReader(""), &out, &errBuf)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user