molecule-core/platform/cmd/cli/cmd_api.go
Hongming Wang 24fec62d7f initial commit — Molecule AI platform
Forked clean from public hackathon repo (Starfire-AgentTeam, BSL 1.1)
with full rebrand to Molecule AI under github.com/Molecule-AI/molecule-monorepo.

Brand: Starfire → Molecule AI.
Slug: starfire / agent-molecule → molecule.
Env vars: STARFIRE_* → MOLECULE_*.
Go module: github.com/agent-molecule/platform → github.com/Molecule-AI/molecule-monorepo/platform.
Python packages: starfire_plugin → molecule_plugin, starfire_agent → molecule_agent.
DB: agentmolecule → molecule.

History truncated; see public repo for prior commits and contributor
attribution. Verified green: go test -race ./... (platform), pytest
(workspace-template 1129 + sdk 132), vitest (canvas 352), build (mcp).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:55:37 -07:00

87 lines
2.6 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/spf13/cobra"
)
// buildAPICmd is the generic escape-hatch — hits any platform endpoint.
// Covers 100% of the platform surface without needing per-endpoint typed
// commands. Typed subcommands (plugin, secret, schedule, etc.) layer on
// top for operator ergonomics.
//
// Usage:
//
// molecli api GET /workspaces
// molecli api POST /workspaces '{"name":"x","tier":1}'
// molecli api PATCH /workspaces/ws-123 '{"role":"Reviewer"}'
// molecli api DELETE /workspaces/ws-123?confirm=true
func buildAPICmd() *cobra.Command {
return &cobra.Command{
Use: "api <METHOD> <PATH> [json-body]",
Short: "Call any platform API endpoint directly (raw escape hatch)",
Long: `Raw HTTP call against the platform API. Useful for endpoints that don't yet have typed subcommands.
The body is optional — pass it as the third argument (must be valid JSON).
Use --json to force JSON output (default prints whatever the server returned).`,
Args: cobra.RangeArgs(2, 3),
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
method := strings.ToUpper(args[0])
path := args[1]
var body io.Reader
if len(args) == 3 {
// Validate that it parses as JSON before sending.
var probe interface{}
if err := json.Unmarshal([]byte(args[2]), &probe); err != nil {
return fmt.Errorf("body is not valid JSON: %w", err)
}
body = bytes.NewBufferString(args[2])
}
base := baseURL()
if _, err := url.Parse(base + path); err != nil {
return fmt.Errorf("invalid URL %s%s: %w", base, path, err)
}
req, err := http.NewRequest(method, base+path, body)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 2 * time.Minute}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
out, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode >= 400 {
fmt.Fprintf(os.Stderr, "http %d\n", resp.StatusCode)
}
// Pretty-print when we got JSON back; fall through to raw bytes otherwise.
if flagJSON || strings.HasPrefix(strings.TrimSpace(string(out)), "{") || strings.HasPrefix(strings.TrimSpace(string(out)), "[") {
var v interface{}
if err := json.Unmarshal(out, &v); err == nil {
return printJSON(v)
}
}
fmt.Println(string(out))
if resp.StatusCode >= 400 {
return fmt.Errorf("non-2xx status: %d", resp.StatusCode)
}
return nil
},
}
}