@series-inc/stowkit-cli 0.6.34 → 0.6.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-tagger.d.ts +13 -0
- package/dist/ai-tagger.js +95 -0
- package/dist/app/disk-project.d.ts +3 -0
- package/dist/assets-package.d.ts +0 -14
- package/dist/assets-package.js +1 -4
- package/dist/cli.js +19 -33
- package/dist/firestore.d.ts +42 -0
- package/dist/firestore.js +273 -0
- package/dist/gcp-auth.d.ts +9 -0
- package/dist/gcp-auth.js +69 -0
- package/dist/gcs.d.ts +0 -5
- package/dist/gcs.js +2 -112
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/init.js +121 -10
- package/dist/publish.js +17 -40
- package/dist/server.js +227 -35
- package/dist/store.d.ts +6 -21
- package/dist/store.js +24 -116
- package/package.json +6 -4
- package/templates/engine/index.html +12 -0
- package/templates/engine/package.json +32 -0
- package/templates/engine/prefabs/build.json +1 -0
- package/templates/engine/scripts/copy-decoders.mjs +40 -0
- package/templates/engine/src/Game.ts +84 -0
- package/templates/engine/src/Prefabs.ts +28 -0
- package/templates/engine/src/main.ts +16 -0
- package/templates/engine/src/styles/main.css +22 -0
- package/templates/engine/stubs/capacitor.ts +6 -0
- package/templates/engine/tsconfig.json +27 -0
- package/templates/engine/vite-env.d.ts +1 -0
- package/templates/engine/vite.config.ts +39 -0
package/dist/server.js
CHANGED
|
@@ -1608,6 +1608,15 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1608
1608
|
if (Object.prototype.hasOwnProperty.call(body, 'defaults')) {
|
|
1609
1609
|
nextConfig.defaults = normalizeProjectDefaults(body.defaults);
|
|
1610
1610
|
}
|
|
1611
|
+
if (Object.prototype.hasOwnProperty.call(body, 'ai')) {
|
|
1612
|
+
const aiBody = body.ai;
|
|
1613
|
+
if (aiBody && typeof aiBody.geminiApiKey === 'string' && aiBody.geminiApiKey.trim()) {
|
|
1614
|
+
nextConfig.ai = { geminiApiKey: aiBody.geminiApiKey.trim() };
|
|
1615
|
+
}
|
|
1616
|
+
else {
|
|
1617
|
+
nextConfig.ai = undefined;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1611
1620
|
const renamedPacks = Array.isArray(body.renamedPacks)
|
|
1612
1621
|
? body.renamedPacks.filter((rename) => typeof rename?.oldName === 'string'
|
|
1613
1622
|
&& typeof rename?.newName === 'string'
|
|
@@ -1780,21 +1789,31 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1780
1789
|
// GET /api/asset-store/registry — fetch the public registry
|
|
1781
1790
|
if (pathname === '/api/asset-store/registry' && req.method === 'GET') {
|
|
1782
1791
|
try {
|
|
1783
|
-
const
|
|
1784
|
-
const
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1792
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1793
|
+
const reader = createFirestoreReader();
|
|
1794
|
+
const allPackages = await reader.listPackages();
|
|
1795
|
+
// Fetch all version keys in parallel
|
|
1796
|
+
const allVersionKeys = await Promise.all(allPackages.map(({ name }) => reader.listVersionKeys(name)));
|
|
1797
|
+
// Fetch all version docs in parallel
|
|
1798
|
+
const allVersionDocs = await Promise.all(allPackages.map(({ name }, i) => Promise.all(allVersionKeys[i].map(ver => reader.getVersion(name, ver).then(doc => ({ ver, doc }))))));
|
|
1799
|
+
const packages = {};
|
|
1800
|
+
for (let i = 0; i < allPackages.length; i++) {
|
|
1801
|
+
const { name, data: pkg } = allPackages[i];
|
|
1802
|
+
const versions = {};
|
|
1803
|
+
for (const { ver, doc } of allVersionDocs[i]) {
|
|
1804
|
+
if (doc)
|
|
1805
|
+
versions[ver] = doc;
|
|
1793
1806
|
}
|
|
1794
|
-
|
|
1807
|
+
packages[name] = {
|
|
1808
|
+
description: pkg.description,
|
|
1809
|
+
author: pkg.author,
|
|
1810
|
+
tags: pkg.tags ?? [],
|
|
1811
|
+
latest: pkg.latest,
|
|
1812
|
+
thumbnail: pkg.thumbnail,
|
|
1813
|
+
versions,
|
|
1814
|
+
};
|
|
1795
1815
|
}
|
|
1796
|
-
|
|
1797
|
-
json(res, registry);
|
|
1816
|
+
json(res, { schemaVersion: 1, packages });
|
|
1798
1817
|
}
|
|
1799
1818
|
catch (err) {
|
|
1800
1819
|
json(res, { error: err.message }, 500);
|
|
@@ -1810,9 +1829,11 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1810
1829
|
const bucketParam = url.searchParams.get('bucket') ?? undefined;
|
|
1811
1830
|
const limitParam = url.searchParams.get('limit');
|
|
1812
1831
|
const limit = limitParam ? parseInt(limitParam, 10) : undefined;
|
|
1813
|
-
const {
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1832
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1833
|
+
const { searchAssets } = await import('./store.js');
|
|
1834
|
+
const reader = createFirestoreReader();
|
|
1835
|
+
const bucket = (bucketParam ?? 'rungame-shared-assets-test').replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
1836
|
+
let results = await searchAssets(reader, bucket, query, { type, package: pkg });
|
|
1816
1837
|
if (limit && limit > 0)
|
|
1817
1838
|
results = results.slice(0, limit);
|
|
1818
1839
|
json(res, results);
|
|
@@ -1826,9 +1847,11 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1826
1847
|
if (pathname === '/api/asset-store/packages' && req.method === 'GET') {
|
|
1827
1848
|
try {
|
|
1828
1849
|
const bucketParam = url.searchParams.get('bucket') ?? undefined;
|
|
1829
|
-
const {
|
|
1830
|
-
const
|
|
1831
|
-
const
|
|
1850
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1851
|
+
const { listStorePackages } = await import('./store.js');
|
|
1852
|
+
const reader = createFirestoreReader();
|
|
1853
|
+
const bucket = (bucketParam ?? 'rungame-shared-assets-test').replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
1854
|
+
const packages = await listStorePackages(reader, bucket);
|
|
1832
1855
|
json(res, packages);
|
|
1833
1856
|
}
|
|
1834
1857
|
catch (err) {
|
|
@@ -1840,22 +1863,22 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1840
1863
|
if (pathname.startsWith('/api/asset-store/package/') && req.method === 'GET') {
|
|
1841
1864
|
try {
|
|
1842
1865
|
const packageName = decodeURIComponent(pathname.slice('/api/asset-store/package/'.length));
|
|
1843
|
-
const
|
|
1844
|
-
const
|
|
1845
|
-
const
|
|
1846
|
-
const pkg = registry.packages[packageName];
|
|
1866
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1867
|
+
const reader = createFirestoreReader();
|
|
1868
|
+
const pkg = await reader.getPackage(packageName);
|
|
1847
1869
|
if (!pkg) {
|
|
1848
1870
|
json(res, { error: `Package "${packageName}" not found` }, 404);
|
|
1849
1871
|
return;
|
|
1850
1872
|
}
|
|
1851
|
-
const ver =
|
|
1873
|
+
const ver = await reader.getVersion(packageName, pkg.latest);
|
|
1874
|
+
const versionKeys = await reader.listVersionKeys(packageName);
|
|
1852
1875
|
json(res, {
|
|
1853
1876
|
name: packageName,
|
|
1854
1877
|
description: pkg.description,
|
|
1855
1878
|
author: pkg.author,
|
|
1856
1879
|
tags: pkg.tags ?? [],
|
|
1857
1880
|
latest: pkg.latest,
|
|
1858
|
-
versions:
|
|
1881
|
+
versions: versionKeys,
|
|
1859
1882
|
assets: ver?.assets ?? [],
|
|
1860
1883
|
});
|
|
1861
1884
|
}
|
|
@@ -1877,21 +1900,17 @@ async function handleRequest(req, res, staticApps) {
|
|
|
1877
1900
|
json(res, { error: 'Missing packageName, version, or stringIds' }, 400);
|
|
1878
1901
|
return;
|
|
1879
1902
|
}
|
|
1880
|
-
const bucketName = (bucketParam ?? '
|
|
1903
|
+
const bucketName = (bucketParam ?? 'rungame-shared-assets-test').replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
1881
1904
|
const baseUrl = `https://storage.googleapis.com/${bucketName}`;
|
|
1882
|
-
//
|
|
1883
|
-
const
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
const registry = await regRes.json();
|
|
1889
|
-
const pkg = registry.packages[packageName];
|
|
1905
|
+
// Resolve deps via Firestore
|
|
1906
|
+
const { createFirestoreReader } = await import('./firestore.js');
|
|
1907
|
+
const reader = createFirestoreReader();
|
|
1908
|
+
const pkg = await reader.getPackage(packageName);
|
|
1890
1909
|
if (!pkg) {
|
|
1891
1910
|
json(res, { error: `Package "${packageName}" not found` }, 404);
|
|
1892
1911
|
return;
|
|
1893
1912
|
}
|
|
1894
|
-
const ver =
|
|
1913
|
+
const ver = await reader.getVersion(packageName, version);
|
|
1895
1914
|
if (!ver) {
|
|
1896
1915
|
json(res, { error: `Version "${version}" not found` }, 404);
|
|
1897
1916
|
return;
|
|
@@ -2004,6 +2023,179 @@ async function handleRequest(req, res, staticApps) {
|
|
|
2004
2023
|
}
|
|
2005
2024
|
return;
|
|
2006
2025
|
}
|
|
2026
|
+
// POST /api/ai/verify-key — validate a Gemini API key
|
|
2027
|
+
if (pathname === '/api/ai/verify-key' && req.method === 'POST') {
|
|
2028
|
+
try {
|
|
2029
|
+
const body = JSON.parse(await readBody(req));
|
|
2030
|
+
const apiKey = body.apiKey;
|
|
2031
|
+
if (!apiKey || typeof apiKey !== 'string') {
|
|
2032
|
+
json(res, { valid: false, error: 'Missing apiKey' });
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const { verifyApiKey } = await import('./ai-tagger.js');
|
|
2036
|
+
const result = await verifyApiKey(apiKey);
|
|
2037
|
+
json(res, result);
|
|
2038
|
+
}
|
|
2039
|
+
catch (err) {
|
|
2040
|
+
json(res, { valid: false, error: err.message }, 500);
|
|
2041
|
+
}
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
// POST /api/ai/tag/:id — AI-tag a single asset
|
|
2045
|
+
if (pathname.startsWith('/api/ai/tag/') && req.method === 'POST' && !pathname.endsWith('/tag-missing')) {
|
|
2046
|
+
if (!projectConfig) {
|
|
2047
|
+
json(res, { error: 'No project open' }, 400);
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
const apiKey = projectConfig.config.ai?.geminiApiKey;
|
|
2051
|
+
if (!apiKey) {
|
|
2052
|
+
json(res, { error: 'Gemini API key not configured' }, 400);
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
const id = decodeURIComponent(pathname.slice('/api/ai/tag/'.length));
|
|
2056
|
+
const asset = assets.find(a => a.id === id);
|
|
2057
|
+
if (!asset) {
|
|
2058
|
+
json(res, { error: 'Asset not found' }, 404);
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
if (asset.type === AssetType.MaterialSchema) {
|
|
2062
|
+
json(res, { error: 'Material schemas cannot be tagged' }, 400);
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
try {
|
|
2066
|
+
const { tagAsset: aiTagAsset } = await import('./ai-tagger.js');
|
|
2067
|
+
let inputData;
|
|
2068
|
+
let mimeType;
|
|
2069
|
+
if (asset.type === AssetType.Audio) {
|
|
2070
|
+
const ext = asset.fileName.split('.').pop()?.toLowerCase() ?? 'wav';
|
|
2071
|
+
const mimeMap = {
|
|
2072
|
+
wav: 'audio/wav', mp3: 'audio/mpeg', ogg: 'audio/ogg',
|
|
2073
|
+
flac: 'audio/flac', aac: 'audio/aac', m4a: 'audio/mp4',
|
|
2074
|
+
};
|
|
2075
|
+
mimeType = mimeMap[ext] ?? 'audio/wav';
|
|
2076
|
+
const raw = await readFile(projectConfig.srcArtDir, id);
|
|
2077
|
+
if (!raw) {
|
|
2078
|
+
json(res, { error: 'Source audio file not found' }, 404);
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
inputData = Buffer.from(raw);
|
|
2082
|
+
}
|
|
2083
|
+
else {
|
|
2084
|
+
const { readManifest, readThumbnailFile } = await import('./app/thumbnail-cache.js');
|
|
2085
|
+
const manifest = await readManifest(projectConfig.srcArtDir);
|
|
2086
|
+
const entry = manifest[asset.stringId];
|
|
2087
|
+
if (!entry) {
|
|
2088
|
+
json(res, { error: 'No cached thumbnail — capture thumbnails first' }, 400);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
const thumbData = await readThumbnailFile(projectConfig.srcArtDir, asset.stringId, entry.format);
|
|
2092
|
+
if (!thumbData) {
|
|
2093
|
+
json(res, { error: 'Thumbnail file missing from cache' }, 400);
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
inputData = thumbData;
|
|
2097
|
+
const formatMime = {
|
|
2098
|
+
png: 'image/png', webp: 'image/webp', webm: 'video/webm',
|
|
2099
|
+
};
|
|
2100
|
+
mimeType = formatMime[entry.format] ?? 'image/webp';
|
|
2101
|
+
}
|
|
2102
|
+
const newTags = await aiTagAsset(apiKey, asset.type, asset.fileName, inputData, mimeType);
|
|
2103
|
+
const existingTags = asset.settings.tags ?? [];
|
|
2104
|
+
const merged = [...new Set([...existingTags, ...newTags])];
|
|
2105
|
+
asset.settings = { ...asset.settings, tags: merged };
|
|
2106
|
+
if (!asset.parentId) {
|
|
2107
|
+
const meta = assetSettingsToStowmeta(asset);
|
|
2108
|
+
await writeStowmeta(projectConfig.srcArtDir, id, meta);
|
|
2109
|
+
}
|
|
2110
|
+
broadcast({ type: 'asset-update', id, updates: { settings: asset.settings } });
|
|
2111
|
+
json(res, { tags: merged });
|
|
2112
|
+
}
|
|
2113
|
+
catch (err) {
|
|
2114
|
+
json(res, { error: err.message }, 500);
|
|
2115
|
+
}
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
// POST /api/ai/tag-missing — bulk AI-tag all assets with empty tags
|
|
2119
|
+
if (pathname === '/api/ai/tag-missing' && req.method === 'POST') {
|
|
2120
|
+
if (!projectConfig) {
|
|
2121
|
+
json(res, { error: 'No project open' }, 400);
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
const apiKey = projectConfig.config.ai?.geminiApiKey;
|
|
2125
|
+
if (!apiKey) {
|
|
2126
|
+
json(res, { error: 'Gemini API key not configured' }, 400);
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
try {
|
|
2130
|
+
const { tagAsset: aiTagAsset } = await import('./ai-tagger.js');
|
|
2131
|
+
const { readManifest, readThumbnailFile } = await import('./app/thumbnail-cache.js');
|
|
2132
|
+
const manifest = await readManifest(projectConfig.srcArtDir);
|
|
2133
|
+
const eligible = assets.filter(a => a.status === 'ready' &&
|
|
2134
|
+
!a.settings.excluded &&
|
|
2135
|
+
(!a.settings.tags || a.settings.tags.length === 0) &&
|
|
2136
|
+
a.type !== AssetType.MaterialSchema &&
|
|
2137
|
+
!a.parentId);
|
|
2138
|
+
let tagged = 0;
|
|
2139
|
+
let skipped = 0;
|
|
2140
|
+
const errors = [];
|
|
2141
|
+
for (const asset of eligible) {
|
|
2142
|
+
try {
|
|
2143
|
+
let inputData;
|
|
2144
|
+
let mimeType;
|
|
2145
|
+
if (asset.type === AssetType.Audio) {
|
|
2146
|
+
const ext = asset.fileName.split('.').pop()?.toLowerCase() ?? 'wav';
|
|
2147
|
+
const mimeMap = {
|
|
2148
|
+
wav: 'audio/wav', mp3: 'audio/mpeg', ogg: 'audio/ogg',
|
|
2149
|
+
flac: 'audio/flac', aac: 'audio/aac', m4a: 'audio/mp4',
|
|
2150
|
+
};
|
|
2151
|
+
mimeType = mimeMap[ext] ?? 'audio/wav';
|
|
2152
|
+
const raw = await readFile(projectConfig.srcArtDir, asset.id);
|
|
2153
|
+
if (!raw) {
|
|
2154
|
+
skipped++;
|
|
2155
|
+
continue;
|
|
2156
|
+
}
|
|
2157
|
+
inputData = Buffer.from(raw);
|
|
2158
|
+
}
|
|
2159
|
+
else {
|
|
2160
|
+
const entry = manifest[asset.stringId];
|
|
2161
|
+
if (!entry) {
|
|
2162
|
+
skipped++;
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
const thumbData = await readThumbnailFile(projectConfig.srcArtDir, asset.stringId, entry.format);
|
|
2166
|
+
if (!thumbData) {
|
|
2167
|
+
skipped++;
|
|
2168
|
+
continue;
|
|
2169
|
+
}
|
|
2170
|
+
inputData = thumbData;
|
|
2171
|
+
const formatMime = {
|
|
2172
|
+
png: 'image/png', webp: 'image/webp', webm: 'video/webm',
|
|
2173
|
+
};
|
|
2174
|
+
mimeType = formatMime[entry.format] ?? 'image/webp';
|
|
2175
|
+
}
|
|
2176
|
+
const tagPromise = aiTagAsset(apiKey, asset.type, asset.fileName, inputData, mimeType);
|
|
2177
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Gemini request timed out (30s)')), 30_000));
|
|
2178
|
+
const newTags = await Promise.race([tagPromise, timeoutPromise]);
|
|
2179
|
+
asset.settings = { ...asset.settings, tags: newTags };
|
|
2180
|
+
if (!asset.parentId) {
|
|
2181
|
+
const meta = assetSettingsToStowmeta(asset);
|
|
2182
|
+
await writeStowmeta(projectConfig.srcArtDir, asset.id, meta);
|
|
2183
|
+
}
|
|
2184
|
+
broadcast({ type: 'asset-update', id: asset.id, updates: { settings: asset.settings } });
|
|
2185
|
+
tagged++;
|
|
2186
|
+
await new Promise(r => setTimeout(r, 200));
|
|
2187
|
+
}
|
|
2188
|
+
catch (err) {
|
|
2189
|
+
errors.push({ id: asset.id, error: err.message });
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
json(res, { tagged, skipped, errors });
|
|
2193
|
+
}
|
|
2194
|
+
catch (err) {
|
|
2195
|
+
json(res, { error: err.message }, 500);
|
|
2196
|
+
}
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2007
2199
|
// GET /api/thumbnails/cached — return which thumbnails are still valid in the cache
|
|
2008
2200
|
if (pathname === '/api/thumbnails/cached' && req.method === 'GET') {
|
|
2009
2201
|
if (!projectConfig) {
|
package/dist/store.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function fetchRegistry(bucket?: string): Promise<Registry>;
|
|
1
|
+
import type { FirestoreReader } from './firestore.js';
|
|
3
2
|
export interface SearchResult {
|
|
4
3
|
stringId: string;
|
|
5
4
|
type: string;
|
|
@@ -27,26 +26,12 @@ export interface PackageInfo {
|
|
|
27
26
|
/** Full URL to pack-level thumbnail, if uploaded */
|
|
28
27
|
thumbnailUrl?: string;
|
|
29
28
|
}
|
|
30
|
-
export declare function searchAssets(
|
|
29
|
+
export declare function searchAssets(firestore: FirestoreReader, bucket: string, query: string, opts?: {
|
|
31
30
|
type?: string;
|
|
32
31
|
package?: string;
|
|
33
|
-
}): SearchResult[]
|
|
34
|
-
export declare function
|
|
35
|
-
export declare function resolveAssetDeps(
|
|
32
|
+
}): Promise<SearchResult[]>;
|
|
33
|
+
export declare function listStorePackages(firestore: FirestoreReader, bucket: string): Promise<PackageInfo[]>;
|
|
34
|
+
export declare function resolveAssetDeps(firestore: FirestoreReader, packageName: string, stringIds: string[], version?: string): Promise<{
|
|
36
35
|
resolvedIds: string[];
|
|
37
36
|
files: string[];
|
|
38
|
-
}
|
|
39
|
-
export declare function storeSearch(query: string, opts?: {
|
|
40
|
-
type?: string;
|
|
41
|
-
json?: boolean;
|
|
42
|
-
bucket?: string;
|
|
43
|
-
limit?: number;
|
|
44
|
-
}): Promise<void>;
|
|
45
|
-
export declare function storeList(opts?: {
|
|
46
|
-
json?: boolean;
|
|
47
|
-
bucket?: string;
|
|
48
|
-
}): Promise<void>;
|
|
49
|
-
export declare function storeInfo(packageName: string, opts?: {
|
|
50
|
-
json?: boolean;
|
|
51
|
-
bucket?: string;
|
|
52
|
-
}): Promise<void>;
|
|
37
|
+
}>;
|
package/dist/store.js
CHANGED
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
import { resolveTransitiveDeps, resolveFiles } from './assets-package.js';
|
|
2
|
-
const DEFAULT_BUCKET = 'venus-shared-assets-test';
|
|
3
|
-
// ─── Fetch Registry ──────────────────────────────────────────────────────────
|
|
4
|
-
export async function fetchRegistry(bucket) {
|
|
5
|
-
const bucketName = (bucket ?? DEFAULT_BUCKET).replace(/^gs:\/\//, '').replace(/\/$/, '');
|
|
6
|
-
const url = `https://storage.googleapis.com/${bucketName}/registry.json?t=${Date.now()}`;
|
|
7
|
-
const res = await fetch(url);
|
|
8
|
-
if (!res.ok) {
|
|
9
|
-
if (res.status === 404)
|
|
10
|
-
return { schemaVersion: 1, packages: {} };
|
|
11
|
-
throw new Error(`Failed to fetch registry: ${res.status}`);
|
|
12
|
-
}
|
|
13
|
-
return res.json();
|
|
14
|
-
}
|
|
15
2
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
16
|
-
export function searchAssets(
|
|
3
|
+
export async function searchAssets(firestore, bucket, query, opts) {
|
|
17
4
|
// Support comma-separated terms: "coral, sea, ocean" matches any term
|
|
18
5
|
const terms = query.split(',').map(t => t.toLowerCase().trim()).filter(Boolean);
|
|
19
6
|
const scored = [];
|
|
20
|
-
const bucket = DEFAULT_BUCKET;
|
|
21
7
|
// When searching within a specific package, skip package-level metadata
|
|
22
8
|
// (name, description, tags) — it matches every asset and adds noise.
|
|
23
9
|
const skipPkgMeta = !!opts?.package;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
let packages = await firestore.listPackages();
|
|
11
|
+
if (opts?.package) {
|
|
12
|
+
packages = packages.filter(p => p.name === opts.package);
|
|
13
|
+
}
|
|
14
|
+
// Fetch all latest versions in parallel
|
|
15
|
+
const versions = await Promise.all(packages.map(({ name, data: pkg }) => firestore.getVersion(name, pkg.latest)));
|
|
16
|
+
for (let i = 0; i < packages.length; i++) {
|
|
17
|
+
const { name: pkgName, data: pkg } = packages[i];
|
|
27
18
|
const verStr = pkg.latest;
|
|
28
|
-
const ver =
|
|
19
|
+
const ver = versions[i];
|
|
29
20
|
if (!ver)
|
|
30
21
|
continue;
|
|
31
22
|
for (const asset of ver.assets) {
|
|
@@ -186,10 +177,16 @@ function scoreAsset(terms, asset, pkg, pkgName, skipPkgMeta = false) {
|
|
|
186
177
|
return total;
|
|
187
178
|
}
|
|
188
179
|
// ─── List Packages ───────────────────────────────────────────────────────────
|
|
189
|
-
export function
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
180
|
+
export async function listStorePackages(firestore, bucket) {
|
|
181
|
+
const packages = await firestore.listPackages();
|
|
182
|
+
// Fetch versions and version keys in parallel for all packages
|
|
183
|
+
const [versions, versionKeysList] = await Promise.all([
|
|
184
|
+
Promise.all(packages.map(({ name, data: pkg }) => firestore.getVersion(name, pkg.latest))),
|
|
185
|
+
Promise.all(packages.map(({ name }) => firestore.listVersionKeys(name))),
|
|
186
|
+
]);
|
|
187
|
+
return packages.map(({ name, data: pkg }, i) => {
|
|
188
|
+
const ver = versions[i];
|
|
189
|
+
const versionKeys = versionKeysList[i];
|
|
193
190
|
const thumbnailUrl = pkg.thumbnail
|
|
194
191
|
? `https://storage.googleapis.com/${bucket}/packages/${name}/${pkg.latest}/${pkg.thumbnail}`
|
|
195
192
|
: undefined;
|
|
@@ -199,7 +196,7 @@ export function listPackages(registry) {
|
|
|
199
196
|
author: pkg.author,
|
|
200
197
|
tags: pkg.tags ?? [],
|
|
201
198
|
latest: pkg.latest,
|
|
202
|
-
versions:
|
|
199
|
+
versions: versionKeys,
|
|
203
200
|
assetCount: ver?.assets.length ?? 0,
|
|
204
201
|
totalSize: ver?.totalSize ?? 0,
|
|
205
202
|
thumbnailUrl,
|
|
@@ -207,104 +204,15 @@ export function listPackages(registry) {
|
|
|
207
204
|
});
|
|
208
205
|
}
|
|
209
206
|
// ─── Resolve Dependencies ────────────────────────────────────────────────────
|
|
210
|
-
export function resolveAssetDeps(
|
|
211
|
-
const pkg =
|
|
207
|
+
export async function resolveAssetDeps(firestore, packageName, stringIds, version) {
|
|
208
|
+
const pkg = await firestore.getPackage(packageName);
|
|
212
209
|
if (!pkg)
|
|
213
210
|
throw new Error(`Package "${packageName}" not found`);
|
|
214
211
|
const verStr = version ?? pkg.latest;
|
|
215
|
-
const ver =
|
|
212
|
+
const ver = await firestore.getVersion(packageName, verStr);
|
|
216
213
|
if (!ver)
|
|
217
214
|
throw new Error(`Version "${verStr}" not found`);
|
|
218
215
|
const resolvedIds = resolveTransitiveDeps(stringIds, ver.assets);
|
|
219
216
|
const files = resolveFiles(resolvedIds, ver.assets);
|
|
220
217
|
return { resolvedIds, files };
|
|
221
218
|
}
|
|
222
|
-
// ─── CLI Commands ────────────────────────────────────────────────────────────
|
|
223
|
-
export async function storeSearch(query, opts) {
|
|
224
|
-
const registry = await fetchRegistry(opts?.bucket);
|
|
225
|
-
let results = searchAssets(registry, query, { type: opts?.type });
|
|
226
|
-
if (opts?.limit && opts.limit > 0)
|
|
227
|
-
results = results.slice(0, opts.limit);
|
|
228
|
-
if (opts?.json) {
|
|
229
|
-
console.log(JSON.stringify(results, null, 2));
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (results.length === 0) {
|
|
233
|
-
console.log(`No assets found matching "${query}"`);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
console.log(`\nFound ${results.length} asset${results.length !== 1 ? 's' : ''} matching "${query}":\n`);
|
|
237
|
-
for (const r of results) {
|
|
238
|
-
const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : '';
|
|
239
|
-
const deps = r.dependencies.length > 0 ? ` → ${r.dependencies.join(', ')}` : '';
|
|
240
|
-
const thumb = r.thumbnail ? ' (has thumbnail)' : '';
|
|
241
|
-
console.log(` [${r.type}] ${r.stringId} — ${r.packageName}@${r.version}${tags}${deps}${thumb}`);
|
|
242
|
-
console.log(` ${r.file} (${formatBytes(r.size)})`);
|
|
243
|
-
}
|
|
244
|
-
console.log('');
|
|
245
|
-
}
|
|
246
|
-
export async function storeList(opts) {
|
|
247
|
-
const registry = await fetchRegistry(opts?.bucket);
|
|
248
|
-
const packages = listPackages(registry);
|
|
249
|
-
if (opts?.json) {
|
|
250
|
-
console.log(JSON.stringify(packages, null, 2));
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (packages.length === 0) {
|
|
254
|
-
console.log('No packages published yet.');
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
console.log(`\n${packages.length} package${packages.length !== 1 ? 's' : ''} in the asset store:\n`);
|
|
258
|
-
for (const p of packages) {
|
|
259
|
-
const tags = p.tags.length > 0 ? ` [${p.tags.join(', ')}]` : '';
|
|
260
|
-
const desc = p.description ? ` — ${p.description}` : '';
|
|
261
|
-
console.log(` ${p.name}@${p.latest}${desc}${tags}`);
|
|
262
|
-
console.log(` ${p.assetCount} assets, ${formatBytes(p.totalSize)}, ${p.versions.length} version${p.versions.length !== 1 ? 's' : ''}`);
|
|
263
|
-
}
|
|
264
|
-
console.log('');
|
|
265
|
-
}
|
|
266
|
-
export async function storeInfo(packageName, opts) {
|
|
267
|
-
const registry = await fetchRegistry(opts?.bucket);
|
|
268
|
-
const pkg = registry.packages[packageName];
|
|
269
|
-
if (!pkg) {
|
|
270
|
-
console.error(`Package "${packageName}" not found.`);
|
|
271
|
-
process.exit(1);
|
|
272
|
-
}
|
|
273
|
-
const ver = pkg.versions[pkg.latest];
|
|
274
|
-
if (opts?.json) {
|
|
275
|
-
console.log(JSON.stringify({
|
|
276
|
-
name: packageName,
|
|
277
|
-
description: pkg.description,
|
|
278
|
-
author: pkg.author,
|
|
279
|
-
tags: pkg.tags ?? [],
|
|
280
|
-
latest: pkg.latest,
|
|
281
|
-
versions: Object.keys(pkg.versions),
|
|
282
|
-
assets: ver?.assets ?? [],
|
|
283
|
-
}, null, 2));
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
console.log(`\n${packageName}@${pkg.latest}`);
|
|
287
|
-
if (pkg.description)
|
|
288
|
-
console.log(` ${pkg.description}`);
|
|
289
|
-
if (pkg.author)
|
|
290
|
-
console.log(` Author: ${pkg.author}`);
|
|
291
|
-
if (pkg.tags?.length)
|
|
292
|
-
console.log(` Tags: ${pkg.tags.join(', ')}`);
|
|
293
|
-
console.log(` Versions: ${Object.keys(pkg.versions).join(', ')}`);
|
|
294
|
-
if (ver) {
|
|
295
|
-
console.log(`\nAssets (${ver.assets.length}):\n`);
|
|
296
|
-
for (const a of ver.assets) {
|
|
297
|
-
const tags = a.tags.length > 0 ? ` [${a.tags.join(', ')}]` : '';
|
|
298
|
-
const deps = a.dependencies.length > 0 ? ` → ${a.dependencies.join(', ')}` : '';
|
|
299
|
-
console.log(` [${a.type}] ${a.stringId}${tags}${deps} (${formatBytes(a.size)})`);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
console.log('');
|
|
303
|
-
}
|
|
304
|
-
function formatBytes(bytes) {
|
|
305
|
-
if (bytes < 1024)
|
|
306
|
-
return `${bytes} B`;
|
|
307
|
-
if (bytes < 1024 * 1024)
|
|
308
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
309
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
310
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@series-inc/stowkit-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.36",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"stowkit": "./dist/cli.js"
|
|
@@ -10,18 +10,20 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"skill.md",
|
|
13
|
-
"wasm"
|
|
13
|
+
"wasm",
|
|
14
|
+
"templates"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
17
|
"build": "tsc",
|
|
17
18
|
"dev": "tsc --watch"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@
|
|
21
|
+
"@google/genai": "^1.46.0",
|
|
21
22
|
"@series-inc/stowkit-editor": "^0.1.8",
|
|
23
|
+
"@series-inc/stowkit-packer-gui": "^0.1.23",
|
|
24
|
+
"@strangeape/ffmpeg-audio-wasm": "^0.1.0",
|
|
22
25
|
"draco3d": "^1.5.7",
|
|
23
26
|
"fbx-parser": "^2.1.3",
|
|
24
|
-
"@strangeape/ffmpeg-audio-wasm": "^0.1.0",
|
|
25
27
|
"sharp": "^0.33.5",
|
|
26
28
|
"three": "^0.182.0",
|
|
27
29
|
"ws": "^8.18.0"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
|
6
|
+
<title>My Game</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<canvas id="renderCanvas"></canvas>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-stowkit-game",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"postinstall": "node scripts/copy-decoders.mjs",
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@dimforge/rapier3d": "^0.11.2",
|
|
14
|
+
"@dimforge/rapier3d-compat": "^0.11.2",
|
|
15
|
+
"@series-inc/rundot-3d-engine": "^0.6.20",
|
|
16
|
+
"@series-inc/stowkit-three-loader": "^0.1.50",
|
|
17
|
+
"three": "^0.180.0",
|
|
18
|
+
"three-stdlib": "^2.36.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@series-inc/rundot-game-sdk": "^5.5.4"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@series-inc/stowkit-reader": "^0.1.45",
|
|
25
|
+
"@types/node": "^20.11.16",
|
|
26
|
+
"@types/three": "^0.180.0",
|
|
27
|
+
"typescript": "^5.3.3",
|
|
28
|
+
"vite": "^5.0.12",
|
|
29
|
+
"vite-plugin-top-level-await": "^1.6.0",
|
|
30
|
+
"vite-plugin-wasm": "^3.5.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "prefabs": [] }
|