game-deploy

安装量: 52
排名: #14241

安装

npx skills add https://github.com/opusgamelabs/game-creator --skill game-deploy
Game Deployment
Deploy your browser game for public access.
here.now is the default
— instant static hosting with zero configuration. GitHub Pages is available as an alternative when you need git-based deploys.
here.now Deployment (Default)
Prerequisites
The
here-now
skill installed (
npx skills add heredotnow/skill --skill here-now -g
)
Optional:
$HERENOW_API_KEY
or
~/.herenow/credentials
for permanent hosting
Quick Deploy
npm
run build
~/.agents/skills/here-now/scripts/publish.sh dist/
The script outputs a live URL like
https://.here.now/
.
Why here.now is the default
Zero config
— no
base
path, no git repo, no GitHub CLI required
Instant
— site is live immediately (no waiting for propagation)
No base path issues
— content served from subdomain root (
base: '/'
or default)
Works everywhere
— only needs
curl
,
file
, and
jq
Vite base path
here.now serves from the subdomain root, so use the default base path:
export
default
defineConfig
(
{
base
:
'/'
,
// ... rest of config
}
)
;
IMPORTANT: Claim your site within 24 hours
Without an API key, publishes are
anonymous and expire in 24 hours
. The publish script returns a
claim URL
— the user MUST visit this URL and create a free here.now account to keep the site permanently.
The claim token is only shown once and cannot be recovered.
If they don't claim it, the site disappears.
You MUST always tell the user about the 24-hour window and the claim URL after every anonymous publish.
This is not optional.
Feature
Anonymous
Authenticated
Expiry
24 hours (then deleted!)
Permanent
Max file size
250 MB
5 GB
Rate limit
5/hour/IP
60/hour/account
To set up an API key for permanent hosting (skip the 24h window entirely):
Ask the user for their email
Send a magic link:
curl -sS https://here.now/api/auth/login -H "content-type: application/json" -d '{"email": "user@example.com"}'
User clicks the link, copies their API key from the dashboard
Save the key:
mkdir -p ~/.herenow && echo "" > ~/.herenow/credentials && chmod 600 ~/.herenow/credentials
Updating a deploy
npm
run build
~/.agents/skills/here-now/scripts/publish.sh dist/
--slug
<
slug
>
The slug is saved in
.herenow/state.json
after each publish — the script auto-loads it for updates.
Deploy script
Add to
package.json
:
{
"scripts"
:
{
"deploy"
:
"npm run build && ~/.agents/skills/here-now/scripts/publish.sh dist/"
}
}
For updates to an existing slug:
{
"scripts"
:
{
"deploy"
:
"npm run build && ~/.agents/skills/here-now/scripts/publish.sh dist/ --slug "
}
}
GitHub Pages Deployment (Alternative)
Use GitHub Pages when you need git-based deployment or already have a GitHub repo set up.
Prerequisites
GitHub CLI installed (
gh
)
Git repository initialized and pushed to GitHub
Quick Deploy
npm
run build
&&
npx gh-pages
-d
dist
Full Setup
Build the game
:
npm
run build
Ensure
vite.config.js
has the correct base path
if deploying to a subdirectory:
export
default
defineConfig
(
{
base
:
'//'
,
// ... rest of config
}
)
;
Deploy with GitHub CLI
:
gh repo create
<
game-name
>
--public
--source
=
.
--push
npm
install
-D
gh-pages
npx gh-pages
-d
dist
Enable GitHub Pages
in repo settings (should auto-detect the
gh-pages
branch).
Your game is live at:
https://.github.io//
Automated Deploys
Add to
package.json
:
{
"scripts"
:
{
"deploy"
:
"npm run build && npx gh-pages -d dist"
}
}
Play.fun Registration
After deploying, register your game on Play.fun for monetization. Use the
/game-creator:playdotfun
skill for integration details.
The deployed URL becomes your
gameUrl
when registering:
await
client
.
games
.
register
(
{
name
:
'Your Game Name'
,
gameUrl
:
'https://.here.now/'
,
// or GitHub Pages URL
maxScorePerSession
:
500
,
maxSessionsPerDay
:
20
,
maxCumulativePointsPerDay
:
5000
}
)
;
Other Hosting Options
Vercel
:
npx vercel --prod
(auto-detects Vite)
Netlify
Connect repo, set build command to
npm run build
, publish dir to
dist
Railway
Use the Railway skill for deployment
itch.io
Upload the dist/ folder as an HTML5 game Pre-Deploy Checklist npm run build succeeds with no errors Test the production build with npm run preview Remove any console.log debug statements Verify all assets are included in the build Check mobile/responsive behavior if applicable Set appropriate and meta tags in index.html</dd> </dl> </dd> </dl> </dd> </dl> </article> <a href="/" class="back-link">← <span data-i18n="detail.backToLeaderboard">返回排行榜</span></a> </div> <aside class="sidebar"> <section class="related-skills" id="relatedSkillsSection"> <h2 class="related-title" data-i18n="detail.relatedSkills">相关 Skills</h2> <div class="related-list" id="relatedSkillsList"> <div class="skeleton-card"></div> <div class="skeleton-card"></div> <div class="skeleton-card"></div> </div> </section> </aside> </div> </div> <script src="https://unpkg.com/i18next@23.11.5/i18next.min.js" defer></script> <script src="https://unpkg.com/i18next-browser-languagedetector@7.2.1/i18nextBrowserLanguageDetector.min.js" defer></script> <script defer> // Language resources - same pattern as index page const resources = { 'zh-CN': null, 'en': null, 'ja': null, 'ko': null, 'zh-TW': null, 'es': null, 'fr': null }; // Load language files (only current + fallback for performance) async function loadLanguageResources() { const savedLang = localStorage.getItem('i18nextLng') || 'en'; const langsToLoad = new Set([savedLang, 'en']); // current + fallback await Promise.all([...langsToLoad].map(async (lang) => { try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } })); } // Load a single language on demand (for language switching) async function loadLanguage(lang) { if (resources[lang]) return; try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; i18next.addResourceBundle(lang, 'translation', resources[lang].translation); } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } } // Initialize i18next async function initI18n() { try { await loadLanguageResources(); // Filter out null values from resources const validResources = {}; for (const [lang, data] of Object.entries(resources)) { if (data !== null) { validResources[lang] = data; } } console.log('Loaded languages:', Object.keys(validResources)); console.log('zh-CN resource:', validResources['zh-CN']); console.log('detail.home in resource:', validResources['zh-CN']?.translation?.detail?.home); // 检查是否有保存的语言偏好 const savedLang = localStorage.getItem('i18nextLng'); // 如果没有保存的语言偏好,默认使用英文 const defaultLang = savedLang && ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'].includes(savedLang) ? savedLang : 'en'; await i18next .use(i18nextBrowserLanguageDetector) .init({ lng: defaultLang, // 强制设置初始语言 fallbackLng: 'en', supportedLngs: ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'], resources: validResources, detection: { order: ['localStorage'], // 只使用 localStorage,不检测浏览器语言 caches: ['localStorage'], lookupLocalStorage: 'i18nextLng' }, interpolation: { escapeValue: false } }); console.log('i18next initialized, language:', i18next.language); console.log('Test translation:', i18next.t('detail.home')); // Set initial language in selector const langSwitcher = document.getElementById('langSwitcher'); langSwitcher.value = i18next.language; // Update page language updatePageLanguage(); // Language switch event langSwitcher.addEventListener('change', async (e) => { await loadLanguage(e.target.value); // load on demand i18next.changeLanguage(e.target.value).then(() => { updatePageLanguage(); localStorage.setItem('i18nextLng', e.target.value); }); }); } catch (error) { console.error('i18next init failed:', error); } } // Translation helper function t(key, options = {}) { return i18next.t(key, options); } // Update all translatable elements function updatePageLanguage() { // Update HTML lang attribute document.documentElement.lang = i18next.language; // Update elements with data-i18n attribute document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); el.textContent = t(key); }); } // Copy command function function copyCommand() { const command = document.getElementById('installCommand').textContent; const btn = document.getElementById('copyBtn'); navigator.clipboard.writeText(command).then(() => { btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }).catch(() => { // Fallback for non-HTTPS const textArea = document.createElement('textarea'); textArea.value = command; textArea.style.position = 'fixed'; textArea.style.left = '-9999px'; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }); } // Initialize document.getElementById('copyBtn').addEventListener('click', copyCommand); initI18n(); // 异步加载相关 Skills async function loadRelatedSkills() { const owner = 'opusgamelabs'; const skillName = 'game-deploy'; const currentLang = 'ja'; const listContainer = document.getElementById('relatedSkillsList'); const section = document.getElementById('relatedSkillsSection'); try { const response = await fetch(`/api/related-skills/${encodeURIComponent(owner)}/${encodeURIComponent(skillName)}?limit=6`); if (!response.ok) { throw new Error('Failed to load'); } const data = await response.json(); const relatedSkills = data.related_skills || []; if (relatedSkills.length === 0) { // 没有相关推荐时隐藏整个区域 section.style.display = 'none'; return; } // 渲染相关 Skills listContainer.innerHTML = relatedSkills.map(skill => { const desc = skill.description || ''; const truncatedDesc = desc.length > 60 ? desc.substring(0, 60) + '...' : desc; return ` <a href="${currentLang === 'en' ? '' : '/' + currentLang}/skill/${skill.owner}/${skill.repo}/${skill.skill_name}" class="related-card"> <div class="related-name">${escapeHtml(skill.skill_name)}</div> <div class="related-meta"> <span class="related-owner">${escapeHtml(skill.owner)}</span> <span class="related-installs">${skill.installs}</span> </div> <div class="related-desc">${escapeHtml(truncatedDesc)}</div> </a> `; }).join(''); } catch (error) { console.error('Failed to load related skills:', error); // 加载失败时显示提示或隐藏 listContainer.innerHTML = '<div class="related-empty">暂无相关推荐</div>'; } } // HTML 转义 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 页面加载完成后异步加载相关 Skills if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadRelatedSkills); } else { loadRelatedSkills(); } </script> </body> </html>