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:
Mathias Bergqvist
2026-05-03 21:27:33 +02:00
parent 5c88eff46f
commit 3c4e8e8bb8
4 changed files with 122 additions and 3 deletions

View File

@@ -25,7 +25,7 @@ func notYet(ctx context.Context, args []string, stdin io.Reader, stdout, stderr
func subcommands() map[string]subcommand {
return map[string]subcommand{
"tier": notYet,
"tier": runTier,
"brain": notYet,
"mode": notYet,
}

View File

@@ -35,8 +35,8 @@ func TestDispatch_UnknownSubcommand_ReturnsTwo(t *testing.T) {
func TestDispatch_KnownSubcommand_RoutesAndReturnsExitCode(t *testing.T) {
var out, errBuf bytes.Buffer
code := dispatch(context.Background(), []string{"tier"}, strings.NewReader(""), &out, &errBuf)
// At Task 1 time, tier returns the not-implemented error → exit 1.
code := dispatch(context.Background(), []string{"brain"}, strings.NewReader(""), &out, &errBuf)
// At this point, brain still returns the not-implemented error → exit 1.
assert.Equal(t, 1, code)
assert.Contains(t, errBuf.String(), "not implemented")
}

42
cmd/hyperguild/tier.go Normal file
View 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
}

View 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)
}