mcpField
U
use-template

use-template

github.com/playableintelligence開発350843 回インストール22 週間前に更新
リポジトリ

use-template について

ゲームテンプレートを新しいプロジェクトにコピーするスキルです。実行可能なコードをすぐに用意して自分でカスタマイズしたい開発者向けで、ローカルの一覧からテンプレートを選び、メタデータ更新と依存関係のインストールを行います。

主な機能

  1. 01ローカル一覧からゲームテンプレートを選択
  2. 02生成ファイルを除外してテンプレートをコピー
  3. 03プロジェクト名とページタイトルを更新
  4. 04新しいディレクトリに依存関係をインストール

活用シーン

  1. 01テンプレートを使ったゲーム開発の開始
  2. 02既存コードを基にしたゲームプレイの調整

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/playableintelligence/game-creator --skill use-template

インストール後、Claude Code で/use-templateを入力して読み込まれたか確認します。

詳細

テンプレートを使用

ギャラリーからゲームテンプレートを新しいプロジェクトにクローンします。これは高速なコピーです。AI パイプラインを使わず、数秒で動作するコードを入手できます。

動作

  1. 引数を解析:<template-id> [project-name]

    • 引数が指定されていない場合は、site/manifest.json を読み込み、エンジン/複雑さ/説明とともにすべてのテンプレートを番号付きリストで表示し、ユーザーに選択を求めます。
    • template-id は必須です。project-name のデフォルト値は template-id です。
  2. site/manifest.json から id でテンプレートを検索します。見つからない場合は、利用可能な ID を表示して中止します。

  3. ターゲットディレクトリを決定:

    • 現在の作業ディレクトリが game-creator リポジトリ内の場合 → examples/<project-name>/
    • それ以外の場合 → ./<project-name>/
    • ターゲットがすでに存在する場合は、エラーで中止します。
  4. テンプレートのソースディレクトリをターゲットにコピーします。ただし、以下は除外します:

    • node_modules/
    • dist/
    • output/
    • .herenow/
    • progress.md
    • test-results/
    • playwright-report/
  5. プロジェクトのメタデータを更新:

    • package.json:"name" をプロジェクト名に設定します
    • index.html(存在する場合):<title> をプロジェクト名の整形版に更新します
  6. 依存関係をインストール:ターゲットディレクトリで npm install を実行します。

  7. 次の手順を表示:

    Template cloned successfully!
    
    cd <project-name>
    npm run dev
    

実装

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

// Find game-creator root (contains site/manifest.json)
function findRoot(dir) {
  let d = dir;
  while (d !== path.dirname(d)) {
    if (fs.existsSync(path.join(d, 'gallery', 'manifest.json'))) return d;
    d = path.dirname(d);
  }
  return null;
}

const root = findRoot(process.cwd());
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'gallery', 'manifest.json'), 'utf-8'));

// Parse args
const [templateId, projectName] = args; // provided by the agent
const template = manifest.find(t => t.id === templateId);
const name = projectName || templateId;

// Validate project name — reject path traversal and special characters
if (/[\/\\]|^\.\.?$|\.\./.test(name)) {
  throw new Error(`Invalid project name: "${name}". Must not contain path separators or ".." sequences.`);
}
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) {
  throw new Error(`Invalid project name: "${name}". Use only alphanumeric characters, hyphens, underscores, and dots.`);
}

// Determine target
const inGameCreator = process.cwd().startsWith(root);
const target = inGameCreator
  ? path.join(root, 'examples', name)
  : path.join(process.cwd(), name);

// Verify resolved path stays within expected parent directory
const expectedParent = inGameCreator ? path.join(root, 'examples') : process.cwd();
if (!path.resolve(target).startsWith(path.resolve(expectedParent))) {
  throw new Error('Target directory resolved outside expected parent. Aborting.');
}

// Copy with exclusions
const EXCLUDE = ['node_modules', 'dist', 'output', '.herenow', 'progress.md', 'test-results', 'playwright-report'];

function copyDir(src, dst) {
  fs.mkdirSync(dst, { recursive: true });
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
    if (EXCLUDE.includes(entry.name)) continue;
    const s = path.join(src, entry.name);
    const d = path.join(dst, entry.name);
    if (entry.isDirectory()) copyDir(s, d);
    else fs.copyFileSync(s, d);
  }
}

copyDir(path.join(root, template.source), target);

// Update package.json
const pkgPath = path.join(target, 'package.json');
if (fs.existsSync(pkgPath)) {
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
  pkg.name = name;
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
}

// Update index.html title
const indexPath = path.join(target, 'index.html');
if (fs.existsSync(indexPath)) {
  let html = fs.readFileSync(indexPath, 'utf-8');
  const prettyName = name.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
  html = html.replace(/<title>[^<]*<\/title>/, `<title>${prettyName}</title>`);
  fs.writeFileSync(indexPath, html);
}

// Install
execSync('npm install', { cwd: target, stdio: 'inherit' });

// Optional anonymous telemetry — tracks which templates are popular
// Data sent: event type ("clone"), template ID, source ("skill"), schema version
// No PII, IP addresses, file paths, or user-identifiable data is transmitted
// Opt out: set DO_NOT_TRACK=1 or DISABLE_TELEMETRY=1 in your environment
if (!process.env.DO_NOT_TRACK && !process.env.DISABLE_TELEMETRY) {
  const https = require('https');
  const telemetryUrl = process.env.TELEMETRY_URL || 'https://gallery-telemetry.up.railway.app';
  https.get(`${telemetryUrl}/t?event=clone&template=${encodeURIComponent(templateId)}&source=skill&v=1`)
    .on('error', () => {});
}

使用例

/use-template flappy-bird my-game
/use-template threejs-3d-starter space-shooter
/use-template castle-siege

セキュリティに関する注意事項

  • パスの検証:プロジェクト名は、パストラバーサル(..)、パス区切り文字、特殊文字を拒否するよう検証されます。解決されたターゲットパスが、想定される親ディレクトリ内に収まっていることを確認します。
  • npm install:コピーしたテンプレートの package.json に記載された内容を使って npm install を実行します。このファイルには、テンプレートに含まれる固定バージョンの依存関係(Phaser/Three.js、Vite)のみが記載されています。任意のパッケージはインストールされません。
  • テレメトリ:匿名のオプトアウト方式の使用状況テレメトリでは、テンプレート ID とイベントタイプのみが送信されます(個人を特定できる情報、パス、ユーザーデータは含まれません)。DO_NOT_TRACK=1 または DISABLE_TELEMETRY=1 の環境変数で無効にできます。
  • テンプレートのソース:テンプレートはプラグイン内のローカルな site/manifest.json レジストリからコピーされます。クローン時に外部のテンプレートが取得されることはありません。

/viral-game および /make-game との主な違い

/use-template は10 秒で完了するコピーです。すぐに動作・実行できるコードを入手し、手動でカスタマイズできます。/viral-game は、テキストプロンプトまたはツイート URL から、スキャフォールディング、デザイン、オーディオ追加、テスト、デプロイ、収益化を行う10 分間の AI パイプラインです。独自の方針に沿って一度に処理します。/make-game は、マイルストーン、ADR、docs/STATE.md を備え、時間をかけて進化させるプロジェクト向けの、より詳細な複数セッション型ゲーム開発ワークフローです。

コメント