@series-inc/stowkit-cli 0.1.15 → 0.1.16
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/asset-commands.d.ts +4 -0
- package/dist/asset-commands.js +122 -0
- package/dist/cli.js +56 -0
- package/dist/create-material.d.ts +3 -0
- package/dist/create-material.js +47 -0
- package/dist/orchestrator.js +25 -57
- package/package.json +2 -2
- package/skill.md +25 -2
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function renameAsset(projectDir: string, assetPath: string, newName: string): Promise<void>;
|
|
2
|
+
export declare function moveAsset(projectDir: string, assetPath: string, targetFolder: string): Promise<void>;
|
|
3
|
+
export declare function deleteAsset(projectDir: string, assetPath: string): Promise<void>;
|
|
4
|
+
export declare function setStringId(projectDir: string, assetPath: string, newStringId: string): Promise<void>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { readProjectConfig, renameFile, deleteFile, readFile, } from './node-fs.js';
|
|
4
|
+
import { readStowmeta, writeStowmeta, } from './app/stowmeta-io.js';
|
|
5
|
+
// ─── Rename ──────────────────────────────────────────────────────────────────
|
|
6
|
+
export async function renameAsset(projectDir, assetPath, newName) {
|
|
7
|
+
const config = await readProjectConfig(projectDir);
|
|
8
|
+
// Build new ID: same folder, new filename (preserve original extension)
|
|
9
|
+
const folder = assetPath.includes('/') ? assetPath.slice(0, assetPath.lastIndexOf('/') + 1) : '';
|
|
10
|
+
const oldBase = assetPath.split('/').pop() ?? assetPath;
|
|
11
|
+
const extMatch = oldBase.match(/\.[^.]+$/);
|
|
12
|
+
const ext = extMatch ? extMatch[0] : '';
|
|
13
|
+
const fullNewName = ext && !newName.endsWith(ext) ? newName + ext : newName;
|
|
14
|
+
const newId = folder + fullNewName;
|
|
15
|
+
if (newId === assetPath) {
|
|
16
|
+
console.log('Nothing to rename — same name.');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// Verify source exists
|
|
20
|
+
const sourceData = await readFile(config.srcArtDir, assetPath);
|
|
21
|
+
if (!sourceData) {
|
|
22
|
+
console.error(`Asset not found: ${assetPath}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
// Rename source, stowmeta, stowcache
|
|
26
|
+
await renameFile(config.srcArtDir, assetPath, newId);
|
|
27
|
+
await renameFile(config.srcArtDir, `${assetPath}.stowmeta`, `${newId}.stowmeta`);
|
|
28
|
+
await renameFile(config.srcArtDir, `${assetPath}.stowcache`, `${newId}.stowcache`);
|
|
29
|
+
// For GLB containers, rename the .children cache directory
|
|
30
|
+
const meta = await readStowmeta(config.srcArtDir, newId);
|
|
31
|
+
if (meta && meta.type === 'glbContainer') {
|
|
32
|
+
await renameFile(config.srcArtDir, `${assetPath}.children`, `${newId}.children`);
|
|
33
|
+
}
|
|
34
|
+
console.log(`Renamed: ${assetPath} → ${newId}`);
|
|
35
|
+
}
|
|
36
|
+
// ─── Move ────────────────────────────────────────────────────────────────────
|
|
37
|
+
export async function moveAsset(projectDir, assetPath, targetFolder) {
|
|
38
|
+
const config = await readProjectConfig(projectDir);
|
|
39
|
+
const fileName = assetPath.split('/').pop() ?? assetPath;
|
|
40
|
+
const newId = targetFolder ? `${targetFolder}/${fileName}` : fileName;
|
|
41
|
+
if (newId === assetPath) {
|
|
42
|
+
console.log('Nothing to move — already in target folder.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Verify source exists
|
|
46
|
+
const sourceData = await readFile(config.srcArtDir, assetPath);
|
|
47
|
+
if (!sourceData) {
|
|
48
|
+
console.error(`Asset not found: ${assetPath}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
// Move source, stowmeta, stowcache
|
|
52
|
+
await renameFile(config.srcArtDir, assetPath, newId);
|
|
53
|
+
await renameFile(config.srcArtDir, `${assetPath}.stowmeta`, `${newId}.stowmeta`);
|
|
54
|
+
await renameFile(config.srcArtDir, `${assetPath}.stowcache`, `${newId}.stowcache`);
|
|
55
|
+
// For GLB containers, move the .children cache directory and update child references
|
|
56
|
+
const meta = await readStowmeta(config.srcArtDir, newId);
|
|
57
|
+
if (meta && meta.type === 'glbContainer') {
|
|
58
|
+
await renameFile(config.srcArtDir, `${assetPath}.children`, `${newId}.children`);
|
|
59
|
+
// Update texture references in material configs
|
|
60
|
+
const glbMeta = meta;
|
|
61
|
+
const oldPrefix = assetPath + '/';
|
|
62
|
+
const newPrefix = newId + '/';
|
|
63
|
+
let updated = false;
|
|
64
|
+
for (const child of glbMeta.children ?? []) {
|
|
65
|
+
if (child.materialConfig) {
|
|
66
|
+
for (const prop of child.materialConfig.properties) {
|
|
67
|
+
if (prop.textureAsset?.startsWith(oldPrefix)) {
|
|
68
|
+
prop.textureAsset = newPrefix + prop.textureAsset.slice(oldPrefix.length);
|
|
69
|
+
updated = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (child.materialOverrides) {
|
|
74
|
+
for (const [k, v] of Object.entries(child.materialOverrides)) {
|
|
75
|
+
if (v?.startsWith(oldPrefix)) {
|
|
76
|
+
child.materialOverrides[k] = newPrefix + v.slice(oldPrefix.length);
|
|
77
|
+
updated = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (updated) {
|
|
83
|
+
await writeStowmeta(config.srcArtDir, newId, glbMeta);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
console.log(`Moved: ${assetPath} → ${newId}`);
|
|
87
|
+
}
|
|
88
|
+
// ─── Delete ──────────────────────────────────────────────────────────────────
|
|
89
|
+
export async function deleteAsset(projectDir, assetPath) {
|
|
90
|
+
const config = await readProjectConfig(projectDir);
|
|
91
|
+
// Verify source exists
|
|
92
|
+
const sourceData = await readFile(config.srcArtDir, assetPath);
|
|
93
|
+
if (!sourceData) {
|
|
94
|
+
console.error(`Asset not found: ${assetPath}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
// For GLB containers, also delete the .children cache directory
|
|
98
|
+
const meta = await readStowmeta(config.srcArtDir, assetPath);
|
|
99
|
+
if (meta && meta.type === 'glbContainer') {
|
|
100
|
+
const childrenDir = path.join(config.srcArtDir, `${assetPath}.children`);
|
|
101
|
+
await fs.rm(childrenDir, { recursive: true, force: true }).catch(() => { });
|
|
102
|
+
}
|
|
103
|
+
// Delete source, stowmeta, stowcache
|
|
104
|
+
await deleteFile(config.srcArtDir, assetPath);
|
|
105
|
+
await deleteFile(config.srcArtDir, `${assetPath}.stowmeta`);
|
|
106
|
+
await deleteFile(config.srcArtDir, `${assetPath}.stowcache`);
|
|
107
|
+
console.log(`Deleted: ${assetPath}`);
|
|
108
|
+
}
|
|
109
|
+
// ─── Set stringId ────────────────────────────────────────────────────────────
|
|
110
|
+
export async function setStringId(projectDir, assetPath, newStringId) {
|
|
111
|
+
const config = await readProjectConfig(projectDir);
|
|
112
|
+
const meta = await readStowmeta(config.srcArtDir, assetPath);
|
|
113
|
+
if (!meta) {
|
|
114
|
+
console.error(`No .stowmeta found for: ${assetPath}`);
|
|
115
|
+
console.error('Run `stowkit build` or `stowkit scan` first to generate it.');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const oldStringId = meta.stringId;
|
|
119
|
+
meta.stringId = newStringId;
|
|
120
|
+
await writeStowmeta(config.srcArtDir, assetPath, meta);
|
|
121
|
+
console.log(`Updated stringId: "${oldStringId}" → "${newStringId}" (${assetPath})`);
|
|
122
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,8 @@ import { fullBuild, scanProject, showStatus } from './orchestrator.js';
|
|
|
6
6
|
import { startServer } from './server.js';
|
|
7
7
|
import { initProject } from './init.js';
|
|
8
8
|
import { cleanupProject } from './cleanup.js';
|
|
9
|
+
import { createMaterial } from './create-material.js';
|
|
10
|
+
import { renameAsset, moveAsset, deleteAsset, setStringId } from './asset-commands.js';
|
|
9
11
|
const args = process.argv.slice(2);
|
|
10
12
|
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
11
13
|
function printUsage() {
|
|
@@ -17,6 +19,11 @@ Usage:
|
|
|
17
19
|
stowkit process [dir] Compress assets (respects cache)
|
|
18
20
|
stowkit status [dir] Show project summary, stale asset count
|
|
19
21
|
stowkit clean [dir] Delete orphaned .stowcache and .stowmeta files
|
|
22
|
+
stowkit create-material <path> Create a .stowmat material file
|
|
23
|
+
stowkit rename <path> <name> Rename an asset file
|
|
24
|
+
stowkit move <path> <folder> Move an asset to a different folder
|
|
25
|
+
stowkit delete <path> Delete an asset and its sidecar files
|
|
26
|
+
stowkit set-id <path> <id> Change an asset's stringId
|
|
20
27
|
stowkit packer [dir] Open the packer GUI
|
|
21
28
|
stowkit editor [dir] Open the level editor
|
|
22
29
|
stowkit serve [dir] Start API server only (no GUI)
|
|
@@ -25,6 +32,7 @@ Options:
|
|
|
25
32
|
--force Ignore cache and reprocess everything
|
|
26
33
|
--verbose Detailed output
|
|
27
34
|
--port Server port (default 3210)
|
|
35
|
+
--schema Material schema template: pbr (default), unlit, or custom name
|
|
28
36
|
--help Show this help message
|
|
29
37
|
`.trim());
|
|
30
38
|
}
|
|
@@ -94,6 +102,54 @@ async function main() {
|
|
|
94
102
|
case 'clean':
|
|
95
103
|
await cleanupProject(projectDir, { verbose });
|
|
96
104
|
break;
|
|
105
|
+
case 'create-material': {
|
|
106
|
+
// For create-material, the positional arg is the material path, not project dir
|
|
107
|
+
const matPath = args.find(a => !a.startsWith('-') && a !== command);
|
|
108
|
+
if (!matPath) {
|
|
109
|
+
console.error('Usage: stowkit create-material <path> [--schema pbr|unlit|<name>]');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
const schemaIdx = args.indexOf('--schema');
|
|
113
|
+
const schema = schemaIdx >= 0 ? args[schemaIdx + 1] : undefined;
|
|
114
|
+
await createMaterial('.', matPath, { schema });
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case 'rename': {
|
|
118
|
+
const positional = args.filter(a => !a.startsWith('-') && a !== command);
|
|
119
|
+
if (positional.length < 2) {
|
|
120
|
+
console.error('Usage: stowkit rename <asset-path> <new-name>');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
await renameAsset('.', positional[0], positional[1]);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'move': {
|
|
127
|
+
const positional = args.filter(a => !a.startsWith('-') && a !== command);
|
|
128
|
+
if (positional.length < 2) {
|
|
129
|
+
console.error('Usage: stowkit move <asset-path> <target-folder>');
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
await moveAsset('.', positional[0], positional[1]);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case 'delete': {
|
|
136
|
+
const assetPath = args.find(a => !a.startsWith('-') && a !== command);
|
|
137
|
+
if (!assetPath) {
|
|
138
|
+
console.error('Usage: stowkit delete <asset-path>');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
await deleteAsset('.', assetPath);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'set-id': {
|
|
145
|
+
const positional = args.filter(a => !a.startsWith('-') && a !== command);
|
|
146
|
+
if (positional.length < 2) {
|
|
147
|
+
console.error('Usage: stowkit set-id <asset-path> <new-string-id>');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
await setStringId('.', positional[0], positional[1]);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
97
153
|
case 'packer': {
|
|
98
154
|
const packerDir = resolveAppDir('@series-inc/stowkit-packer-gui', 'stowkit-packer-gui');
|
|
99
155
|
if (!packerDir) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { readProjectConfig } from './node-fs.js';
|
|
4
|
+
const TEMPLATES = {
|
|
5
|
+
pbr: [
|
|
6
|
+
{ fieldName: 'BaseColor', fieldType: 'texture', previewFlag: 'mainTex', value: [1, 1, 1, 1], textureAsset: null },
|
|
7
|
+
{ fieldName: 'Normal', fieldType: 'texture', previewFlag: 'none', value: [0, 0, 1, 0], textureAsset: null },
|
|
8
|
+
{ fieldName: 'Tint', fieldType: 'color', previewFlag: 'tint', value: [1, 1, 1, 1], textureAsset: null },
|
|
9
|
+
{ fieldName: 'AlphaTest', fieldType: 'float', previewFlag: 'alphaTest', value: [0.5, 0, 0, 0], textureAsset: null },
|
|
10
|
+
],
|
|
11
|
+
unlit: [
|
|
12
|
+
{ fieldName: 'BaseColor', fieldType: 'texture', previewFlag: 'mainTex', value: [1, 1, 1, 1], textureAsset: null },
|
|
13
|
+
{ fieldName: 'Tint', fieldType: 'color', previewFlag: 'tint', value: [1, 1, 1, 1], textureAsset: null },
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
export async function createMaterial(projectDir, relativePath, opts) {
|
|
17
|
+
const config = await readProjectConfig(projectDir);
|
|
18
|
+
// Ensure .stowmat extension
|
|
19
|
+
if (!relativePath.endsWith('.stowmat')) {
|
|
20
|
+
relativePath += '.stowmat';
|
|
21
|
+
}
|
|
22
|
+
const fullPath = path.join(config.srcArtDir, relativePath);
|
|
23
|
+
// Don't overwrite existing
|
|
24
|
+
try {
|
|
25
|
+
await fs.access(fullPath);
|
|
26
|
+
console.error(`Material already exists: ${relativePath}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Does not exist — good
|
|
31
|
+
}
|
|
32
|
+
const schemaName = opts?.schema ?? 'pbr';
|
|
33
|
+
const properties = TEMPLATES[schemaName] ?? [];
|
|
34
|
+
const mat = {
|
|
35
|
+
version: 1,
|
|
36
|
+
schemaName,
|
|
37
|
+
properties,
|
|
38
|
+
};
|
|
39
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
40
|
+
await fs.writeFile(fullPath, JSON.stringify(mat, null, 2) + '\n');
|
|
41
|
+
console.log(`Created material: ${relativePath}`);
|
|
42
|
+
console.log(` Schema: ${schemaName}`);
|
|
43
|
+
console.log(` Properties: ${properties.map(p => p.fieldName).join(', ') || '(none)'}`);
|
|
44
|
+
if (!TEMPLATES[schemaName]) {
|
|
45
|
+
console.log(` (Custom schema — no template properties. Edit the file to add properties.)`);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/orchestrator.js
CHANGED
|
@@ -98,58 +98,8 @@ export async function fullBuild(projectDir, opts) {
|
|
|
98
98
|
}
|
|
99
99
|
assets.push(asset);
|
|
100
100
|
assetsById.set(id, asset);
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
const glbMeta = meta;
|
|
104
|
-
if (glbMeta.children && glbMeta.children.length > 0) {
|
|
105
|
-
asset.status = 'ready'; // Container itself is always "ready"
|
|
106
|
-
for (const child of glbMeta.children) {
|
|
107
|
-
const childId = `${id}/${child.name}`;
|
|
108
|
-
const baseName = child.name.replace(/\.[^.]+$/, '');
|
|
109
|
-
// Read settings from inline child entry
|
|
110
|
-
const { type: cType, settings: cSettings } = glbChildToAssetSettings(child);
|
|
111
|
-
const childAsset = {
|
|
112
|
-
id: childId,
|
|
113
|
-
fileName: child.name,
|
|
114
|
-
stringId: child.stringId || baseName,
|
|
115
|
-
type: cType,
|
|
116
|
-
status: 'pending',
|
|
117
|
-
settings: cSettings,
|
|
118
|
-
sourceSize: 0,
|
|
119
|
-
processedSize: 0,
|
|
120
|
-
parentId: id,
|
|
121
|
-
locked: true,
|
|
122
|
-
};
|
|
123
|
-
// Check cache
|
|
124
|
-
if (!force && child.cache) {
|
|
125
|
-
const cached = await readCacheBlobs(config.srcArtDir, childId);
|
|
126
|
-
if (cached) {
|
|
127
|
-
for (const [key, data] of cached) {
|
|
128
|
-
if (key === `${childId}:__metadata__`) {
|
|
129
|
-
try {
|
|
130
|
-
childAsset.metadata = JSON.parse(new TextDecoder().decode(data));
|
|
131
|
-
}
|
|
132
|
-
catch { /* skip */ }
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
BlobStore.setProcessed(key, data);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
childAsset.status = 'ready';
|
|
139
|
-
childAsset.processedSize = BlobStore.getProcessed(childId)?.length ?? 0;
|
|
140
|
-
if (verbose)
|
|
141
|
-
console.log(` [cached] ${childId}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
assets.push(childAsset);
|
|
145
|
-
assetsById.set(childId, childAsset);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// No children manifest yet — need to parse GLB to discover them
|
|
150
|
-
// This will be handled during processing
|
|
151
|
-
}
|
|
152
|
-
}
|
|
101
|
+
// GlbContainers are always extracted in section 2b (even if children exist in manifest)
|
|
102
|
+
// so that preserveHierarchy changes, re-exports, etc. are always reflected.
|
|
153
103
|
}
|
|
154
104
|
// Materials from .stowmat files
|
|
155
105
|
for (const matFile of scan.matFiles) {
|
|
@@ -178,10 +128,9 @@ export async function fullBuild(projectDir, opts) {
|
|
|
178
128
|
assets.push(asset);
|
|
179
129
|
assetsById.set(id, asset);
|
|
180
130
|
}
|
|
181
|
-
// 2b.
|
|
182
|
-
// Store extract results so mesh/animation children can be processed after encoder init
|
|
131
|
+
// 2b. Extract all GLB containers (always re-parse so preserveHierarchy etc. are reflected)
|
|
183
132
|
const glbExtracts = new Map();
|
|
184
|
-
const glbContainers = assets.filter(a => a.type === AssetType.GlbContainer
|
|
133
|
+
const glbContainers = assets.filter(a => a.type === AssetType.GlbContainer);
|
|
185
134
|
for (const container of glbContainers) {
|
|
186
135
|
try {
|
|
187
136
|
const sourceData = await readFile(config.srcArtDir, container.id);
|
|
@@ -237,8 +186,6 @@ export async function fullBuild(projectDir, opts) {
|
|
|
237
186
|
// Create child assets from inline entries
|
|
238
187
|
for (const child of childrenManifest) {
|
|
239
188
|
const childId = `${container.id}/${child.name}`;
|
|
240
|
-
if (assetsById.has(childId))
|
|
241
|
-
continue;
|
|
242
189
|
const baseName = child.name.replace(/\.[^.]+$/, '');
|
|
243
190
|
const { type: cType, settings: cSettings } = glbChildToAssetSettings(child);
|
|
244
191
|
const childAsset = {
|
|
@@ -274,6 +221,27 @@ export async function fullBuild(projectDir, opts) {
|
|
|
274
221
|
childAsset.processedSize = result.processedSize;
|
|
275
222
|
}
|
|
276
223
|
}
|
|
224
|
+
// Check cache for texture/mesh children (skip if --force)
|
|
225
|
+
if (childAsset.status === 'pending' && !force && child.cache) {
|
|
226
|
+
const cached = await readCacheBlobs(config.srcArtDir, childId);
|
|
227
|
+
if (cached) {
|
|
228
|
+
for (const [key, data] of cached) {
|
|
229
|
+
if (key === `${childId}:__metadata__`) {
|
|
230
|
+
try {
|
|
231
|
+
childAsset.metadata = JSON.parse(new TextDecoder().decode(data));
|
|
232
|
+
}
|
|
233
|
+
catch { /* skip */ }
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
BlobStore.setProcessed(key, data);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
childAsset.status = 'ready';
|
|
240
|
+
childAsset.processedSize = BlobStore.getProcessed(childId)?.length ?? 0;
|
|
241
|
+
if (verbose)
|
|
242
|
+
console.log(` [cached] ${childId}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
277
245
|
assets.push(childAsset);
|
|
278
246
|
assetsById.set(childId, childAsset);
|
|
279
247
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@series-inc/stowkit-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
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.
|
|
20
|
+
"@series-inc/stowkit-packer-gui": "^0.1.9",
|
|
21
21
|
"@series-inc/stowkit-editor": "^0.1.2",
|
|
22
22
|
"draco3d": "^1.5.7",
|
|
23
23
|
"fbx-parser": "^2.1.3",
|
package/skill.md
CHANGED
|
@@ -12,7 +12,7 @@ StowKit is a game asset pipeline that compresses and packs assets into `.stow` b
|
|
|
12
12
|
|
|
13
13
|
**Do not write `.stowmeta` files by hand.** Only edit an existing `.stowmeta` after it has been generated by the CLI (e.g. to change quality settings, pack assignment, or stringId). The same applies to GLB children — the `children` array is populated automatically on the first build.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
To create materials, use `npx stowkit create-material <path>` — this scaffolds a `.stowmat` file with the right structure. You can also create `.stowmat` files manually if needed.
|
|
16
16
|
|
|
17
17
|
## Project Structure
|
|
18
18
|
|
|
@@ -42,6 +42,11 @@ npx stowkit scan [dir] # Detect new assets and generate .stowmeta d
|
|
|
42
42
|
npx stowkit process [dir] # Compress assets (respects cache)
|
|
43
43
|
npx stowkit status [dir] # Show project summary, stale asset count
|
|
44
44
|
npx stowkit clean [dir] # Delete orphaned .stowcache and .stowmeta files
|
|
45
|
+
npx stowkit create-material <path> # Create a .stowmat material file (--schema pbr|unlit|<name>)
|
|
46
|
+
npx stowkit rename <path> <name> # Rename an asset file (preserves extension, updates sidecars)
|
|
47
|
+
npx stowkit move <path> <folder> # Move an asset to a different folder (updates GLB child refs)
|
|
48
|
+
npx stowkit delete <path> # Delete an asset and its .stowmeta/.stowcache files
|
|
49
|
+
npx stowkit set-id <path> <id> # Change an asset's stringId
|
|
45
50
|
npx stowkit packer [dir] # Open the packer GUI in browser
|
|
46
51
|
npx stowkit editor [dir] # Open the level editor in browser
|
|
47
52
|
npx stowkit serve [dir] # Start API server only (no GUI)
|
|
@@ -53,6 +58,7 @@ All commands default to the current directory.
|
|
|
53
58
|
- `--force` — Ignore cache and reprocess everything
|
|
54
59
|
- `--verbose` / `-v` — Detailed output
|
|
55
60
|
- `--port <number>` — Server port (default 3210)
|
|
61
|
+
- `--schema <name>` — Material schema template for `create-material` (default: `pbr`)
|
|
56
62
|
|
|
57
63
|
## Supported Asset Types
|
|
58
64
|
|
|
@@ -359,6 +365,18 @@ Add `*.stowcache` to `.gitignore`.
|
|
|
359
365
|
5. To exclude a child from packing, set `"excluded": true` on that child entry
|
|
360
366
|
6. To preserve the scene graph hierarchy in static meshes, set `"preserveHierarchy": true` on the container
|
|
361
367
|
|
|
368
|
+
### Enabling preserveHierarchy from CLI
|
|
369
|
+
|
|
370
|
+
To enable hierarchy preservation on a GLB, edit its `.stowmeta` and set `"preserveHierarchy": true`, then rebuild:
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
# After the first build has generated the .stowmeta, edit it:
|
|
374
|
+
# Set "preserveHierarchy": true in assets/models/hero.glb.stowmeta
|
|
375
|
+
npx stowkit build
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The CLI always re-extracts GLB containers on every build, so changes to `preserveHierarchy` take effect immediately — no `--force` needed.
|
|
379
|
+
|
|
362
380
|
### When to use preserveHierarchy
|
|
363
381
|
|
|
364
382
|
- **Default (false):** All mesh geometry is baked to world space and flattened into a single mesh. Use this for simple props and environment pieces.
|
|
@@ -370,9 +388,14 @@ Add `*.stowcache` to `.gitignore`.
|
|
|
370
388
|
- **Add a texture:** Place PNG/JPG into `assets/`, run `npx stowkit build`. The CLI auto-generates the `.stowmeta`. Do NOT create it yourself.
|
|
371
389
|
- **Add audio:** Place WAV/MP3/OGG into `assets/`, run `npx stowkit build`. Same rule — never manually create `.stowmeta`.
|
|
372
390
|
- **Add an FBX mesh:** Place FBX into `assets/`, run `npx stowkit build`.
|
|
391
|
+
- **Enable preserve hierarchy on a GLB:** Edit the `.stowmeta` for the GLB container, set `"preserveHierarchy": true`, then `npx stowkit build`
|
|
373
392
|
- **Change compression quality:** Edit the **existing** `.stowmeta` file's quality/resize fields (after it was generated by a build/scan), then `npx stowkit build`
|
|
374
|
-
- **Create a material:**
|
|
393
|
+
- **Create a material:** Run `npx stowkit create-material materials/MyMat` (creates `assets/materials/MyMat.stowmat` with PBR template). Use `--schema unlit` for unlit materials, or `--schema <name>` for a custom schema. Then run `npx stowkit build`.
|
|
375
394
|
- **Assign material to mesh:** Edit the mesh's **existing** `.stowmeta` to add `materialOverrides`
|
|
395
|
+
- **Rename an asset:** `npx stowkit rename textures/old_name.png new_name` (extension preserved automatically, sidecars renamed too)
|
|
396
|
+
- **Move an asset:** `npx stowkit move textures/hero.png characters` (moves to `characters/hero.png`, updates GLB child refs if container)
|
|
397
|
+
- **Delete an asset:** `npx stowkit delete textures/unused.png` (removes source + .stowmeta + .stowcache, cascades for GLB containers)
|
|
398
|
+
- **Change an asset's stringId:** `npx stowkit set-id textures/hero.png hero_diffuse`
|
|
376
399
|
- **Check project health:** Run `npx stowkit status`
|
|
377
400
|
- **Full rebuild:** `npx stowkit build --force`
|
|
378
401
|
- **Clean orphaned files:** `npx stowkit clean`
|