package provider // PLUGIN-SERVED (see AGENTS.md): Command Code's plan ("commandcode-plan") is // a deprecated built-in subscription served by its plugin, // @magpie-community/opencode-commandcode-auth, once moved onto it // (provider.Moved; the default for a new sign-in). A moved one's sign-ins, // models, requests and usage are all the plugin's, never this code's (only // the move, in migrate*.go, still reads its accounts). A fix here alone // doesn't reach those users; fix the plugin // (github.com/magpie-community/plugins, packages/commandcode) and raise the // mover's min in internal/provider/migrate_side.go. // A Command Code subscription is a commandcode.ai plan (Pro, GOAT, Max, // Ultra — every one but Go comes with API access). Signing in to it, as // `cmd auth login` does, mints an API key for the account; that key is // served on Command Code's Provider API (/provider/v1: Chat, Responses and // Anthropic's Messages) and billed against the plan's own credits and its // 5-hour and weekly limits, not as pay-as-you-go. // // Go has no API access: its key is only taken where the CLI itself asks, // POST /alpha/generate, in the CLI's own format (the gateway's // commandcode.go). Which plan an account is on is asked of billing/ // subscriptions, and kept a while (cmdPlanNow). // // The CLI's own account is read, never changed, from ~/.commandcode/ // auth.json. Further accounts are signed in by magpie with the CLI's own // browser sign-in: commandcode.ai/studio/auth/cli sends the new key to a // callback on this machine, which answers as the CLI's does; their keys // are kept in logins.json. // // Its id is commandcode-plan: "commandcode" is the keyed preset's, which a // provider added with a key from Studio may already have. import ( "context" "encoding/json" "errors" "fmt" "io" "net" "net/http" "net/url" "os" "path/filepath" "strings" "sync" "time" "github.com/yetone/magpie/internal/catalog" ) // CommandCodePlanID is the subscription's id, and its sign-in's. const CommandCodePlanID = "commandcode-plan" // CommandCodeMaxOutput is the most max_tokens Command Code takes at // /alpha/generate, for any model: more is refused ("Too big: expected // number to be <=200000 at params.max_tokens"). models.dev gives some of // its models more (DeepSeek V4's 384000, MiniMax M3's 512000). const CommandCodeMaxOutput = 200_000 // CommandCodeOutputOf is the most a reply of a Command Code model may be // asked for: the model's own limit, as models.dev gives it, within // CommandCodeMaxOutput. func CommandCodeOutputOf(model string) int { if n := catalog.OutputOf(model); n > 0 && n < CommandCodeMaxOutput { return n } return CommandCodeMaxOutput } // Where Command Code's API and its sign-in page are; vars so tests can // point them elsewhere. var ( cmdAPI = "https://api.commandcode.ai" cmdStudio = "https://commandcode.ai" ) // cmdModels are the plan's best-known models, before its list is fetched. var cmdModels = []catalog.Model{ {ID: "claude-sonnet-5", Name: "Claude Sonnet 5", Context: 1_000_000}, {ID: "claude-opus-5-5", Name: "Claude Opus 5.5", Context: 1_000_000}, {ID: "gpt-6-sol", Name: "GPT-6 Sol", Context: 1_050_000}, {ID: "deepseek/deepseek-v4-pro", Name: "DeepSeek V4 Pro", Context: 1_000_000}, {ID: "deepseek/deepseek-v4-flash", Name: "DeepSeek V4 Flash", Context: 1_000_000}, {ID: "moonshotai/Kimi-K3", Name: "Kimi K3", Context: 1_000_000}, {ID: "zai-org/GLM-5.3", Name: "GLM-5.3", Context: 1_000_000}, {ID: "MiniMaxAI/MiniMax-M3", Name: "MiniMax M3", Context: 1_000_000}, } // cmdGoModels are the models the Go plan is let use, as the CLI's own // table has it (1.73.0): every model its picker shows (wD, less the // hidden) but the "premium" ones and those it blocks for Go // (cmdGoRefused). They stand in until Command Code's list is fetched // (cmdGoFetch), and give that list the reasoning levels and pictures it // doesn't say. var cmdGoModels = []catalog.Model{ {ID: "gpt-6-luna", Name: "GPT-6 Luna", Context: 1_050_000, Images: true, Efforts: []string{"low", "medium", "high", "xhigh", "max"}}, {ID: "gpt-5.6-luna", Name: "GPT-5.6 Luna", Context: 1_050_000, Images: true, Efforts: []string{"low", "medium", "high", "xhigh", "max"}}, {ID: "deepseek/deepseek-v4-pro", Name: "DeepSeek V4 Pro", Context: 1_000_000, Efforts: []string{"high", "max"}}, {ID: "deepseek/deepseek-v4-flash", Name: "DeepSeek V4 Flash", Context: 1_000_000, Efforts: []string{"high", "max"}}, {ID: "deepseek/deepseek-v4-flash-vision-exp", Name: "DeepSeek V4 Flash Vision (exp)", Context: 1_000_000, Images: true, Efforts: []string{"high", "max"}}, {ID: "deepseek/deepseek-v4-flash-fast", Name: "DeepSeek V4 Flash Fast", Context: 1_000_000, Efforts: []string{"low", "high", "max"}}, {ID: "deepseek/deepseek-v4.1-flash", Name: "DeepSeek V4.1 Flash", Context: 1_000_000, Images: true, Efforts: []string{"low", "high", "max"}}, {ID: "deepseek/deepseek-v4.1-flash-fast", Name: "DeepSeek V4.1 Flash Fast", Context: 1_000_000, Images: true, Efforts: []string{"low", "high", "max"}}, {ID: "moonshotai/Kimi-K3", Name: "Kimi K3", Context: 1_000_000, Images: true, Efforts: []string{"low", "high", "max"}}, {ID: "moonshotai/Kimi-K2.7-Code", Name: "Kimi K2.7 Code", Context: 256_000, Images: true}, {ID: "moonshotai/Kimi-K2.7-Code-Highspeed", Name: "Kimi K2.7 Code HighSpeed", Context: 262_000, Images: true}, {ID: "moonshotai/Kimi-K2.6", Name: "Kimi K2.6", Context: 256_000, Images: true}, {ID: "moonshotai/Kimi-K2.5", Name: "Kimi K2.5", Context: 256_000, Images: true}, {ID: "z-ai/glm-5.3-flash", Name: "GLM-5.3 Flash", Context: 1_048_576, Images: true, Efforts: []string{"low", "high", "max"}}, {ID: "z-ai/glm-5.3-flashx", Name: "GLM-5.3 FlashX", Context: 1_000_000, Images: true, Efforts: []string{"low", "high", "max"}}, {ID: "zai-org/GLM-5.3", Name: "GLM-5.3", Context: 1_000_000, Efforts: []string{"low", "high", "max"}}, {ID: "zai-org/GLM-5.2", Name: "GLM-5.2", Context: 1_000_000, Efforts: []string{"high", "max"}}, {ID: "zai-org/GLM-5.2-Fast", Name: "GLM-5.2 Fast", Context: 1_000_000}, {ID: "zai-org/GLM-5.1", Name: "GLM-5.1", Context: 200_000}, {ID: "zai-org/GLM-5", Name: "GLM-5", Context: 200_000}, {ID: "MiniMaxAI/MiniMax-M3", Name: "MiniMax M3", Context: 1_000_000, Images: true, Efforts: []string{"low", "medium", "high"}}, {ID: "MiniMaxAI/MiniMax-M2.7", Name: "MiniMax M2.7", Context: 200_000}, {ID: "MiniMaxAI/MiniMax-M2.5", Name: "MiniMax M2.5", Context: 200_000}, {ID: "xiaomi/mimo-v2.6-pro", Name: "MiMo V2.6 Pro", Context: 1_048_576, Images: true}, {ID: "xiaomi/mimo-v2.6-flash", Name: "MiMo V2.6 Flash", Context: 1_048_576, Images: true}, {ID: "xiaomi/mimo-v2.5-pro", Name: "MiMo V2.5 Pro", Context: 1_000_000}, {ID: "xiaomi/mimo-v2.5", Name: "MiMo V2.5", Context: 1_000_000, Images: true}, {ID: "Qwen/Qwen3.8-Omni-Flash", Name: "Qwen 3.8 Omni Flash", Context: 1_000_000, Images: true, Efforts: []string{"low", "medium", "xhigh"}}, {ID: "Qwen/Qwen3.8-Max-0902", Name: "Qwen 3.8 Max 0902", Context: 1_000_000, Images: true, Efforts: []string{"low", "medium", "xhigh"}}, {ID: "Qwen/Qwen3.8-Max", Name: "Qwen 3.8 Max", Context: 1_000_000, Images: true, Efforts: []string{"low", "medium", "xhigh"}}, {ID: "Qwen/Qwen3.8-27B", Name: "Qwen 3.8 27B", Context: 262_144, Images: true, Efforts: []string{"low", "medium", "xhigh"}}, {ID: "Qwen/Qwen3.8-Flash", Name: "Qwen 3.8 Flash", Context: 1_000_000, Images: true, Efforts: []string{"low", "medium", "xhigh"}}, {ID: "Qwen/Qwen3.7-Max", Name: "Qwen 3.7 Max", Context: 1_000_000}, {ID: "Qwen/Qwen3.7-Plus", Name: "Qwen 3.7 Plus", Context: 1_000_000, Images: true}, {ID: "Qwen/Qwen3.7-Flash", Name: "Qwen 3.7 Flash", Context: 1_000_000, Images: true}, {ID: "Qwen/Qwen3.6-Max-Preview", Name: "Qwen 3.6 Max Preview", Context: 200_000}, {ID: "Qwen/Qwen3.6-Plus", Name: "Qwen 3.6 Plus", Context: 200_000, Images: true}, {ID: "meituan/LongCat-2.0", Name: "LongCat 2.0", Context: 1_048_576}, {ID: "stepfun/Step-5-Preview", Name: "Step 5 Preview", Context: 1_000_000, Images: true, Efforts: []string{"low", "medium", "high"}}, {ID: "stepfun/Step-3.7-Flash", Name: "Step 3.7 Flash", Context: 256_000, Images: true}, {ID: "stepfun/Step-3.5-Flash", Name: "Step 3.5 Flash", Context: 262_144}, {ID: "tencent/hy3-paid", Name: "Tencent Hy3", Context: 262_144}, {ID: "tencent/hy4-preview", Name: "Tencent Hy4 Preview", Context: 1_048_576, Efforts: []string{"low", "medium", "high"}}, {ID: "google/gemini-3.6-flash", Name: "Gemini 3.6 Flash", Context: 1_000_000, Images: true, Efforts: []string{"low", "medium", "high"}}, {ID: "google/gemini-3.5-flash-lite", Name: "Gemini 3.5 Flash Lite", Context: 1_000_000, Images: true, Efforts: []string{"low", "medium", "high"}}, {ID: "nvidia/nemotron-3-ultra-550b-a55b", Name: "Nemotron 3 Ultra", Context: 1_000_000}, {ID: "thinkingmachines/inkling", Name: "Inkling", Context: 256_000, Images: true}, {ID: "thinkingmachines/inkling-small", Name: "Inkling Small", Context: 1_000_000, Images: true}, {ID: "stealth/space-bunny-alpha", Name: "Space Bunny Alpha", Context: 1_000_000, Images: true, Efforts: []string{"low", "medium", "high"}}, {ID: "stealth/pixel-canary", Name: "Pixel Canary", Context: 262_144, Images: true, Efforts: []string{"low", "medium", "xhigh"}}, {ID: "poolside/laguna-s-2.1-free", Name: "Laguna S 2.1", Context: 256_000}, {ID: "inclusionai/ling-3.0-flash-free", Name: "Ling 3.0 Flash", Context: 256_000}, {ID: "inclusionai/ling-3.0-flash-sante:free", Name: "Ling 3.0 Flash Sante", Context: 262_144}, {ID: "inclusionai/ling-3.1-flash:free", Name: "Ling 3.1 Flash", Context: 262_144, Efforts: []string{"low", "medium", "high"}}, {ID: "meta/muse-spark-1.2-contributor", Name: "Muse Spark 1.2 Contributor", Context: 1_048_576, Images: true, Efforts: []string{"low", "medium", "high", "xhigh"}}, {ID: "meta/muse-spark-1.3-contributor", Name: "Muse Spark 1.3 Contributor", Context: 1_048_576, Images: true, Efforts: []string{"low", "medium", "high", "xhigh"}}, {ID: "xai/grok-4.5", Name: "Grok 4.5", Context: 500_000, Images: true, Efforts: []string{"low", "medium", "high"}}, } // cmdGoRefused are the models of Command Code's list the Go plan is // refused, as the CLI's table has it (1.73.0): its "premium" ones, and // those "individual-go" blocks — // // "individual-go":{allowedCategories:[qo],blockedModels:Xo=[…]} // // A model the table doesn't name the CLI lets any plan pick; one the plan // hasn't after all is refused with MODEL_NOT_IN_PLAN. var cmdGoRefused = map[string]bool{ // premium "claude-sonnet-5": true, "claude-sonnet-4-6": true, "claude-fable-5-1": true, "claude-fable-5": true, "claude-opus-5-5": true, "claude-opus-5": true, "claude-opus-4-8": true, "claude-opus-4-7": true, "claude-haiku-4-5-20251001": true, "gpt-6-astra": true, "gpt-6.1-sol": true, "gpt-6-sol": true, "gpt-5.6-terra": true, "gpt-5.5": true, "gpt-5.4": true, "gpt-5.3-codex": true, "gpt-5.4-mini": true, "google/gemini-3.5-flash": true, "google/gemini-3.1-flash-lite": true, "sakana/fugu-ultra": true, "meta/muse-spark-1.1": true, // blocked for Go "claude-sonnet-5-5": true, "gpt-5.6-sol": true, "xai/grok-4.6": true, "xai/grok-4.7": true, "meta/muse-spark-1.2": true, "meta/muse-spark-1.3": true, "xiaomi/mimo-v2.6-pro-ultraspeed": true, "google/gemini-3.7-flash": true, "google/gemini-3.8-flash": true, } // cmdGoFetch is the Go plan's models: Command Code's list, less what Go // is refused. The list is the Provider API's, which answers without a key // (Go's has no Provider API), and takes its reasoning levels and pictures // from cmdGoModels. func cmdGoFetch(ctx context.Context) ([]catalog.Model, error) { base := cmdAPI + "/provider/v1" ms, err := catalog.FetchURL(ctx, base+"/models", "", false, nil) if err != nil { return nil, err } var out []catalog.Model for _, m := range catalog.Decorate(catalog.Chat(ms), cmdGoModels) { if !cmdGoRefused[m.ID] { out = append(out, m) } } if len(out) == 0 { return nil, errors.New("Command Code listed no models for the Go plan") } cmdMarkFree(out) return out, catalog.SaveLive(CommandCodePlanID, base, out) } // cmdFree are the models Command Code's CLI marks FREE in its picker // (1.73.2: badge:"free", "{name} is free and uses shared capacity"); its // API's list doesn't say so, and Space Bunny Alpha's and Pixel Canary's // ids name nothing free. Neither says a model is served at a discount. var cmdFree = map[string]bool{ "stealth/space-bunny-alpha": true, "stealth/pixel-canary": true, "poolside/laguna-s-2.1-free": true, "inclusionai/ling-3.0-flash-free": true, "inclusionai/ling-3.0-flash-sante:free": true, "inclusionai/ling-3.1-flash:free": true, "MiniMaxAI/MiniMax-M3-Free": true, "minimax/minimax-m3-free": true, "minimax/minimax-m2.7-free": true, "meituan/LongCat-2.0:free": true, "tencent/Hy3": true, } // cmdMarkFree marks the models of ms the CLI calls free, in place. func cmdMarkFree(ms []catalog.Model) []catalog.Model { for i := range ms { if cmdFree[ms[i].ID] { ms[i].Free = true } } return ms } func init() { cmdMarkFree(cmdGoModels) } // cmdAuth is an account's key, as auth.json and the sign-in name it. type cmdAuth struct { APIKey string `json:"apiKey"` UserID string `json:"userId,omitempty"` UserName string `json:"userName,omitempty"` KeyName string `json:"keyName,omitempty"` } // ---- the CLI's own account ---------------------------------------------------- func cmdAuthPath() string { home, _ := os.UserHomeDir() return filepath.Join(home, ".commandcode", "auth.json") } // cmdOwn is the account Command Code's CLI is signed in to; ok is false // when it has none. func cmdOwn() (who string, a cmdAuth, ok bool) { if !readJSON(cmdAuthPath(), &a) || strings.TrimSpace(a.APIKey) == "" { return "", cmdAuth{}, false } return cmdWho(a), a, true } // cmdWho names an account: its user name, or its id. func cmdWho(a cmdAuth) string { return firstNonEmpty(a.UserName, a.UserID, "Command Code") } // ---- the accounts ------------------------------------------------------------- type cmdLogin struct { Login auth cmdAuth } func cmdSaved(l savedLogin) (cmdAuth, bool) { var a cmdAuth if json.Unmarshal(l.Auth, &a) != nil || a.APIKey == "" { return cmdAuth{}, false } return a, true } // cmdLogins is every Command Code account signed in, the first in use first. func cmdLogins() []cmdLogin { ownUser, own, hasOwn := cmdOwn() if !hasOwn { ownUser = "" } var out []cmdLogin for _, l := range sideLogins(CommandCodePlanID, ownUser, func(l savedLogin) bool { _, ok := cmdSaved(l) return ok }) { a := own if !l.saved.own() { a, _ = cmdSaved(l.saved) } out = append(out, cmdLogin{l.Login, a}) } return out } func cmdSide() []sideLogin { var out []sideLogin for _, l := range cmdLogins() { out = append(out, sideLogin{Login: l.Login}) } return out } func cmdLoginList() []Login { return loginsOf(cmdSide()) } func switchCommandCodeLogin(user string) error { return switchSideLogin(CommandCodePlanID, user, cmdSide()) } func setCommandCodeLoginOn(user string, on bool) error { return setSideLoginOn(CommandCodePlanID, user, on, cmdSide()) } func forgetCommandCodeLogin(user string) error { return forgetSideLogin(CommandCodePlanID, user, cmdSide(), nil) } func commandCodeAccount() (Provider, bool) { ls := cmdLogins() if len(ls) == 0 { return Provider{}, false } return cmdProvider(ls[0].User, ls[0].Plan, ls[0].auth), true } // commandCodeAlsoOn is the Command Code accounts in use behind the first. func commandCodeAlsoOn() []Provider { var out []Provider for _, l := range cmdLogins() { if !l.Active && l.On { out = append(out, cmdProvider(l.User, l.Plan, l.auth)) } } return out } func cmdProvider(who, plan string, a cmdAuth) Provider { p := Provider{ID: CommandCodePlanID, Name: "Command Code Plan", Icon: "commandcode", Chat: cmdAPI + "/provider/v1", Responses: cmdAPI + "/provider/v1", Anthropic: cmdAPI + "/provider", Website: cmdStudio} acct := &Account{Agent: CommandCodePlanID, User: who, Plan: cmdPlanKnown(a, plan)} acct.sign = func(ctx context.Context, req *http.Request, body []byte) error { req.Header.Del("Authorization") req.Header.Set("Authorization", "Bearer "+a.APIKey) req.Header.Set("x-api-key", a.APIKey) return nil } acct.models = func() []catalog.Model { if cmdPlanKnown(a, plan) == "Go" { return cmdGoModels } return cmdModels } // the plan's list is the Provider API's, with what each model is // served on; it is asked as a keyed provider's is. Go's key has no // Provider API: its list is the same one, asked without it, less what // Go is refused. acct.fetch = func(ctx context.Context) ([]catalog.Model, error) { if cmdPlanNow(ctx, a, plan) == "Go" { return cmdGoFetch(ctx) } keyed := p keyed.Account, keyed.Key = nil, a.APIKey ms, base, err := keyed.fetchOne(keyed.Via(ctx)) if err != nil { return nil, err } cmdMarkFree(ms) return catalog.Chat(ms), catalog.SaveLive(p.ID, base, ms) } acct.generate = func(ctx context.Context) (string, bool) { return a.APIKey, cmdPlanNow(ctx, a, plan) == "Go" } p.Account = acct return p } // CommandCodeGenerate says whether p is a Command Code account on the Go // plan, which is asked at api+"/alpha/generate" in the CLI's own format // rather than on the Provider API, with key; ok is false for every other // account and provider. func CommandCodeGenerate(ctx context.Context, p Provider) (api, key string, ok bool) { if p.Account == nil || p.Account.generate == nil { return "", "", false } if key, ok = p.Account.generate(ctx); !ok { return "", "", false } return cmdAPI, key, true } // ---- which plan --------------------------------------------------------------- // cmdPlansSeen is each key's plan, as billing/subscriptions last said // ("" when it couldn't be read), and when. var cmdPlansSeen = struct { sync.Mutex m map[string]cmdSeen }{m: map[string]cmdSeen{}} type cmdSeen struct { plan string at time.Time } // How long a plan read is kept, and a failure to read it before it is // asked again. var ( cmdPlanKeep = 10 * time.Minute cmdPlanRetry = time.Minute ) // cmdRemember keeps what billing/subscriptions said of a key's plan. func cmdRemember(key, plan string) { cmdPlansSeen.Lock() cmdPlansSeen.m[key] = cmdSeen{plan, time.Now()} cmdPlansSeen.Unlock() } // cmdPlanKnown is the account's plan as last read, else saved (the one // its sign-in said), asking no one. func cmdPlanKnown(a cmdAuth, saved string) string { cmdPlansSeen.Lock() seen := cmdPlansSeen.m[a.APIKey] cmdPlansSeen.Unlock() return firstNonEmpty(seen.plan, saved) } // cmdPlanNow is the account's plan, read again once what was read is // older than cmdPlanKeep; saved while it can't be read. The CLI's own // account has none saved, so it is always asked. func cmdPlanNow(ctx context.Context, a cmdAuth, saved string) string { cmdPlansSeen.Lock() seen, ok := cmdPlansSeen.m[a.APIKey] cmdPlansSeen.Unlock() switch { case ok && seen.plan != "" && time.Since(seen.at) < cmdPlanKeep: return seen.plan case ok && seen.plan == "" && time.Since(seen.at) < cmdPlanRetry: return saved } ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() _, plan, _, _, read := cmdSubscription(ctx, a) if !read { plan = "" } cmdRemember(a.APIKey, plan) return firstNonEmpty(plan, saved) } // ---- allowance ---------------------------------------------------------------- // cmdPlans names the plans as the CLI does, by their id's start, longest // first, with the dollars of credits each gives a month (its getPlanInfo; // "individual-pro-v1" is the old Pro, $80 of them). var cmdPlans = []struct { id, name string monthly float64 }{ {"individual-provider", "Provider", 15}, {"individual-pro-v1", "Pro", 80}, {"individual-goat", "GOAT", 70}, {"individual-ultra", "Ultra", 300}, {"individual-max", "Max", 150}, {"individual-pro", "Pro", 30}, {"individual-go", "Go", 10}, {"teams-pro", "Teams Pro", 40}, } func cmdPlanOf(id string) (name string, monthly float64) { id = strings.ReplaceAll(strings.ToLower(id), "_", "-") for _, p := range cmdPlans { if strings.HasPrefix(id, p.id) { return p.name, p.monthly } } return "", 0 } func cmdPlanName(id string) string { name, _ := cmdPlanOf(id) return name } // cmdCredits is /alpha/billing/credits as the CLI's /usage reads it // (projectUsageView): the dollars left of the month's credits, and of the // bought and free ones beside them, under "credits"; the plan's windows // next to it, not in it — // // {"credits":{"planId":"individual-goat-monthly","monthlyCredits":41.2, // "purchasedCredits":5,"freeCredits":0}, // "windowLimits":{"limited":true, // "fiveHour":{"used":3.1,"cap":10,"resetAt":1790000000000}, // "weekly":{"used":12,"cap":40,"resetAt":1790400000000}}, // "sandboxMinutes":{…},"sandboxAccess":false} // // resetAt is in milliseconds (the CLI compares it with Date.now()); used // and cap are only ever divided, one by the other. type cmdWindow struct { Used any `json:"used"` Cap any `json:"cap"` ResetAt any `json:"resetAt"` // unix ms (seconds or a time taken too) } type cmdCredits struct { Credits struct { PlanID string `json:"planId"` Monthly any `json:"monthlyCredits"` Purchased any `json:"purchasedCredits"` Free any `json:"freeCredits"` } `json:"credits"` Windows *struct { FiveHour *cmdWindow `json:"fiveHour"` Weekly *cmdWindow `json:"weekly"` } `json:"windowLimits"` } // cmdTime reads a reset time: unix seconds or milliseconds, or RFC 3339. func cmdTime(v any) *time.Time { if s, ok := v.(string); ok { if t, err := time.Parse(time.RFC3339Nano, s); err == nil { return &t } } n, ok := number(v) if !ok || n <= 0 { return nil } if n < 1e12 { n *= 1000 } t := time.UnixMilli(int64(n)) return &t } // cmdWindows is the plan's 5-hour and weekly windows in a credits reply, // those it gives a cap. func cmdWindows(c cmdCredits) []QuotaWindow { out := []QuotaWindow{} if c.Windows == nil { return out } for _, w := range []struct { name string span time.Duration w *cmdWindow }{{"5 hours", 5 * time.Hour, c.Windows.FiveHour}, {"Weekly", 7 * 24 * time.Hour, c.Windows.Weekly}} { if w.w == nil { continue } used, ok1 := number(w.w.Used) limit, ok2 := number(w.w.Cap) if !ok1 || !ok2 || limit <= 0 { continue } out = append(out, QuotaWindow{Name: w.name, Used: min(100, 100*max(0, used)/limit), Span: w.span, ResetsAt: cmdTime(w.w.ResetAt)}) } return out } // cmdLeft is the dollars left on the account, the month's credits and the // bought and free ones together (the CLI's totalRemaining), and the // month's alone; ok is false when the reply tells none of them. func cmdLeft(c cmdCredits) (monthly, left float64, ok bool) { for i, v := range []any{c.Credits.Monthly, c.Credits.Purchased, c.Credits.Free} { if n, is := number(v); is { n = max(0, n) if i == 0 { monthly = n } left += n ok = true } } return monthly, left, ok } // cmdQuotaOf makes the credits reply into the plan's windows: the 5-hour // and weekly limits, and the month's credits as one more. A card's // Balance is shown instead of its windows — it is a key's money, not an // allowance — so, set beside them, it hid both limits on the Usage page, // the panel and the TUI. The dollars are a window when the plan is known, // used of the CLI's pool (the plan's month of credits, or what is left of // it if more, and the bought and free ones) as its /usage bar is; a reply // with neither windows nor a plan is a Balance still. func cmdQuotaOf(q SubscriptionQuota, c cmdCredits) SubscriptionQuota { name, planMonthly := cmdPlanOf(c.Credits.PlanID) if name != "" { q.Plan = name } q.Windows = cmdWindows(c) monthly, left, known := cmdLeft(c) switch { case !known: case planMonthly > 0: pool := max(planMonthly, monthly) + left - monthly q.Windows = append(q.Windows, QuotaWindow{Name: "Credits", Used: min(100, 100*(pool-left)/pool), Display: money("$", pool-left) + " / " + money("$", pool)}) case len(q.Windows) == 0: q.Balance = money("$", left) } return q } func cmdQuota(ctx context.Context, l Login, a cmdAuth) SubscriptionQuota { q := SubscriptionQuota{Provider: CommandCodePlanID, Name: "Command Code", Icon: "commandcode", Plan: l.Plan, User: l.User, Windows: []QuotaWindow{}} // the plan is said even while the credits can't be read (Command Code // answers 503, "Couldn't verify your credit balance just now", at times) id, plan, until, renew, planOK := cmdSubscription(ctx, a) var c cmdCredits if err := accountJSON(ctx, cmdAPI+"/alpha/billing/credits", a.APIKey, nil, &c); err != nil { q.Error = err.Error() } else { // billing/credits leaves the plan out: the month of credits the pool // is made of is the subscription's plan's if c.Credits.PlanID == "" { c.Credits.PlanID = id } q = cmdQuotaOf(q, c) } if planOK { q.Plan, q.Until, q.Renew = plan, until, renew cmdRemember(a.APIKey, plan) } return q } // cmdNoPlan is the plan of an account with no subscription: its key is // billed against the credits it bought, if any. const cmdNoPlan = "No plan" // cmdSubscription is the account's plan (its id, and its name), when its period ends and whether // it renews then, as billing/subscriptions says; cmdNoPlan when it has // none. ok is false when that can't be read. func cmdSubscription(ctx context.Context, a cmdAuth) (id, plan string, until *time.Time, renew string, ok bool) { var r struct { Success *bool `json:"success"` // false when Command Code couldn't tell ("write CONNECTION_CLOSED …"), though a 200 Data *struct { PlanID string `json:"planId"` Status string `json:"status"` CurrentPeriodEnd any `json:"currentPeriodEnd"` CancelAtEnd *bool `json:"cancelAtPeriodEnd"` } `json:"data"` } if accountJSON(ctx, cmdAPI+"/alpha/billing/subscriptions", a.APIKey, nil, &r) != nil || (r.Success != nil && !*r.Success) { return "", "", nil, "", false } if d := r.Data; d != nil && d.PlanID != "" && d.Status != "canceled" && d.Status != "incomplete_expired" { if d.CancelAtEnd != nil { renew = map[bool]string{true: "off", false: "auto"}[*d.CancelAtEnd] } return d.PlanID, firstNonEmpty(cmdPlanName(d.PlanID), d.PlanID), cmdTime(d.CurrentPeriodEnd), renew, true } return "", cmdNoPlan, nil, "", true } func cmdLoginQuota(ctx context.Context, l Login) SubscriptionQuota { for _, c := range cmdLogins() { if strings.EqualFold(c.User, l.User) { return cmdQuota(ctx, l, c.auth) } } return SubscriptionQuota{Provider: CommandCodePlanID, Plan: l.Plan, Windows: []QuotaWindow{}, Error: "not signed in"} } // ---- signing in --------------------------------------------------------------- // startCommandCodeSignIn is the CLI's browser sign-in: Studio asks the // user to approve a key for this machine, then posts it (a form, or JSON // from an older Studio) to the callback, which sends the browser on to a // page saying it is done. func startCommandCodeSignIn(s *signInFlow) error { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return err } port := ln.Addr().(*net.TCPAddr).Port q := url.Values{} q.Set("callback", fmt.Sprintf("http://127.0.0.1:%d/callback", port)) q.Set("state", s.state) q.Set("mode", "redirect") srv := &http.Server{Handler: http.HandlerFunc(s.commandCodeCallback), ReadHeaderTimeout: 10 * time.Second} s.mu.Lock() s.st.URL = cmdStudio + "/studio/auth/cli?" + q.Encode() // Studio's post can't reach a magpie on a server or in Docker: a key // made on its keys page and pasted finishes it instead s.st.PasteKey, s.st.KeysURL = true, cmdKeysURL s.srv = srv s.mu.Unlock() go func() { _ = srv.Serve(ln) }() return nil } // cmdWhoamiWait is how long a sign-in waits to ask whoami again. var cmdWhoamiWait = time.Second // cmdOrigins are the Studio pages that may post to the callback. var cmdOrigins = []string{"https://commandcode.ai", "https://staging.commandcode.ai"} func (s *signInFlow) commandCodeCallback(w http.ResponseWriter, r *http.Request) { origin := cmdOrigins[0] for _, o := range cmdOrigins { if r.Header.Get("Origin") == o { origin = o } } w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") if r.Header.Get("Access-Control-Request-Private-Network") == "true" { w.Header().Set("Access-Control-Allow-Private-Network", "true") } switch { case r.Method == http.MethodOptions: w.WriteHeader(http.StatusNoContent) return case r.URL.Path == "/callback/complete" && r.Method == http.MethodGet: st := s.status() if st.State == "done" { signInPage(w, true, "You're signed in", fmt.Sprintf("%s is added to magpie. You can close this tab.", st.User)) } else { signInPage(w, false, "Sign-in didn't finish", firstNonEmpty(st.Error, "Start it again from magpie.")) } return case r.URL.Path == "/cancel": s.finish(SignInState{State: "canceled"}) w.WriteHeader(http.StatusNoContent) return case r.URL.Path != "/callback": http.NotFound(w, r) return case r.Method != http.MethodPost: w.WriteHeader(http.StatusMethodNotAllowed) return } body, _ := io.ReadAll(io.LimitReader(r.Body, 10_000)) var got struct { cmdAuth State string `json:"state"` Error string `json:"error"` Desc string `json:"error_description"` } isJSON := strings.HasPrefix(strings.TrimSpace(strings.ToLower(r.Header.Get("Content-Type"))), "application/json") if isJSON { _ = json.Unmarshal(body, &got) } else { f, _ := url.ParseQuery(string(body)) got.APIKey, got.UserID, got.UserName, got.KeyName = f.Get("apiKey"), f.Get("userId"), f.Get("userName"), f.Get("keyName") got.State, got.Error, got.Desc = f.Get("state"), f.Get("error"), f.Get("error_description") } answer := func(ok bool, msg string) { if isJSON { w.Header().Set("Content-Type", "application/json") if !ok { w.WriteHeader(http.StatusBadRequest) } _ = json.NewEncoder(w).Encode(map[string]any{"success": ok, "error": msg}) return } // as the CLI answers: on to the page that says how it went w.Header().Set("Location", "/callback/complete?state="+url.QueryEscape(got.State)) w.Header().Set("Cache-Control", "no-store") w.WriteHeader(http.StatusSeeOther) } if got.State != s.state { // not ours: someone else's page, or a stale tab w.WriteHeader(http.StatusForbidden) signInPage(w, false, "This link isn't from magpie's sign-in", "Start it again from magpie.") return } if s.status().State != "waiting" { answer(false, "this sign-in is over") return } if got.Error != "" { msg := firstNonEmpty(got.Desc, got.Error) if got.Error == "access_denied" { msg = "the sign-in was denied" } s.finish(SignInState{State: "failed", Error: msg}) answer(false, msg) return } if got.APIKey == "" { answer(false, "Command Code sent back no key") return } if !s.claim() { // a key was pasted too, and that one is being kept answer(false, "this sign-in is already finishing") return } ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) defer cancel() // the browser comes back for its page after this: the server stays up // a while for it, where finish would close it within a second if err := s.commandCodeKeep(ctx, got.cmdAuth, true); err != nil { answer(false, err.Error()) return } answer(true, "") } // commandCodeKeep names the account a key is Command Code's, keeps it beside // the others, and finishes the sign-in. keepServer leaves the callback // server up a while, for the browser to come back for its page. func (s *signInFlow) commandCodeKeep(ctx context.Context, a cmdAuth, keepServer bool) error { who, plan, err := cmdSignedIn(ctx, a) if err != nil { s.finish(SignInState{State: "failed", Error: err.Error()}) return err } a.UserName = who auth, _ := json.Marshal(a) ownUser, _, hasOwn := cmdOwn() if !hasOwn { ownUser = "" } if err := addSideLogin(savedLogin{Agent: CommandCodePlanID, User: who, Plan: plan, Auth: auth}, ownUser, func(savedLogin) {}); err != nil { s.finish(SignInState{State: "failed", Error: err.Error()}) return err } if keepServer { s.mu.Lock() srv := s.srv s.srv = nil s.mu.Unlock() if srv != nil { time.AfterFunc(10*time.Second, func() { _ = srv.Close() }) } } s.finish(SignInState{State: "done", User: who, Plan: plan, Using: hasOwn && strings.EqualFold(ownUser, who)}) return nil } // cmdKeysURL is Studio's page of API keys, where one is made to paste. var cmdKeysURL = "https://commandcode.ai/settings/keys" // commandCodeKey finishes a sign-in with a key pasted from Studio's keys // page, as the CLI takes one ("Authorize in browser, or paste API key // here"). It is how a magpie the browser can't reach — on a server, in // Docker — signs in: Studio posts the key to the callback on 127.0.0.1 in // the background, so the page the browser ends on has nothing in its // address to paste. func (s *signInFlow) commandCodeKey(raw string) error { key := strings.TrimSpace(raw) switch { case key == "": return errors.New("paste an API key from Command Code's keys page") case strings.Contains(key, "://"): return errors.New("that's an address: Command Code sends its key in the background, so make a key on its keys page and paste that") case strings.ContainsAny(key, " \t\r\n"): return errors.New("that doesn't look like a Command Code API key") } if s.status().State != "waiting" { return errors.New("this sign-in is over; start it again") } if !s.claim() { return errors.New("this sign-in is already finishing") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() return s.commandCodeKeep(ctx, cmdAuth{APIKey: key}, false) } // cmdSignedIn names a new key's account with whoami, and reads its plan. // Only whoami turning the key down (401, 403) fails the sign-in: the CLI's // own browser sign-in keeps the key Studio posts without asking whoami at // all, and whoami has answered a key just made with a 500, so a whoami // that errs is asked again a few times and then passed over for the name // Studio sent with the key. func cmdSignedIn(ctx context.Context, a cmdAuth) (who, plan string, err error) { var me struct { User struct { ID string `json:"id"` UserName string `json:"userName"` Name string `json:"name"` Email string `json:"email"` } `json:"user"` } for try := 0; ; try++ { err := accountJSON(ctx, cmdAPI+"/alpha/whoami", a.APIKey, nil, &me) var st *accountStatusError if err == nil { break } if errors.As(err, &st) && (st.status == http.StatusUnauthorized || st.status == http.StatusForbidden) { return "", "", fmt.Errorf("Command Code didn't take the new key: %w", err) } if try == 2 || ctx.Err() != nil { if firstNonEmpty(a.UserName, a.UserID) == "" { return "", "", fmt.Errorf("Command Code couldn't say which account signed in: %w", err) } break } select { case <-ctx.Done(): case <-time.After(cmdWhoamiWait): } } who = firstNonEmpty(me.User.UserName, a.UserName, me.User.Email, me.User.ID, a.UserID) if who == "" { return "", "", fmt.Errorf("Command Code didn't say which account signed in") } if _, p, _, _, ok := cmdSubscription(ctx, a); ok { plan = p } return who, plan, nil }