feat(hyperguild): mode subcommand
'hyperguild mode <cloud|client-local|sovereign>' writes a per-mode
.mcp.json template:
- cloud: brain MCP only
- client-local: brain + routing placeholder with _routing_pending
pointer to Plan 6
- sovereign: brain only + top-level _mode_note explaining Crush
is primary; .mcp.json is Claude Code fallback
Default output is ./.mcp.json; --out overrides; --force overwrites.
Brain URL sourced from BRAIN_URL (default http://koala:30330) so the
template stays in lockstep with the user's brain host.
All three subcommands now wired; notYet/errNotImplemented removed
from main.go.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func runMode(ctx context.Context, args []string, _ io.Reader, stdout, stderr io.Writer) error {
|
||||
fs := flag.NewFlagSet("mode", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
out := fs.String("out", ".mcp.json", "output file path")
|
||||
force := fs.Bool("force", false, "overwrite an existing file")
|
||||
// Pull the first positional (mode name) out so flags after it still parse
|
||||
// with stdlib flag (which stops at the first non-flag arg).
|
||||
if len(args) < 1 {
|
||||
return errors.New("mode: name required (cloud|client-local|sovereign)")
|
||||
}
|
||||
name := args[0]
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return fmt.Errorf("parse flags: %w", err)
|
||||
}
|
||||
|
||||
brainURL := os.Getenv("BRAIN_URL")
|
||||
if brainURL == "" {
|
||||
brainURL = defaultBrainURL
|
||||
}
|
||||
|
||||
var doc map[string]any
|
||||
switch name {
|
||||
case "cloud":
|
||||
doc = modeCloud(brainURL)
|
||||
case "client-local":
|
||||
doc = modeClientLocal(brainURL)
|
||||
case "sovereign":
|
||||
doc = modeSovereign(brainURL)
|
||||
default:
|
||||
return fmt.Errorf("mode: unknown mode: %s (expected cloud|client-local|sovereign)", name)
|
||||
}
|
||||
|
||||
if !*force {
|
||||
if _, err := os.Stat(*out); err == nil {
|
||||
return fmt.Errorf("mode: %s exists (use --force to overwrite)", *out)
|
||||
}
|
||||
}
|
||||
|
||||
body, err := json.MarshalIndent(doc, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal mode doc: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(*out, append(body, '\n'), 0o644); err != nil {
|
||||
return fmt.Errorf("write %s: %w", *out, err)
|
||||
}
|
||||
fmt.Fprintf(stdout, "wrote %s (mode: %s)\n", *out, name) //nolint:errcheck
|
||||
return nil
|
||||
}
|
||||
|
||||
func modeCloud(brainURL string) map[string]any {
|
||||
return map[string]any{
|
||||
"mcpServers": map[string]any{
|
||||
"brain": map[string]any{
|
||||
"url": brainURL + "/mcp",
|
||||
"description": "Brain MCP — knowledge query, write, ingestion, session log",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func modeClientLocal(brainURL string) map[string]any {
|
||||
return map[string]any{
|
||||
"mcpServers": map[string]any{
|
||||
"brain": map[string]any{
|
||||
"url": brainURL + "/mcp",
|
||||
"description": "Brain MCP — knowledge query, write, ingestion, session log",
|
||||
},
|
||||
"routing": map[string]any{
|
||||
"url": "http://koala:30310/mcp",
|
||||
"description": "Mode 2 routing pod — routes skill calls to LiteLLM/local",
|
||||
"_routing_pending": "Plan 6 — routing pod not deployed yet; this URL is a placeholder",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func modeSovereign(brainURL string) map[string]any {
|
||||
return map[string]any{
|
||||
"_mode_note": "Sovereign mode primarily uses Crush + LiteLLM. This .mcp.json is provided as Claude Code fallback (e.g. emergency offline editing).",
|
||||
"mcpServers": map[string]any{
|
||||
"brain": map[string]any{
|
||||
"url": brainURL + "/mcp",
|
||||
"description": "Brain MCP — knowledge query, write, ingestion, session log",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user