Created
June 26, 2025 10:20
-
-
Save ikemo3/aab77a275a1f46edab33236c48a0fef6 to your computer and use it in GitHub Desktop.
ClaudeのBuilding an AI Appが出してくれたアプリ。中でMCPサーバを使うようにしている。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from 'react'; | |
import { Search, Github, Shield, Users, Code, AlertTriangle, CheckCircle, Clock, Star } from 'lucide-react'; | |
const GitHubRepoInspector = () => { | |
const [url, setUrl] = useState(''); | |
const [loading, setLoading] = useState(false); | |
const [result, setResult] = useState(null); | |
const [error, setError] = useState(null); | |
// GitHubのURLからowner/repoを抽出 | |
const parseGitHubUrl = (url) => { | |
const regex = /github\.com\/([^\/]+)\/([^\/]+)/; | |
const match = url.match(regex); | |
if (match) { | |
return { owner: match[1], name: match[2].replace(/\.git$/, '') }; | |
} | |
return null; | |
}; | |
const inspectRepository = async () => { | |
if (!url.trim()) { | |
setError('GitHubのURLを入力してください'); | |
return; | |
} | |
const repoInfo = parseGitHubUrl(url); | |
if (!repoInfo) { | |
setError('有効なGitHubのURLを入力してください(例: https://github.com/owner/repo)'); | |
return; | |
} | |
setLoading(true); | |
setError(null); | |
setResult(null); | |
try { | |
// Claude APIを使って指定されたツールでリポジトリ評価を実行 | |
const evaluationPrompt = ` | |
GitHubリポジトリ ${repoInfo.owner}/${repoInfo.name} (URL: ${url.trim()}) の詳細な評価を実行してください。 | |
必ず以下の2つのツールを使用してデータを取得してください: | |
1. graphql_library_evaluation ツール(owner: "${repoInfo.owner}", name: "${repoInfo.name}") | |
2. vulnerability_check ツール(github_url: "${url.trim()}") | |
これらのツールの結果を基に、以下の観点で評価してください: | |
## 一般的な利用者としての評価 | |
- 使いやすさ | |
- ドキュメントの質 | |
- コミュニティの活発さ | |
- メンテナンスの状況 | |
## セキュリティスペシャリストとしての評価 | |
以下の基準で厳格に評価: | |
- アーキテクチャ設計によるセキュリティ(攻撃対象の多さ、デフォルトの安全性) | |
- 脆弱性の状況(単純なミスか設計レベルの問題か) | |
- 脆弱性対応の速度 | |
### セキュリティ評価基準: | |
- ⭐️⭐️⭐️⭐️⭐️: 攻撃面が小さく、脆弱性も少ない | |
- ⭐️⭐️⭐️⭐️: 攻撃面は大きいが、適切にセキュリティが管理されている | |
- ⭐️⭐️⭐️: 攻撃面に対して脆弱性が多い、または重要な欠陥 | |
- ⭐️⭐️: 設計レベルでセキュリティ考慮が不足 | |
- ⭐️: アーキテクチャに根本的欠陥 | |
重要:脆弱性対応の速度は「当たり前品質」であり、加点対象にしてはいけません。 | |
CRITICAL: あなたの回答は必ず有効なJSONオブジェクトのみにしてください。マークダウンの\`\`\`jsonや\`\`\`は絶対に使わないでください。テキストの説明も一切含めず、以下のJSON構造のみを返してください: | |
{ | |
"repository": { | |
"name": "リポジトリ名", | |
"description": "リポジトリの説明", | |
"language": "主要な言語", | |
"stars": 0, | |
"forks": 0, | |
"issues": 0 | |
}, | |
"generalEvaluation": { | |
"usability": "使いやすさの詳細評価", | |
"documentation": "ドキュメントの詳細評価", | |
"community": "コミュニティの詳細評価", | |
"maintenance": "メンテナンスの詳細評価", | |
"rating": 5 | |
}, | |
"securityEvaluation": { | |
"architecture": "アーキテクチャ設計の詳細評価", | |
"vulnerabilities": "脆弱性の詳細評価", | |
"responseTime": "対応速度の詳細評価", | |
"rating": 5, | |
"rationale": "セキュリティ評価の根拠と理由" | |
}, | |
"toolResults": { | |
"libraryEvaluation": "graphql_library_evaluationツールの結果サマリー", | |
"vulnerabilityCheck": "vulnerability_checkツールの結果サマリー" | |
}, | |
"recommendations": ["具体的な推奨事項1", "具体的な推奨事項2", "具体的な推奨事項3"], | |
"summary": "総合評価のまとめ" | |
} | |
繰り返しますが、あなたの回答は上記のJSONオブジェクトのみにしてください。それ以外の文字は一切含めないでください。 | |
`; | |
const response = await window.claude.complete(evaluationPrompt); | |
console.log('Claude APIからの生レスポンス:', response); | |
// レスポンスからJSONを抽出(Markdownのコードブロックを除去) | |
let jsonText = response.trim(); | |
// ```json で始まり ``` で終わる場合の処理 | |
if (jsonText.startsWith('```json')) { | |
console.log('Markdownコードブロック形式を検出'); | |
jsonText = jsonText.replace(/^```json\s*/, '').replace(/\s*```$/, ''); | |
} else if (jsonText.startsWith('```')) { | |
console.log('汎用コードブロック形式を検出'); | |
jsonText = jsonText.replace(/^```\s*/, '').replace(/\s*```$/, ''); | |
} | |
// 前後の余分な空白を除去 | |
jsonText = jsonText.trim(); | |
console.log('処理後のJSON文字列:', jsonText); | |
console.log('JSON文字列の長さ:', jsonText.length); | |
console.log('JSON文字列の最初の100文字:', jsonText.substring(0, 100)); | |
try { | |
const evaluation = JSON.parse(jsonText); | |
console.log('JSONパース成功:', evaluation); | |
setResult(evaluation); | |
} catch (parseError) { | |
console.error('JSONパースエラー:', parseError); | |
console.error('パースに失敗したテキスト:', jsonText); | |
throw new Error(`JSONパースに失敗しました: ${parseError.message}\n\n応答の最初の部分: ${jsonText.substring(0, 200)}...`); | |
} | |
} catch (err) { | |
console.error('評価エラー:', err); | |
// JSONパースエラーの場合は詳細情報を提供 | |
if (err instanceof SyntaxError && err.message.includes('JSON')) { | |
setError(`JSONパースエラーが発生しました。Claude APIからの応答形式に問題があります。\n詳細: ${err.message}`); | |
} else { | |
setError(`リポジトリの評価中にエラーが発生しました: ${err.message}`); | |
} | |
} finally { | |
setLoading(false); | |
} | |
}; | |
const renderStars = (rating) => { | |
return Array.from({ length: 5 }, (_, i) => ( | |
<Star | |
key={i} | |
className={`w-5 h-5 ${i < rating ? 'text-yellow-400 fill-current' : 'text-gray-300'}`} | |
/> | |
)); | |
}; | |
return ( | |
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-blue-900 to-purple-900 p-6"> | |
<div className="max-w-4xl mx-auto"> | |
<div className="text-center mb-8"> | |
<div className="flex items-center justify-center mb-4"> | |
<Github className="w-12 h-12 text-blue-400 mr-3" /> | |
<h1 className="text-4xl font-bold text-white">GitHub Repository Inspector</h1> | |
</div> | |
<p className="text-gray-300 text-lg">GitHubリポジトリの詳細な評価と検査を行います</p> | |
</div> | |
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-6 mb-8 border border-white/20"> | |
<div className="space-y-4"> | |
<div> | |
<label htmlFor="github-url" className="block text-white font-medium mb-2"> | |
GitHubリポジトリURL | |
</label> | |
<div className="relative"> | |
<Github className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" /> | |
<input | |
id="github-url" | |
type="url" | |
value={url} | |
onChange={(e) => setUrl(e.target.value)} | |
placeholder="https://github.com/owner/repository" | |
className="w-full pl-12 pr-4 py-3 bg-white/10 border border-white/30 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" | |
disabled={loading} | |
onKeyPress={(e) => e.key === 'Enter' && inspectRepository()} | |
/> | |
</div> | |
</div> | |
<button | |
onClick={inspectRepository} | |
disabled={loading || !url.trim()} | |
className="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white py-3 px-6 rounded-lg font-medium hover:from-blue-700 hover:to-purple-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2 transition-all duration-200" | |
> | |
{loading ? ( | |
<> | |
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div> | |
<span>検査中...</span> | |
</> | |
) : ( | |
<> | |
<Search className="w-5 h-5" /> | |
<span>リポジトリを検査</span> | |
</> | |
)} | |
</button> | |
</div> | |
{error && ( | |
<div className="mt-4 p-4 bg-red-500/20 border border-red-500/50 rounded-lg"> | |
<div className="flex items-start"> | |
<AlertTriangle className="w-5 h-5 text-red-400 mr-2 mt-0.5 flex-shrink-0" /> | |
<div className="text-red-300"> | |
{error.split('\n').map((line, index) => ( | |
<div key={index} className={index > 0 ? 'mt-1' : ''}> | |
{line} | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
)} | |
</div> | |
{result && ( | |
<div className="space-y-6"> | |
{/* リポジトリ基本情報 */} | |
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-6 border border-white/20"> | |
<h2 className="text-2xl font-bold text-white mb-4 flex items-center"> | |
<Code className="w-6 h-6 mr-2" /> | |
{result.repository.name} | |
</h2> | |
<p className="text-gray-300 mb-4">{result.repository.description}</p> | |
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> | |
<div className="text-center"> | |
<div className="text-2xl font-bold text-blue-400">{result.repository.language}</div> | |
<div className="text-gray-400">言語</div> | |
</div> | |
<div className="text-center"> | |
<div className="text-2xl font-bold text-yellow-400">{result.repository.stars}</div> | |
<div className="text-gray-400">スター</div> | |
</div> | |
<div className="text-center"> | |
<div className="text-2xl font-bold text-green-400">{result.repository.forks}</div> | |
<div className="text-gray-400">フォーク</div> | |
</div> | |
<div className="text-center"> | |
<div className="text-2xl font-bold text-red-400">{result.repository.issues}</div> | |
<div className="text-gray-400">イシュー</div> | |
</div> | |
</div> | |
</div> | |
{/* ツール実行結果 */} | |
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-6 border border-white/20"> | |
<h3 className="text-xl font-bold text-white mb-4 flex items-center"> | |
<Code className="w-5 h-5 mr-2" /> | |
ツール実行結果 | |
</h3> | |
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
<div className="bg-blue-500/10 p-4 rounded-lg border border-blue-500/30"> | |
<h4 className="font-medium text-blue-300 mb-2">GraphQL Library Evaluation</h4> | |
<p className="text-gray-300 text-sm">{result.toolResults?.libraryEvaluation || 'データなし'}</p> | |
</div> | |
<div className="bg-red-500/10 p-4 rounded-lg border border-red-500/30"> | |
<h4 className="font-medium text-red-300 mb-2">Vulnerability Check</h4> | |
<p className="text-gray-300 text-sm">{result.toolResults?.vulnerabilityCheck || 'データなし'}</p> | |
</div> | |
</div> | |
</div> | |
{/* 一般評価 */} | |
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-6 border border-white/20"> | |
<h3 className="text-xl font-bold text-white mb-4 flex items-center"> | |
<Users className="w-5 h-5 mr-2" /> | |
一般利用者評価 | |
</h3> | |
<div className="flex items-center mb-4"> | |
<div className="flex">{renderStars(result.generalEvaluation.rating)}</div> | |
<span className="ml-2 text-white font-medium">{result.generalEvaluation.rating}/5</span> | |
</div> | |
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
<div> | |
<h4 className="font-medium text-blue-300 mb-2">使いやすさ</h4> | |
<p className="text-gray-300 text-sm">{result.generalEvaluation.usability}</p> | |
</div> | |
<div> | |
<h4 className="font-medium text-blue-300 mb-2">ドキュメント</h4> | |
<p className="text-gray-300 text-sm">{result.generalEvaluation.documentation}</p> | |
</div> | |
<div> | |
<h4 className="font-medium text-blue-300 mb-2">コミュニティ</h4> | |
<p className="text-gray-300 text-sm">{result.generalEvaluation.community}</p> | |
</div> | |
<div> | |
<h4 className="font-medium text-blue-300 mb-2">メンテナンス</h4> | |
<p className="text-gray-300 text-sm">{result.generalEvaluation.maintenance}</p> | |
</div> | |
</div> | |
</div> | |
{/* セキュリティ評価 */} | |
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-6 border border-white/20"> | |
<h3 className="text-xl font-bold text-white mb-4 flex items-center"> | |
<Shield className="w-5 h-5 mr-2" /> | |
セキュリティ評価 | |
</h3> | |
<div className="flex items-center mb-4"> | |
<div className="flex">{renderStars(result.securityEvaluation.rating)}</div> | |
<span className="ml-2 text-white font-medium">{result.securityEvaluation.rating}/5</span> | |
</div> | |
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4"> | |
<div> | |
<h4 className="font-medium text-red-300 mb-2">アーキテクチャ</h4> | |
<p className="text-gray-300 text-sm">{result.securityEvaluation.architecture}</p> | |
</div> | |
<div> | |
<h4 className="font-medium text-red-300 mb-2">脆弱性</h4> | |
<p className="text-gray-300 text-sm">{result.securityEvaluation.vulnerabilities}</p> | |
</div> | |
<div> | |
<h4 className="font-medium text-red-300 mb-2">対応速度</h4> | |
<p className="text-gray-300 text-sm">{result.securityEvaluation.responseTime}</p> | |
</div> | |
</div> | |
<div className="bg-red-500/10 p-4 rounded-lg border border-red-500/30"> | |
<h4 className="font-medium text-red-300 mb-2">評価根拠</h4> | |
<p className="text-gray-300 text-sm">{result.securityEvaluation.rationale}</p> | |
</div> | |
</div> | |
{/* 推奨事項 */} | |
<div className="bg-white/10 backdrop-blur-lg rounded-xl p-6 border border-white/20"> | |
<h3 className="text-xl font-bold text-white mb-4 flex items-center"> | |
<CheckCircle className="w-5 h-5 mr-2" /> | |
推奨事項 | |
</h3> | |
<ul className="space-y-2"> | |
{result.recommendations.map((recommendation, index) => ( | |
<li key={index} className="flex items-start"> | |
<div className="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3 mt-0.5 flex-shrink-0"> | |
{index + 1} | |
</div> | |
<span className="text-gray-300">{recommendation}</span> | |
</li> | |
))} | |
</ul> | |
</div> | |
{/* 総合評価 */} | |
<div className="bg-gradient-to-r from-blue-600/20 to-purple-600/20 backdrop-blur-lg rounded-xl p-6 border border-blue-500/30"> | |
<h3 className="text-xl font-bold text-white mb-4">総合評価</h3> | |
<p className="text-gray-300 leading-relaxed">{result.summary}</p> | |
</div> | |
</div> | |
)} | |
</div> | |
</div> | |
); | |
}; | |
export default GitHubRepoInspector; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment