profile

package
v5.14.2 Latest Latest
Warning

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

Go to latest
Published: Mar 19, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

ABOUTME: Applies a profile to Claude Code using replace strategy ABOUTME: Computes diff, resolves secrets, executes via claude CLI

ABOUTME: Concurrent execution engine for profile apply operations ABOUTME: Handles parallel marketplace/plugin installs with progress tracking

ABOUTME: Category detection and plugin mapping for marketplaces ABOUTME: Hardcoded support for known marketplaces (wshobson/agents)

ABOUTME: Non-interactive profile creation from flags or file input ABOUTME: Provides CreateSpec validation and profile construction

ABOUTME: Project detection logic for profile suggestions ABOUTME: Matches project files and content against profile detect rules

ABOUTME: Diff logic for comparing a saved profile against live Claude Code state ABOUTME: Types and functions for scope-aware profile comparison

ABOUTME: Embedded default profiles for claudeup ABOUTME: Uses Go embed to bundle profiles in the binary

ABOUTME: Writes .mcp.json files in Claude Code's native format ABOUTME: Enables project-scoped MCP servers that Claude auto-loads

ABOUTME: Profile struct and Load/Save functionality for claudeup ABOUTME: Profiles define a desired state of Claude Code configuration

ABOUTME: Resolution engine for profile includes (composable stacks) ABOUTME: Flattens include trees into a single merged profile with cycle detection

ABOUTME: Defines the Scope type for profile application targets ABOUTME: Supports user, project, and local scopes for Claude Code configuration

ABOUTME: Creates a profile from current Claude Code state ABOUTME: Reads installed plugins, marketplaces, MCP servers, and extensions

ABOUTME: Interactive wizard for creating profiles ABOUTME: Handles name validation, marketplace selection, plugin selection

ABOUTME: Worker pool for concurrent job execution during profile apply ABOUTME: Executes jobs in parallel with configurable worker count

Index

Constants

View Source
const DefaultWorkers = 4

DefaultWorkers is the default number of concurrent workers

View Source
const MCPConfigFile = ".mcp.json"

MCPConfigFile is the filename for Claude's native MCP configuration

View Source
const MaxIncludeDepth = 50

MaxIncludeDepth limits how deeply nested include chains can be. Prevents resource exhaustion from pathological deep nesting.

View Source
const MaxInputSize = 10 * 1024 * 1024

MaxInputSize is the maximum size for JSON input (10MB)

Variables

This section is empty.

Functions

func BuildRepoToNameLookup

func BuildRepoToNameLookup(claudeDir string) map[string]string

BuildRepoToNameLookup reads known_marketplaces.json and builds a map from repo to name

func DefaultClaudeDir

func DefaultClaudeDir() string

DefaultClaudeDir returns the Claude configuration directory Respects CLAUDE_CONFIG_DIR environment variable if set

func Detect

func Detect(dir string, p *Profile) (bool, error)

Detect checks if a profile's detect rules match the given directory Files: ANY file existing is a match (OR-based within files) Contains: ANY pattern matching is a match (OR-based within contains) Overall: If both are specified, BOTH categories must have at least one match

func EnsureDefaultProfiles

func EnsureDefaultProfiles(profilesDir string) error

EnsureDefaultProfiles extracts embedded profiles to the profiles directory if they don't already exist

func FindProfilePaths

func FindProfilePaths(profilesDir, name string) ([]string, error)

FindProfilePaths walks profilesDir recursively and returns absolute paths to .json files whose filename stem matches name. If name contains a "/", it is treated as a relative path reference: only profilesDir/name.json is checked (after validating the path stays within profilesDir). Returns an empty slice (not an error) if profilesDir does not exist. The profilesDir argument is resolved to an absolute path internally.

func GenerateWizardDescription

func GenerateWizardDescription(marketplaceCount, pluginCount int) string

GenerateWizardDescription creates description based on wizard selections

func GetEmbeddedProfileScriptDir

func GetEmbeddedProfileScriptDir(profileName string) string

GetEmbeddedProfileScriptDir extracts embedded scripts for a profile to a temp directory Returns empty string if no scripts exist or extraction fails

func HasCategories

func HasCategories(marketplaceRepo string) bool

HasCategories returns true if the marketplace has category metadata

func IsEmbeddedProfile

func IsEmbeddedProfile(name string) bool

IsEmbeddedProfile checks if a profile with the given name exists in embedded profiles

func MCPJSONExists

func MCPJSONExists(projectDir string) bool

MCPJSONExists returns true if a .mcp.json file exists in the directory

func ProjectProfilesDir

func ProjectProfilesDir(projectDir string) string

ProjectProfilesDir returns the path to project-local profiles directory

func PromptForDescription

func PromptForDescription(autoGenerated string) (string, error)

PromptForDescription shows auto-generated description and allows editing

func PromptForName

func PromptForName() (string, error)

PromptForName prompts the user to enter a profile name Returns the validated name or an error

func RunHook

func RunHook(profile *Profile, opts HookOptions) error

RunHook executes the post-apply hook

func Save

func Save(profilesDir string, p *Profile) error

Save writes a profile to the profiles directory

func SaveToProject

func SaveToProject(projectDir string, p *Profile) error

SaveToProject saves a profile to the project's .claudeup/profiles/ directory

func SelectPluginsForMarketplace

func SelectPluginsForMarketplace(marketplace Marketplace) ([]string, error)

SelectPluginsForMarketplace prompts user to select plugins from a marketplace Uses category-based selection if marketplace has categories, otherwise flat list

func ShouldRunHook

func ShouldRunHook(profile *Profile, claudeDir, claudeJSONPath, claudeupHome string, opts HookOptions) bool

ShouldRunHook checks if the post-apply hook should run based on condition and current state

func ValidateCreateSpec

func ValidateCreateSpec(description string, marketplaces []string, plugins []string) error

ValidateCreateSpec validates input for non-interactive profile creation. Requires a description and at least one marketplace. Validates marketplace and plugin formats using ParseMarketplaceArg and ValidatePluginFormat.

func ValidateName

func ValidateName(name string) error

ValidateName checks if a profile name is valid

func ValidatePluginFormat

func ValidatePluginFormat(plugin string) error

ValidatePluginFormat validates a plugin string is in "name@marketplace-ref" format. Plugin names can contain colons (e.g., "backend:api-design@marketplace"). Uses LastIndex to find @ since plugin names may contain @, but the last @ is the separator.

func ValidatePluginMarketplaces added in v5.8.0

func ValidatePluginMarketplaces(plugins []string, marketplaces []Marketplace, registryKeys []string) error

ValidatePluginMarketplaces checks that every plugin's marketplace ref resolves to either a marketplace in the profile or an installed marketplace registry key. Plugins without an @ separator are skipped (they're invalid format but that's caught by ValidatePluginFormat).

func WriteMCPJSON

func WriteMCPJSON(projectDir string, servers []MCPServer) error

WriteMCPJSON writes a .mcp.json file to the given directory

Types

type AmbiguousProfileError

type AmbiguousProfileError struct {
	Name  string   // the profile name that was searched for
	Paths []string // relative paths of all matching profiles (forward-slash separated, without .json)
}

AmbiguousProfileError is returned when a profile name matches multiple files in the profiles directory (e.g. both "profiles/api.json" and "profiles/backend/api.json").

func (*AmbiguousProfileError) Error

func (e *AmbiguousProfileError) Error() string

type ApplyAllScopesOptions

type ApplyAllScopesOptions struct {
	// ReplaceUserScope controls whether user-scope settings are replaced (true)
	// or merged additively (false). Default is false (additive).
	// Project and local scopes always use declarative (replace) semantics.
	ReplaceUserScope bool
	Reinstall        bool            // Force reinstall of plugins even if already installed
	ShowProgress     bool            // Reserved; concurrent progress UI not yet integrated
	Executor         CommandExecutor // CLI executor; nil = create DefaultExecutor
	Output           io.Writer       // Progress output destination; nil = os.Stdout
}

ApplyAllScopesOptions controls how multi-scope profiles are applied.

type ApplyOptions

type ApplyOptions struct {
	Scope        Scope            // user, project, or local
	ProjectDir   string           // Required for project/local scope
	DryRun       bool             // If true, don't make changes (not yet implemented)
	Reinstall    bool             // If true, reinstall even if already installed
	ShowProgress bool             // If true, use concurrent apply with progress UI (project/local scope only)
	Progress     ProgressCallback // Optional progress callback for sequential installs
}

ApplyOptions controls how a profile is applied

type ApplyResult

type ApplyResult struct {
	PluginsRemoved           []string
	PluginsInstalled         []string
	PluginsAlreadyRemoved    []string // Plugins that were already uninstalled
	PluginsAlreadyPresent    []string // Plugins that were already installed
	MCPServersRemoved        []string
	MCPServersInstalled      []string
	MCPServersAlreadyPresent []string // MCP servers that were already configured
	MarketplacesAdded        []string
	MarketplacesRemoved      []string
	Warnings                 []error // Non-fatal pre-operation notices (e.g. load failures with fallback)
	Errors                   []error // Actual install/operation failures
}

ApplyResult contains the results of applying a profile

func Apply

func Apply(profile *Profile, claudeDir, claudeJSONPath, claudeupHome string, secretChain *secrets.Chain) (*ApplyResult, error)

Apply executes the profile changes using the default executor

func ApplyAllScopes

func ApplyAllScopes(profile *Profile, claudeDir, claudeJSONPath, projectDir, claudeupHome string, secretChain *secrets.Chain, opts *ApplyAllScopesOptions) (*ApplyResult, error)

ApplyAllScopes applies a profile to all scope levels. For multi-scope profiles (with PerScope), it applies each scope independently: settings files, plugin CLI installs, and extensions per scope. Project-scope MCP servers are written as .mcp.json; user/local MCP servers are installed via CLI. For legacy profiles (flat format), it applies to user scope only. The secretChain parameter is optional and used for MCP server secret resolution.

func ApplyWithExecutor

func ApplyWithExecutor(profile *Profile, claudeDir, claudeJSONPath, claudeupHome string, secretChain *secrets.Chain, executor CommandExecutor) (*ApplyResult, error)

ApplyWithExecutor executes the profile changes using the provided executor. This is the legacy API for backward compatibility; use ApplyWithOptions for new code.

func ApplyWithOptions

func ApplyWithOptions(profile *Profile, claudeDir, claudeJSONPath, claudeupHome string, secretChain *secrets.Chain, opts ApplyOptions) (*ApplyResult, error)

ApplyWithOptions applies a profile with the specified scope options

type Category

type Category struct {
	Name        string
	Description string
	Plugins     []string
}

Category represents a plugin category in a marketplace

func GetCategories

func GetCategories(marketplaceRepo string) []Category

GetCategories returns available categories for a marketplace

type ClaudeJSON

type ClaudeJSON struct {
	MCPServers map[string]ClaudeMCPServer `json:"mcpServers"`
}

ClaudeJSON represents the ~/.claude.json file structure (relevant parts)

type ClaudeMCPServer

type ClaudeMCPServer struct {
	Type    string            `json:"type"`
	Command string            `json:"command"`
	Args    []string          `json:"args"`
	Env     map[string]string `json:"env"`
}

ClaudeMCPServer represents an MCP server in ~/.claude.json

type CommandExecutor

type CommandExecutor interface {
	Run(args ...string) error
	RunWithOutput(args ...string) (string, error)
}

CommandExecutor runs claude CLI commands

type ConcurrentApplyOptions

type ConcurrentApplyOptions struct {
	ClaudeDir string
	Scope     string // "user", "project", "local"
	Reinstall bool   // Force reinstall even if already installed
	Output    io.Writer
	Executor  CommandExecutor
}

ConcurrentApplyOptions configures concurrent apply behavior

type ConcurrentApplyResult

type ConcurrentApplyResult struct {
	MarketplacesInstalled []string
	MarketplacesSkipped   []string
	PluginsInstalled      []string
	PluginsSkipped        []string
	MCPServersInstalled   []string
	MCPServersSkipped     []string // MCP servers skipped (already configured)
	Warnings              []error  // Non-fatal pre-operation notices (e.g. load failures with fallback)
	Errors                []error  // Actual install/operation failures
}

ConcurrentApplyResult contains results from concurrent apply

func ApplyConcurrently

func ApplyConcurrently(profile *Profile, opts ConcurrentApplyOptions) (*ConcurrentApplyResult, error)

ApplyConcurrently installs marketplaces and plugins concurrently with progress tracking

type CreateSpec

type CreateSpec struct {
	Description  string            `json:"description"`
	Marketplaces json.RawMessage   `json:"marketplaces"`
	Plugins      []string          `json:"plugins"`
	MCPServers   []MCPServer       `json:"mcpServers,omitempty"`
	PerScope     *PerScopeSettings `json:"perScope,omitempty"`
	Detect       DetectRules       `json:"detect,omitempty"`
}

CreateSpec is the input format for file/stdin profile creation

type DefaultExecutor

type DefaultExecutor struct {
	ClaudeDir string // Directory to use for CLAUDE_CONFIG_DIR env var
}

DefaultExecutor runs commands using the real claude CLI

func (*DefaultExecutor) Run

func (e *DefaultExecutor) Run(args ...string) error

Run executes the claude CLI with the given arguments

func (*DefaultExecutor) RunWithOutput

func (e *DefaultExecutor) RunWithOutput(args ...string) (string, error)

RunWithOutput executes the claude CLI and returns captured output

type DetectRules

type DetectRules struct {
	Files    []string          `json:"files,omitempty"`
	Contains map[string]string `json:"contains,omitempty"`
}

DetectRules defines how to auto-detect if a profile matches a project

type Diff

type Diff struct {
	PluginsToRemove      []string
	PluginsToInstall     []string
	MCPToRemove          []string
	MCPToInstall         []MCPServer
	MarketplacesToAdd    []Marketplace
	MarketplacesToRemove []Marketplace
}

Diff represents what needs to change to apply a profile

func ComputeDiff

func ComputeDiff(profile *Profile, claudeDir, claudeJSONPath, claudeupHome string) (*Diff, error)

ComputeDiff calculates what changes are needed to apply a profile. This compares against user scope by default; use ComputeDiffWithScope for scope-aware comparison.

func ComputeDiffWithScope

func ComputeDiffWithScope(profile *Profile, claudeDir, claudeJSONPath, claudeupHome string, opts DiffOptions) (*Diff, error)

ComputeDiffWithScope calculates what changes are needed to apply a profile at a specific scope. For project/local scope, it compares only against that scope's current state (not user scope). This prevents confusing "Remove" actions for user-scope items when applying at project scope.

type DiffItem added in v5.8.0

type DiffItem struct {
	Op     DiffOp
	Kind   DiffItemKind
	Name   string
	Detail string // optional context (e.g., extension category, changed MCP field)
}

DiffItem represents a single difference

func UserScopeExtras added in v5.11.0

func UserScopeExtras(saved, live *Profile) []DiffItem

UserScopeExtras returns plugins that exist in live user-scope config but not in the saved profile. Both inputs should be in PerScope form (call AsPerScope() before passing). Returns nil if the saved profile has no user scope or there are no extras.

Only plugins are compared because ReplaceUserScope only affects enabledPlugins in settings.json. MCP servers, extensions, and marketplaces are managed separately and not affected by the replace toggle.

type DiffItemKind added in v5.8.0

type DiffItemKind string

DiffItemKind represents what type of config item differs

const (
	DiffPlugin      DiffItemKind = "plugin"
	DiffMCP         DiffItemKind = "mcp"
	DiffExtension   DiffItemKind = "extension"
	DiffMarketplace DiffItemKind = "marketplace"
)

type DiffOp added in v5.8.0

type DiffOp string

DiffOp represents the type of difference

const (
	DiffAdded    DiffOp = "added"
	DiffRemoved  DiffOp = "removed"
	DiffModified DiffOp = "modified"
)

type DiffOptions

type DiffOptions struct {
	Scope      Scope  // Target scope for comparison
	ProjectDir string // Required for project/local scope
	Reinstall  bool   // Force all profile items into install lists
}

DiffOptions controls how a diff is computed

type DirLoader

type DirLoader struct {
	ProfilesDir string
}

DirLoader loads profiles from disk via Load() with embedded fallback.

func (*DirLoader) LoadProfile

func (l *DirLoader) LoadProfile(name string) (*Profile, error)

LoadProfile loads a profile by name, delegating to Load() which handles both short names (recursive search) and path-qualified names (direct lookup). Falls back to embedded profiles only when the profile is not found on disk. Other errors (ambiguous names, invalid JSON) propagate without fallback.

type ExtensionSettings added in v5.3.0

type ExtensionSettings struct {
	Agents       []string `json:"agents,omitempty"`
	Commands     []string `json:"commands,omitempty"`
	Skills       []string `json:"skills,omitempty"`
	Hooks        []string `json:"hooks,omitempty"`
	Rules        []string `json:"rules,omitempty"`
	OutputStyles []string `json:"output-styles,omitempty"`
}

ExtensionSettings contains extension patterns to enable. These are items from ~/.claudeup/ext/ that get symlinked to ~/.claude/

func ReadExtensions added in v5.9.0

func ReadExtensions(claudeDir, claudeupHome string) (*ExtensionSettings, error)

ReadExtensions reads enabled extensions from enabled.json, verifying each item exists in the active directory. Stale entries are filtered out.

func ReadProjectExtensions added in v5.9.0

func ReadProjectExtensions(projectDir string) *ExtensionSettings

ReadProjectExtensions scans .claude/{agents,rules}/ in the project directory for regular files (not symlinks). Regular files are project-scoped extensions; symlinks are user-scoped extensions managed by claudeup and should be skipped.

type HookEntry

type HookEntry struct {
	Type    string `json:"type"`
	Command string `json:"command"`
}

HookEntry represents a single hook configuration for settings.json

type HookOptions

type HookOptions struct {
	ForceSetup    bool   // Run hook even if first-run check would skip
	NoInteractive bool   // Skip hook entirely (for CI/scripting)
	ScriptDir     string // Directory containing hook scripts (for built-in profiles)
}

HookOptions controls post-apply hook behavior

type InstallPluginsOptions

type InstallPluginsOptions struct {
	// Scope for plugin installation: "", "project", or "local"
	// Empty string means user scope (no --scope flag)
	Scope string

	// InstalledPlugins is an optional map of already-installed plugins.
	// If provided, plugins in this map are skipped before running install.
	// If nil, no pre-filtering occurs (relies on "already installed" output).
	InstalledPlugins map[string]bool

	// Progress is an optional callback for reporting installation progress.
	// Called with (current, total, pluginName) for each plugin being installed.
	Progress ProgressCallback
}

InstallPluginsOptions configures plugin installation behavior

type InstallPluginsResult

type InstallPluginsResult struct {
	Installed []string
	Skipped   []string // Already installed
	Errors    []error
}

InstallPluginsResult contains the result of installing plugins

func InstallPluginsWithProgress

func InstallPluginsWithProgress(
	plugins []string,
	executor CommandExecutor,
	opts InstallPluginsOptions,
) *InstallPluginsResult

InstallPluginsWithProgress installs plugins and reports progress. It handles both pre-filtering (when InstalledPlugins is provided) and fallback detection of "already installed" from command output.

type Job

type Job struct {
	Name    string       // Identifier for the job (e.g., plugin name)
	Type    string       // Job type (e.g., "marketplace", "plugin", "mcp")
	Execute func() error // The work to perform
}

Job represents a unit of work to execute

type JobResult

type JobResult struct {
	Name    string // Job identifier
	Type    string // Job type
	Success bool   // True if Execute returned nil
	Error   error  // The error if Execute failed
}

JobResult represents the outcome of executing a job

func RunWorkerPoolWithCallback

func RunWorkerPoolWithCallback(jobs []Job, workers int, callback func(JobResult)) []JobResult

RunWorkerPoolWithCallback executes jobs and calls callback after each completion Useful for updating progress UI as jobs complete

type MCPJSONConfig

type MCPJSONConfig struct {
	MCPServers map[string]MCPJSONServer `json:"mcpServers"`
}

MCPJSONConfig represents Claude Code's native .mcp.json format

type MCPJSONServer

type MCPJSONServer struct {
	Command string            `json:"command"`
	Args    []string          `json:"args,omitempty"`
	Env     map[string]string `json:"env,omitempty"`
}

MCPJSONServer represents an MCP server in Claude's .mcp.json format

type MCPServer

type MCPServer struct {
	Name    string               `json:"name"`
	Command string               `json:"command"`
	Args    []string             `json:"args,omitempty"`
	Scope   string               `json:"scope,omitempty"`
	Secrets map[string]SecretRef `json:"secrets,omitempty"`
}

MCPServer represents an MCP server configuration

func ReadMCPServersForScope added in v5.9.0

func ReadMCPServersForScope(claudeJSONPath, projectDir, scope string) ([]MCPServer, error)

ReadMCPServersForScope reads MCP servers from the appropriate config file for the given scope. claudeJSONPath is the path to the user-level Claude config file (typically ~/.claude/.claude.json). projectDir is the project root directory, used when scope is "project" to read .mcp.json.

type Marketplace

type Marketplace struct {
	Source string `json:"source"`
	Repo   string `json:"repo,omitempty"` // Used for github sources
	URL    string `json:"url,omitempty"`  // Used for git sources
}

Marketplace represents a plugin marketplace source

func GetAvailableMarketplaces

func GetAvailableMarketplaces() []Marketplace

GetAvailableMarketplaces returns all available marketplaces for selection Loads from ~/.claude/plugins/known_marketplaces.json, falling back to embedded profiles Filters out invalid entries where both repo and url are empty

func ParseMarketplaceArg

func ParseMarketplaceArg(arg string) (Marketplace, error)

ParseMarketplaceArg parses a marketplace argument in "owner/repo" format. Whitespace around owner and repo is trimmed for robustness. Additional path segments (owner/repo/extra) are preserved in Repo field and will be validated when the marketplace is actually accessed.

func SelectMarketplaces

func SelectMarketplaces(available []Marketplace) ([]Marketplace, error)

SelectMarketplaces prompts user to select marketplaces Returns selected marketplaces or error

func UsedMarketplaces added in v5.6.0

func UsedMarketplaces(claudeDir string, plugins []string) ([]Marketplace, error)

UsedMarketplaces returns marketplaces referenced by the given plugins.

func (Marketplace) DisplayName

func (m Marketplace) DisplayName() string

DisplayName returns the repo or URL for display purposes

type MarketplaceMetadata

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

MarketplaceMetadata represents metadata for a marketplace

type MarketplaceRegistry

type MarketplaceRegistry map[string]MarketplaceMetadata

MarketplaceRegistry represents known_marketplaces.json

type MarketplaceSource

type MarketplaceSource struct {
	Source string `json:"source"`
	Repo   string `json:"repo,omitempty"`
	URL    string `json:"url,omitempty"`
}

MarketplaceSource represents the source of a marketplace

type PerScopeSettings

type PerScopeSettings struct {
	User    *ScopeSettings `json:"user,omitempty"`
	Project *ScopeSettings `json:"project,omitempty"`
	Local   *ScopeSettings `json:"local,omitempty"`
}

PerScopeSettings organizes configuration by scope level. This enables profiles to capture and restore settings to the correct scope.

type PostApplyHook

type PostApplyHook struct {
	Script    string `json:"script,omitempty"`    // Script path relative to profile (takes precedence)
	Command   string `json:"command,omitempty"`   // Direct command to run (used if Script is empty)
	Condition string `json:"condition,omitempty"` // "always" (default) or "first-run"
}

PostApplyHook defines a hook to run after a profile is applied.

Execution order: Script takes precedence over Command. If both are set, only Script will be executed.

Condition types:

  • "always" (default): Hook runs every time the profile is applied
  • "first-run": Hook only runs if no plugins from the profile's marketplaces are currently enabled

Security note: Hooks execute arbitrary shell commands. Only use profiles from trusted sources.

type Profile

type Profile struct {
	Name           string         `json:"name"`
	Description    string         `json:"description,omitempty"`
	Includes       []string       `json:"includes,omitempty"`
	MCPServers     []MCPServer    `json:"mcpServers,omitempty"`
	Marketplaces   []Marketplace  `json:"marketplaces,omitempty"`
	Plugins        []string       `json:"plugins,omitempty"`
	SkipPluginDiff bool           `json:"skipPluginDiff,omitempty"` // If true, don't add/remove plugins (managed externally e.g. by wizard)
	Detect         DetectRules    `json:"detect,omitempty"`
	PostApply      *PostApplyHook `json:"postApply,omitempty"`

	// PerScope contains settings organized by scope (user, project, local).
	// When present, this takes precedence over the flat Plugins/MCPServers fields.
	// When absent, the flat fields are treated as user-scope (backward compatibility).
	PerScope *PerScopeSettings `json:"perScope,omitempty"`

	// Extensions contains patterns for extensions to enable (agents, commands, etc.)
	Extensions *ExtensionSettings `json:"extensions,omitempty"`

	// SettingsHooks contains hooks to merge into settings.json by event type
	SettingsHooks map[string][]HookEntry `json:"settingsHooks,omitempty"`
}

Profile represents a Claude Code configuration profile

func CreateFromFlags

func CreateFromFlags(name, description string, marketplaceArgs, plugins []string, scope string) (*Profile, error)

CreateFromFlags creates a profile from CLI flag values. Uses ValidateCreateSpec for input validation and ParseMarketplaceArg to convert marketplace strings to Marketplace structs. Plugins and MCPServers are placed under the specified scope in PerScope.

func CreateFromReader

func CreateFromReader(name string, r io.Reader, descOverride string, scope string, scopeExplicit bool) (*Profile, error)

CreateFromReader creates a profile from JSON input. If the input contains perScope, it is used directly and the scope argument is unused. If scopeExplicit is true and the input contains perScope, an error is returned because the caller's explicit scope flag would be silently disregarded. If the input uses flat plugins/mcpServers, they are wrapped into perScope under the specified scope. It is an error to specify both flat fields and perScope.

func FilterToScopes added in v5.10.1

func FilterToScopes(p *Profile, scopes map[string]bool) *Profile

FilterToScopes returns a copy of the profile containing only the scopes present in the given map (keyed by scope name). Marketplaces are included only when user scope is active (they are always user-scoped).

func FindMatchingProfiles

func FindMatchingProfiles(dir string, profiles []*Profile) []*Profile

FindMatchingProfiles returns all profiles that match the given directory

func GetEmbeddedProfile

func GetEmbeddedProfile(name string) (*Profile, error)

GetEmbeddedProfile returns an embedded profile by name

func ListEmbeddedProfiles

func ListEmbeddedProfiles() ([]*Profile, error)

ListEmbeddedProfiles returns all embedded profiles

func Load

func Load(profilesDir, name string) (*Profile, error)

Load reads a profile from the profiles directory. If name contains "/", it is treated as a relative path within profilesDir. Otherwise, profilesDir is searched recursively for a matching .json file. Returns an error if the name matches multiple profiles (ambiguous).

func LoadFromPath

func LoadFromPath(path string) (*Profile, error)

LoadFromPath loads a profile from an absolute file path. If the JSON does not contain a name field, the name is derived from the filename.

func ResolveIncludes

func ResolveIncludes(p *Profile, loader ProfileLoader) (*Profile, error)

ResolveIncludes recursively resolves includes and returns a merged profile. Returns an error if:

  • p is nil
  • the profile has includes alongside config fields (stacks must be pure)
  • a cycle is detected
  • an included profile cannot be loaded

If the profile has no includes, it is returned as-is.

func Snapshot

func Snapshot(name, claudeDir, claudeJSONPath, claudeupHome string) (*Profile, error)

Snapshot creates a Profile from the current Claude Code state (user scope)

func SnapshotAllScopes

func SnapshotAllScopes(name, claudeDir, claudeJSONPath, projectDir, claudeupHome string) (*Profile, error)

SnapshotAllScopes creates a Profile capturing settings from all three scopes (user, project, local) and organizing them in the PerScope structure. This is the preferred way to save profiles as it preserves scope information.

func SnapshotWithScope

func SnapshotWithScope(name, claudeDir, claudeJSONPath, claudeupHome string, opts SnapshotOptions) (*Profile, error)

SnapshotWithScope creates a Profile from a specific scope

func SuggestProfile

func SuggestProfile(dir string, profiles []*Profile) *Profile

SuggestProfile finds the best matching profile for a directory Returns nil if no profiles match

func (*Profile) AsPerScope added in v5.8.0

func (p *Profile) AsPerScope() *Profile

AsPerScope returns a copy of the profile with flat fields lifted into PerScope.User. If PerScope is already set, returns a shallow copy as-is.

func (*Profile) Clone

func (p *Profile) Clone(newName string) *Profile

Clone creates a deep copy of the profile with a new name

func (*Profile) CombinedScopes

func (p *Profile) CombinedScopes() *Profile

CombinedScopes returns a flat Profile combining all scopes (user + project + local). This aggregates plugins and MCP servers from all scopes into single lists, matching how Claude Code accumulates settings from user → project → local. Useful for comparing a multi-scope profile against the combined system state.

func (*Profile) Equal

func (p *Profile) Equal(other *Profile) bool

Equal compares two profiles for semantic equality, ignoring the Name field. Name is treated as an identifier, not content - two profiles with different names but identical content are considered equal. Nil and empty slices are treated as equivalent.

func (*Profile) FilterToScope added in v5.4.0

func (p *Profile) FilterToScope(scope string)

FilterToScope removes all scope data except the specified scope. This is used when saving a profile for a single scope.

func (*Profile) ForScope

func (p *Profile) ForScope(scope string) *Profile

ForScope returns a flat Profile containing only settings for the specified scope. This is useful for applying a single scope from a multi-scope profile. Marketplaces are always included since they're user-scoped.

func (*Profile) GenerateDescription

func (p *Profile) GenerateDescription() string

GenerateDescription creates a human-readable description of the profile contents

func (*Profile) HasConfigFields

func (p *Profile) HasConfigFields() bool

HasConfigFields returns true if the profile has any configuration fields beyond name, description, and includes.

func (*Profile) HasMCPServersWithSecrets

func (p *Profile) HasMCPServersWithSecrets() bool

HasMCPServersWithSecrets returns true if any MCP server in the profile has secrets defined. This is used to warn users that sync cannot resolve secrets.

func (*Profile) IsMultiScope

func (p *Profile) IsMultiScope() bool

IsMultiScope returns true if this profile uses per-scope settings.

func (*Profile) IsStack

func (p *Profile) IsStack() bool

IsStack returns true if this profile composes other profiles via includes.

func (*Profile) PreserveFrom

func (p *Profile) PreserveFrom(existing *Profile)

PreserveFrom copies extensions from an existing profile. When re-saving, this keeps only the extensions the user originally saved, preventing accumulation of items enabled by other tools.

func (*Profile) PreserveMCPSecrets added in v5.11.2

func (p *Profile) PreserveMCPSecrets(existing *Profile) []string

PreserveMCPSecrets restores $VAR references and Secrets metadata from an existing profile onto a snapshotted profile. When profile save captures live MCP server configs, secret values are resolved to plaintext. This function matches servers by name and replaces resolved arg values with the original $KEY references using positional matching against the existing profile's args.

Returns warning messages for servers that could not be restored (e.g., when arg counts have changed since the profile was last saved).

func (*Profile) UnmarshalJSON added in v5.3.0

func (p *Profile) UnmarshalJSON(data []byte) error

UnmarshalJSON handles migration from the old "localItems" JSON field to the new "extensions" field. Profiles saved before the rename used "localItems"; this ensures they load correctly.

func (*Profile) ValidateMarketplaceRefs added in v5.8.0

func (p *Profile) ValidateMarketplaceRefs(registryKeys []string) error

ValidateMarketplaceRefs checks that all plugin marketplace refs in this profile resolve to either a marketplace in the profile or an installed registry key. Uses CombinedScopes() to aggregate plugins from all scopes.

type ProfileDiff added in v5.8.0

type ProfileDiff struct {
	ProfileName       string
	DescriptionChange *[2]string // [profile, live] if different
	Scopes            []ScopeDiff
}

ProfileDiff contains the full diff result

func ComputeProfileDiff added in v5.8.0

func ComputeProfileDiff(saved, live *Profile) *ProfileDiff

ComputeProfileDiff compares a saved profile against a live snapshot. Both inputs should already be in PerScope form (caller normalizes via AsPerScope).

func (*ProfileDiff) Counts added in v5.8.0

func (d *ProfileDiff) Counts() (added, removed, modified int)

Counts returns the number of additions, removals, and modifications across all scopes

func (*ProfileDiff) IsEmpty added in v5.8.0

func (d *ProfileDiff) IsEmpty() bool

IsEmpty returns true if there are no differences

type ProfileEntry

type ProfileEntry struct {
	*Profile
	RelPath string
}

ProfileEntry is a profile with its location relative to the profiles directory. RelPath uses forward slashes (e.g. "backend/api.json" or "mobile.json").

func List

func List(profilesDir string) ([]ProfileEntry, error)

List returns all profiles in the profiles directory (including subdirectories), sorted by name then by relative path for duplicates.

func (ProfileEntry) DisplayName

func (e ProfileEntry) DisplayName() string

DisplayName returns the profile's display name for listing. For root profiles, this is just the profile name. For nested profiles, this is the relative path without the .json extension.

type ProfileLoader

type ProfileLoader interface {
	LoadProfile(name string) (*Profile, error)
}

ProfileLoader loads a profile by name or path-qualified name.

type ProfileWithSource

type ProfileWithSource struct {
	*Profile
	Source  string // "user" or "project"
	RelPath string // relative path within profiles dir (e.g. "backend/api.json")
}

ProfileWithSource wraps a profile with its source location and relative path

func ListAll

func ListAll(userProfilesDir, projectDir string) ([]*ProfileWithSource, error)

ListAll returns profiles from both user and project directories. Project profiles take precedence over user profiles with the same name.

func (*ProfileWithSource) DisplayName

func (p *ProfileWithSource) DisplayName() string

DisplayName returns the profile's display name for listing. For root profiles, this is just the profile name. For nested profiles, this is the relative path without the .json extension.

type ProgressCallback

type ProgressCallback func(current, total int, item string)

ProgressCallback reports installation progress for multi-item operations

type ResetResult

type ResetResult struct {
	PluginsRemoved      []string
	MCPServersRemoved   []string
	MarketplacesRemoved []string
	Errors              []error
}

ResetResult contains the results of resetting a profile

func Reset

func Reset(profile *Profile, claudeDir, claudeJSONPath, claudeupHome string) (*ResetResult, error)

Reset removes everything a profile installed (plugins, MCP servers, marketplaces)

func ResetWithExecutor

func ResetWithExecutor(profile *Profile, claudeDir, claudeJSONPath, claudeupHome string, executor CommandExecutor) (*ResetResult, error)

ResetWithExecutor removes everything a profile installed using the provided executor

type Scope

type Scope string

Scope represents where a profile should be applied

const (
	// ScopeUser applies profile at user level (~/.claude/)
	ScopeUser Scope = "user"
	// ScopeProject applies profile at project level (.claude/settings.json + .mcp.json)
	ScopeProject Scope = "project"
	// ScopeLocal applies profile locally for this project only (~/.claudeup/projects.json)
	ScopeLocal Scope = "local"
)

func ParseScope

func ParseScope(s string) (Scope, error)

ParseScope converts a string to a Scope, returning an error for invalid values

func (Scope) IsValid

func (s Scope) IsValid() bool

IsValid returns true if the scope is a recognized value

func (Scope) String

func (s Scope) String() string

type ScopeDiff added in v5.8.0

type ScopeDiff struct {
	Scope string // "user", "project", "local"
	Items []DiffItem
}

ScopeDiff contains all differences for a single scope

type ScopeSettings

type ScopeSettings struct {
	Plugins    []string           `json:"plugins,omitempty"`
	MCPServers []MCPServer        `json:"mcpServers,omitempty"`
	Extensions *ExtensionSettings `json:"extensions,omitempty"`
}

ScopeSettings contains settings for a single scope level.

type SecretRef

type SecretRef struct {
	Description string         `json:"description,omitempty"`
	Sources     []SecretSource `json:"sources"`
}

SecretRef defines a secret requirement with multiple resolution sources

type SecretSource

type SecretSource struct {
	Type    string `json:"type"`              // env, 1password, keychain
	Key     string `json:"key,omitempty"`     // for env
	Ref     string `json:"ref,omitempty"`     // for 1password
	Service string `json:"service,omitempty"` // for keychain
	Account string `json:"account,omitempty"` // for keychain
}

SecretSource defines a single source for resolving a secret

type SnapshotOptions

type SnapshotOptions struct {
	Scope      string // user, project, or local
	ProjectDir string // Required for project/local scope
}

SnapshotOptions controls how a snapshot is taken

Jump to

Keyboard shortcuts

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