claude

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 4, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package claude implements the Claude Code credential and agent provider.

The Claude provider acquires and manages Anthropic credentials for container runs. Credentials can be obtained from:

  • OAuth tokens via `claude setup-token` (recommended for Pro/Max subscribers)
  • Anthropic API keys from console.anthropic.com
  • Importing existing Claude Code credentials from keychain or file

The provider configures the proxy to inject Bearer tokens (OAuth) or x-api-key headers (API keys) for api.anthropic.com. Containers receive placeholder tokens that pass local validation, while the real credentials are injected at the network layer by the proxy.

For OAuth tokens, the provider registers a response transformer to handle 403 errors on OAuth endpoints that require scopes not available in long-lived tokens. This allows Claude Code to degrade gracefully.

As an AgentProvider, this package also handles:

  • Container preparation (staging directories, config files)
  • Session management for Claude Code runs
  • CLI commands (moat claude, moat claude sessions)
  • Loading and merging Claude settings from multiple sources
  • Generating Dockerfile snippets for plugin installation

Package claude handles Claude Code plugin and settings management.

Index

Constants

View Source
const ClaudeInitMountPath = "/moat/claude-init"

ClaudeInitMountPath is the path where the Claude staging directory is mounted. The moat-init script reads from this path and copies files to ~/.claude.

View Source
const ClaudeMarketplacesPath = ClaudePluginsPath + "/marketplaces"

ClaudeMarketplacesPath is the path where marketplaces are mounted in the container.

View Source
const ClaudePluginsPath = "/home/moatuser/.claude/plugins"

ClaudePluginsPath is the base path for Claude plugins in the container. This matches Claude Code's expected location at ~/.claude/plugins. We use the absolute path for moatuser since that's our standard container user.

View Source
const ProxyInjectedPlaceholder = "moat-proxy-injected"

ProxyInjectedPlaceholder is a placeholder value for credentials that will be injected by the Moat proxy at runtime.

Variables

View Source
var HostConfigAllowlist = []string{

	"oauthAccount",
	"userID",
	"anonymousId",
	"installMethod",

	"lastOnboardingVersion",
	"lastReleaseNotesSeen",
	"numStartups",

	"sonnet45MigrationComplete",
	"opus45MigrationComplete",
	"opusProMigrationComplete",
	"thinkingMigrationComplete",

	"clientDataCache",

	"cachedGrowthBookFeatures",

	"firstStartTime",
}

HostConfigAllowlist lists fields from the host's ~/.claude.json that are safe and useful to copy into containers. These avoid startup API calls and ensure consistent behavior.

Fields are categorized by purpose:

  • OAuth authentication: oauthAccount, userID, anonymousId (required for x-organization-uuid header)
  • Installation tracking: installMethod, lastOnboardingVersion, numStartups (affects auth behavior)
  • Feature flags: migration flags, clientDataCache (contains system_prompt_variant)
  • Performance: cachedGrowthBookFeatures (optional, reduces startup API calls)
View Source
var OAuthEndpointWorkarounds = []string{
	"/api/oauth/profile",
	"/api/oauth/usage",
}

OAuthEndpointWorkarounds defines OAuth API endpoints that require response transformation to work around scope limitations in long-lived tokens.

Background

Long-lived tokens created via `claude setup-token` do not include the `user:profile` scope, causing 403 "permission_error" responses on these endpoints. However, these endpoints are non-critical for core Claude Code functionality - they provide usage statistics and profile information for the UI status line and user display.

Why Transform Instead of Fail

Rather than forcing users to re-authenticate with different scopes (which `claude setup-token` doesn't support anyway) or causing hard crashes in Claude Code's status line, we intercept 403 permission errors on these specific endpoints and return empty success responses. This allows Claude Code to degrade gracefully: no usage stats displayed, but no crashes either.

Security Consideration

This transformation only applies to: 1. These explicitly listed endpoints 2. 403 status codes (all 403s on these endpoints, not just permission errors) 3. Requests using OAuth tokens (not API keys)

Other errors (401, 500, etc.) pass through unchanged to preserve observability.

Functions

func ConfigureBaseURLProxy added in v0.3.0

func ConfigureBaseURLProxy(p provider.ProxyConfigurer, cred *provider.Credential, baseURLHost string)

ConfigureBaseURLProxy registers credential injection for a custom base URL host, mirroring the standard api.anthropic.com injection. This is called by the run manager when claude.base_url is configured, so that a host-side LLM proxy receives requests with credentials already injected.

The function checks cred.Provider to determine the correct header format: - "claude" (OAuth): Bearer Authorization header + beta flag + response transformer - "anthropic" (API key): x-api-key header

func CreateOAuthEndpointTransformer

func CreateOAuthEndpointTransformer() func(req, resp interface{}) (interface{}, bool)

CreateOAuthEndpointTransformer creates a response transformer that handles 403 errors on OAuth endpoints by returning empty success responses.

The transformer: 1. Only acts on 403 status codes 2. Checks if the request path matches one of OAuthEndpointWorkarounds 3. Returns an empty but valid JSON response for that endpoint 4. Adds X-Moat-Transformed header for observability

We don't check the response body because: - These are explicitly listed OAuth endpoints (not wildcards) - Any 403 on these endpoints is almost certainly a scope issue - Body checking requires handling gzip/compression which adds complexity - Transforming a non-scope 403 is harmless (returns empty data, no crash)

Original 403 responses are still logged for debugging, but the client receives a success response to prevent crashes.

func LoadKnownMarketplaces

func LoadKnownMarketplaces(path string) (map[string]MarketplaceEntry, error)

LoadKnownMarketplaces loads Claude's known_marketplaces.json file. This file contains marketplace URLs that Claude Code has registered via `claude plugin marketplace add`. Returns nil, nil if the file doesn't exist.

URL normalization: - "github" sources are normalized to git URLs (https://github.com/owner/repo.git) - We assume repos don't contain trailing slashes or .git suffixes (Claude CLI standard) - Git URLs are used as-is without normalization

Entries are skipped (with debug logging) if they have: - Empty repo/URL fields - Invalid characters in repo format (shell injection protection)

func ReadHostConfig

func ReadHostConfig(path string) (map[string]any, error)

ReadHostConfig reads the host's ~/.claude.json and returns allowlisted fields. Returns nil, nil if the file doesn't exist (same pattern as LoadSettings).

func WorkspaceToClaudeDir added in v0.3.0

func WorkspaceToClaudeDir(absPath string) string

WorkspaceToClaudeDir converts an absolute workspace path to Claude's project directory format. Example: /home/alice/projects/myapp -> -home-alice-projects-myapp

func WriteClaudeConfig

func WriteClaudeConfig(stagingDir string, mcpServers map[string]MCPServerForContainer, hostConfig map[string]any) error

WriteClaudeConfig writes a minimal ~/.claude.json to the staging directory. This skips the onboarding flow, sets dark theme, and optionally configures MCP servers. mcpServers is a map of server names to their configurations. hostConfig contains allowlisted fields from the host's ~/.claude.json to merge in.

func WriteCredentialsFile

func WriteCredentialsFile(cred *provider.Credential, stagingDir string) error

WriteCredentialsFile writes a placeholder credentials file to the staging directory. This should only be called for OAuth tokens - API keys don't need credential files.

SECURITY: The real OAuth token is NEVER written to the container filesystem. Authentication is handled by the TLS-intercepting proxy at the network layer.

Types

type AnthropicProvider added in v0.3.0

type AnthropicProvider struct{}

AnthropicProvider implements provider.CredentialProvider for Anthropic API keys.

API keys work with any tool or agent — they are not restricted to Claude Code. This provider uses the x-api-key header with no OAuth workarounds.

func (*AnthropicProvider) Cleanup added in v0.3.0

func (p *AnthropicProvider) Cleanup(cleanupPath string)

Cleanup cleans up Anthropic resources.

func (*AnthropicProvider) ConfigureProxy added in v0.3.0

func (p *AnthropicProvider) ConfigureProxy(proxy provider.ProxyConfigurer, cred *provider.Credential)

ConfigureProxy sets up proxy headers for API keys on the Anthropic API.

func (*AnthropicProvider) ContainerEnv added in v0.3.0

func (p *AnthropicProvider) ContainerEnv(cred *provider.Credential) []string

ContainerEnv returns environment variables for API key injection.

func (*AnthropicProvider) ContainerMounts added in v0.3.0

func (p *AnthropicProvider) ContainerMounts(cred *provider.Credential, containerHome string) ([]provider.MountConfig, string, error)

ContainerMounts returns mounts needed for the Anthropic provider (none).

func (*AnthropicProvider) Grant added in v0.3.0

Grant acquires an Anthropic API key interactively.

func (*AnthropicProvider) ImpliedDependencies added in v0.3.0

func (p *AnthropicProvider) ImpliedDependencies() []string

ImpliedDependencies returns dependencies implied by the Anthropic provider.

func (*AnthropicProvider) Name added in v0.3.0

func (p *AnthropicProvider) Name() string

Name returns the provider identifier.

type KnownMarketplace

type KnownMarketplace struct {
	Source          KnownMarketplaceSource `json:"source"`
	InstallLocation string                 `json:"installLocation"`
	LastUpdated     string                 `json:"lastUpdated"`
}

KnownMarketplace represents a single marketplace entry in known_marketplaces.json.

type KnownMarketplaceSource

type KnownMarketplaceSource struct {
	Source string `json:"source"` // "github" or "git"
	Repo   string `json:"repo,omitempty"`
	URL    string `json:"url,omitempty"`
}

KnownMarketplaceSource is the source info in known_marketplaces.json.

type KnownMarketplacesFile

type KnownMarketplacesFile struct {
	Marketplaces map[string]KnownMarketplace
}

KnownMarketplacesFile documents the ~/.claude/plugins/known_marketplaces.json format.

This file is created and maintained by Claude Code when users run `claude plugin marketplace add <repo>`. It stores the repository URLs for installed marketplaces, allowing moat to know where to fetch plugins from.

Note: This is an internal Claude Code file format that may change between versions. We parse only the fields we need (source info) and ignore others.

This type is defined for documentation; LoadKnownMarketplaces() parses the JSON directly into a map[string]KnownMarketplace.

type MCPServerForContainer

type MCPServerForContainer struct {
	Type    string            `json:"type"`
	URL     string            `json:"url,omitempty"`
	Headers map[string]string `json:"headers,omitempty"`
	Command string            `json:"command,omitempty"`
	Args    []string          `json:"args,omitempty"`
}

MCPServerForContainer represents an MCP server in Claude's .claude.json format. Supports both HTTP relay servers (type: "http") and local process servers (type: "stdio").

type MarketplaceConfig

type MarketplaceConfig struct {
	Name   string // Marketplace name (e.g., "claude-plugins-official")
	Source string // "github" or "git"
	Repo   string // Repository path (e.g., "anthropics/claude-plugins-official")
}

MarketplaceConfig represents a Claude Code plugin marketplace for image building.

type MarketplaceEntry

type MarketplaceEntry struct {
	Source MarketplaceSource `json:"source"`
}

MarketplaceEntry represents a marketplace in Claude's settings format.

type MarketplaceSource

type MarketplaceSource struct {
	// Source is the type: "git", "github", or "directory"
	Source string `json:"source"`

	// URL is the git URL (for source: git or github)
	URL string `json:"url,omitempty"`

	// Repo is the GitHub owner/repo shorthand (for source: github)
	// Claude Code's native settings.json uses this format.
	Repo string `json:"repo,omitempty"`

	// Path is the local directory path (for source: directory)
	Path string `json:"path,omitempty"`
}

MarketplaceSource defines the source location for a marketplace.

type OAuthProvider added in v0.3.0

type OAuthProvider struct{}

OAuthProvider implements provider.CredentialProvider and provider.AgentProvider for Claude Code OAuth tokens (from Claude Pro/Max subscriptions).

OAuth tokens are restricted by Anthropic's ToS to Claude Code and Claude.ai only. This provider uses Bearer auth with the required beta flag and response transformer.

func (*OAuthProvider) Cleanup added in v0.3.0

func (p *OAuthProvider) Cleanup(cleanupPath string)

Cleanup cleans up Claude resources.

func (*OAuthProvider) ConfigureProxy added in v0.3.0

func (p *OAuthProvider) ConfigureProxy(proxy provider.ProxyConfigurer, cred *provider.Credential)

ConfigureProxy sets up proxy headers for OAuth tokens on the Anthropic API.

func (*OAuthProvider) ContainerEnv added in v0.3.0

func (p *OAuthProvider) ContainerEnv(cred *provider.Credential) []string

ContainerEnv returns environment variables for OAuth token injection.

func (*OAuthProvider) ContainerMounts added in v0.3.0

func (p *OAuthProvider) ContainerMounts(cred *provider.Credential, containerHome string) ([]provider.MountConfig, string, error)

ContainerMounts returns mounts needed for Claude Code. This method returns empty because Claude Code setup uses the staging directory approach instead of direct mounts.

func (*OAuthProvider) Grant added in v0.3.0

Grant acquires a Claude Code OAuth token interactively. Offers OAuth-specific options: setup-token, paste existing token, or import from local Claude Code installation.

func (*OAuthProvider) ImpliedDependencies added in v0.3.0

func (p *OAuthProvider) ImpliedDependencies() []string

ImpliedDependencies returns dependencies implied by the Claude OAuth provider.

func (*OAuthProvider) Name added in v0.3.0

func (p *OAuthProvider) Name() string

Name returns the provider identifier.

func (*OAuthProvider) OnRunStopped added in v0.3.0

func (p *OAuthProvider) OnRunStopped(ctx provider.RunStoppedContext) map[string]string

OnRunStopped extracts the Claude session ID from the projects directory after the container exits. It implements provider.RunStoppedHook.

func (*OAuthProvider) PrepareContainer added in v0.3.0

func (p *OAuthProvider) PrepareContainer(ctx context.Context, opts provider.PrepareOpts) (*provider.ContainerConfig, error)

PrepareContainer sets up staging directories and config files for Claude Code. It creates the necessary files that will be copied into the container at startup.

If opts.HostConfig is nil, this method reads the host's ~/.claude.json automatically.

This method works with both OAuth tokens and API keys. The credential type determines which environment variable placeholder is set.

func (*OAuthProvider) RegisterCLI added in v0.3.0

func (p *OAuthProvider) RegisterCLI(root *cobra.Command)

RegisterCLI adds provider-specific commands to the root command. This adds the `moat claude` command group with subcommands.

type PluginSnippetResult

type PluginSnippetResult struct {
	// DockerfileSnippet is the Dockerfile text to append (COPY + RUN).
	DockerfileSnippet string
	// ScriptName is the context file name (empty if no plugins).
	ScriptName string
	// ScriptContent is the shell script content (nil if no plugins).
	ScriptContent []byte
}

PluginSnippetResult holds the Dockerfile snippet and optional script context file.

func GenerateDockerfileSnippet

func GenerateDockerfileSnippet(marketplaces []MarketplaceConfig, plugins []string, containerUser string) PluginSnippetResult

GenerateDockerfileSnippet generates Dockerfile commands for Claude Code plugin installation. Returns an empty result if no marketplaces or plugins are configured.

Plugin install commands are written to a separate shell script (returned as a context file) rather than inline Dockerfile RUN steps. This keeps the Dockerfile under the Apple containers builder's ~16KB gRPC transport limit which causes "Transport became inactive" errors for larger Dockerfiles.

The containerUser parameter specifies the user to install plugins as. This is used in Dockerfile USER and WORKDIR commands. Callers must ensure this is a safe, validated value (e.g., hardcoded "moatuser") since it's inserted directly into the Dockerfile. The function does not validate this parameter to allow flexibility in user naming.

type SettingSource

type SettingSource string

SettingSource identifies where a setting came from.

const (
	SourceClaudeUser SettingSource = "~/.claude/settings.json"
	SourceMoatUser   SettingSource = "~/.moat/claude/settings.json"
	SourceProject    SettingSource = ".claude/settings.json"
	SourceMoatYAML   SettingSource = "moat.yaml"
	SourceUnknown    SettingSource = "unknown"
)

type Settings

type Settings struct {
	// EnabledPlugins maps "plugin-name@marketplace" to enabled/disabled state
	EnabledPlugins map[string]bool `json:"enabledPlugins,omitempty"`

	// ExtraKnownMarketplaces defines additional plugin marketplaces
	ExtraKnownMarketplaces map[string]MarketplaceEntry `json:"extraKnownMarketplaces,omitempty"`

	// PluginSources tracks where each plugin setting came from (not serialized)
	PluginSources map[string]SettingSource `json:"-"`

	// MarketplaceSources tracks where each marketplace setting came from (not serialized)
	MarketplaceSources map[string]SettingSource `json:"-"`
}

Settings represents Claude's native settings.json format. This is the format used by Claude Code in .claude/settings.json files.

func ConfigToSettings

func ConfigToSettings(cfg *config.Config) *Settings

ConfigToSettings converts moat.yaml claude config to Settings format.

func LoadAllSettings

func LoadAllSettings(workspacePath string, cfg *config.Config) (*Settings, error)

LoadAllSettings loads and merges settings from all sources. Merge precedence (lowest to highest): 1. ~/.claude/plugins/known_marketplaces.json (Claude's registered marketplaces) 2. ~/.claude/settings.json (Claude's native user settings) 3. ~/.moat/claude/settings.json (user defaults for moat runs) 4. <workspace>/.claude/settings.json (project defaults) 5. moat.yaml claude.* fields (run overrides)

func LoadSettings

func LoadSettings(path string) (*Settings, error)

LoadSettings loads a single Claude settings.json file. Returns nil, nil if the file doesn't exist.

func MergeSettings

func MergeSettings(base, override *Settings, overrideSource SettingSource) *Settings

MergeSettings merges two settings objects with override taking precedence. This implements the merge rules: - enabledPlugins: Union all sources; later overrides earlier for same plugin - extraKnownMarketplaces: Union all sources; later overrides earlier for same name The overrideSource is used to track where override settings came from.

func (*Settings) GetMarketplaceNames

func (s *Settings) GetMarketplaceNames() []string

GetMarketplaceNames returns the names of all marketplaces referenced in settings. This includes marketplaces from extraKnownMarketplaces and those inferred from plugin names.

func (*Settings) HasPluginsOrMarketplaces

func (s *Settings) HasPluginsOrMarketplaces() bool

HasPluginsOrMarketplaces returns true if the settings contain any plugins or marketplaces.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL