@series-inc/stowkit-cli 0.6.18 → 0.6.20

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.
@@ -40,6 +40,8 @@ export interface RegistryPackage {
40
40
  tags: string[];
41
41
  latest: string;
42
42
  versions: Record<string, RegistryVersion>;
43
+ /** Pack-level thumbnail filename (e.g. "thumbnail.webp"), uploaded during publish */
44
+ thumbnail?: string;
43
45
  }
44
46
  export interface Registry {
45
47
  schemaVersion: number;
package/dist/publish.js CHANGED
@@ -321,6 +321,23 @@ export async function publishPackage(projectDir, opts = {}) {
321
321
  const receiptPath = `${uploadPrefix}/assets-package.json`;
322
322
  await gcs.upload(receiptPath, JSON.stringify(pkg, null, 2), 'application/json');
323
323
  totalDone++;
324
+ // Step 12b: Upload pack-level thumbnail if present in project root
325
+ let packThumbnail;
326
+ for (const candidate of ['thumbnail.webp', 'thumbnail.png', 'thumbnail.jpg']) {
327
+ const thumbPath = path.join(projectDir, candidate);
328
+ try {
329
+ const thumbData = new Uint8Array(await fs.readFile(thumbPath));
330
+ const ext = candidate.split('.').pop();
331
+ const mime = ext === 'webp' ? 'image/webp' : ext === 'png' ? 'image/png' : 'image/jpeg';
332
+ const objectPath = `${uploadPrefix}/${candidate}`;
333
+ await gcs.upload(objectPath, thumbData, mime);
334
+ packThumbnail = candidate;
335
+ totalDone++;
336
+ log(` Uploaded pack thumbnail: ${candidate} (${formatBytes(thumbData.length)})`);
337
+ break;
338
+ }
339
+ catch { /* file doesn't exist, try next */ }
340
+ }
324
341
  // Step 13: Update and upload registry
325
342
  log('Updating registry...');
326
343
  if (!registry.packages[pkg.name]) {
@@ -338,6 +355,8 @@ export async function publishPackage(projectDir, opts = {}) {
338
355
  regPkg.description = pkg.description;
339
356
  regPkg.author = pkg.author;
340
357
  regPkg.tags = pkg.tags ?? [];
358
+ if (packThumbnail)
359
+ regPkg.thumbnail = packThumbnail;
341
360
  emitProgress({ phase: 'registry', done: totalDone, total: totalUploads, message: 'Updating registry...' });
342
361
  await gcs.uploadWithGeneration('registry.json', JSON.stringify(registry, null, 2), generation);
343
362
  totalDone++;
package/dist/store.d.ts CHANGED
@@ -23,6 +23,8 @@ export interface PackageInfo {
23
23
  versions: string[];
24
24
  assetCount: number;
25
25
  totalSize: number;
26
+ /** Full URL to pack-level thumbnail, if uploaded */
27
+ thumbnailUrl?: string;
26
28
  }
27
29
  export declare function searchAssets(registry: Registry, query: string, opts?: {
28
30
  type?: string;
package/dist/store.js CHANGED
@@ -184,8 +184,12 @@ function scoreAsset(terms, asset, pkg, pkgName, skipPkgMeta = false) {
184
184
  }
185
185
  // ─── List Packages ───────────────────────────────────────────────────────────
186
186
  export function listPackages(registry) {
187
+ const bucket = DEFAULT_BUCKET;
187
188
  return Object.entries(registry.packages).map(([name, pkg]) => {
188
189
  const ver = pkg.versions[pkg.latest];
190
+ const thumbnailUrl = pkg.thumbnail
191
+ ? `https://storage.googleapis.com/${bucket}/packages/${name}/${pkg.latest}/${pkg.thumbnail}`
192
+ : undefined;
189
193
  return {
190
194
  name,
191
195
  description: pkg.description,
@@ -195,6 +199,7 @@ export function listPackages(registry) {
195
199
  versions: Object.keys(pkg.versions),
196
200
  assetCount: ver?.assets.length ?? 0,
197
201
  totalSize: ver?.totalSize ?? 0,
202
+ thumbnailUrl,
198
203
  };
199
204
  });
200
205
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@series-inc/stowkit-cli",
3
- "version": "0.6.18",
3
+ "version": "0.6.20",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "stowkit": "./dist/cli.js"
@@ -17,7 +17,7 @@
17
17
  "dev": "tsc --watch"
18
18
  },
19
19
  "dependencies": {
20
- "@series-inc/stowkit-packer-gui": "^0.1.17",
20
+ "@series-inc/stowkit-packer-gui": "^0.1.19",
21
21
  "@series-inc/stowkit-editor": "^0.1.8",
22
22
  "draco3d": "^1.5.7",
23
23
  "fbx-parser": "^2.1.3",
package/skill.md CHANGED
@@ -532,20 +532,86 @@ To refresh skill files without updating the CLI: `stowkit init --update`
532
532
 
533
533
  ## Asset Store
534
534
 
535
- StowKit includes a shared asset store for publishing, searching, and importing reusable asset packs across projects. Assets are stored in a public GCS bucket with a central `registry.json` manifest.
535
+ StowKit includes a shared asset store for publishing, searching, and importing reusable asset packs across projects. Assets are stored in a GCS bucket with a central `registry.json` manifest.
536
536
 
537
- ### Searching the store
537
+ ### Publishing to the store
538
+
539
+ To publish a pack, you need an `assets-package.json` in the project root:
540
+
541
+ ```json
542
+ {
543
+ "name": "my-pack",
544
+ "version": "1.0.0",
545
+ "author": "your-name",
546
+ "description": "A short description of what's in this pack",
547
+ "tags": ["environment", "fantasy", "low-poly"],
548
+ "bucket": "gs://venus-shared-assets-test"
549
+ }
550
+ ```
551
+
552
+ Then run `stowkit publish` from the packer GUI's Publish modal, or programmatically via the CLI server's `/api/publish` endpoint. The publish flow:
553
+
554
+ 1. Scans all assets in the `srcArtDir` and builds a dependency graph
555
+ 2. Uploads all source files to `packages/{name}/{version}/` in GCS
556
+ 3. Uploads per-asset thumbnails (captured in the packer GUI)
557
+ 4. Uploads a pack-level thumbnail if present (see below)
558
+ 5. Updates the central `registry.json` with the new version
559
+
560
+ **Pack-level thumbnail:** Place a `thumbnail.webp`, `thumbnail.png`, or `thumbnail.jpg` in the project root directory. During publish, this file is uploaded alongside the package and displayed as the pack's cover image in the store. If no custom thumbnail is provided, the store shows a collage of individual asset thumbnails.
561
+
562
+ **Version management:** Bump the `version` field in `assets-package.json` before each publish. Publishing the same version again requires `--force`.
563
+
564
+ ### Searching the store (CLI)
538
565
 
539
566
  ```bash
540
567
  stowkit store search coral # Find assets matching "coral"
568
+ stowkit store search "boss, idle" # AND search — both terms must match
541
569
  stowkit store search ocean --type texture # Find textures tagged/named "ocean"
542
570
  stowkit store search staticMesh --json # Machine-readable JSON output
543
571
  stowkit store list # List all published packages
544
- stowkit store info package_test # Show all assets in a package
545
- stowkit store info package_test --json # Full package details as JSON
572
+ stowkit store info ninja-adventure # Show all assets in a package
573
+ stowkit store info ninja-adventure --json # Full package details as JSON
546
574
  ```
547
575
 
548
- The `--json` flag outputs structured data with `stringId`, `type`, `packageName`, `version`, `file`, `size`, `tags`, `dependencies`, `thumbnail`, and `thumbnailUrl` for each asset. This is the recommended format for AI agents to consume.
576
+ The `--json` flag outputs structured data for programmatic/AI consumption.
577
+
578
+ ### Search scoring algorithm
579
+
580
+ Search uses a ranked scoring system with AND semantics. Each search term must independently clear a minimum threshold — weak matches alone won't surface results.
581
+
582
+ **Scoring tiers (per term, highest match wins):**
583
+
584
+ | Score | Match type | Example |
585
+ |-------|-----------|---------|
586
+ | 100 | Exact stringId match | "boss" matches asset "boss" |
587
+ | 90 | Exact package name match | "ninja-adventure" matches the pack |
588
+ | 80 | Exact asset tag match | "coral" matches tag "coral" |
589
+ | 60 | Word match in stringId or tag | "boss" matches "actor/boss/idle" |
590
+ | 50 | stringId starts with term | "act" matches "actor/boss/idle" |
591
+ | 45-50 | Word match in package name/tag | "ninja" matches pack "ninja-adventure" |
592
+ | 40 | Word prefix in stringId | "att" matches "attack" |
593
+ | 25-35 | Substring/prefix matches | Various partial matches |
594
+ | < 20 | File path, type, description | Below threshold — boost only |
595
+
596
+ **Key behaviors:**
597
+ - **AND semantics:** Multi-term queries (comma-separated) require ALL terms to match. "boss, idle" only returns assets matching both words.
598
+ - **Minimum threshold (20):** Weak signals like file path matches or type name matches can't surface assets on their own. They only boost already-qualifying results.
599
+ - **Tags score high:** Asset tags are curated metadata and rank just below exact name matches. If an asset has a tag that matches your query, it will surface prominently.
600
+ - **Package metadata scoped:** When searching within a specific pack, package-level fields (name, description, tags) are excluded from scoring to prevent every asset from matching.
601
+
602
+ **Word splitting:** StringIds and names are split on underscores, hyphens, dots, slashes, camelCase, and spaces. "NinjaBoss_Attack" becomes ["ninja", "boss", "attack"].
603
+
604
+ ### Searching in the Packer GUI
605
+
606
+ The packer GUI has a **Store** button that opens a full-featured asset store browser:
607
+
608
+ - **Global search:** Type a query without selecting a pack — shows matching **packs** (not individual assets), sorted by number of matches. Click a pack to browse its assets with the search term preserved.
609
+ - **In-pack search:** Select a pack first, then search — shows individual assets within that pack, flat (no folder grouping), with AND scoring.
610
+ - **Type filters:** Filter by asset type (texture, staticMesh, skinnedMesh, audio, materialSchema, animationClip) using toggle pills.
611
+ - **Show All:** Checkbox to flatten the folder hierarchy and see all assets in the current pack at once. Combines with type filters.
612
+ - **Folder browsing:** When not searching, navigate the pack's folder structure. Breadcrumbs below the filter bar show your current path.
613
+ - **Thumbnail size:** Slider in the footer to adjust grid card size.
614
+ - **Selection + dependencies:** Click assets to select them. Transitive dependencies are auto-resolved and shown with amber badges. The Import button downloads all selected assets and their deps into the project's `srcArtDir`.
549
615
 
550
616
  ### JSON search result format
551
617
 
@@ -561,6 +627,7 @@ The `--json` flag outputs structured data with `stringId`, `type`, `packageName`
561
627
  "tags": ["coral", "environment"],
562
628
  "dependencies": ["M_Sea_Floor"],
563
629
  "thumbnail": true,
630
+ "thumbnailFormat": "png",
564
631
  "thumbnailUrl": "https://storage.googleapis.com/venus-shared-assets-test/packages/ocean_pack/1.0.0/thumbnails/coral_1.png"
565
632
  }
566
633
  ]
@@ -572,16 +639,21 @@ Assets have dependency chains: meshes depend on materials, materials depend on t
572
639
 
573
640
  ### Importing from the store
574
641
 
575
- The packer GUI has a **Store** button to browse, search, and import assets. Selected assets and their transitive dependencies are downloaded directly into the project's `srcArtDir`.
642
+ The packer GUI's Store modal handles importing. Selected assets and their transitive dependencies are downloaded directly into the project's `srcArtDir`. Already-imported assets are marked with a green "Imported" badge.
576
643
 
577
644
  ### Thumbnail URLs
578
645
 
579
- Published assets may have thumbnails at:
646
+ Per-asset thumbnails (captured in the packer GUI during publish):
647
+ ```
648
+ https://storage.googleapis.com/{bucket}/packages/{packageName}/{version}/thumbnails/{stringId}.{format}
649
+ ```
650
+
651
+ Pack-level thumbnails (from project root `thumbnail.webp`):
580
652
  ```
581
- https://storage.googleapis.com/{bucket}/packages/{packageName}/{version}/thumbnails/{stringId}.png
653
+ https://storage.googleapis.com/{bucket}/packages/{packageName}/{version}/thumbnail.webp
582
654
  ```
583
655
 
584
- Check the `thumbnail` boolean in search results to know if a thumbnail exists.
656
+ Check the `thumbnail` boolean in search results to know if an asset has a thumbnail. Pack-level thumbnails are returned as `thumbnailUrl` in the package listing.
585
657
 
586
658
  ## Common Tasks for AI Agents
587
659
 
@@ -649,7 +721,11 @@ This is useful for verifying build output, checking which assets ended up in whi
649
721
  - **Set up with engine:** `stowkit init --with-engine` (installs `@series-inc/rundot-3d-engine` + `three`)
650
722
  - **Update CLI + skill files:** `stowkit update`
651
723
  - **Search the asset store:** `stowkit store search sword --json` (returns JSON array of matching assets with thumbnailUrls)
724
+ - **AND search:** `stowkit store search "boss, idle" --json` (both terms must match)
652
725
  - **Find all meshes in the store:** `stowkit store search mesh --type staticMesh --json`
653
726
  - **List all published packages:** `stowkit store list --json`
654
727
  - **Get package details:** `stowkit store info ocean_pack --json`
655
- - **Show a thumbnail to the user:** Use the `thumbnailUrl` from search results — it's a public PNG URL
728
+ - **Show a thumbnail to the user:** Use the `thumbnailUrl` from search results — it's a public URL
729
+ - **Add a pack thumbnail:** Place `thumbnail.webp` (or `.png`/`.jpg`) in the project root before publishing
730
+ - **Publish a pack:** Open the packer GUI (`stowkit packer`), click Publish, fill in version/description/tags, and publish
731
+ - **Browse the store visually:** Open the packer GUI, click the Store button to browse, search, filter by type, and import assets