@series-inc/stowkit-cli 0.6.36 → 0.6.38

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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { GoogleGenAI } from '@google/genai';
2
2
  import { AssetType } from './core/types.js';
3
- const MODEL = 'gemini-2.5-flash-lite-preview';
3
+ const MODEL = 'gemini-3.1-flash-lite-preview';
4
4
  const ASSET_TYPE_LABELS = {
5
5
  [AssetType.Texture2D]: 'texture',
6
6
  [AssetType.StaticMesh]: '3D mesh',
@@ -29,6 +29,7 @@ export interface FirestoreClient extends FirestoreReader {
29
29
  deletePackage(name: string): Promise<void>;
30
30
  setVersion(packageName: string, version: string, data: FirestoreVersionDoc): Promise<void>;
31
31
  }
32
+ export declare function clearFirestoreCache(): void;
32
33
  /**
33
34
  * Create a read-only Firestore client using the public API key.
34
35
  * No service account or credentials needed — safe for CLI store commands,
package/dist/firestore.js CHANGED
@@ -73,6 +73,9 @@ function objectToFields(data) {
73
73
  // ─── In-memory cache ─────────────────────────────────────────────────────────
74
74
  const CACHE_TTL_MS = 60_000; // 1 minute
75
75
  const cache = new Map();
76
+ export function clearFirestoreCache() {
77
+ cache.clear();
78
+ }
76
79
  function cacheGet(key) {
77
80
  const entry = cache.get(key);
78
81
  if (!entry)
package/dist/server.js CHANGED
@@ -1789,6 +1789,10 @@ async function handleRequest(req, res, staticApps) {
1789
1789
  // GET /api/asset-store/registry — fetch the public registry
1790
1790
  if (pathname === '/api/asset-store/registry' && req.method === 'GET') {
1791
1791
  try {
1792
+ if (url.searchParams.get('refresh')) {
1793
+ const { clearFirestoreCache } = await import('./firestore.js');
1794
+ clearFirestoreCache();
1795
+ }
1792
1796
  const { createFirestoreReader } = await import('./firestore.js');
1793
1797
  const reader = createFirestoreReader();
1794
1798
  const allPackages = await reader.listPackages();
@@ -1846,6 +1850,10 @@ async function handleRequest(req, res, staticApps) {
1846
1850
  // GET /api/asset-store/packages — list all packages
1847
1851
  if (pathname === '/api/asset-store/packages' && req.method === 'GET') {
1848
1852
  try {
1853
+ if (url.searchParams.get('refresh')) {
1854
+ const { clearFirestoreCache } = await import('./firestore.js');
1855
+ clearFirestoreCache();
1856
+ }
1849
1857
  const bucketParam = url.searchParams.get('bucket') ?? undefined;
1850
1858
  const { createFirestoreReader } = await import('./firestore.js');
1851
1859
  const { listStorePackages } = await import('./store.js');
@@ -2196,6 +2204,94 @@ async function handleRequest(req, res, staticApps) {
2196
2204
  }
2197
2205
  return;
2198
2206
  }
2207
+ // POST /api/ai/tag-batch — AI-tag specific assets by ID, concurrently
2208
+ if (pathname === '/api/ai/tag-batch' && req.method === 'POST') {
2209
+ if (!projectConfig) {
2210
+ json(res, { error: 'No project open' }, 400);
2211
+ return;
2212
+ }
2213
+ const apiKey = projectConfig.config.ai?.geminiApiKey;
2214
+ if (!apiKey) {
2215
+ json(res, { error: 'Gemini API key not configured' }, 400);
2216
+ return;
2217
+ }
2218
+ try {
2219
+ const body = JSON.parse(await readBody(req));
2220
+ const ids = body.ids;
2221
+ if (!Array.isArray(ids) || ids.length === 0) {
2222
+ json(res, { error: 'ids must be a non-empty array' }, 400);
2223
+ return;
2224
+ }
2225
+ const { tagAsset: aiTagAsset } = await import('./ai-tagger.js');
2226
+ const { readManifest, readThumbnailFile } = await import('./app/thumbnail-cache.js');
2227
+ const manifest = await readManifest(projectConfig.srcArtDir);
2228
+ const eligible = assets.filter(a => ids.includes(a.id) &&
2229
+ a.status === 'ready' &&
2230
+ !a.settings.excluded &&
2231
+ (!a.settings.tags || a.settings.tags.length === 0) &&
2232
+ a.type !== AssetType.MaterialSchema &&
2233
+ !a.parentId);
2234
+ let tagged = 0;
2235
+ let skipped = ids.length - eligible.length;
2236
+ const errors = [];
2237
+ const CONCURRENCY = 20;
2238
+ // Process in batches of CONCURRENCY
2239
+ for (let i = 0; i < eligible.length; i += CONCURRENCY) {
2240
+ const batch = eligible.slice(i, i + CONCURRENCY);
2241
+ const results = await Promise.allSettled(batch.map(async (asset) => {
2242
+ let inputData;
2243
+ let mimeType;
2244
+ if (asset.type === AssetType.Audio) {
2245
+ const ext = asset.fileName.split('.').pop()?.toLowerCase() ?? 'wav';
2246
+ const mimeMap = {
2247
+ wav: 'audio/wav', mp3: 'audio/mpeg', ogg: 'audio/ogg',
2248
+ flac: 'audio/flac', aac: 'audio/aac', m4a: 'audio/mp4',
2249
+ };
2250
+ mimeType = mimeMap[ext] ?? 'audio/wav';
2251
+ const raw = await readFile(projectConfig.srcArtDir, asset.id);
2252
+ if (!raw)
2253
+ throw new Error('Source file not found');
2254
+ inputData = Buffer.from(raw);
2255
+ }
2256
+ else {
2257
+ const entry = manifest[asset.stringId];
2258
+ if (!entry)
2259
+ throw new Error('No thumbnail cached');
2260
+ const thumbData = await readThumbnailFile(projectConfig.srcArtDir, asset.stringId, entry.format);
2261
+ if (!thumbData)
2262
+ throw new Error('Thumbnail file missing');
2263
+ inputData = thumbData;
2264
+ const formatMime = {
2265
+ png: 'image/png', webp: 'image/webp', webm: 'video/webm',
2266
+ };
2267
+ mimeType = formatMime[entry.format] ?? 'image/webp';
2268
+ }
2269
+ const newTags = await Promise.race([
2270
+ aiTagAsset(apiKey, asset.type, asset.fileName, inputData, mimeType),
2271
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Gemini request timed out (30s)')), 30_000)),
2272
+ ]);
2273
+ asset.settings = { ...asset.settings, tags: newTags };
2274
+ if (!asset.parentId) {
2275
+ const meta = assetSettingsToStowmeta(asset);
2276
+ await writeStowmeta(projectConfig.srcArtDir, asset.id, meta);
2277
+ }
2278
+ broadcast({ type: 'asset-update', id: asset.id, updates: { settings: asset.settings } });
2279
+ return asset.id;
2280
+ }));
2281
+ for (const r of results) {
2282
+ if (r.status === 'fulfilled')
2283
+ tagged++;
2284
+ else
2285
+ errors.push({ id: 'unknown', error: r.reason?.message ?? String(r.reason) });
2286
+ }
2287
+ }
2288
+ json(res, { tagged, skipped, errors });
2289
+ }
2290
+ catch (err) {
2291
+ json(res, { error: err.message }, 500);
2292
+ }
2293
+ return;
2294
+ }
2199
2295
  // GET /api/thumbnails/cached — return which thumbnails are still valid in the cache
2200
2296
  if (pathname === '/api/thumbnails/cached' && req.method === 'GET') {
2201
2297
  if (!projectConfig) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@series-inc/stowkit-cli",
3
- "version": "0.6.36",
3
+ "version": "0.6.38",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "stowkit": "./dist/cli.js"
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "@google/genai": "^1.46.0",
22
22
  "@series-inc/stowkit-editor": "^0.1.8",
23
- "@series-inc/stowkit-packer-gui": "^0.1.23",
23
+ "@series-inc/stowkit-packer-gui": "^0.1.25",
24
24
  "@strangeape/ffmpeg-audio-wasm": "^0.1.0",
25
25
  "draco3d": "^1.5.7",
26
26
  "fbx-parser": "^2.1.3",