type OutboundMode = 'rule' | 'global' | 'direct' type LogLevel = 'info' | 'debug' | 'warning' | 'error' | 'silent' type SysProxyMode = 'auto' | 'manual' type CardStatus = 'col-span-2' | 'col-span-1' | 'hidden' type SiderCardKey = | 'sysproxy' | 'tun' | 'profile' | 'proxy' | 'rule' | 'resource' | 'override' | 'connection' | 'mihomo' | 'dns' | 'sniff' | 'log' | 'substore' | 'network' | 'usage' type NetworkInfoCardKey = 'ip' | 'topology' | 'latency' type AppTheme = 'system' | 'light' | 'dark' type MihomoGroupType = 'Selector' | 'URLTest' | 'Fallback' | 'LoadBalance' | 'Relay' type Priority = | 'PRIORITY_LOW' | 'PRIORITY_BELOW_NORMAL' | 'PRIORITY_NORMAL' | 'PRIORITY_ABOVE_NORMAL' | 'PRIORITY_HIGH' | 'PRIORITY_HIGHEST' type MihomoProxyType = | 'Direct' | 'Reject' | 'RejectDrop' | 'Pass' | 'Dns' | 'Compatible' | 'Socks5' | 'Http' | 'Ssh' | 'Shadowsocks' | 'ShadowsocksR' | 'Snell' | 'Vmess' | 'Vless' | 'Trojan' | 'Hysteria' | 'Hysteria2' | 'Tuic' | 'WireGuard' | 'Mieru' | 'AnyTLS' | 'Sudoku' | 'Masque' | 'TrustTunnel' type TunStack = 'gvisor' | 'mixed' | 'system' type FindProcessMode = 'off' | 'strict' | 'always' type DnsMode = 'normal' | 'fake-ip' | 'redir-host' | 'hosts' type FilterMode = 'blacklist' | 'whitelist' | 'rule' type NetworkInterfaceInfo = os.NetworkInterfaceInfo interface IAppVersion { version: string changelog: string } interface IMihomoVersion { version: string meta: boolean } interface IMihomoTrafficInfo { up: number down: number } interface IMihomoMemoryInfo { inuse: number oslimit: number } interface IMihomoLogInfo { type: LogLevel payload: string time?: string } interface IMihomoRulesInfo { rules: IMihomoRulesDetail[] } interface IMihomoRulesDetail { type: string payload: string proxy: string size: number index: number extra?: { disabled: boolean hitCount: number hitAt: string missCount: number missAt: string } } interface IMihomoConnectionsInfo { downloadTotal: number uploadTotal: number connections?: IMihomoConnectionDetail[] memory: number } interface IMihomoConnectionDetail { id: string isActive: boolean metadata: { network: 'tcp' | 'udp' type: string sourceIP: string sourceGeoIP: string[] sourceIPASN: string destinationIP: string destinationGeoIP: string[] destinationIPASN: string sourcePort: string destinationPort: string inboundIP: string inboundPort: string inboundName: string inboundUser: string host: string dnsMode: string uid: number process: string processPath: string specialProxy: string specialRules: string remoteDestination: string dscp: number sniffHost: string } uploadSpeed?: number downloadSpeed?: number upload: number download: number start: string chains: string[] providerChains: string[] rule: string rulePayload: string } interface IMihomoHistory { time: string delay: number } type IMihomoGroupDelay = Record interface IMihomoDelay { delay?: number message?: string } interface IMihomoProxy { alive: boolean extra: Record history: IMihomoHistory[] id: string name: string tfo: boolean type: MihomoProxyType udp: boolean uot: boolean xudp: boolean mptcp: boolean smux: boolean interface?: string 'routing-mark'?: number 'provider-name'?: string 'dialer-proxy'?: string } interface IMihomoGroup { alive: boolean all: string[] extra: Record testUrl?: string expectedStatus?: string fixed?: string hidden: boolean history: IMihomoHistory[] icon: string name: string now: string tfo: boolean type: MihomoGroupType udp: boolean xudp: boolean } interface IMihomoProxies { proxies: Record } interface IMihomoMixedGroup extends IMihomoGroup { all: (IMihomoProxy | IMihomoGroup)[] } interface IMihomoRuleProviders { providers: Record } interface IMihomoRuleProvider { behavior: string format: string name: string ruleCount: number type: string updatedAt: string vehicleType: string payload?: string[] } interface IMihomoProxyProviders { providers: Record } interface ISubscriptionUserInfoUpper { Upload: number Download: number Total: number Expire: number } interface IMihomoProxyProvider { name: string type: string proxies?: IMihomoProxy[] subscriptionInfo?: ISubscriptionUserInfoUpper expectedStatus: string testUrl?: string updatedAt?: string vehicleType: string } interface ISysProxyConfig { enable: boolean host?: string mode?: SysProxyMode bypass?: string[] pacScript?: string } interface INetworkLatencyTarget { name: string url: string } interface ICustomTrayIcons { off?: string sysProxy?: string tun?: string } interface IAppConfig { core: 'mihomo' | 'mihomo-alpha' | 'mihomo-smart' | 'mihomo-specific' specificVersion?: string enableSmartCore: boolean enableSmartOverride: boolean smartCoreUseLightGBM: boolean smartCoreCollectData: boolean smartCoreStrategy: 'sticky-sessions' | 'round-robin' smartCollectorSize?: number proxyDisplayMode: 'simple' | 'full' proxyDisplayOrder: 'default' | 'delay' | 'name' profileDisplayDate?: 'expire' | 'update' envType?: ('bash' | 'cmd' | 'powershell' | 'fish' | 'nushell')[] proxyCols: 'auto' | '1' | '2' | '3' | '4' hideUnavailableProxies?: boolean connectionDirection: 'asc' | 'desc' connectionOrderBy: 'time' | 'upload' | 'download' | 'uploadSpeed' | 'downloadSpeed' connectionViewMode?: 'list' | 'table' connectionTableColumns?: string[] connectionTableColumnWidths?: Record connectionTableSortColumn?: string connectionTableSortDirection?: 'asc' | 'desc' displayIcon?: boolean displayAppName?: boolean spinFloatingIcon?: boolean disableTray?: boolean swapTrayClick?: boolean showFloatingWindow?: boolean floatingWindowCompatMode?: boolean disableHardwareAcceleration?: boolean connectionCardStatus?: CardStatus dnsCardStatus?: CardStatus logCardStatus?: CardStatus hideConnectionCardWave?: boolean pauseSSID?: string[] disableDnsOnPauseSSID?: boolean controlDnsBeforePause?: boolean ssidProfileMap?: Record ssidProfileRestore?: boolean mihomoCoreCardStatus?: CardStatus overrideCardStatus?: CardStatus profileCardStatus?: CardStatus proxyCardStatus?: CardStatus networkCardStatus?: CardStatus resourceCardStatus?: CardStatus ruleCardStatus?: CardStatus sniffCardStatus?: CardStatus substoreCardStatus?: CardStatus sysproxyCardStatus?: CardStatus tunCardStatus?: CardStatus usageCardStatus?: CardStatus githubToken?: string gistAgeEncrypt?: boolean gistAgeRecipient?: string gistAgeSecretKey?: string useSubStore: boolean subStoreHost?: string subStoreBackendSyncCron?: string subStoreBackendDownloadCron?: string subStoreBackendUploadCron?: string autoQuitWithoutCore?: boolean autoQuitWithoutCoreDelay?: number autoQuitWithoutCoreMode?: 'core' | 'tray' useCustomSubStore?: boolean useProxyInSubStore?: boolean pluginUseProxy?: boolean // 新装插件默认路由模式:true → proxy(安全保证降级),false → auto mihomoCpuPriority?: Priority coreStartupMode?: 'log' | 'post-up' customSubStoreUrl?: string diffWorkDir?: boolean autoSetDNS?: boolean originDNS?: string useWindowFrame: boolean proxyInTray: boolean showCurrentProxyInTray: boolean enableTrafficLogger?: boolean siderOrder: string[] lastSelectedSiderCard?: SiderCardKey rememberSelectedSiderCard?: boolean lockSiderCards?: boolean siderWidth: number appTheme: AppTheme customTheme?: string autoCheckUpdate: boolean autoUpdateProfileOnStart: boolean silentUpdate: boolean githubProxy?: string silentStart: boolean autoCloseConnection: boolean sysProxy: ISysProxyConfig maxLogDays: number maxLogFileSize: number disableAppLog?: boolean disableCoreLog?: boolean userAgent?: string delayTestConcurrency?: number delayTestUrl?: string delayTestTimeout?: number networkLatencyTargets?: INetworkLatencyTarget[] networkIPProvider?: 'ip.sb' | 'ipwho.is' | 'ipapi.is' networkInfoCardOrder?: NetworkInfoCardKey[] subscriptionTimeout?: number encryptedPassword?: number[] controlDns?: boolean controlSniff?: boolean useDockIcon?: boolean showTraffic?: boolean disableTrayIconColor?: boolean customTrayIcon?: string customTrayIcons?: ICustomTrayIcons trayProxyGroupStyle?: 'default' | 'submenu' disableAnimations?: boolean webdavUrl?: string webdavDir?: string webdavUsername?: string webdavPassword?: string webdavMaxBackups?: number webdavBackupCron?: string webdavIgnoreCert?: boolean useNameserverPolicy: boolean nameserverPolicy: { [key: string]: string | string[] } showWindowShortcut?: string showFloatingWindowShortcut?: string triggerSysProxyShortcut?: string triggerTunShortcut?: string ruleModeShortcut?: string globalModeShortcut?: string directModeShortcut?: string restartAppShortcut?: string quitWithoutCoreShortcut?: string copyEnvShortcut?: string language?: 'zh-CN' | 'zh-TW' | 'en-US' | 'ru-RU' | 'fa-IR' triggerMainWindowBehavior?: 'show' | 'toggle' showMixedPort?: number enableMixedPort?: boolean showSocksPort?: number enableSocksPort?: boolean showHttpPort?: number enableHttpPort?: boolean showRedirPort?: number enableRedirPort?: boolean showTproxyPort?: number enableTproxyPort?: boolean testProfileOnStart?: boolean useHotReloadProfile?: boolean hotReloadProfileAutoCloseConnection?: boolean } interface IMihomoTunConfig { enable?: boolean stack?: TunStack 'auto-route'?: boolean 'auto-redirect'?: boolean 'auto-detect-interface'?: boolean 'dns-hijack'?: string[] device?: string mtu?: number 'strict-route'?: boolean gso?: boolean 'gso-max-size'?: number 'udp-timeout'?: number 'iproute2-table-index'?: number 'iproute2-rule-index'?: number 'endpoint-independent-nat'?: boolean 'route-address-set'?: string[] 'route-exclude-address-set'?: string[] 'route-address'?: string[] 'route-exclude-address'?: string[] 'include-interface'?: string[] 'exclude-interface'?: string[] 'include-uid'?: number[] 'include-uid-range'?: string[] 'exclude-uid'?: number[] 'exclude-uid-range'?: string[] 'include-android-user'?: string[] 'include-package'?: string[] 'exclude-package'?: string[] } interface IMihomoDNSConfig { enable?: boolean listen?: string ipv6?: boolean 'ipv6-timeout'?: number 'prefer-h3'?: boolean 'enhanced-mode'?: DnsMode 'fake-ip-range'?: string 'fake-ip-filter'?: string[] 'fake-ip-filter-mode'?: FilterMode 'use-hosts'?: boolean 'use-system-hosts'?: boolean 'respect-rules'?: boolean 'default-nameserver'?: string[] nameserver?: string[] fallback?: string[] 'fallback-filter'?: { [key: string]: boolean | string | string[] } 'proxy-server-nameserver'?: string[] 'direct-nameserver'?: string[] 'direct-nameserver-follow-policy'?: boolean 'nameserver-policy'?: { [key: string]: string | string[] } 'cache-algorithm'?: string } interface IMihomoSnifferConfig { enable?: boolean 'parse-pure-ip'?: boolean 'override-destination'?: boolean 'force-dns-mapping'?: boolean 'force-domain'?: string[] 'skip-domain'?: string[] 'skip-dst-address'?: string[] 'skip-src-address'?: string[] sniff?: { HTTP?: { ports: (number | string)[] 'override-destination'?: boolean } TLS?: { ports: (number | string)[] } QUIC?: { ports: (number | string)[] } } } interface IMihomoProfileConfig { 'store-selected'?: boolean 'store-fake-ip'?: boolean } interface IMihomoConfig { 'external-controller-pipe': string 'external-controller-unix': string 'external-controller': string 'external-ui': string 'external-ui-url': string 'external-controller-cors'?: { 'allow-origins'?: string[] 'allow-private-network'?: boolean } secret?: string ipv6: boolean mode: OutboundMode 'mixed-port': number 'allow-lan': boolean 'unified-delay': boolean 'tcp-concurrent': boolean 'log-level': LogLevel 'find-process-mode': FindProcessMode 'socks-port'?: number 'redir-port'?: number 'tproxy-port'?: number 'skip-auth-prefixes'?: string[] 'bind-address'?: string 'lan-allowed-ips'?: string[] 'lan-disallowed-ips'?: string[] authentication: string[] port?: number proxies?: [] 'proxy-groups'?: [] rules?: [] hosts?: { [key: string]: string | string[] } 'geodata-mode'?: boolean 'geo-auto-update'?: boolean 'geo-update-interval'?: number 'geox-url'?: { geoip?: string geosite?: string mmdb?: string asn?: string } tun: IMihomoTunConfig dns: IMihomoDNSConfig sniffer: IMihomoSnifferConfig profile: IMihomoProfileConfig } interface IProfileConfig { current?: string items: IProfileItem[] } interface IOverrideItem { id: string type: 'remote' | 'local' ext: 'js' | 'yaml' name: string updated: number global?: boolean url?: string file?: string } interface IOverrideConfig { items: IOverrideItem[] } interface ISubscriptionUserInfo { upload: number download: number total: number expire: number } interface IProfileItem { id: string type: 'remote' | 'local' | 'plugin' name: string url?: string // remote file?: string // local interval?: number | string home?: string updated?: number override?: string[] useProxy?: boolean extra?: ISubscriptionUserInfo substore?: boolean allowFixedInterval?: boolean autoUpdate?: boolean authToken?: string userAgent?: string ageSecretKey?: string updateTimeout?: number pluginId?: string } interface ISubStoreSub { name: string displayName?: string icon?: string tag?: string[] } interface IPluginProvider { name: string icon?: string site?: string description?: string // §4 机场静态说明,≤500 码点,已清洗 } // .cpx v2 — public, unencrypted descriptor. Contains NO secrets. interface IPluginDescriptor { magic: 'CPXF' v: 2 spec: 'cpx-plugin/2' loginUrl: string // OAuth authorize endpoint, https, no query/fragment provider: IPluginProvider discoveryUrls?: string[] // §3 备用发现源:公网 https origin,1..8,去重,不含 loginUrl 的 origin providerPubKey?: string // §5 Ed25519 原始 32 字节公钥,标准 base64 带 padding;每个 .cpx 谱系独立密钥 } // Subset returned by previewPlugin for the install-confirm page (no records, no network) interface IPluginDescriptorPreview { name: string icon?: string site?: string loginUrl: string // full url; UI shows the host spec: string discoveryHosts?: string[] // §3 备用发现域名(纯文本 host) description?: string // §4 } // 发现结果(§3/§5):来自任一发现源的归一化候选。seq 与 digest 成对出现(仅签名文档)。 interface IDiscoveryCandidate { gateways: string[] endpoints: IGatewayEndpoints seq?: number digest?: string loginUrl?: string discoveryUrls?: string[] } // 签名发现文档的 payload(§5.2) interface IDiscoveryPayload { spec: 'cpx-plugin/2' seq: number // 1 ≤ seq ≤ 2^53−1 gateways: string[] endpoints: IGatewayEndpoints loginUrl?: string discoveryUrls?: string[] // 缺失 = 不改;[] = 清空 } // 有 providerPubKey 的插件在发现时携带:minSeq / currentDigest 来自 plugin.yaml interface DiscoverySigner { pubKeyB64: string minSeq?: number currentDigest?: string } interface IPluginFilePayload { name: string fileBytesB64: string } interface IGatewayEndpoints { enroll: string challenge: string config: string revoke: string } // /.well-known/cpx-gateway discovery response(归一化后)。线格式仍带 `gateway`(= gateways[0])供旧客户端读取。 interface IGatewayWellKnown { spec: 'cpx-plugin/2' gateways: string[] // https origins, no path/query/fragment; 1..3, deduplicated endpoints: IGatewayEndpoints } type IPluginStatus = 'needs-login' | 'active' | 'needs-reauth' // 路由模式:auto = 直连优先、失败回退代理(§1);direct / proxy 为用户显式覆盖,不回退。 type IPluginRouteMode = 'auto' | 'direct' | 'proxy' interface IPluginItem { id: string name: string icon?: string site?: string loginUrl: string // public metadata; required to re-open the browser after restart spec: string profileId?: string // absent while 'needs-login'; present once 'active'/'needs-reauth' status: IPluginStatus interval?: number autoUpdate?: boolean useProxy?: boolean // 过渡期镜像写:routeMode === 'proxy';供降级到旧版本读取 routeMode?: IPluginRouteMode // §1;缺失时按 useProxy / 全局 pluginUseProxy 推导 lastGoodRoute?: 'direct' | 'proxy' // §1;仅 auto 模式读写,不是秘密 lastUpdateErrorReason?: 'blocked' | 'network' | 'server' // §4.2;客户端侧枚举,不含 host/IP discoveryUrls?: string[] // §3 公开元数据,与 loginUrl 同级的静态信任根 description?: string // §4 机场静态说明 lastProviderMessage?: string // §4 上次失败时机场返回的 message;成功后清空 providerPubKey?: string // §5 公开元数据 discoverySeq?: number // §5 提交标记,与 discoveryDigest 成对(同时存在或同时缺失) discoveryDigest?: string // §5 SHA-256(payloadBytes) hex created: number updated: number lastUpdateErrorType?: 'auth' | 'transient' lastUpdateErrorAt?: number nextRetryAt?: number failureCount?: number } interface IPluginConfig { items: IPluginItem[] } // 缓存的网关状态(§2.3)。写出时始终镜像 gateway.gateway = lastGood ?? gateways[0],供降级到旧版本读取。 interface IPluginGatewayState { gateway: string // 过渡期镜像:= lastGood ?? gateways[0] gateways: string[] // 归一化后,1..3 endpoints: IGatewayEndpoints lastGood?: string // 必须 ∈ gateways,否则视为未设置 } // safeStorage-encrypted vault payload — the ONLY place secrets live. interface IPluginVault { devicePrivKey: string // Ed25519 raw 32-byte seed, base64 (standard, padded) deviceId: string // UUIDv4 gateway: IPluginGatewayState // 被新设备替换、但尚未在服务端回收的旧设备:重新登录成功后 best-effort 回收,失败留在这里等下次拉取 / 删除 staleDevices?: IPluginStaleDevice[] } interface IPluginStaleDevice { deviceId: string devicePrivKey: string }