'array', 'build_config' => 'array', 'used_in_projects' => 'array', 'licence_expires_at' => 'date', 'last_checked_at' => 'datetime', 'auto_update' => 'boolean', 'is_active' => 'boolean', ]; // Relationships public function versions(): HasMany { return $this->hasMany(AssetVersion::class); } // Scopes public function scopeActive($query) { return $query->where('is_active', true); } public function scopeComposer($query) { return $query->where('type', self::TYPE_COMPOSER); } public function scopeNpm($query) { return $query->where('type', self::TYPE_NPM); } public function scopeNeedsUpdate($query) { return $query->whereColumn('installed_version', '!=', 'latest_version') ->whereNotNull('latest_version'); } public function scopeAutoUpdate($query) { return $query->where('auto_update', true); } // Helpers public function hasUpdate(): bool { return $this->latest_version && $this->installed_version && version_compare($this->latest_version, $this->installed_version, '>'); } public function isLicenceExpired(): bool { return $this->licence_expires_at && $this->licence_expires_at->isPast(); } public function isLicenceExpiringSoon(int $days = 30): bool { return $this->licence_expires_at && $this->licence_expires_at->isFuture() && $this->licence_expires_at->diffInDays(now()) <= $days; } public function getTypeIcon(): string { return match ($this->type) { self::TYPE_COMPOSER => 'πŸ“¦', self::TYPE_NPM => 'πŸ“¦', self::TYPE_FONT => 'πŸ”€', self::TYPE_THEME => '🎨', self::TYPE_CDN => '🌐', self::TYPE_MANUAL => 'πŸ“', default => 'πŸ“„', }; } public function getTypeLabel(): string { return match ($this->type) { self::TYPE_COMPOSER => 'Composer', self::TYPE_NPM => 'NPM', self::TYPE_FONT => 'Font', self::TYPE_THEME => 'Theme', self::TYPE_CDN => 'CDN', self::TYPE_MANUAL => 'Manual', default => ucfirst($this->type), }; } public function getLicenceIcon(): string { if ($this->isLicenceExpired()) { return 'πŸ”΄'; } if ($this->isLicenceExpiringSoon()) { return '🟑'; } return match ($this->licence_type) { self::LICENCE_LIFETIME => '♾️', self::LICENCE_SUBSCRIPTION => 'πŸ”„', self::LICENCE_OSS => '🌐', self::LICENCE_TRIAL => '⏳', default => 'πŸ“„', }; } public function getInstallCommand(): ?string { if (! $this->package_name) { return null; } return match ($this->type) { self::TYPE_COMPOSER => "composer require {$this->package_name}", self::TYPE_NPM => "npm install {$this->package_name}", default => null, }; } public function getUpdateCommand(): ?string { if (! $this->package_name) { return null; } return match ($this->type) { self::TYPE_COMPOSER => "composer update {$this->package_name}", self::TYPE_NPM => "npm update {$this->package_name}", default => null, }; } // For MCP context public function toMcpContext(): array { return [ 'name' => $this->name, 'slug' => $this->slug, 'type' => $this->type, 'package' => $this->package_name, 'version' => $this->installed_version, 'latest' => $this->latest_version, 'has_update' => $this->hasUpdate(), 'licence' => $this->licence_type, 'install_path' => $this->install_path, 'install_command' => $this->getInstallCommand(), 'setup_notes' => $this->setup_notes, 'build_config' => $this->build_config, ]; } }