source dump of claude code
at main 123 lines 3.9 kB view raw
1import type { 2 EditableSettingSource, 3 SettingSource, 4} from '../settings/constants.js' 5import { 6 ALLOWED_OFFICIAL_MARKETPLACE_NAMES, 7 type PluginScope, 8} from './schemas.js' 9 10/** 11 * Extended scope type that includes 'flag' for session-only plugins. 12 * 'flag' scope is NOT persisted to installed_plugins.json. 13 */ 14export type ExtendedPluginScope = PluginScope | 'flag' 15 16/** 17 * Scopes that are persisted to installed_plugins.json. 18 * Excludes 'flag' which is session-only. 19 */ 20export type PersistablePluginScope = Exclude<ExtendedPluginScope, 'flag'> 21 22/** 23 * Map from SettingSource to plugin scope. 24 * Note: flagSettings maps to 'flag' which is session-only and not persisted. 25 */ 26export const SETTING_SOURCE_TO_SCOPE = { 27 policySettings: 'managed', 28 userSettings: 'user', 29 projectSettings: 'project', 30 localSettings: 'local', 31 flagSettings: 'flag', 32} as const satisfies Record<SettingSource, ExtendedPluginScope> 33 34/** 35 * Parsed plugin identifier with name and optional marketplace 36 */ 37export type ParsedPluginIdentifier = { 38 name: string 39 marketplace?: string 40} 41 42/** 43 * Parse a plugin identifier string into name and marketplace components 44 * @param plugin The plugin identifier (name or name@marketplace) 45 * @returns Parsed plugin name and optional marketplace 46 * 47 * Note: Only the first '@' is used as separator. If the input contains multiple '@' symbols 48 * (e.g., "plugin@market@place"), everything after the second '@' is ignored. 49 * This is intentional as marketplace names should not contain '@'. 50 */ 51export function parsePluginIdentifier(plugin: string): ParsedPluginIdentifier { 52 if (plugin.includes('@')) { 53 const parts = plugin.split('@') 54 return { name: parts[0] || '', marketplace: parts[1] } 55 } 56 return { name: plugin } 57} 58 59/** 60 * Build a plugin ID from name and marketplace 61 * @param name The plugin name 62 * @param marketplace Optional marketplace name 63 * @returns Plugin ID in format "name" or "name@marketplace" 64 */ 65export function buildPluginId(name: string, marketplace?: string): string { 66 return marketplace ? `${name}@${marketplace}` : name 67} 68 69/** 70 * Check if a marketplace name is an official (Anthropic-controlled) marketplace. 71 * Used for telemetry redaction — official plugin identifiers are safe to log to 72 * general-access additional_metadata; third-party identifiers go only to the 73 * PII-tagged _PROTO_* BQ columns. 74 */ 75export function isOfficialMarketplaceName( 76 marketplace: string | undefined, 77): boolean { 78 return ( 79 marketplace !== undefined && 80 ALLOWED_OFFICIAL_MARKETPLACE_NAMES.has(marketplace.toLowerCase()) 81 ) 82} 83 84/** 85 * Map from installable plugin scope to editable setting source. 86 * This is the inverse of SETTING_SOURCE_TO_SCOPE for editable scopes only. 87 * Note: 'managed' scope cannot be installed to, so it's not included here. 88 */ 89const SCOPE_TO_EDITABLE_SOURCE: Record< 90 Exclude<PluginScope, 'managed'>, 91 EditableSettingSource 92> = { 93 user: 'userSettings', 94 project: 'projectSettings', 95 local: 'localSettings', 96} 97 98/** 99 * Convert a plugin scope to its corresponding editable setting source 100 * @param scope The plugin installation scope 101 * @returns The corresponding setting source for reading/writing settings 102 * @throws Error if scope is 'managed' (cannot install plugins to managed scope) 103 */ 104export function scopeToSettingSource( 105 scope: PluginScope, 106): EditableSettingSource { 107 if (scope === 'managed') { 108 throw new Error('Cannot install plugins to managed scope') 109 } 110 return SCOPE_TO_EDITABLE_SOURCE[scope] 111} 112 113/** 114 * Convert an editable setting source to its corresponding plugin scope. 115 * Derived from SETTING_SOURCE_TO_SCOPE to maintain a single source of truth. 116 * @param source The setting source 117 * @returns The corresponding plugin scope 118 */ 119export function settingSourceToScope( 120 source: EditableSettingSource, 121): Exclude<PluginScope, 'managed'> { 122 return SETTING_SOURCE_TO_SCOPE[source] as Exclude<PluginScope, 'managed'> 123}