@doufunao123/asset-gateway 0.6.0 → 0.7.3
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/README.md +30 -3
- package/dist/index.js +1390 -457
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command12 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/auth.ts
|
|
7
7
|
import { existsSync as existsSync2, unlinkSync } from "fs";
|
|
@@ -11,68 +11,13 @@ import { Command } from "commander";
|
|
|
11
11
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
12
12
|
import { homedir } from "os";
|
|
13
13
|
import { dirname, join } from "path";
|
|
14
|
-
|
|
15
|
-
// src/errors.ts
|
|
16
|
-
var GatewayError = class extends Error {
|
|
17
|
-
code;
|
|
18
|
-
exitCode;
|
|
19
|
-
suggestion;
|
|
20
|
-
constructor(message, options) {
|
|
21
|
-
super(message);
|
|
22
|
-
this.name = "GatewayError";
|
|
23
|
-
this.code = options.code;
|
|
24
|
-
this.exitCode = options.exitCode ?? 1;
|
|
25
|
-
this.suggestion = options.suggestion;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
function configError(message, suggestion) {
|
|
29
|
-
return new GatewayError(message, {
|
|
30
|
-
code: "CONFIG_ERROR",
|
|
31
|
-
exitCode: 1,
|
|
32
|
-
suggestion: suggestion ?? "Run asset-gateway auth set <token> to configure credentials"
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function notFoundError(message) {
|
|
36
|
-
return new GatewayError(message, {
|
|
37
|
-
code: "NOT_FOUND",
|
|
38
|
-
exitCode: 1
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
function apiError(message, suggestion) {
|
|
42
|
-
return new GatewayError(message, {
|
|
43
|
-
code: "GATEWAY_API_ERROR",
|
|
44
|
-
exitCode: 3,
|
|
45
|
-
suggestion: suggestion ?? "Check if the gateway is running: asset-gateway provider health"
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
function httpClientError(message, suggestion) {
|
|
49
|
-
return new GatewayError(message, {
|
|
50
|
-
code: "HTTP_CLIENT_ERROR",
|
|
51
|
-
exitCode: 3,
|
|
52
|
-
suggestion: suggestion ?? "Check network connectivity to the gateway"
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function internalError(message) {
|
|
56
|
-
return new GatewayError(message, {
|
|
57
|
-
code: "INTERNAL_ERROR",
|
|
58
|
-
exitCode: 2
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
function normalizeError(error2) {
|
|
62
|
-
if (error2 instanceof GatewayError) {
|
|
63
|
-
return error2;
|
|
64
|
-
}
|
|
65
|
-
if (error2 instanceof Error) {
|
|
66
|
-
return internalError(error2.message);
|
|
67
|
-
}
|
|
68
|
-
return internalError(String(error2));
|
|
69
|
-
}
|
|
14
|
+
import { AssetForgeError } from "@doufunao123/assetforge-sdk";
|
|
70
15
|
|
|
71
16
|
// src/meta.ts
|
|
72
17
|
var CLI_NAME = "asset-gateway";
|
|
73
|
-
var CLI_VERSION = "0.
|
|
18
|
+
var CLI_VERSION = "0.19.0";
|
|
74
19
|
var CLI_DESCRIPTION = "Universal asset generation gateway CLI";
|
|
75
|
-
var DEFAULT_GATEWAY_URL = "https://
|
|
20
|
+
var DEFAULT_GATEWAY_URL = "https://asset.origingame.dev";
|
|
76
21
|
|
|
77
22
|
// src/config.ts
|
|
78
23
|
function configDir() {
|
|
@@ -94,8 +39,9 @@ function loadAuthConfig() {
|
|
|
94
39
|
const parsed = JSON.parse(content);
|
|
95
40
|
return { token: parsed.token, gateway_url: parsed.gateway_url };
|
|
96
41
|
} catch (error2) {
|
|
97
|
-
throw
|
|
98
|
-
`Failed to parse auth config at ${path}: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
42
|
+
throw new AssetForgeError(
|
|
43
|
+
`Failed to parse auth config at ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
44
|
+
{ code: "CONFIG_ERROR" }
|
|
99
45
|
);
|
|
100
46
|
}
|
|
101
47
|
}
|
|
@@ -166,107 +112,8 @@ ${formatHuman(val, indent + 2)}`;
|
|
|
166
112
|
}).join("\n");
|
|
167
113
|
}
|
|
168
114
|
|
|
169
|
-
// src/client.ts
|
|
170
|
-
import { readFile } from "fs/promises";
|
|
171
|
-
import { basename } from "path";
|
|
172
|
-
var GatewayClient = class {
|
|
173
|
-
constructor(baseUrl, token) {
|
|
174
|
-
this.baseUrl = baseUrl;
|
|
175
|
-
this.token = token;
|
|
176
|
-
}
|
|
177
|
-
async get(path) {
|
|
178
|
-
return this.request("GET", path);
|
|
179
|
-
}
|
|
180
|
-
async post(path, body) {
|
|
181
|
-
return this.request("POST", path, { body });
|
|
182
|
-
}
|
|
183
|
-
async put(path, body) {
|
|
184
|
-
return this.request("PUT", path, { body });
|
|
185
|
-
}
|
|
186
|
-
async delete(path) {
|
|
187
|
-
return this.request("DELETE", path);
|
|
188
|
-
}
|
|
189
|
-
async uploadFile(filePath) {
|
|
190
|
-
let content;
|
|
191
|
-
try {
|
|
192
|
-
content = await readFile(filePath);
|
|
193
|
-
} catch (error2) {
|
|
194
|
-
throw configError(
|
|
195
|
-
`Failed to read file ${filePath}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
const form = new FormData();
|
|
199
|
-
const arrayBuffer = content.buffer.slice(
|
|
200
|
-
content.byteOffset,
|
|
201
|
-
content.byteOffset + content.byteLength
|
|
202
|
-
);
|
|
203
|
-
form.append("file", new Blob([arrayBuffer]), basename(filePath));
|
|
204
|
-
return this.request("POST", "/api/assets/upload", { form });
|
|
205
|
-
}
|
|
206
|
-
async request(method, path, options = {}) {
|
|
207
|
-
const url = new URL(path, ensureTrailingSlash(this.baseUrl));
|
|
208
|
-
const headers = new Headers(options.headers);
|
|
209
|
-
if (this.token) {
|
|
210
|
-
headers.set("authorization", `Bearer ${this.token}`);
|
|
211
|
-
}
|
|
212
|
-
let body;
|
|
213
|
-
if (options.form) {
|
|
214
|
-
body = options.form;
|
|
215
|
-
} else if (options.body !== void 0) {
|
|
216
|
-
headers.set("content-type", "application/json");
|
|
217
|
-
body = JSON.stringify(options.body);
|
|
218
|
-
}
|
|
219
|
-
let response;
|
|
220
|
-
try {
|
|
221
|
-
response = await fetch(url, { method, headers, body });
|
|
222
|
-
} catch (error2) {
|
|
223
|
-
throw httpClientError(
|
|
224
|
-
error2 instanceof Error ? error2.message : String(error2)
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
const text = await response.text();
|
|
228
|
-
const payload = parseResponse(text);
|
|
229
|
-
if (!response.ok) {
|
|
230
|
-
const preview = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
231
|
-
if (response.status === 404) {
|
|
232
|
-
throw notFoundError(`HTTP 404 - ${truncate(preview, 512)}`);
|
|
233
|
-
}
|
|
234
|
-
throw apiError(`HTTP ${response.status} - ${truncate(preview, 512)}`);
|
|
235
|
-
}
|
|
236
|
-
if (isEnvelope(payload)) {
|
|
237
|
-
if (!payload.ok) {
|
|
238
|
-
const errMsg = typeof payload.error === "object" && payload.error !== null ? JSON.stringify(payload.error) : String(payload.error);
|
|
239
|
-
throw apiError(errMsg);
|
|
240
|
-
}
|
|
241
|
-
return payload.data;
|
|
242
|
-
}
|
|
243
|
-
return payload;
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
function isEnvelope(value) {
|
|
247
|
-
return typeof value === "object" && value !== null && "ok" in value;
|
|
248
|
-
}
|
|
249
|
-
function ensureTrailingSlash(url) {
|
|
250
|
-
return url.endsWith("/") ? url : `${url}/`;
|
|
251
|
-
}
|
|
252
|
-
function parseResponse(text) {
|
|
253
|
-
if (!text) {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
try {
|
|
257
|
-
return JSON.parse(text);
|
|
258
|
-
} catch {
|
|
259
|
-
return text;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
function truncate(value, maxLength) {
|
|
263
|
-
if (value.length <= maxLength) {
|
|
264
|
-
return value;
|
|
265
|
-
}
|
|
266
|
-
return `${value.slice(0, maxLength)}...[truncated]`;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
115
|
// src/commands/common.ts
|
|
116
|
+
import { AssetForge, AssetForgeError as AssetForgeError2, toAssetForgeError } from "@doufunao123/assetforge-sdk";
|
|
270
117
|
function getGlobalOptions(command) {
|
|
271
118
|
return command.optsWithGlobals();
|
|
272
119
|
}
|
|
@@ -275,13 +122,13 @@ function createContext(command, requireAuth = true) {
|
|
|
275
122
|
const url = resolveGatewayUrl({ gatewayUrl: globals.gatewayUrl });
|
|
276
123
|
const token = resolveToken({ token: globals.token });
|
|
277
124
|
if (requireAuth && !token) {
|
|
278
|
-
throw
|
|
125
|
+
throw new AssetForgeError2(
|
|
279
126
|
"Authentication required. Use --token, set ASSET_GATEWAY_TOKEN, or run asset-gateway auth set <token>.",
|
|
280
|
-
|
|
127
|
+
{ code: "CONFIG_ERROR" }
|
|
281
128
|
);
|
|
282
129
|
}
|
|
283
130
|
return {
|
|
284
|
-
client: new
|
|
131
|
+
client: new AssetForge({ apiKey: token ?? "anonymous", baseUrl: url }),
|
|
285
132
|
human: Boolean(globals.human),
|
|
286
133
|
fields: globals.fields
|
|
287
134
|
};
|
|
@@ -291,16 +138,40 @@ function printSuccess(commandName, data, ctx) {
|
|
|
291
138
|
output(success(commandName, filtered), ctx.human);
|
|
292
139
|
}
|
|
293
140
|
function printError(commandName, err, human = false) {
|
|
294
|
-
const normalized =
|
|
141
|
+
const normalized = toAssetForgeError(err);
|
|
295
142
|
output(
|
|
296
|
-
error(commandName, normalized.code, normalized.message, normalized
|
|
143
|
+
error(commandName, normalized.code, normalized.message, getSuggestion(normalized)),
|
|
297
144
|
human
|
|
298
145
|
);
|
|
299
|
-
process.exit(normalized
|
|
146
|
+
process.exit(getExitCode(normalized));
|
|
300
147
|
}
|
|
301
148
|
function isRecord(value) {
|
|
302
149
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
303
150
|
}
|
|
151
|
+
function getSuggestion(error2) {
|
|
152
|
+
if (isRecord(error2.details) && typeof error2.details.suggestion === "string") {
|
|
153
|
+
return error2.details.suggestion;
|
|
154
|
+
}
|
|
155
|
+
if (error2.code === "CONFIG_ERROR") {
|
|
156
|
+
return "asset-gateway auth set <token>";
|
|
157
|
+
}
|
|
158
|
+
if (error2.code === "NETWORK_ERROR" || error2.code === "FETCH_UNAVAILABLE") {
|
|
159
|
+
return "Check network connectivity to the gateway";
|
|
160
|
+
}
|
|
161
|
+
if (error2.code === "API_ERROR") {
|
|
162
|
+
return "Check if the gateway is running: asset-gateway provider health";
|
|
163
|
+
}
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
function getExitCode(error2) {
|
|
167
|
+
if (error2.code === "INTERNAL_ERROR") {
|
|
168
|
+
return 2;
|
|
169
|
+
}
|
|
170
|
+
if (error2.status || error2.code === "API_ERROR" || error2.code === "NETWORK_ERROR") {
|
|
171
|
+
return 3;
|
|
172
|
+
}
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
304
175
|
|
|
305
176
|
// src/commands/auth.ts
|
|
306
177
|
function createAuthCommand() {
|
|
@@ -356,13 +227,17 @@ function mask(token) {
|
|
|
356
227
|
|
|
357
228
|
// src/commands/describe.ts
|
|
358
229
|
import { Command as Command2 } from "commander";
|
|
230
|
+
|
|
231
|
+
// src/describe-schemas.ts
|
|
359
232
|
var SCHEMAS = {
|
|
360
233
|
auth: {
|
|
361
234
|
description: "Credential management",
|
|
362
235
|
subcommands: {
|
|
363
236
|
set: {
|
|
364
237
|
description: "Save token and gateway URL locally",
|
|
365
|
-
params: {
|
|
238
|
+
params: {
|
|
239
|
+
token: { type: "string", required: true, description: "API token or admin token" }
|
|
240
|
+
}
|
|
366
241
|
},
|
|
367
242
|
status: { description: "Show current authentication status" },
|
|
368
243
|
clear: { description: "Remove saved credentials" }
|
|
@@ -372,91 +247,327 @@ var SCHEMAS = {
|
|
|
372
247
|
description: "Generate assets via the gateway",
|
|
373
248
|
subcommands: {
|
|
374
249
|
image: {
|
|
375
|
-
description: "Generate an image
|
|
250
|
+
description: "Generate or edit an image",
|
|
376
251
|
params: {
|
|
377
|
-
"--prompt": { type: "string", required: true, description: "Image
|
|
378
|
-
"--provider": { type: "string", required: false
|
|
379
|
-
"--transparent": { type: "bool",
|
|
380
|
-
"--model": { type: "string"
|
|
381
|
-
"--size": { type: "string",
|
|
382
|
-
"--
|
|
252
|
+
"--prompt": { type: "string", required: true, description: "Image prompt" },
|
|
253
|
+
"--provider": { type: "string", required: false },
|
|
254
|
+
"--transparent": { type: "bool", description: "Transparent background" },
|
|
255
|
+
"--model": { type: "string" },
|
|
256
|
+
"--size": { type: "string", description: 'e.g. "1024x1024"' },
|
|
257
|
+
"--input": { type: "string", description: "Image URL for editing" },
|
|
258
|
+
"--ref": { type: "string[]", description: "Reference image URLs (repeatable)" },
|
|
259
|
+
"--edit-mode": { type: "string", description: "edit | inpaint | restyle | expand" },
|
|
260
|
+
"--session": { type: "string", description: "Multi-turn session id" },
|
|
261
|
+
"--output-dir": { type: "string", default: "." }
|
|
383
262
|
}
|
|
384
263
|
},
|
|
385
264
|
video: {
|
|
386
|
-
description: "Generate a video from
|
|
265
|
+
description: "Generate a video from text or an input image",
|
|
387
266
|
params: {
|
|
388
|
-
"--prompt": { type: "string", required: true
|
|
389
|
-
"--provider": { type: "string"
|
|
390
|
-
"--
|
|
267
|
+
"--prompt": { type: "string", required: true },
|
|
268
|
+
"--provider": { type: "string" },
|
|
269
|
+
"--input": { type: "string", description: "Image URL for I2V" },
|
|
270
|
+
"--output-dir": { type: "string", default: "." }
|
|
391
271
|
}
|
|
392
272
|
},
|
|
393
|
-
|
|
394
|
-
description: "
|
|
273
|
+
batch: {
|
|
274
|
+
description: "Batch-generate assets with shared parameters and optional compose step",
|
|
395
275
|
params: {
|
|
396
|
-
"--prompt": { type: "string", required: true, description: "
|
|
397
|
-
"--type": { type: "string",
|
|
398
|
-
"--
|
|
399
|
-
"--
|
|
276
|
+
"--prompt": { type: "string[]", required: true, description: "One prompt per frame" },
|
|
277
|
+
"--asset-type": { type: "string", default: "image" },
|
|
278
|
+
"--transparent": { type: "bool" },
|
|
279
|
+
"--size": { type: "string" },
|
|
280
|
+
"--ref": { type: "string[]" },
|
|
281
|
+
"--compose": { type: "string", description: "horizontal | vertical | grid" },
|
|
282
|
+
"--columns": { type: "number" },
|
|
283
|
+
"--frame-size": { type: "string", description: "e.g. 64x64" },
|
|
284
|
+
"--output-dir": { type: "string", default: "." }
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
sfx: {
|
|
288
|
+
description: "Generate sound effects (impacts, footsteps, UI sounds, ambience)",
|
|
289
|
+
params: {
|
|
290
|
+
"--prompt": { type: "string", required: true },
|
|
291
|
+
"--duration": { type: "number", required: true, description: "Duration in seconds (1-5s for short SFX, 5-15s for ambience, max 30s)" },
|
|
292
|
+
"--output-dir": { type: "string", default: "." }
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
music: {
|
|
296
|
+
description: "Generate music using the gateway music provider",
|
|
297
|
+
params: {
|
|
298
|
+
"--prompt": { type: "string", required: true },
|
|
299
|
+
"--duration": { type: "number", description: "Seconds" },
|
|
300
|
+
"--force-instrumental": { type: "bool", description: "Request instrumental output when supported" },
|
|
301
|
+
"--output-format": { type: "string", description: "Provider-specific output format override when supported" },
|
|
302
|
+
"--output-dir": { type: "string", default: "." }
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
tts: {
|
|
306
|
+
description: "Text-to-speech via MiMo v2.5 TTS",
|
|
307
|
+
params: {
|
|
308
|
+
"--prompt": { type: "string", required: true },
|
|
309
|
+
"--voice": { type: "string", description: "MiMo prebuilt voice name (default: server config, usually Mia)" },
|
|
310
|
+
"--context": { type: "string", description: "Natural-language style or director instruction" },
|
|
311
|
+
"--output-dir": { type: "string", default: "." }
|
|
400
312
|
}
|
|
401
313
|
},
|
|
402
314
|
model: {
|
|
403
|
-
description: "
|
|
315
|
+
description: "3D model generation (Meshy AI)",
|
|
316
|
+
params: {
|
|
317
|
+
"--image": { type: "string", description: "Reference image path or URL" },
|
|
318
|
+
"--format": { type: "string", default: "glb" },
|
|
319
|
+
"--prompt": { type: "string" },
|
|
320
|
+
"--ai-model": { type: "string", description: "meshy-5 | meshy-6 | latest" },
|
|
321
|
+
"--polycount": { type: "number" },
|
|
322
|
+
"--pbr": { type: "bool" },
|
|
323
|
+
"--hd-texture": { type: "bool" },
|
|
324
|
+
"--pose-mode": { type: "string", description: "a-pose | t-pose" },
|
|
325
|
+
"--auto-size": { type: "bool" },
|
|
326
|
+
"--negative-prompt": { type: "string" },
|
|
327
|
+
"--multiview": { type: "string", description: "Comma-separated multiview image paths or URLs" },
|
|
328
|
+
"--output-dir": { type: "string", default: "." }
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
character: {
|
|
332
|
+
description: "Generate a fully processed 3D character (Meshy AI)",
|
|
404
333
|
params: {
|
|
405
|
-
"--
|
|
406
|
-
"--
|
|
407
|
-
"--
|
|
334
|
+
"--prompt": { type: "string", description: "Character description prompt" },
|
|
335
|
+
"--image": { type: "string", description: "Reference image path or URL" },
|
|
336
|
+
"--images": { type: "string", description: "Comma-separated reference image paths or URLs" },
|
|
337
|
+
"--format": { type: "string", default: "glb" },
|
|
338
|
+
"--polycount": { type: "number" },
|
|
339
|
+
"--pbr": { type: "bool" },
|
|
340
|
+
"--hd-texture": { type: "bool" },
|
|
341
|
+
"--pose-mode": { type: "string", description: "a-pose | t-pose" },
|
|
342
|
+
"--ai-model": { type: "string", description: "meshy-5 | meshy-6 | latest" },
|
|
343
|
+
"--auto-size": { type: "bool" },
|
|
344
|
+
"--output-dir": { type: "string", default: "." }
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
prop: {
|
|
348
|
+
description: "Generate a fully processed 3D prop (Meshy AI)",
|
|
349
|
+
params: {
|
|
350
|
+
"--prompt": { type: "string", description: "Prop description prompt" },
|
|
351
|
+
"--image": { type: "string", description: "Reference image path or URL" },
|
|
352
|
+
"--images": { type: "string", description: "Comma-separated reference image paths or URLs" },
|
|
353
|
+
"--format": { type: "string", default: "glb" },
|
|
354
|
+
"--polycount": { type: "number" },
|
|
355
|
+
"--pbr": { type: "bool" },
|
|
356
|
+
"--hd-texture": { type: "bool" },
|
|
357
|
+
"--ai-model": { type: "string", description: "meshy-5 | meshy-6 | latest" },
|
|
358
|
+
"--auto-size": { type: "bool" },
|
|
359
|
+
"--texture-prompt": { type: "string" },
|
|
360
|
+
"--output-dir": { type: "string", default: "." }
|
|
408
361
|
}
|
|
409
362
|
},
|
|
410
363
|
text: {
|
|
411
|
-
description: "
|
|
364
|
+
description: "LLM text via proxy",
|
|
365
|
+
params: {
|
|
366
|
+
"--prompt": { type: "string", required: true },
|
|
367
|
+
"--model": { type: "string" },
|
|
368
|
+
"--max-tokens": { type: "number" },
|
|
369
|
+
"--output-dir": { type: "string", default: "." }
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
sprite: {
|
|
373
|
+
description: "Generate character animation spritesheet (AutoSprite)",
|
|
374
|
+
params: {
|
|
375
|
+
"--prompt": { type: "string", required: true, description: "Character description" },
|
|
376
|
+
"--input": { type: "string", description: "Reference image path or URL" },
|
|
377
|
+
"--animation-type": { type: "string", default: "walk", description: "walk, run, idle, jump, attack, death, cast, dance, wave, interact, or custom text" },
|
|
378
|
+
"--style": { type: "string", description: "Art style: 16-bit, hd-pixel, isometric, retro-8bit, anime, chibi, painterly, vector" },
|
|
379
|
+
"--frame-count": { type: "number", default: 8, description: "Number of animation frames" },
|
|
380
|
+
"--frame-size": { type: "number", default: 256, description: "Frame size in pixels (square)" },
|
|
381
|
+
"--is-humanoid": { type: "boolean", default: true },
|
|
382
|
+
"--output-dir": { type: "string", default: "." }
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
world: {
|
|
386
|
+
description: "Generate a 3D world or environment",
|
|
412
387
|
params: {
|
|
413
|
-
"--prompt": { type: "string", required: true
|
|
414
|
-
"--
|
|
415
|
-
"--
|
|
416
|
-
"--
|
|
388
|
+
"--prompt": { type: "string", required: true },
|
|
389
|
+
"--input": { type: "string", description: "Input image path or URL" },
|
|
390
|
+
"--model": { type: "string", default: "marble-1.1" },
|
|
391
|
+
"--display-name": { type: "string" },
|
|
392
|
+
"--output-dir": { type: "string", default: "." }
|
|
417
393
|
}
|
|
418
394
|
}
|
|
419
395
|
}
|
|
420
396
|
},
|
|
421
|
-
|
|
422
|
-
description: "
|
|
397
|
+
process: {
|
|
398
|
+
description: "Image/video post-process on gateway (ImageMagick/ffmpeg/rembg)",
|
|
399
|
+
subcommands: {
|
|
400
|
+
crop: {
|
|
401
|
+
description: "Smart crop (trim / power_of2)",
|
|
402
|
+
params: {
|
|
403
|
+
"--input": { type: "string", required: true, description: "File path or URL" },
|
|
404
|
+
"--mode": { type: "string", default: "tightest" },
|
|
405
|
+
"--output-dir": { type: "string", default: "." }
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
resize: {
|
|
409
|
+
description: "Resize to exact width/height",
|
|
410
|
+
params: {
|
|
411
|
+
"--input": { type: "string", required: true },
|
|
412
|
+
"--width": { type: "number", required: true },
|
|
413
|
+
"--height": { type: "number", required: true },
|
|
414
|
+
"--output-dir": { type: "string", default: "." }
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
compose: {
|
|
418
|
+
description: "Sprite sheet from multiple images",
|
|
419
|
+
params: {
|
|
420
|
+
"--input": { type: "string[]", required: true },
|
|
421
|
+
"--direction": { type: "string", default: "horizontal" },
|
|
422
|
+
"--columns": { type: "number" },
|
|
423
|
+
"--padding": { type: "string" },
|
|
424
|
+
"--frame-width": { type: "number" },
|
|
425
|
+
"--frame-height": { type: "number" },
|
|
426
|
+
"--output-dir": { type: "string", default: "." }
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
"remove-bg": {
|
|
430
|
+
description: "Remove background (rembg / fallback)",
|
|
431
|
+
params: {
|
|
432
|
+
"--input": { type: "string[]", required: true },
|
|
433
|
+
"--bg-color": { type: "string" },
|
|
434
|
+
"--output-dir": { type: "string", default: "." }
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
process3d: {
|
|
440
|
+
description: "3D model post-processing (Meshy AI)",
|
|
423
441
|
subcommands: {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
442
|
+
remesh: {
|
|
443
|
+
description: "Remesh a model and optionally change format or polygon count",
|
|
444
|
+
params: {
|
|
445
|
+
"--task-id": { type: "string", required: false, description: "Meshy task ID" },
|
|
446
|
+
"--model-url": { type: "string", required: false, description: "Model URL when remeshing without a task" },
|
|
447
|
+
"--format": { type: "string", required: true },
|
|
448
|
+
"--polycount": { type: "number" },
|
|
449
|
+
"--topology": { type: "string", description: "triangle | quad" },
|
|
450
|
+
"--auto-size": { type: "bool" },
|
|
451
|
+
"--output-dir": { type: "string", default: "." }
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
retexture: {
|
|
455
|
+
description: "Regenerate textures for an existing Meshy model",
|
|
456
|
+
params: {
|
|
457
|
+
"--task-id": { type: "string", required: true },
|
|
458
|
+
"--prompt": { type: "string", description: "Text style prompt for the material pass" },
|
|
459
|
+
"--style-image": { type: "string", description: "Style image path or URL" },
|
|
460
|
+
"--pbr": { type: "bool" },
|
|
461
|
+
"--hd-texture": { type: "bool" },
|
|
462
|
+
"--ai-model": { type: "string", description: "meshy-5 | meshy-6 | latest" },
|
|
463
|
+
"--output-dir": { type: "string", default: "." }
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
rig: {
|
|
467
|
+
description: "Rig a Meshy model for character animation",
|
|
468
|
+
params: {
|
|
469
|
+
"--task-id": { type: "string", required: false, description: "Meshy task ID" },
|
|
470
|
+
"--height": { type: "number", description: "Character height in meters" },
|
|
471
|
+
"--model-url": { type: "string", description: "Model URL when rigging without a task" },
|
|
472
|
+
"--output-dir": { type: "string", default: "." }
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
animate: {
|
|
476
|
+
description: "Apply a preset animation to a rigged character. Common presets: 0=Idle, 1=Walk, 4=Attack, 8=Dead, 14=Run, 16=RunFast, 466=Jump. 500+ presets available.",
|
|
477
|
+
params: {
|
|
478
|
+
"--task-id": { type: "string", required: true, description: "Meshy rigging task ID" },
|
|
479
|
+
"--action-id": {
|
|
480
|
+
type: "number",
|
|
481
|
+
required: true,
|
|
482
|
+
description: "Animation preset ID (see docs for full list)"
|
|
483
|
+
},
|
|
484
|
+
"--fps": { type: "number", description: "Target frame rate: 24, 25, 30, or 60" },
|
|
485
|
+
"--output-dir": { type: "string", default: "." }
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
refine: {
|
|
489
|
+
description: "Refine a Meshy preview model into a higher quality result",
|
|
490
|
+
params: {
|
|
491
|
+
"--task-id": { type: "string", required: true, description: "Meshy preview task ID" },
|
|
492
|
+
"--pbr": { type: "bool" },
|
|
493
|
+
"--hd-texture": { type: "bool" },
|
|
494
|
+
"--texture-prompt": { type: "string" },
|
|
495
|
+
"--ai-model": { type: "string", description: "meshy-5 | meshy-6 | latest" },
|
|
496
|
+
"--output-dir": { type: "string", default: "." }
|
|
497
|
+
}
|
|
428
498
|
}
|
|
429
499
|
}
|
|
430
500
|
},
|
|
501
|
+
upload: {
|
|
502
|
+
description: "Upload and list gateway assets",
|
|
503
|
+
subcommands: {
|
|
504
|
+
file: { description: "Upload file \u2192 URL", params: { "<path>": { required: true } } },
|
|
505
|
+
list: { description: "List uploads" },
|
|
506
|
+
delete: { description: "Delete by filename (admin)", params: { "<filename>": { required: true } } }
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
provider: {
|
|
510
|
+
description: "Provider discovery and health",
|
|
511
|
+
subcommands: {
|
|
512
|
+
list: { description: "List providers" },
|
|
513
|
+
health: { description: "Health check", params: { "[name]": { type: "string", required: false } } }
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
voice: {
|
|
517
|
+
description: "Design, clone, list, and delete MiMo voices",
|
|
518
|
+
subcommands: {
|
|
519
|
+
design: {
|
|
520
|
+
description: "Generate a MiMo voice from a text description",
|
|
521
|
+
params: {
|
|
522
|
+
"--voice-prompt": { type: "string", required: true },
|
|
523
|
+
"--preview-text": { type: "string", required: true },
|
|
524
|
+
"--style": { type: "string" },
|
|
525
|
+
"--name": { type: "string" },
|
|
526
|
+
"--save-as": { type: "string" },
|
|
527
|
+
"--output": { type: "string", description: "Write preview WAV to this path" }
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
clone: {
|
|
531
|
+
description: "Clone a MiMo voice from an mp3/wav sample",
|
|
532
|
+
params: {
|
|
533
|
+
"--audio": { type: "string", required: true, description: "Voice sample mp3/wav file or data URL" },
|
|
534
|
+
"--preview-text": { type: "string", required: true },
|
|
535
|
+
"--audio-mime": { type: "string" },
|
|
536
|
+
"--style": { type: "string" },
|
|
537
|
+
"--name": { type: "string" },
|
|
538
|
+
"--save-as": { type: "string" },
|
|
539
|
+
"--output": { type: "string", description: "Write preview WAV to this path" }
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
list: { description: "List saved voices", params: { "--type": { type: "string", description: "vc | vd" } } },
|
|
543
|
+
delete: { description: "Delete a saved voice", params: { "<voice-id>": { type: "string", required: true }, "--type": { type: "string", description: "vc | vd" } } }
|
|
544
|
+
}
|
|
545
|
+
},
|
|
431
546
|
job: {
|
|
432
|
-
description: "
|
|
547
|
+
description: "Async job history",
|
|
433
548
|
subcommands: {
|
|
434
549
|
list: {
|
|
435
550
|
description: "List jobs",
|
|
436
551
|
params: {
|
|
437
|
-
"--status": { type: "string"
|
|
438
|
-
"--limit": { type: "number"
|
|
552
|
+
"--status": { type: "string" },
|
|
553
|
+
"--limit": { type: "number" }
|
|
439
554
|
}
|
|
440
555
|
},
|
|
441
|
-
status: {
|
|
442
|
-
|
|
443
|
-
params: { id: { type: "string", required: true, description: "Job ID" } }
|
|
444
|
-
},
|
|
445
|
-
cancel: {
|
|
446
|
-
description: "Cancel a job",
|
|
447
|
-
params: { id: { type: "string", required: true, description: "Job ID" } }
|
|
448
|
-
}
|
|
556
|
+
status: { description: "Job detail", params: { "<id>": { type: "string", required: true } } },
|
|
557
|
+
cancel: { description: "Cancel pending/running", params: { "<id>": { type: "string", required: true } } }
|
|
449
558
|
}
|
|
450
559
|
},
|
|
451
560
|
describe: {
|
|
452
|
-
description: "
|
|
561
|
+
description: "Command introspection (this output)",
|
|
453
562
|
params: {
|
|
454
|
-
command: { type: "string", required: false, description: "
|
|
563
|
+
"[command]": { type: "string", required: false, description: "Top-level group: generate, process, \u2026" }
|
|
455
564
|
}
|
|
456
565
|
}
|
|
457
566
|
};
|
|
567
|
+
|
|
568
|
+
// src/commands/describe.ts
|
|
458
569
|
function createDescribeCommand() {
|
|
459
|
-
return new Command2("describe").description("Self-describe available commands (JSON Schema)").argument("[command]", "Specific command to describe").action(function(commandArg) {
|
|
570
|
+
return new Command2("describe").description("Self-describe available commands (JSON Schema)").argument("[command]", "Specific command group to describe (e.g. generate, process)").action(function(commandArg) {
|
|
460
571
|
const globals = this.optsWithGlobals();
|
|
461
572
|
if (!commandArg) {
|
|
462
573
|
output(
|
|
@@ -486,19 +597,109 @@ function createDescribeCommand() {
|
|
|
486
597
|
}
|
|
487
598
|
|
|
488
599
|
// src/commands/generate.ts
|
|
489
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
490
|
-
import { join as join2 } from "path";
|
|
600
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
601
|
+
import { dirname as dirname2, extname, join as join2 } from "path";
|
|
602
|
+
import { AssetForgeError as AssetForgeError3 } from "@doufunao123/assetforge-sdk";
|
|
491
603
|
import { Command as Command3 } from "commander";
|
|
492
604
|
function inferExtension(assetType) {
|
|
493
|
-
const map = {
|
|
605
|
+
const map = {
|
|
606
|
+
image: "png",
|
|
607
|
+
audio: "mp3",
|
|
608
|
+
sfx: "mp3",
|
|
609
|
+
music: "mp3",
|
|
610
|
+
tts: "wav",
|
|
611
|
+
video: "mp4",
|
|
612
|
+
model3d: "glb",
|
|
613
|
+
character: "glb",
|
|
614
|
+
prop: "glb",
|
|
615
|
+
text: "txt",
|
|
616
|
+
sprite: "png",
|
|
617
|
+
world: "spz",
|
|
618
|
+
animation: "glb",
|
|
619
|
+
material: "zip",
|
|
620
|
+
hdri: "hdr",
|
|
621
|
+
vfx: "zip",
|
|
622
|
+
lut: "cube",
|
|
623
|
+
shader: "glsl",
|
|
624
|
+
font_typeface: "json",
|
|
625
|
+
skybox: "png",
|
|
626
|
+
decal: "png",
|
|
627
|
+
heightmap: "png"
|
|
628
|
+
};
|
|
494
629
|
return map[assetType] ?? "bin";
|
|
495
630
|
}
|
|
631
|
+
function inferExtFromResult(result) {
|
|
632
|
+
const meta = result.metadata;
|
|
633
|
+
if (!meta) return null;
|
|
634
|
+
const ct = meta.content_type;
|
|
635
|
+
if (!ct) return null;
|
|
636
|
+
const map = {
|
|
637
|
+
"image/gif": "gif",
|
|
638
|
+
"image/webp": "webp",
|
|
639
|
+
"image/png": "png",
|
|
640
|
+
"image/jpeg": "jpg",
|
|
641
|
+
"video/mp4": "mp4",
|
|
642
|
+
"audio/mpeg": "mp3",
|
|
643
|
+
"audio/wav": "wav",
|
|
644
|
+
"audio/x-wav": "wav",
|
|
645
|
+
"model/gltf-binary": "glb",
|
|
646
|
+
"model/gltf+json": "gltf",
|
|
647
|
+
"model/stl": "stl",
|
|
648
|
+
"model/vnd.usdz+zip": "usdz"
|
|
649
|
+
};
|
|
650
|
+
return map[ct] ?? null;
|
|
651
|
+
}
|
|
652
|
+
function infer3dExtension(format) {
|
|
653
|
+
const map = {
|
|
654
|
+
glb: "glb",
|
|
655
|
+
fbx: "fbx",
|
|
656
|
+
obj: "obj",
|
|
657
|
+
usdz: "usdz",
|
|
658
|
+
stl: "stl",
|
|
659
|
+
"3mf": "3mf",
|
|
660
|
+
gltf: "gltf"
|
|
661
|
+
};
|
|
662
|
+
return format ? map[String(format).toLowerCase()] ?? null : null;
|
|
663
|
+
}
|
|
496
664
|
function stripDataUri(data) {
|
|
497
665
|
const idx = data.indexOf(";base64,");
|
|
498
666
|
return idx >= 0 ? data.slice(idx + 8) : data;
|
|
499
667
|
}
|
|
500
|
-
|
|
501
|
-
const ext =
|
|
668
|
+
function inferMimeType(filePath) {
|
|
669
|
+
const ext = extname(filePath).toLowerCase();
|
|
670
|
+
const map = {
|
|
671
|
+
".jpg": "image/jpeg",
|
|
672
|
+
".jpeg": "image/jpeg",
|
|
673
|
+
".png": "image/png",
|
|
674
|
+
".webp": "image/webp",
|
|
675
|
+
".gif": "image/gif",
|
|
676
|
+
".mp4": "video/mp4",
|
|
677
|
+
".mov": "video/quicktime"
|
|
678
|
+
};
|
|
679
|
+
return map[ext] ?? "application/octet-stream";
|
|
680
|
+
}
|
|
681
|
+
function toInputFile(value) {
|
|
682
|
+
if (!value) {
|
|
683
|
+
return void 0;
|
|
684
|
+
}
|
|
685
|
+
if (!existsSync3(value)) {
|
|
686
|
+
return value;
|
|
687
|
+
}
|
|
688
|
+
const b64 = readFileSync2(value).toString("base64");
|
|
689
|
+
return `data:${inferMimeType(value)};base64,${b64}`;
|
|
690
|
+
}
|
|
691
|
+
function parseCommaSeparatedValues(raw) {
|
|
692
|
+
if (!raw) {
|
|
693
|
+
return [];
|
|
694
|
+
}
|
|
695
|
+
return String(raw).split(",").map((value) => value.trim()).filter(Boolean);
|
|
696
|
+
}
|
|
697
|
+
function collectValue(value, previous = []) {
|
|
698
|
+
previous.push(value);
|
|
699
|
+
return previous;
|
|
700
|
+
}
|
|
701
|
+
async function saveOutput(result, assetType, outputDir, preferredFormat) {
|
|
702
|
+
const ext = inferExtFromResult(result) ?? infer3dExtension(preferredFormat) ?? inferExtension(assetType);
|
|
502
703
|
const timestamp = Date.now();
|
|
503
704
|
const filename = `${assetType}_${timestamp}.${ext}`;
|
|
504
705
|
mkdirSync2(outputDir, { recursive: true });
|
|
@@ -524,22 +725,109 @@ async function saveOutput(result, assetType, outputDir) {
|
|
|
524
725
|
}
|
|
525
726
|
return null;
|
|
526
727
|
}
|
|
728
|
+
async function saveNamedOutput(result, assetType, filePath) {
|
|
729
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
730
|
+
if (result.output_data) {
|
|
731
|
+
const raw = String(result.output_data);
|
|
732
|
+
if (assetType === "text") {
|
|
733
|
+
writeFileSync2(filePath, raw, "utf8");
|
|
734
|
+
} else {
|
|
735
|
+
writeFileSync2(filePath, Buffer.from(stripDataUri(raw), "base64"));
|
|
736
|
+
}
|
|
737
|
+
return filePath;
|
|
738
|
+
}
|
|
739
|
+
if (result.output_url) {
|
|
740
|
+
const response = await fetch(String(result.output_url));
|
|
741
|
+
if (!response.ok) {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
745
|
+
writeFileSync2(filePath, buffer);
|
|
746
|
+
return filePath;
|
|
747
|
+
}
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
function parseFrameSize(raw) {
|
|
751
|
+
const [width, height] = raw.split("x");
|
|
752
|
+
const frameWidth = Number(width);
|
|
753
|
+
const frameHeight = Number(height);
|
|
754
|
+
if (!Number.isFinite(frameWidth) || !Number.isFinite(frameHeight)) {
|
|
755
|
+
throw new Error("frame size must be formatted as WIDTHxHEIGHT");
|
|
756
|
+
}
|
|
757
|
+
return { frame_width: frameWidth, frame_height: frameHeight };
|
|
758
|
+
}
|
|
759
|
+
function assertHas3dInput(prompt, image, images) {
|
|
760
|
+
if (!prompt && !image && parseCommaSeparatedValues(images).length === 0) {
|
|
761
|
+
throw new Error("Provide at least one of --prompt, --image, or --images");
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function toJsonObject(value) {
|
|
765
|
+
return value;
|
|
766
|
+
}
|
|
767
|
+
function isPackOnlyResult(value) {
|
|
768
|
+
return value.ok === false && value.pack_only === true;
|
|
769
|
+
}
|
|
770
|
+
async function runGeneratedAssetCommand(command, sdkMethod, commandName, assetType, options) {
|
|
771
|
+
const ctx = createContext(command);
|
|
772
|
+
const generate = ctx.client[sdkMethod];
|
|
773
|
+
const data = await generate.call(ctx.client, String(options.prompt), {
|
|
774
|
+
provider: options.provider,
|
|
775
|
+
model: options.model,
|
|
776
|
+
size: options.size,
|
|
777
|
+
quality: options.quality,
|
|
778
|
+
background: options.background,
|
|
779
|
+
output_format: options.outputFormat,
|
|
780
|
+
n: options.n ? Number(options.n) : void 0,
|
|
781
|
+
preset: options.preset,
|
|
782
|
+
transparent: options.transparent ? true : void 0,
|
|
783
|
+
input: toInputFile(options.input)
|
|
784
|
+
});
|
|
785
|
+
if (isPackOnlyResult(data)) {
|
|
786
|
+
throw new AssetForgeError3("This asset type requires a library pack. Run: asset-gateway pack list", {
|
|
787
|
+
code: "PACK_ONLY",
|
|
788
|
+
details: data
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
const localPath = await saveOutput(data, assetType, String(options.outputDir ?? "."));
|
|
792
|
+
if (localPath) data.local_path = localPath;
|
|
793
|
+
printSuccess(commandName, data, ctx);
|
|
794
|
+
}
|
|
795
|
+
function createGeneratedAssetCommand(name, sdkMethod, assetType, description) {
|
|
796
|
+
const command = new Command3(name).description(description).requiredOption("--prompt <text>", "Asset description prompt").option("--provider <id>", "Provider to use").option("--model <model>", "Model to use").option("--size <size>", "Output size when supported").option("--input <path>", "Input file path or URL when supported").option("--quality <quality>", "Image quality: low, medium, high").option("--background <background>", "Image background: auto, opaque, transparent").option("--output-format <fmt>", "Image output format: png, webp, jpeg").option("--n <num>", "Number of image variants (1-10)").option("--preset <preset>", "Image preset id").option("--transparent", "Request transparent background when supported").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
797
|
+
const commandName = `generate.${assetType}`;
|
|
798
|
+
try {
|
|
799
|
+
await runGeneratedAssetCommand(this, sdkMethod, commandName, assetType, options);
|
|
800
|
+
} catch (error2) {
|
|
801
|
+
printError(commandName, error2);
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
if (name === "font-typeface") {
|
|
805
|
+
command.alias("font_typeface");
|
|
806
|
+
}
|
|
807
|
+
return command;
|
|
808
|
+
}
|
|
527
809
|
function createGenerateCommand() {
|
|
528
810
|
const command = new Command3("generate").description("Generate assets via the gateway");
|
|
529
811
|
command.addCommand(
|
|
530
|
-
new Command3("image").description("Generate an image from a text prompt").requiredOption("--prompt <text>", "Image description prompt").option("--provider <id>", "Provider to use").option("--transparent", "Request transparent background").option("--model <model>", "Model to use").option("--size <size>", "Image size (e.g. 1024x1024)").option("--input <url>", "Input image URL for editing (Gemini/Grok)").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
812
|
+
new Command3("image").description("Generate an image from a text prompt").requiredOption("--prompt <text>", "Image description prompt").option("--provider <id>", "Provider to use").option("--transparent", "Request transparent background").option("--model <model>", "Model to use").option("--size <size>", "Image size (e.g. 1024x1024)").option("--quality <quality>", "Image quality: low, medium, high").option("--background <background>", "Image background: auto, opaque, transparent").option("--output-format <fmt>", "Image output format: png, webp, jpeg").option("--n <num>", "Number of image variants (1-10)").option("--preset <preset>", "Image preset id").option("--mask <path>", "PNG alpha mask path or URL for inpainting").option("--input <url>", "Input image URL for editing (Gemini/Grok)").option("--ref <urls...>", "Reference image URLs for multi-image editing (repeatable)").option("--edit-mode <mode>", "Edit mode: edit, inpaint, restyle, expand").option("--session <id>", "Session ID for multi-turn editing").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
531
813
|
try {
|
|
532
814
|
const ctx = createContext(this);
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
815
|
+
const data = await ctx.client.image(options.prompt, {
|
|
816
|
+
provider: options.provider,
|
|
817
|
+
model: options.model,
|
|
818
|
+
size: options.size,
|
|
819
|
+
quality: options.quality,
|
|
820
|
+
background: options.background,
|
|
821
|
+
output_format: options.outputFormat,
|
|
822
|
+
n: options.n ? Number(options.n) : void 0,
|
|
823
|
+
preset: options.preset,
|
|
824
|
+
mask: toInputFile(options.mask),
|
|
825
|
+
transparent: options.transparent ? true : void 0,
|
|
826
|
+
input: toInputFile(options.input),
|
|
827
|
+
reference_images: options.ref,
|
|
828
|
+
edit_mode: options.editMode,
|
|
829
|
+
session_id: options.session
|
|
830
|
+
});
|
|
543
831
|
const localPath = await saveOutput(data, "image", options.outputDir);
|
|
544
832
|
if (localPath) data.local_path = localPath;
|
|
545
833
|
printSuccess("generate.image", data, ctx);
|
|
@@ -549,15 +837,16 @@ function createGenerateCommand() {
|
|
|
549
837
|
})
|
|
550
838
|
);
|
|
551
839
|
command.addCommand(
|
|
552
|
-
new Command3("video").description("Generate a video from a text prompt").requiredOption("--prompt <text>", "Video description prompt").option("--provider <id>", "Provider to use").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
840
|
+
new Command3("video").description("Generate a video from a text prompt (or image-to-video with --input)").requiredOption("--prompt <text>", "Video description prompt").option("--provider <id>", "Provider to use").option("--input <url>", "Reference image URL for image-to-video (Grok)").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
553
841
|
try {
|
|
554
842
|
const ctx = createContext(this);
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
843
|
+
const requestOptions = {
|
|
844
|
+
provider: options.provider,
|
|
845
|
+
input: options.input
|
|
558
846
|
};
|
|
559
|
-
|
|
560
|
-
|
|
847
|
+
const data = await ctx.client.video(options.prompt, {
|
|
848
|
+
...requestOptions
|
|
849
|
+
});
|
|
561
850
|
const localPath = await saveOutput(data, "video", options.outputDir);
|
|
562
851
|
if (localPath) data.local_path = localPath;
|
|
563
852
|
printSuccess("generate.video", data, ctx);
|
|
@@ -567,41 +856,100 @@ function createGenerateCommand() {
|
|
|
567
856
|
})
|
|
568
857
|
);
|
|
569
858
|
command.addCommand(
|
|
570
|
-
new Command3("
|
|
859
|
+
new Command3("batch").description("Batch generate multiple assets with shared parameters").requiredOption("--prompt <texts...>", "Multiple prompts (one per frame)").option("--asset-type <type>", "Asset type", "image").option("--transparent", "Request transparent background").option("--size <size>", "Image size").option("--ref <urls...>", "Reference image URLs").option("--compose <direction>", "Auto-compose: horizontal, vertical, grid").option("--columns <n>", "Grid columns for compose").option("--frame-size <size>", "Frame size for compose (e.g. 64x64)").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
571
860
|
try {
|
|
572
861
|
const ctx = createContext(this);
|
|
862
|
+
const shared = {};
|
|
863
|
+
if (options.transparent) shared.transparent = true;
|
|
864
|
+
if (options.size) shared.size = options.size;
|
|
865
|
+
if (options.ref && options.ref.length > 0) shared.reference_images = options.ref;
|
|
573
866
|
const body = {
|
|
574
|
-
asset_type:
|
|
575
|
-
|
|
867
|
+
asset_type: options.assetType,
|
|
868
|
+
prompts: options.prompt,
|
|
869
|
+
shared
|
|
576
870
|
};
|
|
577
|
-
if (options.
|
|
578
|
-
|
|
579
|
-
|
|
871
|
+
if (options.compose) {
|
|
872
|
+
const compose = { direction: options.compose };
|
|
873
|
+
if (options.columns) compose.columns = Number(options.columns);
|
|
874
|
+
if (options.frameSize) Object.assign(compose, parseFrameSize(options.frameSize));
|
|
875
|
+
body.compose = compose;
|
|
876
|
+
}
|
|
877
|
+
const data = await ctx.client.batch(body);
|
|
878
|
+
const assetType = String(options.assetType);
|
|
879
|
+
mkdirSync2(options.outputDir, { recursive: true });
|
|
880
|
+
if (Array.isArray(data.frames)) {
|
|
881
|
+
for (const frame of data.frames) {
|
|
882
|
+
if (typeof frame !== "object" || frame === null) continue;
|
|
883
|
+
const record = frame;
|
|
884
|
+
const index = typeof record.index === "number" ? record.index : 0;
|
|
885
|
+
const localPath = await saveNamedOutput(
|
|
886
|
+
record,
|
|
887
|
+
assetType,
|
|
888
|
+
join2(options.outputDir, `frame_${String(index).padStart(3, "0")}.${inferExtension(assetType)}`)
|
|
889
|
+
);
|
|
890
|
+
if (localPath) record.local_path = localPath;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (typeof data.spritesheet === "object" && data.spritesheet !== null) {
|
|
894
|
+
const record = data.spritesheet;
|
|
895
|
+
const localPath = await saveNamedOutput(
|
|
896
|
+
record,
|
|
897
|
+
"image",
|
|
898
|
+
join2(options.outputDir, "spritesheet.png")
|
|
899
|
+
);
|
|
900
|
+
if (localPath) record.local_path = localPath;
|
|
901
|
+
}
|
|
902
|
+
printSuccess("generate.batch", data, ctx);
|
|
903
|
+
} catch (error2) {
|
|
904
|
+
printError("generate.batch", error2);
|
|
905
|
+
}
|
|
906
|
+
})
|
|
907
|
+
);
|
|
908
|
+
command.addCommand(
|
|
909
|
+
new Command3("sfx").description("Generate sound effects (short audio clips: impacts, footsteps, UI sounds, ambience)").requiredOption("--prompt <text>", "Sound effect description").requiredOption("--duration <seconds>", "Duration in seconds (1-5s for short SFX, 5-15s for ambience, max 30s)").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
910
|
+
try {
|
|
911
|
+
const ctx = createContext(this);
|
|
912
|
+
const data = await ctx.client.audio(options.prompt, {
|
|
913
|
+
duration: Number(options.duration)
|
|
914
|
+
});
|
|
580
915
|
const localPath = await saveOutput(data, "audio", options.outputDir);
|
|
581
916
|
if (localPath) data.local_path = localPath;
|
|
582
|
-
printSuccess("generate.
|
|
917
|
+
printSuccess("generate.sfx", data, ctx);
|
|
918
|
+
} catch (error2) {
|
|
919
|
+
printError("generate.sfx", error2);
|
|
920
|
+
}
|
|
921
|
+
})
|
|
922
|
+
);
|
|
923
|
+
command.addCommand(
|
|
924
|
+
new Command3("music").description("Generate music using the gateway music provider").requiredOption("--prompt <text>", "Music description prompt").option("--duration <seconds>", "Duration in seconds").option("--force-instrumental", "Request instrumental output when supported").option(
|
|
925
|
+
"--output-format <fmt>",
|
|
926
|
+
"Provider-specific output format override when supported"
|
|
927
|
+
).option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
928
|
+
try {
|
|
929
|
+
const ctx = createContext(this);
|
|
930
|
+
const data = await ctx.client.music(options.prompt, {
|
|
931
|
+
duration: options.duration ? Number(options.duration) : void 0,
|
|
932
|
+
force_instrumental: options.forceInstrumental ? true : void 0,
|
|
933
|
+
output_format: options.outputFormat
|
|
934
|
+
});
|
|
935
|
+
const localPath = await saveOutput(data, "music", options.outputDir);
|
|
936
|
+
if (localPath) data.local_path = localPath;
|
|
937
|
+
printSuccess("generate.music", data, ctx);
|
|
583
938
|
} catch (error2) {
|
|
584
|
-
printError("generate.
|
|
939
|
+
printError("generate.music", error2);
|
|
585
940
|
}
|
|
586
941
|
})
|
|
587
942
|
);
|
|
588
943
|
command.addCommand(
|
|
589
|
-
new Command3("tts").description("Text-to-speech
|
|
944
|
+
new Command3("tts").description("Text-to-speech via MiMo v2.5 TTS").requiredOption("--prompt <text>", "Text to synthesize").option("--voice <name>", "MiMo prebuilt voice name (default: server config, usually Mia)").option("--context <text>", "Natural-language style or director instruction").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
590
945
|
try {
|
|
591
946
|
const ctx = createContext(this);
|
|
592
947
|
const params = {};
|
|
593
|
-
if (options.
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
asset_type: "tts",
|
|
599
|
-
prompt: options.prompt,
|
|
600
|
-
params
|
|
601
|
-
};
|
|
602
|
-
if (options.model) body.model = options.model;
|
|
603
|
-
if (options.provider) body.provider = options.provider;
|
|
604
|
-
const data = await ctx.client.post("/api/generate", body);
|
|
948
|
+
if (options.context) params.context = options.context;
|
|
949
|
+
const data = await ctx.client.tts(options.prompt, {
|
|
950
|
+
voice: options.voice,
|
|
951
|
+
params: Object.keys(params).length > 0 ? toJsonObject(params) : void 0
|
|
952
|
+
});
|
|
605
953
|
const localPath = await saveOutput(data, "tts", options.outputDir);
|
|
606
954
|
if (localPath) data.local_path = localPath;
|
|
607
955
|
printSuccess("generate.tts", data, ctx);
|
|
@@ -611,27 +959,27 @@ function createGenerateCommand() {
|
|
|
611
959
|
})
|
|
612
960
|
);
|
|
613
961
|
command.addCommand(
|
|
614
|
-
new Command3("model").description("Generate a 3D model").option("--image <
|
|
962
|
+
new Command3("model").description("Generate a 3D model with Meshy AI").option("--image <input>", "Reference image path or URL").option("--format <fmt>", "Output format: glb, fbx, obj, usdz, stl, 3mf", "glb").option("--prompt <text>", "Model description prompt").option("--ai-model <name>", "Meshy model: meshy-5, meshy-6, latest").option("--polycount <n>", "Target polygon count").option("--pbr", "Enable PBR textures").option("--hd-texture", "Request 4K texture output").option("--pose-mode <mode>", "Character pose mode: a-pose or t-pose").option("--auto-size", "Estimate real-world size automatically").option("--negative-prompt <text>", "Negative prompt").option("--multiview <inputs>", "Comma-separated multiview image paths or URLs").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
615
963
|
try {
|
|
964
|
+
assertHas3dInput(options.prompt, options.image, options.multiview);
|
|
616
965
|
const ctx = createContext(this);
|
|
617
|
-
const body = {
|
|
618
|
-
asset_type: "model3d"
|
|
619
|
-
};
|
|
620
966
|
const params = {};
|
|
621
|
-
if (options.image) body.input_file = options.image;
|
|
622
|
-
if (options.prompt) body.prompt = options.prompt;
|
|
623
|
-
if (options.modelVersion) params.model_version = options.modelVersion;
|
|
624
|
-
if (options.faceLimit) params.face_limit = Number(options.faceLimit);
|
|
625
|
-
if (options.pbr) params.pbr = true;
|
|
626
|
-
if (options.textureQuality) params.texture_quality = options.textureQuality;
|
|
627
|
-
if (options.autoSize) params.auto_size = true;
|
|
628
967
|
if (options.negativePrompt) params.negative_prompt = options.negativePrompt;
|
|
629
968
|
if (options.multiview) {
|
|
630
|
-
params.multiview =
|
|
969
|
+
params.multiview = parseCommaSeparatedValues(options.multiview).map((value) => toInputFile(value)).filter((value) => Boolean(value));
|
|
631
970
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
971
|
+
const data = await ctx.client.model3d(options.prompt ?? "", {
|
|
972
|
+
input: toInputFile(options.image),
|
|
973
|
+
format: options.format,
|
|
974
|
+
ai_model: options.aiModel,
|
|
975
|
+
polycount: options.polycount ? Number(options.polycount) : void 0,
|
|
976
|
+
pbr: options.pbr ? true : void 0,
|
|
977
|
+
hd_texture: options.hdTexture ? true : void 0,
|
|
978
|
+
pose_mode: options.poseMode,
|
|
979
|
+
auto_size: options.autoSize ? true : void 0,
|
|
980
|
+
params: Object.keys(params).length > 0 ? toJsonObject(params) : void 0
|
|
981
|
+
});
|
|
982
|
+
const localPath = await saveOutput(data, "model3d", options.outputDir, options.format);
|
|
635
983
|
if (localPath) data.local_path = localPath;
|
|
636
984
|
printSuccess("generate.model", data, ctx);
|
|
637
985
|
} catch (error2) {
|
|
@@ -640,55 +988,179 @@ function createGenerateCommand() {
|
|
|
640
988
|
})
|
|
641
989
|
);
|
|
642
990
|
command.addCommand(
|
|
643
|
-
new Command3("
|
|
991
|
+
new Command3("character").description("Generate a fully processed 3D character with Meshy AI").option("--prompt <text>", "Character description prompt").option("--image <input>", "Reference image path or URL").option("--images <inputs>", "Comma-separated reference image paths or URLs").option("--format <fmt>", "Output format: glb, fbx, obj, usdz, stl, 3mf", "glb").option("--polycount <n>", "Target polygon count").option("--pbr", "Enable PBR textures").option("--hd-texture", "Request 4K texture output").option("--pose-mode <mode>", "Character pose mode: a-pose or t-pose").option("--ai-model <name>", "Meshy model: meshy-5, meshy-6, latest").option("--auto-size", "Estimate real-world size automatically").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
644
992
|
try {
|
|
993
|
+
assertHas3dInput(options.prompt, options.image, options.images);
|
|
645
994
|
const ctx = createContext(this);
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
995
|
+
const images = parseCommaSeparatedValues(options.images).map((value) => toInputFile(value)).filter((value) => Boolean(value));
|
|
996
|
+
const data = await ctx.client.character(options.prompt ?? "", {
|
|
997
|
+
input: toInputFile(options.image),
|
|
998
|
+
images: images.length > 0 ? images : void 0,
|
|
999
|
+
format: options.format,
|
|
1000
|
+
polycount: options.polycount ? Number(options.polycount) : void 0,
|
|
1001
|
+
pbr: options.pbr ? true : void 0,
|
|
1002
|
+
hd_texture: options.hdTexture ? true : void 0,
|
|
1003
|
+
pose_mode: options.poseMode,
|
|
1004
|
+
ai_model: options.aiModel,
|
|
1005
|
+
auto_size: options.autoSize ? true : void 0
|
|
1006
|
+
});
|
|
1007
|
+
const localPath = await saveOutput(data, "character", options.outputDir, options.format);
|
|
654
1008
|
if (localPath) data.local_path = localPath;
|
|
655
|
-
printSuccess("generate.
|
|
1009
|
+
printSuccess("generate.character", data, ctx);
|
|
656
1010
|
} catch (error2) {
|
|
657
|
-
printError("generate.
|
|
1011
|
+
printError("generate.character", error2);
|
|
658
1012
|
}
|
|
659
1013
|
})
|
|
660
1014
|
);
|
|
661
|
-
return command;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// src/commands/job.ts
|
|
665
|
-
import { Command as Command4 } from "commander";
|
|
666
|
-
function createJobCommand() {
|
|
667
|
-
const command = new Command4("job").description("Job management");
|
|
668
1015
|
command.addCommand(
|
|
669
|
-
new
|
|
1016
|
+
new Command3("prop").description("Generate a fully processed 3D prop with Meshy AI").option("--prompt <text>", "Prop description prompt").option("--image <input>", "Reference image path or URL").option("--images <inputs>", "Comma-separated reference image paths or URLs").option("--format <fmt>", "Output format: glb, fbx, obj, usdz, stl, 3mf", "glb").option("--polycount <n>", "Target polygon count").option("--pbr", "Enable PBR textures").option("--hd-texture", "Request 4K texture output").option("--ai-model <name>", "Meshy model: meshy-5, meshy-6, latest").option("--auto-size", "Estimate real-world size automatically").option("--texture-prompt <text>", "Optional texture prompt for the final material pass").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
670
1017
|
try {
|
|
1018
|
+
assertHas3dInput(options.prompt, options.image, options.images);
|
|
671
1019
|
const ctx = createContext(this);
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
1020
|
+
const images = parseCommaSeparatedValues(options.images).map((value) => toInputFile(value)).filter((value) => Boolean(value));
|
|
1021
|
+
const data = await ctx.client.prop(options.prompt ?? "", {
|
|
1022
|
+
input: toInputFile(options.image),
|
|
1023
|
+
images: images.length > 0 ? images : void 0,
|
|
1024
|
+
format: options.format,
|
|
1025
|
+
polycount: options.polycount ? Number(options.polycount) : void 0,
|
|
1026
|
+
pbr: options.pbr ? true : void 0,
|
|
1027
|
+
hd_texture: options.hdTexture ? true : void 0,
|
|
1028
|
+
ai_model: options.aiModel,
|
|
1029
|
+
auto_size: options.autoSize ? true : void 0,
|
|
1030
|
+
texture_prompt: options.texturePrompt
|
|
1031
|
+
});
|
|
1032
|
+
const localPath = await saveOutput(data, "prop", options.outputDir, options.format);
|
|
1033
|
+
if (localPath) data.local_path = localPath;
|
|
1034
|
+
printSuccess("generate.prop", data, ctx);
|
|
679
1035
|
} catch (error2) {
|
|
680
|
-
printError("
|
|
1036
|
+
printError("generate.prop", error2);
|
|
681
1037
|
}
|
|
682
1038
|
})
|
|
683
1039
|
);
|
|
684
1040
|
command.addCommand(
|
|
685
|
-
new
|
|
1041
|
+
new Command3("text").description("Generate text via LLM").requiredOption("--prompt <text>", "Text prompt").option("--model <model>", "Model to use").option("--max-tokens <n>", "Maximum tokens").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
686
1042
|
try {
|
|
687
1043
|
const ctx = createContext(this);
|
|
688
|
-
const data = await ctx.client.
|
|
689
|
-
|
|
1044
|
+
const data = await ctx.client.text(options.prompt, {
|
|
1045
|
+
model: options.model,
|
|
1046
|
+
max_tokens: options.maxTokens ? Number(options.maxTokens) : void 0
|
|
1047
|
+
});
|
|
1048
|
+
const localPath = await saveOutput(data, "text", options.outputDir);
|
|
1049
|
+
if (localPath) data.local_path = localPath;
|
|
1050
|
+
printSuccess("generate.text", data, ctx);
|
|
690
1051
|
} catch (error2) {
|
|
691
|
-
printError("
|
|
1052
|
+
printError("generate.text", error2);
|
|
1053
|
+
}
|
|
1054
|
+
})
|
|
1055
|
+
);
|
|
1056
|
+
command.addCommand(
|
|
1057
|
+
new Command3("sprite").description("Generate character animation spritesheet (AutoSprite)").requiredOption("--prompt <text>", "Character description").option("--input <path>", "Reference image for character consistency (local path or URL)").option("--animation-type <type>", "Animation type: walk, run, idle, jump, attack, death, cast, dance, wave, interact, or custom text", "walk").option("--style <style>", "Art style: 16-bit, hd-pixel, isometric, retro-8bit, anime, chibi, painterly, vector, or any text").option("--frame-count <n>", "Number of animation frames", "8").option("--frame-size <n>", "Frame size in pixels (square)", "256").option("--is-humanoid", "Character is humanoid (default true)", true).option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
1058
|
+
try {
|
|
1059
|
+
const ctx = createContext(this);
|
|
1060
|
+
const params = {
|
|
1061
|
+
frame_count: Number(options.frameCount),
|
|
1062
|
+
frame_size: Number(options.frameSize),
|
|
1063
|
+
is_humanoid: options.isHumanoid
|
|
1064
|
+
};
|
|
1065
|
+
const data = await ctx.client.sprite(options.prompt, {
|
|
1066
|
+
input: toInputFile(options.input),
|
|
1067
|
+
animation_type: options.animationType,
|
|
1068
|
+
style: options.style,
|
|
1069
|
+
params: toJsonObject(params)
|
|
1070
|
+
});
|
|
1071
|
+
const localPath = await saveOutput(data, "sprite", options.outputDir);
|
|
1072
|
+
if (localPath) data.local_path = localPath;
|
|
1073
|
+
printSuccess("generate.sprite", data, ctx);
|
|
1074
|
+
} catch (error2) {
|
|
1075
|
+
printError("generate.sprite", error2);
|
|
1076
|
+
}
|
|
1077
|
+
})
|
|
1078
|
+
);
|
|
1079
|
+
command.addCommand(
|
|
1080
|
+
new Command3("world").description("Generate a 3D world/environment using WorldLabs Marble").option("--prompt <text>", "Text prompt describing the environment").option("--input <path>", "Input image (local path or URL) for image-to-world").option("--image <path>", "Input image (local path or URL) for image-to-world").option("--panorama <path>", "360 panorama image path or URL").option("--video <path>", "Input video path or URL").option("--multi-image <path>", "Multi-view image path or URL (repeatable)", collectValue, []).option("--quality <quality>", "World quality: low, high").option("--model <model>", "Model: marble-1.0-draft, marble-1.0, marble-1.1, marble-1.1-plus", "marble-1.1").option("--display-name <name>", "Display name for the generated world").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
1081
|
+
try {
|
|
1082
|
+
const prompt = options.prompt ?? "";
|
|
1083
|
+
const multiImage = options.multiImage.map((value) => toInputFile(value)).filter((value) => Boolean(value));
|
|
1084
|
+
if (!prompt && !options.input && !options.image && !options.panorama && !options.video && multiImage.length === 0) {
|
|
1085
|
+
throw new Error("Provide at least one of --prompt, --image, --input, --panorama, --video, or --multi-image");
|
|
1086
|
+
}
|
|
1087
|
+
const ctx = createContext(this);
|
|
1088
|
+
const data = await ctx.client.world(prompt, {
|
|
1089
|
+
input: toInputFile(options.input ?? options.image),
|
|
1090
|
+
model: options.model,
|
|
1091
|
+
panorama_url: toInputFile(options.panorama),
|
|
1092
|
+
video_url: toInputFile(options.video),
|
|
1093
|
+
multi_image_url: multiImage.length > 0 ? multiImage : void 0,
|
|
1094
|
+
quality: options.quality,
|
|
1095
|
+
display_name: options.displayName
|
|
1096
|
+
});
|
|
1097
|
+
const localPath = await saveOutput(data, "world", options.outputDir);
|
|
1098
|
+
if (localPath) data.local_path = localPath;
|
|
1099
|
+
printSuccess("generate.world", data, ctx);
|
|
1100
|
+
} catch (error2) {
|
|
1101
|
+
printError("generate.world", error2);
|
|
1102
|
+
}
|
|
1103
|
+
})
|
|
1104
|
+
);
|
|
1105
|
+
command.addCommand(
|
|
1106
|
+
createGeneratedAssetCommand("animation", "animation", "animation", "Generate a 3D animation clip with Meshy AI")
|
|
1107
|
+
);
|
|
1108
|
+
command.addCommand(
|
|
1109
|
+
createGeneratedAssetCommand("material", "material", "material", "Resolve a PBR material from library packs")
|
|
1110
|
+
);
|
|
1111
|
+
command.addCommand(
|
|
1112
|
+
createGeneratedAssetCommand("hdri", "hdri", "hdri", "Resolve an HDRI environment from library packs")
|
|
1113
|
+
);
|
|
1114
|
+
command.addCommand(
|
|
1115
|
+
createGeneratedAssetCommand("vfx", "vfx", "vfx", "Resolve a Three.js VFX bundle from library packs")
|
|
1116
|
+
);
|
|
1117
|
+
command.addCommand(
|
|
1118
|
+
createGeneratedAssetCommand("lut", "lut", "lut", "Resolve a LUT cube from library packs")
|
|
1119
|
+
);
|
|
1120
|
+
command.addCommand(
|
|
1121
|
+
createGeneratedAssetCommand("shader", "shader", "shader", "Resolve a GLSL shader from library packs")
|
|
1122
|
+
);
|
|
1123
|
+
command.addCommand(
|
|
1124
|
+
createGeneratedAssetCommand("font-typeface", "fontTypeface", "font_typeface", "Resolve a Three.js typeface font from library packs")
|
|
1125
|
+
);
|
|
1126
|
+
command.addCommand(
|
|
1127
|
+
createGeneratedAssetCommand("skybox", "skybox", "skybox", "Generate an equirectangular skybox image")
|
|
1128
|
+
);
|
|
1129
|
+
command.addCommand(
|
|
1130
|
+
createGeneratedAssetCommand("decal", "decal", "decal", "Generate a transparent decal image")
|
|
1131
|
+
);
|
|
1132
|
+
command.addCommand(
|
|
1133
|
+
createGeneratedAssetCommand("heightmap", "heightmap", "heightmap", "Generate a grayscale terrain heightmap")
|
|
1134
|
+
);
|
|
1135
|
+
return command;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/commands/job.ts
|
|
1139
|
+
import { Command as Command4 } from "commander";
|
|
1140
|
+
function createJobCommand() {
|
|
1141
|
+
const command = new Command4("job").description("Job management");
|
|
1142
|
+
command.addCommand(
|
|
1143
|
+
new Command4("list").description("List jobs").option("--status <status>", "Filter by status").option("--limit <n>", "Maximum number of jobs to return").action(async function(options) {
|
|
1144
|
+
try {
|
|
1145
|
+
const ctx = createContext(this);
|
|
1146
|
+
const data = await ctx.client.job.list({
|
|
1147
|
+
status: options.status,
|
|
1148
|
+
limit: options.limit ? Number(options.limit) : void 0
|
|
1149
|
+
});
|
|
1150
|
+
printSuccess("job.list", data, ctx);
|
|
1151
|
+
} catch (error2) {
|
|
1152
|
+
printError("job.list", error2);
|
|
1153
|
+
}
|
|
1154
|
+
})
|
|
1155
|
+
);
|
|
1156
|
+
command.addCommand(
|
|
1157
|
+
new Command4("status").description("Get job status").argument("<id>", "Job ID").action(async function(id) {
|
|
1158
|
+
try {
|
|
1159
|
+
const ctx = createContext(this);
|
|
1160
|
+
const data = await ctx.client.job.status(id);
|
|
1161
|
+
printSuccess("job.status", data, ctx);
|
|
1162
|
+
} catch (error2) {
|
|
1163
|
+
printError("job.status", error2);
|
|
692
1164
|
}
|
|
693
1165
|
})
|
|
694
1166
|
);
|
|
@@ -696,7 +1168,7 @@ function createJobCommand() {
|
|
|
696
1168
|
new Command4("cancel").description("Cancel a job").argument("<id>", "Job ID").action(async function(id) {
|
|
697
1169
|
try {
|
|
698
1170
|
const ctx = createContext(this);
|
|
699
|
-
const data = await ctx.client.
|
|
1171
|
+
const data = await ctx.client.job.cancel(id);
|
|
700
1172
|
printSuccess("job.cancel", data, ctx);
|
|
701
1173
|
} catch (error2) {
|
|
702
1174
|
printError("job.cancel", error2);
|
|
@@ -706,95 +1178,451 @@ function createJobCommand() {
|
|
|
706
1178
|
return command;
|
|
707
1179
|
}
|
|
708
1180
|
|
|
1181
|
+
// src/commands/library.ts
|
|
1182
|
+
import { readFile, writeFile } from "fs/promises";
|
|
1183
|
+
import { AssetForgeError as AssetForgeError4 } from "@doufunao123/assetforge-sdk";
|
|
1184
|
+
import { Command as Command5 } from "commander";
|
|
1185
|
+
function createLibraryCommand() {
|
|
1186
|
+
const command = new Command5("library").description(
|
|
1187
|
+
"Search and manage the asset library"
|
|
1188
|
+
);
|
|
1189
|
+
command.addCommand(
|
|
1190
|
+
new Command5("search").description("Search the asset library").argument("[query]", "Search query").option("-q, --query <query>", "Search query").option("-t, --type <type>", "Filter by asset type (audio, music, image, etc.)").option("--tags <tags>", "Filter by tags (comma-separated)").option("--source <source>", "Filter by source (manual, generated, imported)").option("--mode <mode>", "Search mode (fts, vector, hybrid)").option("--style <style>", "Filter by visual style").option("--composition <composition>", "Filter by composition").option("--lighting <lighting>", "Filter by lighting").option("--color-tone <tone>", "Filter by color tone").option("--mood <mood>", "Filter by mood (comma-separated)").option("--theme <theme>", "Filter by theme (comma-separated)").option("--era <era>", "Filter by era").option("--rig-name <rig>", "Filter by exact rig name").option("--compatible-rig <rig>", "Filter by compatible rig").option("--no-rerank", "Disable LLM reranking").option("-n, --limit <limit>", "Max results", "20").option("--offset <offset>", "Offset for pagination", "0").action(async function(query) {
|
|
1191
|
+
const ctx = createContext(this);
|
|
1192
|
+
const opts = this.opts();
|
|
1193
|
+
try {
|
|
1194
|
+
const data = await ctx.client.library.search({
|
|
1195
|
+
q: opts.query ?? query,
|
|
1196
|
+
type: opts.type,
|
|
1197
|
+
tags: opts.tags,
|
|
1198
|
+
source: opts.source,
|
|
1199
|
+
mode: opts.mode,
|
|
1200
|
+
style: opts.style,
|
|
1201
|
+
composition: opts.composition,
|
|
1202
|
+
lighting: opts.lighting,
|
|
1203
|
+
color_tone: opts.colorTone,
|
|
1204
|
+
mood: opts.mood,
|
|
1205
|
+
theme: opts.theme,
|
|
1206
|
+
era: opts.era,
|
|
1207
|
+
rig_name: opts.rigName,
|
|
1208
|
+
compatible_rig: opts.compatibleRig,
|
|
1209
|
+
rerank: opts.rerank,
|
|
1210
|
+
limit: Number(opts.limit),
|
|
1211
|
+
offset: Number(opts.offset)
|
|
1212
|
+
});
|
|
1213
|
+
printSuccess("library.search", data, ctx);
|
|
1214
|
+
} catch (error2) {
|
|
1215
|
+
printError("library.search", error2, ctx.human);
|
|
1216
|
+
}
|
|
1217
|
+
})
|
|
1218
|
+
);
|
|
1219
|
+
command.addCommand(
|
|
1220
|
+
new Command5("related").description("Find rig-compatible related assets").argument("<id>", "Library item ID").option("-t, --type <type>", "Filter related assets by type").option("-n, --limit <limit>", "Max results", "20").action(async function(id) {
|
|
1221
|
+
const ctx = createContext(this);
|
|
1222
|
+
const opts = this.opts();
|
|
1223
|
+
try {
|
|
1224
|
+
const data = await ctx.client.library.related(id, {
|
|
1225
|
+
type: opts.type,
|
|
1226
|
+
limit: Number(opts.limit)
|
|
1227
|
+
});
|
|
1228
|
+
printSuccess("library.related", data, ctx);
|
|
1229
|
+
} catch (error2) {
|
|
1230
|
+
printError("library.related", error2, ctx.human);
|
|
1231
|
+
}
|
|
1232
|
+
})
|
|
1233
|
+
);
|
|
1234
|
+
command.addCommand(
|
|
1235
|
+
new Command5("bundle").description("Download a zip bundle of library assets").argument("<ids...>", "Library item IDs").requiredOption("-o, --output <path>", "Output zip path").action(async function(ids) {
|
|
1236
|
+
const ctx = createContext(this);
|
|
1237
|
+
const opts = this.opts();
|
|
1238
|
+
try {
|
|
1239
|
+
const blob = await ctx.client.library.bundle({ asset_ids: ids, format: "zip" });
|
|
1240
|
+
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
1241
|
+
await writeFile(opts.output, buffer);
|
|
1242
|
+
printSuccess(
|
|
1243
|
+
"library.bundle",
|
|
1244
|
+
{ output: opts.output, bytes: buffer.length, asset_ids: ids },
|
|
1245
|
+
ctx
|
|
1246
|
+
);
|
|
1247
|
+
} catch (error2) {
|
|
1248
|
+
printError("library.bundle", error2, ctx.human);
|
|
1249
|
+
}
|
|
1250
|
+
})
|
|
1251
|
+
);
|
|
1252
|
+
command.addCommand(
|
|
1253
|
+
new Command5("ingest").description("Queue or run a library ingest job (admin only)").requiredOption("--source <source>", "Ingest source (manual, generated, pack, imported)").requiredOption("-t, --type <type>", "Asset type").option("--file-url <url>", "Public asset URL").option("--file <path>", "Local file to encode as base64").option("--thumbnail-url <url>", "Thumbnail URL").option("--name <name>", "Display name").option("--tag <tag>", "User tag (repeatable)", collect, []).option("--source-job-id <id>", "Source generate job ID").option("--pack-id <id>", "Pack ID").option("--pack-asset-key <key>", "Pack asset key").option("--sync", "Run ingest synchronously and return the library result").action(async function() {
|
|
1254
|
+
const ctx = createContext(this);
|
|
1255
|
+
const opts = this.opts();
|
|
1256
|
+
try {
|
|
1257
|
+
const request = {
|
|
1258
|
+
source: opts.source,
|
|
1259
|
+
asset_type: opts.type,
|
|
1260
|
+
file_url: opts.fileUrl,
|
|
1261
|
+
file_data_b64: opts.file ? await readBase64(opts.file) : void 0,
|
|
1262
|
+
thumbnail_url: opts.thumbnailUrl,
|
|
1263
|
+
name: opts.name,
|
|
1264
|
+
user_tags: opts.tag,
|
|
1265
|
+
source_job_id: opts.sourceJobId,
|
|
1266
|
+
pack_id: opts.packId,
|
|
1267
|
+
pack_asset_key: opts.packAssetKey
|
|
1268
|
+
};
|
|
1269
|
+
const data = opts.sync ? await ctx.client.library.ingestSync(request) : await ctx.client.library.ingest(request);
|
|
1270
|
+
printSuccess(opts.sync ? "library.ingest_sync" : "library.ingest", data, ctx);
|
|
1271
|
+
} catch (error2) {
|
|
1272
|
+
printError("library.ingest", error2, ctx.human);
|
|
1273
|
+
}
|
|
1274
|
+
})
|
|
1275
|
+
);
|
|
1276
|
+
command.addCommand(
|
|
1277
|
+
new Command5("ingest-status").description("Get a library ingest job status").argument("<id>", "Ingest job ID").action(async function(id) {
|
|
1278
|
+
const ctx = createContext(this);
|
|
1279
|
+
try {
|
|
1280
|
+
const data = await ctx.client.library.ingestStatus(id);
|
|
1281
|
+
printSuccess("library.ingest_status", data, ctx);
|
|
1282
|
+
} catch (error2) {
|
|
1283
|
+
printError("library.ingest_status", error2, ctx.human);
|
|
1284
|
+
}
|
|
1285
|
+
})
|
|
1286
|
+
);
|
|
1287
|
+
command.addCommand(
|
|
1288
|
+
new Command5("ingest-wait").description("Poll a library ingest job until completion").argument("<id>", "Ingest job ID").option("--interval-ms <ms>", "Polling interval", "1000").action(async function(id) {
|
|
1289
|
+
const ctx = createContext(this);
|
|
1290
|
+
const opts = this.opts();
|
|
1291
|
+
try {
|
|
1292
|
+
const data = await ctx.client.library.ingestWait(id, {
|
|
1293
|
+
intervalMs: Number(opts.intervalMs)
|
|
1294
|
+
});
|
|
1295
|
+
printSuccess("library.ingest_wait", data, ctx);
|
|
1296
|
+
} catch (error2) {
|
|
1297
|
+
printError("library.ingest_wait", error2, ctx.human);
|
|
1298
|
+
}
|
|
1299
|
+
})
|
|
1300
|
+
);
|
|
1301
|
+
command.addCommand(
|
|
1302
|
+
new Command5("add").description("Add an item to the asset library").requiredOption("--name <name>", "Display name").requiredOption("--url <url>", "File URL (public)").requiredOption("-t, --type <type>", "Asset type (audio, music, image, etc.)").option("-d, --description <desc>", "Description").option("--tags <tags>", "Comma-separated tags").option("--duration <seconds>", "Duration in seconds (for audio/video)").option("--source <source>", "Source (manual, generated, imported)", "manual").action(async function() {
|
|
1303
|
+
const ctx = createContext(this);
|
|
1304
|
+
const opts = this.opts();
|
|
1305
|
+
try {
|
|
1306
|
+
const body = {
|
|
1307
|
+
asset_type: opts.type,
|
|
1308
|
+
name: opts.name,
|
|
1309
|
+
file_url: opts.url,
|
|
1310
|
+
source: opts.source
|
|
1311
|
+
};
|
|
1312
|
+
if (opts.description) body.description = opts.description;
|
|
1313
|
+
if (opts.tags)
|
|
1314
|
+
body.tags = opts.tags.split(",").map((t) => t.trim());
|
|
1315
|
+
if (opts.duration)
|
|
1316
|
+
body.duration_seconds = parseFloat(opts.duration);
|
|
1317
|
+
const data = await ctx.client.library.add(body);
|
|
1318
|
+
printSuccess("library.add", data, ctx);
|
|
1319
|
+
} catch (error2) {
|
|
1320
|
+
printError("library.add", error2, ctx.human);
|
|
1321
|
+
}
|
|
1322
|
+
})
|
|
1323
|
+
);
|
|
1324
|
+
command.addCommand(
|
|
1325
|
+
new Command5("get").description("Get a library item by ID").argument("<id>", "Library item ID").action(async function(id) {
|
|
1326
|
+
const ctx = createContext(this);
|
|
1327
|
+
try {
|
|
1328
|
+
const data = await ctx.client.library.get(id);
|
|
1329
|
+
printSuccess("library.get", data, ctx);
|
|
1330
|
+
} catch (error2) {
|
|
1331
|
+
printError("library.get", error2, ctx.human);
|
|
1332
|
+
}
|
|
1333
|
+
})
|
|
1334
|
+
);
|
|
1335
|
+
command.addCommand(
|
|
1336
|
+
new Command5("delete").description("Delete a library item (admin only)").argument("<id>", "Library item ID").action(async function(id) {
|
|
1337
|
+
const ctx = createContext(this);
|
|
1338
|
+
try {
|
|
1339
|
+
const data = await ctx.client.library.delete(id);
|
|
1340
|
+
printSuccess("library.delete", data, ctx);
|
|
1341
|
+
} catch (error2) {
|
|
1342
|
+
printError("library.delete", error2, ctx.human);
|
|
1343
|
+
}
|
|
1344
|
+
})
|
|
1345
|
+
);
|
|
1346
|
+
command.addCommand(
|
|
1347
|
+
new Command5("catalog-jobs").description("Catalog completed generation jobs into the library (admin)").option("-t, --type <type>", "Filter jobs by asset type").option("--provider <id>", "Filter jobs by provider ID").option("--after <date>", "Only catalog jobs after this date").option("--dry-run", "Just count, don't actually catalog").action(async function() {
|
|
1348
|
+
const ctx = createContext(this);
|
|
1349
|
+
const opts = this.opts();
|
|
1350
|
+
try {
|
|
1351
|
+
const body = {};
|
|
1352
|
+
if (opts.type) body.asset_type = opts.type;
|
|
1353
|
+
if (opts.provider) body.provider_id = opts.provider;
|
|
1354
|
+
if (opts.after) body.after = opts.after;
|
|
1355
|
+
if (opts.dryRun) body.dry_run = true;
|
|
1356
|
+
const data = await ctx.client.library.catalogJobs(body);
|
|
1357
|
+
printSuccess("library.catalog_jobs", data, ctx);
|
|
1358
|
+
} catch (error2) {
|
|
1359
|
+
printError("library.catalog_jobs", error2, ctx.human);
|
|
1360
|
+
}
|
|
1361
|
+
})
|
|
1362
|
+
);
|
|
1363
|
+
command.addCommand(
|
|
1364
|
+
new Command5("backfill").description("Queue library embedding/enrichment backfill jobs (admin)").option("-t, --asset-type <type>", "Filter by asset type").option("--since <date>", "Only backfill assets created after this RFC3339 date").option("-n, --limit <limit>", "Max items to enqueue", "100").option("--dry-run", "Return matching IDs without enqueueing jobs").action(async function() {
|
|
1365
|
+
const ctx = createContext(this);
|
|
1366
|
+
const opts = this.opts();
|
|
1367
|
+
try {
|
|
1368
|
+
const request = {
|
|
1369
|
+
asset_type: opts.assetType,
|
|
1370
|
+
since: opts.since,
|
|
1371
|
+
limit: Number(opts.limit),
|
|
1372
|
+
dry_run: Boolean(opts.dryRun)
|
|
1373
|
+
};
|
|
1374
|
+
const data = await ctx.client.library.backfill(request);
|
|
1375
|
+
printSuccess("library.backfill", data, ctx);
|
|
1376
|
+
} catch (error2) {
|
|
1377
|
+
printError("library.backfill", error2, ctx.human);
|
|
1378
|
+
}
|
|
1379
|
+
})
|
|
1380
|
+
);
|
|
1381
|
+
command.addCommand(
|
|
1382
|
+
new Command5("reenrich").description("Queue library re-enrichment jobs (admin)").option("--id <id>", "Library item ID (repeatable)", collect, []).option("-t, --asset-type <type>", "Filter by asset type").option("--taxonomy-version-lt <version>", "Only items below this taxonomy version").option("--force-visual", "Force visual/textual analysis", true).option("--no-force-visual", "Do not force visual/textual analysis").option("--recompute-embedding", "Recompute embedding vectors").option("-n, --limit <limit>", "Max items to enqueue", "100").action(async function() {
|
|
1383
|
+
const ctx = createContext(this);
|
|
1384
|
+
const opts = this.opts();
|
|
1385
|
+
try {
|
|
1386
|
+
const request = {
|
|
1387
|
+
ids: opts.id,
|
|
1388
|
+
asset_type: opts.assetType,
|
|
1389
|
+
taxonomy_version_lt: opts.taxonomyVersionLt ? Number(opts.taxonomyVersionLt) : void 0,
|
|
1390
|
+
force_visual: opts.forceVisual,
|
|
1391
|
+
recompute_embedding: Boolean(opts.recomputeEmbedding),
|
|
1392
|
+
limit: Number(opts.limit)
|
|
1393
|
+
};
|
|
1394
|
+
const data = await ctx.client.library.reenrich(request);
|
|
1395
|
+
printSuccess("library.reenrich", data, ctx);
|
|
1396
|
+
} catch (error2) {
|
|
1397
|
+
printError("library.reenrich", error2, ctx.human);
|
|
1398
|
+
}
|
|
1399
|
+
})
|
|
1400
|
+
);
|
|
1401
|
+
return command;
|
|
1402
|
+
}
|
|
1403
|
+
function collect(value, previous) {
|
|
1404
|
+
previous.push(value);
|
|
1405
|
+
return previous;
|
|
1406
|
+
}
|
|
1407
|
+
async function readBase64(filePath) {
|
|
1408
|
+
try {
|
|
1409
|
+
return (await readFile(filePath)).toString("base64");
|
|
1410
|
+
} catch (error2) {
|
|
1411
|
+
throw new AssetForgeError4(
|
|
1412
|
+
`Failed to read file ${filePath}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
1413
|
+
{ code: "CONFIG_ERROR" }
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// src/commands/pack.ts
|
|
1419
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1420
|
+
import { createInterface } from "readline/promises";
|
|
1421
|
+
import { stdin as input, stdout as output2 } from "process";
|
|
1422
|
+
import { AssetForgeError as AssetForgeError5 } from "@doufunao123/assetforge-sdk";
|
|
1423
|
+
import { Command as Command6 } from "commander";
|
|
1424
|
+
function createPackCommand() {
|
|
1425
|
+
const command = new Command6("pack").description("Manage library asset packs");
|
|
1426
|
+
command.addCommand(
|
|
1427
|
+
new Command6("list").description("List installed library packs").action(async function() {
|
|
1428
|
+
const ctx = createContext(this);
|
|
1429
|
+
try {
|
|
1430
|
+
const data = await ctx.client.library.packs();
|
|
1431
|
+
printSuccess("pack.list", data, ctx);
|
|
1432
|
+
} catch (error2) {
|
|
1433
|
+
printError("pack.list", error2, ctx.human);
|
|
1434
|
+
}
|
|
1435
|
+
})
|
|
1436
|
+
);
|
|
1437
|
+
command.addCommand(
|
|
1438
|
+
new Command6("install").description("Install a library pack").argument("[source]", "Pack source (builtin, url, upload)").option("--source <source>", "Pack source (builtin, url, upload)").option("--id <id>", "Builtin pack ID").option("--version <version>", "Pack version").option("--url <url>", "HTTP tarball URL").option("--file <tarball>", "Local tarball to upload as base64").action(async function(sourceArg) {
|
|
1439
|
+
const ctx = createContext(this);
|
|
1440
|
+
const opts = this.opts();
|
|
1441
|
+
try {
|
|
1442
|
+
const source = opts.source ?? sourceArg;
|
|
1443
|
+
if (!source) {
|
|
1444
|
+
throw new AssetForgeError5("Pack source is required", { code: "CONFIG_ERROR" });
|
|
1445
|
+
}
|
|
1446
|
+
const request = {
|
|
1447
|
+
source,
|
|
1448
|
+
id: opts.id,
|
|
1449
|
+
version: opts.version,
|
|
1450
|
+
url: opts.url,
|
|
1451
|
+
tarball_b64: opts.file ? await readBase642(opts.file) : void 0
|
|
1452
|
+
};
|
|
1453
|
+
const data = await ctx.client.library.packsInstall(request);
|
|
1454
|
+
printSuccess("pack.install", data, ctx);
|
|
1455
|
+
} catch (error2) {
|
|
1456
|
+
printError("pack.install", error2, ctx.human);
|
|
1457
|
+
}
|
|
1458
|
+
})
|
|
1459
|
+
);
|
|
1460
|
+
command.addCommand(
|
|
1461
|
+
new Command6("delete").description("Delete a library pack (admin only)").argument("<id>", "Pack ID").option("-y, --yes", "Skip confirmation prompt").action(async function(id) {
|
|
1462
|
+
const ctx = createContext(this);
|
|
1463
|
+
const opts = this.opts();
|
|
1464
|
+
try {
|
|
1465
|
+
if (!opts.yes) {
|
|
1466
|
+
await confirmDelete(id);
|
|
1467
|
+
}
|
|
1468
|
+
const data = await ctx.client.library.packsDelete(id);
|
|
1469
|
+
printSuccess("pack.delete", data, ctx);
|
|
1470
|
+
} catch (error2) {
|
|
1471
|
+
printError("pack.delete", error2, ctx.human);
|
|
1472
|
+
}
|
|
1473
|
+
})
|
|
1474
|
+
);
|
|
1475
|
+
command.addCommand(
|
|
1476
|
+
new Command6("refresh").description("Refresh an installed library pack (admin only)").argument("<id>", "Pack ID").action(async function(id) {
|
|
1477
|
+
const ctx = createContext(this);
|
|
1478
|
+
try {
|
|
1479
|
+
const data = await ctx.client.library.packsRefresh(id);
|
|
1480
|
+
printSuccess("pack.refresh", data, ctx);
|
|
1481
|
+
} catch (error2) {
|
|
1482
|
+
printError("pack.refresh", error2, ctx.human);
|
|
1483
|
+
}
|
|
1484
|
+
})
|
|
1485
|
+
);
|
|
1486
|
+
return command;
|
|
1487
|
+
}
|
|
1488
|
+
async function readBase642(filePath) {
|
|
1489
|
+
try {
|
|
1490
|
+
return (await readFile2(filePath)).toString("base64");
|
|
1491
|
+
} catch (error2) {
|
|
1492
|
+
throw new AssetForgeError5(
|
|
1493
|
+
`Failed to read file ${filePath}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
1494
|
+
{ code: "CONFIG_ERROR" }
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
async function confirmDelete(id) {
|
|
1499
|
+
if (!input.isTTY) {
|
|
1500
|
+
throw new AssetForgeError5("Refusing to delete pack without confirmation. Pass --yes to confirm.", {
|
|
1501
|
+
code: "CONFIG_ERROR"
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
const rl = createInterface({ input, output: output2 });
|
|
1505
|
+
try {
|
|
1506
|
+
const answer = await rl.question(`Delete pack ${id}? Type the pack ID to confirm: `);
|
|
1507
|
+
if (answer !== id) {
|
|
1508
|
+
throw new AssetForgeError5("Pack deletion cancelled", { code: "CONFIG_ERROR" });
|
|
1509
|
+
}
|
|
1510
|
+
} finally {
|
|
1511
|
+
rl.close();
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
709
1515
|
// src/commands/process.ts
|
|
710
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
711
|
-
import { existsSync as
|
|
1516
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1517
|
+
import { existsSync as existsSync4 } from "fs";
|
|
712
1518
|
import { join as join3 } from "path";
|
|
713
|
-
import { Command as
|
|
714
|
-
function readInputAsBase64(
|
|
715
|
-
if (
|
|
716
|
-
const bytes =
|
|
1519
|
+
import { Command as Command7 } from "commander";
|
|
1520
|
+
function readInputAsBase64(input2) {
|
|
1521
|
+
if (existsSync4(input2)) {
|
|
1522
|
+
const bytes = readFileSync3(input2);
|
|
717
1523
|
return `data:image/png;base64,${bytes.toString("base64")}`;
|
|
718
1524
|
}
|
|
719
|
-
return
|
|
1525
|
+
return input2;
|
|
720
1526
|
}
|
|
721
1527
|
function saveProcessOutput(data, outputDir) {
|
|
722
|
-
const outputData = data.output_data;
|
|
723
|
-
if (typeof outputData !== "string" || !outputData) return null;
|
|
724
1528
|
mkdirSync3(outputDir, { recursive: true });
|
|
725
1529
|
const timestamp = Date.now();
|
|
1530
|
+
const outputs = data.outputs;
|
|
1531
|
+
if (Array.isArray(outputs) && outputs.length > 0) {
|
|
1532
|
+
const localPaths = [];
|
|
1533
|
+
for (let i = 0; i < outputs.length; i++) {
|
|
1534
|
+
const item = outputs[i];
|
|
1535
|
+
const b64 = item.output_data;
|
|
1536
|
+
if (typeof b64 !== "string" || !b64) continue;
|
|
1537
|
+
const filePath2 = join3(outputDir, `frame_${timestamp}_${String(i).padStart(4, "0")}.png`);
|
|
1538
|
+
writeFileSync3(filePath2, Buffer.from(b64, "base64"));
|
|
1539
|
+
localPaths.push(filePath2);
|
|
1540
|
+
}
|
|
1541
|
+
delete data.outputs;
|
|
1542
|
+
data.local_paths = localPaths;
|
|
1543
|
+
return localPaths[0] ?? null;
|
|
1544
|
+
}
|
|
1545
|
+
const outputData = data.output_data;
|
|
1546
|
+
if (typeof outputData !== "string" || !outputData) return null;
|
|
726
1547
|
const filePath = join3(outputDir, `processed_${timestamp}.png`);
|
|
727
1548
|
writeFileSync3(filePath, Buffer.from(outputData, "base64"));
|
|
728
1549
|
delete data.output_data;
|
|
729
1550
|
return filePath;
|
|
730
1551
|
}
|
|
731
1552
|
function createProcessCommand() {
|
|
732
|
-
const command = new
|
|
1553
|
+
const command = new Command7("process").description("Post-process images (crop, resize, compose, remove-bg)");
|
|
733
1554
|
command.addCommand(
|
|
734
|
-
new
|
|
1555
|
+
new Command7("crop").description("Smart crop an image (trim transparent borders)").requiredOption("--input <path>", "Input image (file path or URL)").option("--mode <mode>", "Crop mode: tightest or power_of2", "tightest").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
735
1556
|
try {
|
|
736
1557
|
const ctx = createContext(this);
|
|
737
|
-
const
|
|
738
|
-
if (options.smartCrop) {
|
|
739
|
-
ops.push({ op: "smart_crop", mode: "power_of2" });
|
|
740
|
-
}
|
|
741
|
-
const data = await ctx.client.post("/api/process", {
|
|
1558
|
+
const data = await ctx.client.process({
|
|
742
1559
|
input: readInputAsBase64(options.input),
|
|
743
|
-
operations:
|
|
1560
|
+
operations: [{ op: "smart_crop", mode: options.mode }]
|
|
744
1561
|
});
|
|
745
1562
|
const localPath = saveProcessOutput(data, options.outputDir);
|
|
746
1563
|
if (localPath) data.local_path = localPath;
|
|
747
|
-
printSuccess("process.
|
|
1564
|
+
printSuccess("process.crop", data, ctx);
|
|
748
1565
|
} catch (error2) {
|
|
749
|
-
printError("process.
|
|
1566
|
+
printError("process.crop", error2);
|
|
750
1567
|
}
|
|
751
1568
|
})
|
|
752
1569
|
);
|
|
753
1570
|
command.addCommand(
|
|
754
|
-
new
|
|
1571
|
+
new Command7("resize").description("Resize an image to exact dimensions").requiredOption("--input <path>", "Input image (file path or URL)").requiredOption("--width <n>", "Target width").requiredOption("--height <n>", "Target height").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
755
1572
|
try {
|
|
756
1573
|
const ctx = createContext(this);
|
|
757
|
-
const data = await ctx.client.
|
|
1574
|
+
const data = await ctx.client.process({
|
|
758
1575
|
input: readInputAsBase64(options.input),
|
|
759
|
-
operations: [{ op: "
|
|
1576
|
+
operations: [{ op: "resize", width: Number(options.width), height: Number(options.height) }]
|
|
760
1577
|
});
|
|
761
1578
|
const localPath = saveProcessOutput(data, options.outputDir);
|
|
762
1579
|
if (localPath) data.local_path = localPath;
|
|
763
|
-
printSuccess("process.
|
|
1580
|
+
printSuccess("process.resize", data, ctx);
|
|
764
1581
|
} catch (error2) {
|
|
765
|
-
printError("process.
|
|
1582
|
+
printError("process.resize", error2);
|
|
766
1583
|
}
|
|
767
1584
|
})
|
|
768
1585
|
);
|
|
769
1586
|
command.addCommand(
|
|
770
|
-
new
|
|
1587
|
+
new Command7("compose").description("Compose multiple images into a sprite sheet").requiredOption("--input <paths...>", "Input images (files or URLs)").option("--direction <dir>", "Layout: horizontal, vertical, grid", "horizontal").option("--columns <n>", "Columns for grid layout").option("--padding <n>", "Padding between frames in px", "0").option("--frame-width <n>", "Normalize each frame to this width").option("--frame-height <n>", "Normalize each frame to this height").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
771
1588
|
try {
|
|
772
1589
|
const ctx = createContext(this);
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
1590
|
+
const inputs = Array.isArray(options.input) ? options.input : [options.input];
|
|
1591
|
+
const data = await ctx.client.process({
|
|
1592
|
+
inputs: inputs.map(readInputAsBase64),
|
|
1593
|
+
operations: [{
|
|
1594
|
+
op: "compose",
|
|
1595
|
+
direction: options.direction,
|
|
1596
|
+
columns: options.columns ? Number(options.columns) : void 0,
|
|
1597
|
+
padding: Number(options.padding),
|
|
1598
|
+
frame_width: options.frameWidth ? Number(options.frameWidth) : void 0,
|
|
1599
|
+
frame_height: options.frameHeight ? Number(options.frameHeight) : void 0
|
|
1600
|
+
}]
|
|
776
1601
|
});
|
|
777
1602
|
const localPath = saveProcessOutput(data, options.outputDir);
|
|
778
1603
|
if (localPath) data.local_path = localPath;
|
|
779
|
-
printSuccess("process.
|
|
1604
|
+
printSuccess("process.compose", data, ctx);
|
|
780
1605
|
} catch (error2) {
|
|
781
|
-
printError("process.
|
|
1606
|
+
printError("process.compose", error2);
|
|
782
1607
|
}
|
|
783
1608
|
})
|
|
784
1609
|
);
|
|
785
1610
|
command.addCommand(
|
|
786
|
-
new
|
|
1611
|
+
new Command7("remove-bg").description("Remove background from image(s)").requiredOption("--input <paths...>", "Input images (files or URLs)").option("--bg-color <color>", "Background color hint for fallback (e.g. white, black)").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
787
1612
|
try {
|
|
788
1613
|
const ctx = createContext(this);
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
1614
|
+
const inputs = Array.isArray(options.input) ? options.input : [options.input];
|
|
1615
|
+
const op = { op: "remove_bg" };
|
|
1616
|
+
if (options.bgColor) op.bg_color = options.bgColor;
|
|
1617
|
+
const data = await ctx.client.process({
|
|
1618
|
+
inputs: inputs.map(readInputAsBase64),
|
|
1619
|
+
operations: [op]
|
|
792
1620
|
});
|
|
793
1621
|
const localPath = saveProcessOutput(data, options.outputDir);
|
|
794
1622
|
if (localPath) data.local_path = localPath;
|
|
795
|
-
printSuccess("process.
|
|
1623
|
+
printSuccess("process.remove_bg", data, ctx);
|
|
796
1624
|
} catch (error2) {
|
|
797
|
-
printError("process.
|
|
1625
|
+
printError("process.remove_bg", error2);
|
|
798
1626
|
}
|
|
799
1627
|
})
|
|
800
1628
|
);
|
|
@@ -802,10 +1630,10 @@ function createProcessCommand() {
|
|
|
802
1630
|
}
|
|
803
1631
|
|
|
804
1632
|
// src/commands/process3d.ts
|
|
805
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1633
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
806
1634
|
import { join as join4 } from "path";
|
|
807
|
-
import { Command as
|
|
808
|
-
function
|
|
1635
|
+
import { Command as Command8 } from "commander";
|
|
1636
|
+
function infer3dExtension2(format) {
|
|
809
1637
|
const map = {
|
|
810
1638
|
fbx: "fbx",
|
|
811
1639
|
usdz: "usdz",
|
|
@@ -817,11 +1645,32 @@ function infer3dExtension(format) {
|
|
|
817
1645
|
};
|
|
818
1646
|
return map[String(format ?? "glb").toLowerCase()] ?? "glb";
|
|
819
1647
|
}
|
|
1648
|
+
function inferMimeType2(filePath) {
|
|
1649
|
+
const lower = filePath.toLowerCase();
|
|
1650
|
+
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
1651
|
+
if (lower.endsWith(".png")) return "image/png";
|
|
1652
|
+
if (lower.endsWith(".webp")) return "image/webp";
|
|
1653
|
+
if (lower.endsWith(".gif")) return "image/gif";
|
|
1654
|
+
return "application/octet-stream";
|
|
1655
|
+
}
|
|
1656
|
+
function toInputFile2(value) {
|
|
1657
|
+
if (!value) {
|
|
1658
|
+
return void 0;
|
|
1659
|
+
}
|
|
1660
|
+
if (!existsSync5(value)) {
|
|
1661
|
+
return value;
|
|
1662
|
+
}
|
|
1663
|
+
const b64 = readFileSync4(value).toString("base64");
|
|
1664
|
+
return `data:${inferMimeType2(value)};base64,${b64}`;
|
|
1665
|
+
}
|
|
1666
|
+
function toJsonObject2(value) {
|
|
1667
|
+
return value;
|
|
1668
|
+
}
|
|
820
1669
|
async function saveProcess3dOutput(data, operation, outputDir, format) {
|
|
821
1670
|
if (!data.output_url) {
|
|
822
1671
|
return null;
|
|
823
1672
|
}
|
|
824
|
-
const ext =
|
|
1673
|
+
const ext = infer3dExtension2(format);
|
|
825
1674
|
const timestamp = Date.now();
|
|
826
1675
|
const filePath = join4(outputDir, `${operation}_${timestamp}.${ext}`);
|
|
827
1676
|
mkdirSync4(outputDir, { recursive: true });
|
|
@@ -834,63 +1683,73 @@ async function saveProcess3dOutput(data, operation, outputDir, format) {
|
|
|
834
1683
|
return filePath;
|
|
835
1684
|
}
|
|
836
1685
|
function createProcess3dCommand() {
|
|
837
|
-
const command = new
|
|
1686
|
+
const command = new Command8("process3d").description("3D model post-processing via Meshy AI");
|
|
838
1687
|
command.addCommand(
|
|
839
|
-
new
|
|
1688
|
+
new Command8("remesh").description("Remesh a model and optionally change format or polygon count").option("--task-id <id>", "Meshy task ID").option("--model-url <url>", "Model URL to remesh without an existing task").requiredOption("--format <fmt>", "Target format: glb, fbx, obj, usdz, stl, 3mf").option("--polycount <n>", "Target polygon count").option("--topology <type>", "Topology type: triangle or quad").option("--auto-size", "Estimate real-world size automatically").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
840
1689
|
try {
|
|
1690
|
+
if (!options.taskId && !options.modelUrl) {
|
|
1691
|
+
throw new Error("Either --task-id or --model-url is required");
|
|
1692
|
+
}
|
|
841
1693
|
const ctx = createContext(this);
|
|
842
1694
|
const params = {
|
|
843
|
-
|
|
1695
|
+
target_formats: [options.format]
|
|
844
1696
|
};
|
|
845
|
-
if (options.
|
|
846
|
-
if (options.
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1697
|
+
if (options.modelUrl) params.model_url = options.modelUrl;
|
|
1698
|
+
if (options.polycount) params.polycount = Number(options.polycount);
|
|
1699
|
+
if (options.topology) params.topology = options.topology;
|
|
1700
|
+
if (options.autoSize) params.auto_size = true;
|
|
1701
|
+
const data = await ctx.client.process3d({
|
|
1702
|
+
task_id: options.taskId ?? "",
|
|
1703
|
+
operation: "remesh",
|
|
1704
|
+
params: toJsonObject2(params)
|
|
851
1705
|
});
|
|
852
|
-
const localPath = await saveProcess3dOutput(data, "
|
|
1706
|
+
const localPath = await saveProcess3dOutput(data, "remesh", options.outputDir, options.format);
|
|
853
1707
|
if (localPath) data.local_path = localPath;
|
|
854
|
-
printSuccess("process3d.
|
|
1708
|
+
printSuccess("process3d.remesh", data, ctx);
|
|
855
1709
|
} catch (error2) {
|
|
856
|
-
printError("process3d.
|
|
1710
|
+
printError("process3d.remesh", error2);
|
|
857
1711
|
}
|
|
858
1712
|
})
|
|
859
1713
|
);
|
|
860
1714
|
command.addCommand(
|
|
861
|
-
new
|
|
1715
|
+
new Command8("retexture").description("Regenerate textures for an existing Meshy model").requiredOption("--task-id <id>", "Meshy task ID").option("--prompt <text>", "Text style prompt for the new material pass").option("--style-image <input>", "Texture style reference image URL or local file path").option("--pbr", "Enable PBR materials").option("--hd-texture", "Request 4K texture output").option("--ai-model <name>", "Meshy model: meshy-5, meshy-6, latest").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
862
1716
|
try {
|
|
863
1717
|
const ctx = createContext(this);
|
|
864
1718
|
const params = {};
|
|
865
|
-
if (options.prompt) params.
|
|
1719
|
+
if (options.prompt) params.text_style_prompt = options.prompt;
|
|
1720
|
+
if (options.styleImage) params.image_style_url = toInputFile2(options.styleImage);
|
|
866
1721
|
if (options.pbr) params.pbr = true;
|
|
867
|
-
if (options.
|
|
868
|
-
|
|
1722
|
+
if (options.hdTexture) params.hd_texture = true;
|
|
1723
|
+
if (options.aiModel) params.ai_model = options.aiModel;
|
|
1724
|
+
const data = await ctx.client.process3d({
|
|
869
1725
|
task_id: options.taskId,
|
|
870
|
-
operation: "
|
|
871
|
-
params
|
|
1726
|
+
operation: "retexture",
|
|
1727
|
+
params: toJsonObject2(params)
|
|
872
1728
|
});
|
|
873
|
-
const localPath = await saveProcess3dOutput(data, "
|
|
1729
|
+
const localPath = await saveProcess3dOutput(data, "retexture", options.outputDir);
|
|
874
1730
|
if (localPath) data.local_path = localPath;
|
|
875
|
-
printSuccess("process3d.
|
|
1731
|
+
printSuccess("process3d.retexture", data, ctx);
|
|
876
1732
|
} catch (error2) {
|
|
877
|
-
printError("process3d.
|
|
1733
|
+
printError("process3d.retexture", error2);
|
|
878
1734
|
}
|
|
879
1735
|
})
|
|
880
1736
|
);
|
|
881
1737
|
command.addCommand(
|
|
882
|
-
new
|
|
1738
|
+
new Command8("rig").description("Rig a Meshy model for character animation").option("--task-id <id>", "Meshy task ID").option("--height <meters>", "Estimated character height in meters").option("--model-url <url>", "Model URL to rig without an existing task").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
883
1739
|
try {
|
|
1740
|
+
if (!options.taskId && !options.modelUrl) {
|
|
1741
|
+
throw new Error("Either --task-id or --model-url is required");
|
|
1742
|
+
}
|
|
884
1743
|
const ctx = createContext(this);
|
|
885
|
-
const
|
|
886
|
-
|
|
1744
|
+
const params = {};
|
|
1745
|
+
if (options.height) params.height_meters = Number(options.height);
|
|
1746
|
+
if (options.modelUrl) params.model_url = options.modelUrl;
|
|
1747
|
+
const data = await ctx.client.process3d({
|
|
1748
|
+
task_id: options.taskId ?? "",
|
|
887
1749
|
operation: "rig",
|
|
888
|
-
params:
|
|
889
|
-
format: options.format,
|
|
890
|
-
spec: options.spec
|
|
891
|
-
}
|
|
1750
|
+
params: toJsonObject2(params)
|
|
892
1751
|
});
|
|
893
|
-
const localPath = await saveProcess3dOutput(data, "rig", options.outputDir
|
|
1752
|
+
const localPath = await saveProcess3dOutput(data, "rig", options.outputDir);
|
|
894
1753
|
if (localPath) data.local_path = localPath;
|
|
895
1754
|
printSuccess("process3d.rig", data, ctx);
|
|
896
1755
|
} catch (error2) {
|
|
@@ -899,18 +1758,21 @@ function createProcess3dCommand() {
|
|
|
899
1758
|
})
|
|
900
1759
|
);
|
|
901
1760
|
command.addCommand(
|
|
902
|
-
new
|
|
1761
|
+
new Command8("animate").description("Apply a preset animation to a rigged Meshy character").requiredOption("--task-id <id>", "Meshy rigging task ID").requiredOption("--action-id <n>", "Animation preset ID (see docs for full list)").option("--fps <n>", "Target frame rate: 24, 25, 30, or 60").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
903
1762
|
try {
|
|
904
1763
|
const ctx = createContext(this);
|
|
905
|
-
const
|
|
1764
|
+
const params = {
|
|
1765
|
+
action_id: Number(options.actionId)
|
|
1766
|
+
};
|
|
1767
|
+
if (options.fps) {
|
|
1768
|
+
params.fps = Number(options.fps);
|
|
1769
|
+
}
|
|
1770
|
+
const data = await ctx.client.process3d({
|
|
906
1771
|
task_id: options.taskId,
|
|
907
1772
|
operation: "animate",
|
|
908
|
-
params:
|
|
909
|
-
animation: options.animation,
|
|
910
|
-
format: options.format
|
|
911
|
-
}
|
|
1773
|
+
params: toJsonObject2(params)
|
|
912
1774
|
});
|
|
913
|
-
const localPath = await saveProcess3dOutput(data, "animate", options.outputDir
|
|
1775
|
+
const localPath = await saveProcess3dOutput(data, "animate", options.outputDir);
|
|
914
1776
|
if (localPath) data.local_path = localPath;
|
|
915
1777
|
printSuccess("process3d.animate", data, ctx);
|
|
916
1778
|
} catch (error2) {
|
|
@@ -919,162 +1781,233 @@ function createProcess3dCommand() {
|
|
|
919
1781
|
})
|
|
920
1782
|
);
|
|
921
1783
|
command.addCommand(
|
|
922
|
-
new
|
|
1784
|
+
new Command8("refine").description("Refine a Meshy preview model into a higher quality result").requiredOption("--task-id <id>", "Meshy preview task ID").option("--pbr", "Enable PBR materials").option("--hd-texture", "Request 4K texture output").option("--texture-prompt <text>", "Optional texture prompt for the refinement pass").option("--ai-model <name>", "Meshy model: meshy-5, meshy-6, latest").option("--output-dir <dir>", "Directory to save output", ".").action(async function(options) {
|
|
923
1785
|
try {
|
|
924
1786
|
const ctx = createContext(this);
|
|
925
1787
|
const params = {};
|
|
926
|
-
if (options.
|
|
927
|
-
if (options.
|
|
928
|
-
|
|
1788
|
+
if (options.pbr) params.pbr = true;
|
|
1789
|
+
if (options.hdTexture) params.hd_texture = true;
|
|
1790
|
+
if (options.texturePrompt) params.texture_prompt = options.texturePrompt;
|
|
1791
|
+
if (options.aiModel) params.ai_model = options.aiModel;
|
|
1792
|
+
const data = await ctx.client.process3d({
|
|
929
1793
|
task_id: options.taskId,
|
|
930
|
-
operation: "
|
|
931
|
-
params
|
|
1794
|
+
operation: "refine",
|
|
1795
|
+
params: toJsonObject2(params)
|
|
932
1796
|
});
|
|
933
|
-
const localPath = await saveProcess3dOutput(data, "
|
|
1797
|
+
const localPath = await saveProcess3dOutput(data, "refine", options.outputDir);
|
|
934
1798
|
if (localPath) data.local_path = localPath;
|
|
935
|
-
printSuccess("process3d.
|
|
1799
|
+
printSuccess("process3d.refine", data, ctx);
|
|
936
1800
|
} catch (error2) {
|
|
937
|
-
printError("process3d.
|
|
1801
|
+
printError("process3d.refine", error2);
|
|
938
1802
|
}
|
|
939
1803
|
})
|
|
940
1804
|
);
|
|
1805
|
+
return command;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// src/commands/provider.ts
|
|
1809
|
+
import { Command as Command9 } from "commander";
|
|
1810
|
+
function createProviderCommand() {
|
|
1811
|
+
const command = new Command9("provider").description("Provider management");
|
|
941
1812
|
command.addCommand(
|
|
942
|
-
new
|
|
1813
|
+
new Command9("list").description("List available providers").action(async function() {
|
|
943
1814
|
try {
|
|
944
1815
|
const ctx = createContext(this);
|
|
945
|
-
const data = await ctx.client.
|
|
946
|
-
|
|
947
|
-
operation: "stylize",
|
|
948
|
-
params: {
|
|
949
|
-
style: options.style
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
const localPath = await saveProcess3dOutput(data, "stylize", options.outputDir);
|
|
953
|
-
if (localPath) data.local_path = localPath;
|
|
954
|
-
printSuccess("process3d.stylize", data, ctx);
|
|
1816
|
+
const data = await ctx.client.providers.list();
|
|
1817
|
+
printSuccess("provider.list", data, ctx);
|
|
955
1818
|
} catch (error2) {
|
|
956
|
-
printError("
|
|
1819
|
+
printError("provider.list", error2);
|
|
957
1820
|
}
|
|
958
1821
|
})
|
|
959
1822
|
);
|
|
960
1823
|
command.addCommand(
|
|
961
|
-
new
|
|
1824
|
+
new Command9("health").description("Check provider health").argument("[name]", "Specific provider name").action(async function(name) {
|
|
962
1825
|
try {
|
|
963
1826
|
const ctx = createContext(this);
|
|
964
|
-
const data = await ctx.client.
|
|
965
|
-
|
|
966
|
-
operation: "segment",
|
|
967
|
-
params: {}
|
|
968
|
-
});
|
|
969
|
-
const localPath = await saveProcess3dOutput(data, "segment", options.outputDir);
|
|
970
|
-
if (localPath) data.local_path = localPath;
|
|
971
|
-
printSuccess("process3d.segment", data, ctx);
|
|
1827
|
+
const data = name ? await ctx.client.providers.health(name) : await ctx.client.providers.health();
|
|
1828
|
+
printSuccess("provider.health", data, ctx);
|
|
972
1829
|
} catch (error2) {
|
|
973
|
-
printError("
|
|
1830
|
+
printError("provider.health", error2);
|
|
974
1831
|
}
|
|
975
1832
|
})
|
|
976
1833
|
);
|
|
1834
|
+
return command;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// src/commands/upload.ts
|
|
1838
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1839
|
+
import { basename } from "path";
|
|
1840
|
+
import { AssetForgeError as AssetForgeError6 } from "@doufunao123/assetforge-sdk";
|
|
1841
|
+
import { Command as Command10 } from "commander";
|
|
1842
|
+
function createUploadCommand() {
|
|
1843
|
+
const command = new Command10("upload").description("Upload and manage assets");
|
|
977
1844
|
command.addCommand(
|
|
978
|
-
new
|
|
1845
|
+
new Command10("file").description("Upload a file and get a public URL").argument("<path>", "Path to file to upload").action(async function(filePath) {
|
|
1846
|
+
const ctx = createContext(this);
|
|
979
1847
|
try {
|
|
980
|
-
const
|
|
981
|
-
const data = await ctx.client.
|
|
982
|
-
|
|
983
|
-
operation: "prerigcheck",
|
|
984
|
-
params: {}
|
|
985
|
-
});
|
|
986
|
-
const localPath = await saveProcess3dOutput(data, "prerigcheck", options.outputDir);
|
|
987
|
-
if (localPath) data.local_path = localPath;
|
|
988
|
-
printSuccess("process3d.prerigcheck", data, ctx);
|
|
1848
|
+
const content = await readLocalFile(filePath);
|
|
1849
|
+
const data = await ctx.client.assets.upload(content, { filename: basename(filePath) });
|
|
1850
|
+
printSuccess("asset.upload", data, ctx);
|
|
989
1851
|
} catch (error2) {
|
|
990
|
-
printError("
|
|
1852
|
+
printError("asset.upload", error2, ctx.human);
|
|
991
1853
|
}
|
|
992
1854
|
})
|
|
993
1855
|
);
|
|
994
|
-
return command;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// src/commands/provider.ts
|
|
998
|
-
import { Command as Command7 } from "commander";
|
|
999
|
-
function createProviderCommand() {
|
|
1000
|
-
const command = new Command7("provider").description("Provider management");
|
|
1001
1856
|
command.addCommand(
|
|
1002
|
-
new
|
|
1857
|
+
new Command10("list").description("List uploaded assets").action(async function() {
|
|
1858
|
+
const ctx = createContext(this);
|
|
1003
1859
|
try {
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
printSuccess("provider.list", data, ctx);
|
|
1860
|
+
const data = await ctx.client.assets.list();
|
|
1861
|
+
printSuccess("asset.list", data, ctx);
|
|
1007
1862
|
} catch (error2) {
|
|
1008
|
-
printError("
|
|
1863
|
+
printError("asset.list", error2, ctx.human);
|
|
1009
1864
|
}
|
|
1010
1865
|
})
|
|
1011
1866
|
);
|
|
1012
1867
|
command.addCommand(
|
|
1013
|
-
new
|
|
1868
|
+
new Command10("delete").description("Delete an uploaded asset (admin only)").argument("<filename>", "Filename to delete").action(async function(filename) {
|
|
1869
|
+
const ctx = createContext(this);
|
|
1014
1870
|
try {
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
const data = await ctx.client.get(path);
|
|
1018
|
-
printSuccess("provider.health", data, ctx);
|
|
1871
|
+
const data = await ctx.client.assets.delete(filename);
|
|
1872
|
+
printSuccess("asset.delete", data, ctx);
|
|
1019
1873
|
} catch (error2) {
|
|
1020
|
-
printError("
|
|
1874
|
+
printError("asset.delete", error2, ctx.human);
|
|
1021
1875
|
}
|
|
1022
1876
|
})
|
|
1023
1877
|
);
|
|
1024
1878
|
return command;
|
|
1025
1879
|
}
|
|
1880
|
+
async function readLocalFile(filePath) {
|
|
1881
|
+
try {
|
|
1882
|
+
return await readFile3(filePath);
|
|
1883
|
+
} catch (error2) {
|
|
1884
|
+
throw new AssetForgeError6(
|
|
1885
|
+
`Failed to read file ${filePath}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
1886
|
+
{ code: "CONFIG_ERROR" }
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1026
1890
|
|
|
1027
|
-
// src/commands/
|
|
1028
|
-
import {
|
|
1029
|
-
|
|
1030
|
-
|
|
1891
|
+
// src/commands/voice.ts
|
|
1892
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
1893
|
+
import { dirname as dirname3, extname as extname2 } from "path";
|
|
1894
|
+
import { Command as Command11 } from "commander";
|
|
1895
|
+
function createVoiceCommand() {
|
|
1896
|
+
const command = new Command11("voice").description("Design, clone, list, and delete voices");
|
|
1031
1897
|
command.addCommand(
|
|
1032
|
-
new
|
|
1898
|
+
new Command11("design").description("Generate a MiMo voice from a text description").requiredOption("--voice-prompt <text>", "Voice description / style prompt").requiredOption("--preview-text <text>", "Text to synthesize for preview").option("--style <text>", "Additional director/style instruction").option("--name <name>", "Save generated voice with this name").option("--save-as <name>", "Alias for --name").option("--output <path>", "Write preview WAV to this path").action(async function(options) {
|
|
1033
1899
|
const ctx = createContext(this);
|
|
1034
1900
|
try {
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1901
|
+
const request = {
|
|
1902
|
+
voice_prompt: options.voicePrompt,
|
|
1903
|
+
preview_text: options.previewText,
|
|
1904
|
+
style: options.style,
|
|
1905
|
+
name: options.name,
|
|
1906
|
+
save_as: options.saveAs
|
|
1907
|
+
};
|
|
1908
|
+
const data = await ctx.client.voice.design(request);
|
|
1909
|
+
const localPath = writeWavOutput(data, options.output);
|
|
1910
|
+
if (localPath) data.local_path = localPath;
|
|
1911
|
+
printSuccess("voice.design", data, ctx);
|
|
1037
1912
|
} catch (error2) {
|
|
1038
|
-
printError("
|
|
1913
|
+
printError("voice.design", error2, ctx.human);
|
|
1039
1914
|
}
|
|
1040
1915
|
})
|
|
1041
1916
|
);
|
|
1042
1917
|
command.addCommand(
|
|
1043
|
-
new
|
|
1918
|
+
new Command11("clone").description("Clone a MiMo voice from an mp3/wav sample").requiredOption("--audio <path-or-data-url>", "Voice sample mp3/wav file or data URL").requiredOption("--preview-text <text>", "Text to synthesize for preview").option("--audio-mime <mime>", "Audio MIME type when --audio is raw base64").option("--style <text>", "Director/style instruction").option("--name <name>", "Save cloned voice with this name").option("--save-as <name>", "Alias for --name").option("--output <path>", "Write preview WAV to this path").action(async function(options) {
|
|
1044
1919
|
const ctx = createContext(this);
|
|
1045
1920
|
try {
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1921
|
+
const sample = readVoiceSample(options.audio, options.audioMime);
|
|
1922
|
+
const request = {
|
|
1923
|
+
...sample,
|
|
1924
|
+
preview_text: options.previewText,
|
|
1925
|
+
style: options.style,
|
|
1926
|
+
name: options.name,
|
|
1927
|
+
save_as: options.saveAs
|
|
1928
|
+
};
|
|
1929
|
+
const data = await ctx.client.voice.clone(request);
|
|
1930
|
+
const localPath = writeWavOutput(data, options.output);
|
|
1931
|
+
if (localPath) data.local_path = localPath;
|
|
1932
|
+
printSuccess("voice.clone", data, ctx);
|
|
1048
1933
|
} catch (error2) {
|
|
1049
|
-
printError("
|
|
1934
|
+
printError("voice.clone", error2, ctx.human);
|
|
1050
1935
|
}
|
|
1051
1936
|
})
|
|
1052
1937
|
);
|
|
1053
1938
|
command.addCommand(
|
|
1054
|
-
new
|
|
1939
|
+
new Command11("list").description("List saved voices").option("--type <type>", "Voice type: vc or vd").action(async function(options) {
|
|
1055
1940
|
const ctx = createContext(this);
|
|
1056
1941
|
try {
|
|
1057
|
-
const data = await ctx.client.
|
|
1058
|
-
printSuccess("
|
|
1942
|
+
const data = await ctx.client.voice.list({ type: options.type });
|
|
1943
|
+
printSuccess("voice.list", data, ctx);
|
|
1059
1944
|
} catch (error2) {
|
|
1060
|
-
printError("
|
|
1945
|
+
printError("voice.list", error2, ctx.human);
|
|
1946
|
+
}
|
|
1947
|
+
})
|
|
1948
|
+
);
|
|
1949
|
+
command.addCommand(
|
|
1950
|
+
new Command11("delete").description("Delete a saved voice").argument("<voice-id>", "Voice ID").option("--type <type>", "Voice type: vc or vd").action(async function(voiceId, options) {
|
|
1951
|
+
const ctx = createContext(this);
|
|
1952
|
+
try {
|
|
1953
|
+
const data = await ctx.client.voice.delete(voiceId, { type: options.type });
|
|
1954
|
+
printSuccess("voice.delete", data, ctx);
|
|
1955
|
+
} catch (error2) {
|
|
1956
|
+
printError("voice.delete", error2, ctx.human);
|
|
1061
1957
|
}
|
|
1062
1958
|
})
|
|
1063
1959
|
);
|
|
1064
1960
|
return command;
|
|
1065
1961
|
}
|
|
1962
|
+
function readVoiceSample(input2, audioMime) {
|
|
1963
|
+
if (input2.startsWith("data:")) {
|
|
1964
|
+
return { sample_data_url: input2 };
|
|
1965
|
+
}
|
|
1966
|
+
if (existsSync6(input2)) {
|
|
1967
|
+
return {
|
|
1968
|
+
audio_base64: readFileSync5(input2).toString("base64"),
|
|
1969
|
+
audio_mime: audioMime ?? inferAudioMime(input2)
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
return { audio_base64: input2, audio_mime: audioMime ?? "audio/wav" };
|
|
1973
|
+
}
|
|
1974
|
+
function inferAudioMime(filePath) {
|
|
1975
|
+
const ext = extname2(filePath).toLowerCase();
|
|
1976
|
+
if (ext === ".mp3") return "audio/mpeg";
|
|
1977
|
+
if (ext === ".wav") return "audio/wav";
|
|
1978
|
+
return "audio/wav";
|
|
1979
|
+
}
|
|
1980
|
+
function writeWavOutput(data, outputPath) {
|
|
1981
|
+
if (!outputPath) {
|
|
1982
|
+
return null;
|
|
1983
|
+
}
|
|
1984
|
+
const raw = data.wav_base64 ?? data.output_data;
|
|
1985
|
+
if (typeof raw !== "string" || !raw) {
|
|
1986
|
+
return null;
|
|
1987
|
+
}
|
|
1988
|
+
mkdirSync5(dirname3(outputPath), { recursive: true });
|
|
1989
|
+
writeFileSync5(outputPath, Buffer.from(stripDataUri2(raw), "base64"));
|
|
1990
|
+
return outputPath;
|
|
1991
|
+
}
|
|
1992
|
+
function stripDataUri2(data) {
|
|
1993
|
+
const idx = data.indexOf(";base64,");
|
|
1994
|
+
return idx >= 0 ? data.slice(idx + 8) : data;
|
|
1995
|
+
}
|
|
1066
1996
|
|
|
1067
1997
|
// src/index.ts
|
|
1068
|
-
var program = new
|
|
1998
|
+
var program = new Command12().name("asset-gateway").description("Universal asset generation gateway CLI").version(CLI_VERSION).option(
|
|
1069
1999
|
"--gateway-url <url>",
|
|
1070
2000
|
`Gateway URL (default: $ASSET_GATEWAY_URL, auth config, or ${DEFAULT_GATEWAY_URL})`
|
|
1071
2001
|
).option("--token <token>", "API token for authentication").option("--human", "Human-readable output instead of JSON").option("--fields <fields>", "Comma-separated list of output fields");
|
|
1072
2002
|
program.addCommand(createAuthCommand());
|
|
1073
2003
|
program.addCommand(createGenerateCommand());
|
|
2004
|
+
program.addCommand(createLibraryCommand());
|
|
2005
|
+
program.addCommand(createPackCommand());
|
|
1074
2006
|
program.addCommand(createProcessCommand());
|
|
1075
2007
|
program.addCommand(createProcess3dCommand());
|
|
1076
2008
|
program.addCommand(createProviderCommand());
|
|
1077
2009
|
program.addCommand(createUploadCommand());
|
|
1078
2010
|
program.addCommand(createJobCommand());
|
|
2011
|
+
program.addCommand(createVoiceCommand());
|
|
1079
2012
|
program.addCommand(createDescribeCommand());
|
|
1080
2013
|
await program.parseAsync(process.argv);
|