@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/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 bucketParam = url.searchParams.get('bucket') ?? 'venus-shared-assets-test';
1784
- const bucketName = bucketParam.replace(/^gs:\/\//, '').replace(/\/$/, '');
1785
- const registryUrl = `https://storage.googleapis.com/${bucketName}/registry.json?t=${Date.now()}`;
1786
- const fetchRes = await fetch(registryUrl);
1787
- if (!fetchRes.ok) {
1788
- if (fetchRes.status === 404) {
1789
- json(res, { schemaVersion: 1, packages: {} });
1790
- }
1791
- else {
1792
- json(res, { error: `Failed to fetch registry: ${fetchRes.status}` }, 502);
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
- return;
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
- const registry = await fetchRes.json();
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 { fetchRegistry, searchAssets } = await import('./store.js');
1814
- const registry = await fetchRegistry(bucketParam);
1815
- let results = searchAssets(registry, query, { type, package: pkg });
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 { fetchRegistry, listPackages } = await import('./store.js');
1830
- const registry = await fetchRegistry(bucketParam);
1831
- const packages = listPackages(registry);
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 bucketParam = url.searchParams.get('bucket') ?? undefined;
1844
- const { fetchRegistry } = await import('./store.js');
1845
- const registry = await fetchRegistry(bucketParam);
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 = pkg.versions[pkg.latest];
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: Object.keys(pkg.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 ?? 'venus-shared-assets-test').replace(/^gs:\/\//, '').replace(/\/$/, '');
1903
+ const bucketName = (bucketParam ?? 'rungame-shared-assets-test').replace(/^gs:\/\//, '').replace(/\/$/, '');
1881
1904
  const baseUrl = `https://storage.googleapis.com/${bucketName}`;
1882
- // Fetch registry to resolve deps
1883
- const regRes = await fetch(`${baseUrl}/registry.json`);
1884
- if (!regRes.ok) {
1885
- json(res, { error: 'Could not fetch registry' }, 502);
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 = pkg.versions[version];
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 { Registry } from './assets-package.js';
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(registry: Registry, query: string, opts?: {
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 listPackages(registry: Registry): PackageInfo[];
35
- export declare function resolveAssetDeps(registry: Registry, packageName: string, stringIds: string[], version?: string): {
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(registry, query, opts) {
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
- for (const [pkgName, pkg] of Object.entries(registry.packages)) {
25
- if (opts?.package && pkgName !== opts.package)
26
- continue;
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 = pkg.versions[verStr];
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 listPackages(registry) {
190
- const bucket = DEFAULT_BUCKET;
191
- return Object.entries(registry.packages).map(([name, pkg]) => {
192
- const ver = pkg.versions[pkg.latest];
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: Object.keys(pkg.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(registry, packageName, stringIds, version) {
211
- const pkg = registry.packages[packageName];
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 = pkg.versions[verStr];
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.34",
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
- "@series-inc/stowkit-packer-gui": "^0.1.22",
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": [] }