Skip to content

Instantly share code, notes, and snippets.

@ikemo3
Created June 26, 2025 10:20
Show Gist options
  • Save ikemo3/aab77a275a1f46edab33236c48a0fef6 to your computer and use it in GitHub Desktop.
Save ikemo3/aab77a275a1f46edab33236c48a0fef6 to your computer and use it in GitHub Desktop.
ClaudeのBuilding an AI Appが出してくれたアプリ。中でMCPサーバを使うようにしている。
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