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 是一个10 分钟的 AI 流水线,可根据文本提示词或推文 URL 搭建项目脚手架、设计、添加音频、测试、部署并实现变现——它有明确的偏好,且一次性完成。/make-game 则是更深入、跨多个会话的游戏开发工作流,包含里程碑、ADR 和 docs/STATE.md,适用于需要随时间持续演进的项目。

评论

支持 Markdown