@series-inc/stowkit-cli 0.6.19 → 0.6.21

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/cli.js CHANGED
@@ -110,6 +110,7 @@ Options:
110
110
  --dry-run Show what would be published without uploading
111
111
  --json Output store results as JSON (for AI agents)
112
112
  --type Filter store search by asset type
113
+ --limit Max number of results to return (default: all)
113
114
  --help Show this help message
114
115
  `.trim());
115
116
  }
@@ -167,6 +168,8 @@ async function main() {
167
168
  const bucket = bucketIdx >= 0 ? args[bucketIdx + 1] : undefined;
168
169
  const typeIdx = args.indexOf('--type');
169
170
  const typeFilter = typeIdx >= 0 ? args[typeIdx + 1] : undefined;
171
+ const limitIdx = args.indexOf('--limit');
172
+ const limitFilter = limitIdx >= 0 ? parseInt(args[limitIdx + 1], 10) : undefined;
170
173
  const opts = { force, verbose };
171
174
  try {
172
175
  switch (command) {
@@ -301,17 +304,37 @@ async function main() {
301
304
  break;
302
305
  case 'store': {
303
306
  const subCmd = args[1];
304
- const storeOpts = { json: jsonOutput, bucket };
307
+ const serverUrl = `http://localhost:${port}`;
308
+ const serverUp = await isStowKitRunning(port);
305
309
  if (subCmd === 'search') {
306
310
  const query = args.filter(a => !a.startsWith('-') && a !== 'store' && a !== 'search').join(' ');
307
311
  if (!query) {
308
- console.error('Usage: stowkit store search <query> [--type <type>] [--json]');
312
+ console.error('Usage: stowkit store search <query> [--type <type>] [--limit <n>] [--json]');
309
313
  process.exit(1);
310
314
  }
311
- await storeSearch(query, { ...storeOpts, type: typeFilter });
315
+ if (serverUp) {
316
+ const params = new URLSearchParams({ q: query });
317
+ if (typeFilter)
318
+ params.set('type', typeFilter);
319
+ if (limitFilter)
320
+ params.set('limit', String(limitFilter));
321
+ const res = await fetch(`${serverUrl}/api/asset-store/search?${params}`);
322
+ const results = await res.json();
323
+ console.log(JSON.stringify(results, null, 2));
324
+ }
325
+ else {
326
+ await storeSearch(query, { json: jsonOutput, bucket, type: typeFilter, limit: limitFilter });
327
+ }
312
328
  }
313
329
  else if (subCmd === 'list') {
314
- await storeList(storeOpts);
330
+ if (serverUp) {
331
+ const res = await fetch(`${serverUrl}/api/asset-store/packages`);
332
+ const packages = await res.json();
333
+ console.log(JSON.stringify(packages, null, 2));
334
+ }
335
+ else {
336
+ await storeList({ json: jsonOutput, bucket });
337
+ }
315
338
  }
316
339
  else if (subCmd === 'info') {
317
340
  const pkgName = args.find(a => !a.startsWith('-') && a !== 'store' && a !== 'info');
@@ -319,7 +342,14 @@ async function main() {
319
342
  console.error('Usage: stowkit store info <package-name> [--json]');
320
343
  process.exit(1);
321
344
  }
322
- await storeInfo(pkgName, storeOpts);
345
+ if (serverUp) {
346
+ const res = await fetch(`${serverUrl}/api/asset-store/package/${encodeURIComponent(pkgName)}`);
347
+ const info = await res.json();
348
+ console.log(JSON.stringify(info, null, 2));
349
+ }
350
+ else {
351
+ await storeInfo(pkgName, { json: jsonOutput, bucket });
352
+ }
323
353
  }
324
354
  else {
325
355
  console.error('Usage: stowkit store <search|list|info> [args]');
package/dist/server.js CHANGED
@@ -1785,9 +1785,13 @@ async function handleRequest(req, res, staticApps) {
1785
1785
  const type = url.searchParams.get('type') ?? undefined;
1786
1786
  const pkg = url.searchParams.get('package') ?? undefined;
1787
1787
  const bucketParam = url.searchParams.get('bucket') ?? undefined;
1788
+ const limitParam = url.searchParams.get('limit');
1789
+ const limit = limitParam ? parseInt(limitParam, 10) : undefined;
1788
1790
  const { fetchRegistry, searchAssets } = await import('./store.js');
1789
1791
  const registry = await fetchRegistry(bucketParam);
1790
- const results = searchAssets(registry, query, { type, package: pkg });
1792
+ let results = searchAssets(registry, query, { type, package: pkg });
1793
+ if (limit && limit > 0)
1794
+ results = results.slice(0, limit);
1791
1795
  json(res, results);
1792
1796
  }
1793
1797
  catch (err) {
@@ -1809,6 +1813,34 @@ async function handleRequest(req, res, staticApps) {
1809
1813
  }
1810
1814
  return;
1811
1815
  }
1816
+ // GET /api/asset-store/package/:name — get package details
1817
+ if (pathname.startsWith('/api/asset-store/package/') && req.method === 'GET') {
1818
+ try {
1819
+ const packageName = decodeURIComponent(pathname.slice('/api/asset-store/package/'.length));
1820
+ const bucketParam = url.searchParams.get('bucket') ?? undefined;
1821
+ const { fetchRegistry } = await import('./store.js');
1822
+ const registry = await fetchRegistry(bucketParam);
1823
+ const pkg = registry.packages[packageName];
1824
+ if (!pkg) {
1825
+ json(res, { error: `Package "${packageName}" not found` }, 404);
1826
+ return;
1827
+ }
1828
+ const ver = pkg.versions[pkg.latest];
1829
+ json(res, {
1830
+ name: packageName,
1831
+ description: pkg.description,
1832
+ author: pkg.author,
1833
+ tags: pkg.tags ?? [],
1834
+ latest: pkg.latest,
1835
+ versions: Object.keys(pkg.versions),
1836
+ assets: ver?.assets ?? [],
1837
+ });
1838
+ }
1839
+ catch (err) {
1840
+ json(res, { error: err.message }, 500);
1841
+ }
1842
+ return;
1843
+ }
1812
1844
  // POST /api/asset-store/download — download assets (with transitive deps) into project
1813
1845
  if (pathname === '/api/asset-store/download' && req.method === 'POST') {
1814
1846
  if (!projectConfig) {
package/dist/store.d.ts CHANGED
@@ -39,6 +39,7 @@ export declare function storeSearch(query: string, opts?: {
39
39
  type?: string;
40
40
  json?: boolean;
41
41
  bucket?: string;
42
+ limit?: number;
42
43
  }): Promise<void>;
43
44
  export declare function storeList(opts?: {
44
45
  json?: boolean;
package/dist/store.js CHANGED
@@ -219,7 +219,9 @@ export function resolveAssetDeps(registry, packageName, stringIds, version) {
219
219
  // ─── CLI Commands ────────────────────────────────────────────────────────────
220
220
  export async function storeSearch(query, opts) {
221
221
  const registry = await fetchRegistry(opts?.bucket);
222
- const results = searchAssets(registry, query, { type: opts?.type });
222
+ let results = searchAssets(registry, query, { type: opts?.type });
223
+ if (opts?.limit && opts.limit > 0)
224
+ results = results.slice(0, opts.limit);
223
225
  if (opts?.json) {
224
226
  console.log(JSON.stringify(results, null, 2));
225
227
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@series-inc/stowkit-cli",
3
- "version": "0.6.19",
3
+ "version": "0.6.21",
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.18",
20
+ "@series-inc/stowkit-packer-gui": "^0.1.21",
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
@@ -68,6 +68,7 @@ All commands default to the current directory.
68
68
  - `--verbose` / `-v` — Detailed output
69
69
  - `--json` — Output store results as JSON (for programmatic/AI use)
70
70
  - `--type <type>` — Filter store search by asset type (e.g. `staticMesh`, `texture`)
71
+ - `--limit <n>` — Max number of store search results to return
71
72
  - `--port <number>` — Server port (default 3210)
72
73
  - `--schema <name>` — Material schema template for `create-material` (default: `pbr`)
73
74
 
@@ -532,20 +533,101 @@ To refresh skill files without updating the CLI: `stowkit init --update`
532
533
 
533
534
  ## Asset Store
534
535
 
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.
536
+ 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
537
 
537
- ### Searching the store
538
+ ### Publishing to the store
539
+
540
+ To publish a pack, you need an `assets-package.json` in the project root:
541
+
542
+ ```json
543
+ {
544
+ "name": "my-pack",
545
+ "version": "1.0.0",
546
+ "author": "your-name",
547
+ "description": "A short description of what's in this pack",
548
+ "tags": ["environment", "fantasy", "low-poly"],
549
+ "bucket": "gs://venus-shared-assets-test"
550
+ }
551
+ ```
552
+
553
+ Then run `stowkit publish` from the packer GUI's Publish modal, or programmatically via the CLI server's `/api/publish` endpoint. The publish flow:
554
+
555
+ 1. Scans all assets in the `srcArtDir` and builds a dependency graph
556
+ 2. Uploads all source files to `packages/{name}/{version}/` in GCS
557
+ 3. Uploads per-asset thumbnails (captured in the packer GUI)
558
+ 4. Uploads a pack-level thumbnail if present (see below)
559
+ 5. Updates the central `registry.json` with the new version
560
+
561
+ **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.
562
+
563
+ **Version management:** Bump the `version` field in `assets-package.json` before each publish. Publishing the same version again requires `--force`.
564
+
565
+ ### Searching the store (REST API — preferred for AI)
566
+
567
+ When the StowKit server is running, use the REST endpoints directly for clean JSON — no CLI parsing needed:
568
+
569
+ ```
570
+ GET /api/asset-store/search?q=coral&limit=10 # Search assets, returns JSON array
571
+ GET /api/asset-store/search?q=ocean&type=texture # Filter by type
572
+ GET /api/asset-store/search?q=boss,idle&limit=5 # AND search (comma-separated)
573
+ GET /api/asset-store/packages # List all packages
574
+ GET /api/asset-store/package/<name> # Package details + all assets
575
+ ```
576
+
577
+ All responses are JSON. Search results include `thumbnailUrl` for assets that have thumbnails.
578
+
579
+ ### Searching the store (CLI fallback)
580
+
581
+ If the server isn't running, the CLI falls back to direct registry fetch:
538
582
 
539
583
  ```bash
540
584
  stowkit store search coral # Find assets matching "coral"
585
+ stowkit store search "boss, idle" # AND search — both terms must match
541
586
  stowkit store search ocean --type texture # Find textures tagged/named "ocean"
542
- stowkit store search staticMesh --json # Machine-readable JSON output
587
+ stowkit store search sword --json --limit 10 # Top 10 results as JSON
543
588
  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
589
+ stowkit store info ninja-adventure --json # Full package details as JSON
546
590
  ```
547
591
 
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.
592
+ When the server IS running, CLI store commands automatically proxy through it and always output JSON.
593
+
594
+ ### Search scoring algorithm
595
+
596
+ 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.
597
+
598
+ **Scoring tiers (per term, highest match wins):**
599
+
600
+ | Score | Match type | Example |
601
+ |-------|-----------|---------|
602
+ | 100 | Exact stringId match | "boss" matches asset "boss" |
603
+ | 90 | Exact package name match | "ninja-adventure" matches the pack |
604
+ | 80 | Exact asset tag match | "coral" matches tag "coral" |
605
+ | 60 | Word match in stringId or tag | "boss" matches "actor/boss/idle" |
606
+ | 50 | stringId starts with term | "act" matches "actor/boss/idle" |
607
+ | 45-50 | Word match in package name/tag | "ninja" matches pack "ninja-adventure" |
608
+ | 40 | Word prefix in stringId | "att" matches "attack" |
609
+ | 25-35 | Substring/prefix matches | Various partial matches |
610
+ | < 20 | File path, type, description | Below threshold — boost only |
611
+
612
+ **Key behaviors:**
613
+ - **AND semantics:** Multi-term queries (comma-separated) require ALL terms to match. "boss, idle" only returns assets matching both words.
614
+ - **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.
615
+ - **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.
616
+ - **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.
617
+
618
+ **Word splitting:** StringIds and names are split on underscores, hyphens, dots, slashes, camelCase, and spaces. "NinjaBoss_Attack" becomes ["ninja", "boss", "attack"].
619
+
620
+ ### Searching in the Packer GUI
621
+
622
+ The packer GUI has a **Store** button that opens a full-featured asset store browser:
623
+
624
+ - **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.
625
+ - **In-pack search:** Select a pack first, then search — shows individual assets within that pack, flat (no folder grouping), with AND scoring.
626
+ - **Type filters:** Filter by asset type (texture, staticMesh, skinnedMesh, audio, materialSchema, animationClip) using toggle pills.
627
+ - **Show All:** Checkbox to flatten the folder hierarchy and see all assets in the current pack at once. Combines with type filters.
628
+ - **Folder browsing:** When not searching, navigate the pack's folder structure. Breadcrumbs below the filter bar show your current path.
629
+ - **Thumbnail size:** Slider in the footer to adjust grid card size.
630
+ - **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
631
 
550
632
  ### JSON search result format
551
633
 
@@ -561,6 +643,7 @@ The `--json` flag outputs structured data with `stringId`, `type`, `packageName`
561
643
  "tags": ["coral", "environment"],
562
644
  "dependencies": ["M_Sea_Floor"],
563
645
  "thumbnail": true,
646
+ "thumbnailFormat": "png",
564
647
  "thumbnailUrl": "https://storage.googleapis.com/venus-shared-assets-test/packages/ocean_pack/1.0.0/thumbnails/coral_1.png"
565
648
  }
566
649
  ]
@@ -572,16 +655,21 @@ Assets have dependency chains: meshes depend on materials, materials depend on t
572
655
 
573
656
  ### Importing from the store
574
657
 
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`.
658
+ 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
659
 
577
660
  ### Thumbnail URLs
578
661
 
579
- Published assets may have thumbnails at:
662
+ Per-asset thumbnails (captured in the packer GUI during publish):
663
+ ```
664
+ https://storage.googleapis.com/{bucket}/packages/{packageName}/{version}/thumbnails/{stringId}.{format}
665
+ ```
666
+
667
+ Pack-level thumbnails (from project root `thumbnail.webp`):
580
668
  ```
581
- https://storage.googleapis.com/{bucket}/packages/{packageName}/{version}/thumbnails/{stringId}.png
669
+ https://storage.googleapis.com/{bucket}/packages/{packageName}/{version}/thumbnail.webp
582
670
  ```
583
671
 
584
- Check the `thumbnail` boolean in search results to know if a thumbnail exists.
672
+ 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
673
 
586
674
  ## Common Tasks for AI Agents
587
675
 
@@ -648,8 +736,12 @@ This is useful for verifying build output, checking which assets ended up in whi
648
736
  - **Clean orphaned files:** `stowkit clean`
649
737
  - **Set up with engine:** `stowkit init --with-engine` (installs `@series-inc/rundot-3d-engine` + `three`)
650
738
  - **Update CLI + skill files:** `stowkit update`
651
- - **Search the asset store:** `stowkit store search sword --json` (returns JSON array of matching assets with thumbnailUrls)
652
- - **Find all meshes in the store:** `stowkit store search mesh --type staticMesh --json`
653
- - **List all published packages:** `stowkit store list --json`
654
- - **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
739
+ - **Search the asset store (REST):** `curl http://localhost:3210/api/asset-store/search?q=sword&limit=10` (returns JSON array with thumbnailUrls)
740
+ - **AND search (REST):** `curl "http://localhost:3210/api/asset-store/search?q=boss,idle&limit=10"` (both terms must match)
741
+ - **Find all meshes (REST):** `curl "http://localhost:3210/api/asset-store/search?q=mesh&type=staticMesh&limit=10"`
742
+ - **List all packages (REST):** `curl http://localhost:3210/api/asset-store/packages`
743
+ - **Get package details (REST):** `curl http://localhost:3210/api/asset-store/package/ocean_pack`
744
+ - **Show a thumbnail to the user:** Use the `thumbnailUrl` from search results — it's a public URL
745
+ - **Add a pack thumbnail:** Place `thumbnail.webp` (or `.png`/`.jpg`) in the project root before publishing
746
+ - **Publish a pack:** Open the packer GUI (`stowkit packer`), click Publish, fill in version/description/tags, and publish
747
+ - **Browse the store visually:** Open the packer GUI, click the Store button to browse, search, filter by type, and import assets