// SPDX-License-Identifier: EUPL-1.2 package agentic import ( "context" core "dappco.re/go/core" ) // key := agentic.AgentApiKey{ID: 7, Name: "codex local", Prefix: "ak_abcd", IPRestrictions: []string{"10.0.0.0/8"}, RateLimit: 60} type AgentApiKey struct { ID int `json:"id"` WorkspaceID int `json:"workspace_id,omitempty"` Name string `json:"name,omitempty"` Key string `json:"key,omitempty"` Prefix string `json:"prefix,omitempty"` Permissions []string `json:"permissions,omitempty"` IPRestrictions []string `json:"ip_restrictions,omitempty"` RateLimit int `json:"rate_limit,omitempty"` CallCount int `json:"call_count,omitempty"` LastUsedAt string `json:"last_used_at,omitempty"` ExpiresAt string `json:"expires_at,omitempty"` RevokedAt string `json:"revoked_at,omitempty"` CreatedAt string `json:"created_at,omitempty"` } // input := agentic.AuthLoginInput{Code: "123456"} // Login exchanges a 6-digit pairing code (generated at app.lthn.ai/device by a // logged-in user) for an AgentApiKey. See RFC §9 Fleet Mode — bootstrap via // `core-agent login CODE`. type AuthLoginInput struct { Code string `json:"code"` } // out := agentic.AuthLoginOutput{Success: true, Key: agentic.AgentApiKey{Prefix: "ak_abcd", Key: "ak_live_secret"}} // The Key.Key field carries the new AgentApiKey raw value that the caller // should persist to `~/.claude/brain.key` (or `CORE_BRAIN_KEY` env) so // subsequent platform requests authenticate successfully. type AuthLoginOutput struct { Success bool `json:"success"` Key AgentApiKey `json:"key"` } // input := agentic.AuthProvisionInput{OAuthUserID: "user-42", Permissions: []string{"plans:read"}, IPRestrictions: []string{"10.0.0.0/8"}} type AuthProvisionInput struct { OAuthUserID string `json:"oauth_user_id"` Name string `json:"name,omitempty"` Permissions []string `json:"permissions,omitempty"` IPRestrictions []string `json:"ip_restrictions,omitempty"` RateLimit int `json:"rate_limit,omitempty"` ExpiresAt string `json:"expires_at,omitempty"` } // out := agentic.AuthProvisionOutput{Success: true, Key: agentic.AgentApiKey{Prefix: "ak_abcd"}} type AuthProvisionOutput struct { Success bool `json:"success"` Key AgentApiKey `json:"key"` } // input := agentic.AuthRevokeInput{KeyID: "7"} type AuthRevokeInput struct { KeyID string `json:"key_id"` } // out := agentic.AuthRevokeOutput{Success: true, KeyID: "7", Revoked: true} type AuthRevokeOutput struct { Success bool `json:"success"` KeyID string `json:"key_id"` Revoked bool `json:"revoked"` } // result := c.Action("agentic.auth.provision").Run(ctx, core.NewOptions( // // core.Option{Key: "oauth_user_id", Value: "user-42"}, // core.Option{Key: "permissions", Value: "plans:read,plans:write"}, // // )) func (s *PrepSubsystem) handleAuthProvision(ctx context.Context, options core.Options) core.Result { input := AuthProvisionInput{ OAuthUserID: optionStringValue(options, "oauth_user_id", "oauth-user-id", "user_id", "user-id", "_arg"), Name: optionStringValue(options, "name"), Permissions: optionStringSliceValue(options, "permissions"), IPRestrictions: optionStringSliceValue(options, "ip_restrictions", "ip-restrictions", "allowed_ips", "allowed-ips"), RateLimit: optionIntValue(options, "rate_limit", "rate-limit"), ExpiresAt: optionStringValue(options, "expires_at", "expires-at"), } if input.OAuthUserID == "" { return core.Result{Value: core.E("agentic.auth.provision", "oauth_user_id is required", nil), OK: false} } body := map[string]any{ "oauth_user_id": input.OAuthUserID, } if input.Name != "" { body["name"] = input.Name } if len(input.Permissions) > 0 { body["permissions"] = input.Permissions } if len(input.IPRestrictions) > 0 { body["ip_restrictions"] = input.IPRestrictions } if input.RateLimit > 0 { body["rate_limit"] = input.RateLimit } if input.ExpiresAt != "" { body["expires_at"] = input.ExpiresAt } result := s.platformPayload(ctx, "agentic.auth.provision", "POST", "/v1/agent/auth/provision", body) if !result.OK { return result } return core.Result{Value: AuthProvisionOutput{ Success: true, Key: parseAgentApiKey(payloadResourceMap(result.Value.(map[string]any), "key", "api_key", "agent_api_key")), }, OK: true} } // result := c.Action("agentic.auth.login").Run(ctx, core.NewOptions(core.Option{Key: "code", Value: "123456"})) // Login exchanges a 6-digit pairing code for an AgentApiKey without requiring // a pre-existing API key. The caller is responsible for persisting the // returned Key.Key value to `~/.claude/brain.key` (the CLI command does this // automatically). func (s *PrepSubsystem) handleAuthLogin(ctx context.Context, options core.Options) core.Result { input := AuthLoginInput{ Code: optionStringValue(options, "code", "pairing_code", "pairing-code", "_arg"), } if input.Code == "" { return core.Result{Value: core.E("agentic.auth.login", "code is required (6-digit pairing code)", nil), OK: false} } body := core.JSONMarshalString(map[string]any{"code": input.Code}) url := core.Concat(s.syncAPIURL(), "/v1/agent/auth/login") // Login is intentionally unauthenticated — the pairing code IS the proof. requestResult := HTTPDo(ctx, "POST", url, body, "", "") if !requestResult.OK { return core.Result{Value: platformResultError("agentic.auth.login", requestResult), OK: false} } var payload map[string]any parseResult := core.JSONUnmarshalString(requestResult.Value.(string), &payload) if !parseResult.OK { err, _ := parseResult.Value.(error) return core.Result{Value: core.E("agentic.auth.login", "failed to parse platform response", err), OK: false} } key := parseAgentApiKey(payloadResourceMap(payload, "key", "api_key", "agent_api_key")) if key.Key == "" { return core.Result{Value: core.E("agentic.auth.login", "platform did not return an api key", nil), OK: false} } return core.Result{Value: AuthLoginOutput{Success: true, Key: key}, OK: true} } // result := c.Action("agentic.auth.revoke").Run(ctx, core.NewOptions(core.Option{Key: "key_id", Value: "7"})) func (s *PrepSubsystem) handleAuthRevoke(ctx context.Context, options core.Options) core.Result { keyID := optionStringValue(options, "key_id", "key-id", "_arg") if keyID == "" { return core.Result{Value: core.E("agentic.auth.revoke", "key_id is required", nil), OK: false} } path := core.Concat("/v1/agent/auth/revoke/", keyID) result := s.platformPayload(ctx, "agentic.auth.revoke", "DELETE", path, nil) if !result.OK { return result } output := AuthRevokeOutput{ Success: true, KeyID: keyID, Revoked: true, } payload, ok := result.Value.(map[string]any) if !ok { return core.Result{Value: output, OK: true} } if data := payloadResourceMap(payload, "result", "revocation"); len(data) > 0 { if value := stringValue(data["key_id"]); value != "" { output.KeyID = value } if value, ok := boolValueOK(data["revoked"]); ok { output.Revoked = value } if value, ok := boolValueOK(data["success"]); ok { output.Success = value } return core.Result{Value: output, OK: output.Success && output.Revoked} } if data, exists := payload["data"]; exists { if value, ok := boolValueOK(data); ok { output.Revoked = value return core.Result{Value: output, OK: output.Success && output.Revoked} } } return core.Result{Value: output, OK: true} } func parseAgentApiKey(values map[string]any) AgentApiKey { return AgentApiKey{ ID: intValue(values["id"]), WorkspaceID: intValue(values["workspace_id"]), Name: stringValue(values["name"]), Key: stringValue(values["key"]), Prefix: stringValue(values["prefix"]), Permissions: listValue(values["permissions"]), IPRestrictions: listValue(values["ip_restrictions"]), RateLimit: intValue(values["rate_limit"]), CallCount: intValue(values["call_count"]), LastUsedAt: stringValue(values["last_used_at"]), ExpiresAt: stringValue(values["expires_at"]), RevokedAt: stringValue(values["revoked_at"]), CreatedAt: stringValue(values["created_at"]), } } func boolValueOK(value any) (bool, bool) { switch typed := value.(type) { case bool: return typed, true case string: trimmed := core.Lower(core.Trim(typed)) switch trimmed { case "true", "1", "yes": return true, true case "false", "0", "no": return false, true } case int: return typed != 0, true case int64: return typed != 0, true case float64: return typed != 0, true } return false, false }