@assethub/cli 0.1.1 → 0.1.2
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 +24 -5
- package/dist/index.js +479 -44
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -8,10 +8,11 @@ export ASSETHUB_API_KEY=ah_live_xxx
|
|
|
8
8
|
assethub models list
|
|
9
9
|
assethub models list --domain meshGen --ids-only
|
|
10
10
|
assethub files import https://example.com/input.png --media-type image
|
|
11
|
+
assethub source create --file ./input.png --media-type image
|
|
11
12
|
assethub image generate --prompt "stylized prop concept" --wait
|
|
12
13
|
assethub mesh generate --file ./input.png --model-id meshGen.hunyuan31 --wait
|
|
13
|
-
assethub jobs watch job_123
|
|
14
|
-
assethub
|
|
14
|
+
assethub jobs watch job_123 --download --out-dir ./out/job
|
|
15
|
+
assethub parts split --file ./input.png --part-extractor "V1.5" --wait --all-ready --download --out-dir ./out/parts
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
Configuration:
|
|
@@ -24,11 +25,29 @@ Agent-friendly login:
|
|
|
24
25
|
|
|
25
26
|
```sh
|
|
26
27
|
printf '%s' "$ASSETHUB_API_KEY" | assethub auth login --api-key-stdin
|
|
27
|
-
assethub image generate --prompt "small stylized game asset chest" --wait --
|
|
28
|
-
assethub mesh generate --source-url https://example.com/input.png --model-id meshGen.hunyuan31 --wait --
|
|
29
|
-
assethub
|
|
28
|
+
assethub image generate --prompt "small stylized game asset chest" --wait --download --out-dir ./out/image
|
|
29
|
+
assethub mesh generate --source-url https://example.com/input.png --model-id meshGen.hunyuan31 --wait --download --out-dir ./out/mesh
|
|
30
|
+
cat ./character.png | assethub parts split --stdin --file-name character.png --part-extractor "V1.5" --wait --all-ready --download --out-dir ./out/parts
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
Agent-oriented input:
|
|
34
|
+
|
|
35
|
+
- `--file <path>` uploads a local image or mesh through the public file API.
|
|
36
|
+
- `--stdin --file-name <name>` accepts piped bytes. Give a file name with an
|
|
37
|
+
extension when possible.
|
|
38
|
+
- `--data-uri <uri>` accepts `data:image/png;base64,...` style inputs and uploads
|
|
39
|
+
them before running the target command.
|
|
40
|
+
- `--source-url`, `--source-id`/`--source-resource-id`, and `--file-ref-json`
|
|
41
|
+
can be reused across image, mesh, and part extraction commands where the API
|
|
42
|
+
supports them.
|
|
43
|
+
|
|
44
|
+
Agent-oriented output:
|
|
45
|
+
|
|
46
|
+
- stdout is one final JSON object.
|
|
47
|
+
- polling progress goes to stderr.
|
|
48
|
+
- `--download --out-dir <dir>` recursively downloads public artifact URLs from
|
|
49
|
+
job or production status payloads and writes a `manifest.json`.
|
|
50
|
+
|
|
32
51
|
Image generation defaults to the deployed `v1` endpoint. Pass `--api-version v2`
|
|
33
52
|
only when the `v2` image endpoint is available in the target environment.
|
|
34
53
|
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { basename, dirname, join } from 'node:path';
|
|
4
|
+
import { basename, dirname, extname, join } from 'node:path';
|
|
5
5
|
import { argv, env, exit, stdin, stderr, stdout } from 'node:process';
|
|
6
6
|
import { AssetHubApiError, createAssetHubClient, } from '@assethub/api-client';
|
|
7
7
|
const defaultBaseUrl = 'https://app.assethub.io';
|
|
@@ -11,32 +11,26 @@ const defaultProfileName = 'default';
|
|
|
11
11
|
const defaultPartExtractorName = 'V1.5';
|
|
12
12
|
const partExtractorOptions = [
|
|
13
13
|
{
|
|
14
|
-
internalId: 'ah_part_extractor_v3',
|
|
15
14
|
publicName: 'Part Extractor V3',
|
|
16
15
|
aliases: ['part extractor v3', 'v3'],
|
|
17
16
|
},
|
|
18
17
|
{
|
|
19
|
-
internalId: 'ah_part_extractor_v5',
|
|
20
18
|
publicName: 'V1',
|
|
21
19
|
aliases: ['v1', '1'],
|
|
22
20
|
},
|
|
23
21
|
{
|
|
24
|
-
internalId: 'ah_part_extractor_v55',
|
|
25
22
|
publicName: 'V1.5 alpha',
|
|
26
23
|
aliases: ['v1.5 alpha', 'v1.5-alpha', '1.5 alpha', '1.5-alpha'],
|
|
27
24
|
},
|
|
28
25
|
{
|
|
29
|
-
internalId: 'ah_part_extractor_v65',
|
|
30
26
|
publicName: 'V1.5',
|
|
31
27
|
aliases: ['v1.5', '1.5', 'latest', 'default'],
|
|
32
28
|
},
|
|
33
29
|
{
|
|
34
|
-
internalId: 'ah_part_extractor_v20_alpha',
|
|
35
30
|
publicName: 'V2.0 alpha',
|
|
36
31
|
aliases: ['v2.0 alpha', 'v2 alpha', 'v2.0-alpha', 'v2-alpha', '2.0 alpha'],
|
|
37
32
|
},
|
|
38
33
|
{
|
|
39
|
-
internalId: 'ah_part_extractor_v21_alpha',
|
|
40
34
|
publicName: 'V2.1 alpha',
|
|
41
35
|
aliases: ['v2.1 alpha', 'v2.1-alpha', '2.1 alpha'],
|
|
42
36
|
},
|
|
@@ -45,15 +39,14 @@ const partExtractorPublicNames = partExtractorOptions
|
|
|
45
39
|
.map(option => option.publicName)
|
|
46
40
|
.join(', ');
|
|
47
41
|
const normalizePartExtractorName = (value) => value.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
[normalizePartExtractorName(option.publicName), option.internalId],
|
|
42
|
+
const partExtractorPublicNameByInput = new Map(partExtractorOptions.flatMap(option => [
|
|
43
|
+
[normalizePartExtractorName(option.publicName), option.publicName],
|
|
51
44
|
...option.aliases.map(alias => [
|
|
52
45
|
normalizePartExtractorName(alias),
|
|
53
|
-
option.
|
|
46
|
+
option.publicName,
|
|
54
47
|
]),
|
|
55
48
|
]));
|
|
56
|
-
const isInternalPartExtractorId = (value) => value
|
|
49
|
+
const isInternalPartExtractorId = (value) => value.trim().includes('_');
|
|
57
50
|
const usage = `AssetHub CLI
|
|
58
51
|
|
|
59
52
|
Usage:
|
|
@@ -65,15 +58,17 @@ Usage:
|
|
|
65
58
|
assethub models get <model-id>
|
|
66
59
|
assethub files import <url> --media-type image|mesh
|
|
67
60
|
assethub files upload <file> --media-type image|mesh
|
|
68
|
-
assethub
|
|
61
|
+
assethub source create (--file <path> | --source-url <url> | --source-id <id> | --stdin | --data-uri <uri>) --media-type image|mesh
|
|
62
|
+
assethub image generate --prompt <text> [--file <path> | --source-url <url> | --stdin | --data-uri <uri>] [--model-id <id>] [--batch-size <n>] [--wait] [--download --out-dir <dir>] [--api-version v1|v2]
|
|
69
63
|
assethub image generate --prompt <text> --source-resource-id <id> --api-version v2 [--model-id <id>] [--batch-size <n>] [--wait]
|
|
70
|
-
assethub mesh generate (--file <path> | --source-url <url> | --source-resource-id <id> | --file-ref-json <json>) [--model-id <id>] [--name <name>] [--wait]
|
|
71
|
-
assethub mesh retopo (--source-resource-id <id> | --source-url <url> | --file-ref-json <json>) --model-id <id> [--wait]
|
|
72
|
-
assethub jobs get <job-id>
|
|
73
|
-
assethub jobs watch <job-id> [--interval-ms <ms>] [--timeout-ms <ms>]
|
|
74
|
-
assethub production analyze --image-url <url> [--part-extractor <name>] [--name <name>]
|
|
75
|
-
assethub production status <order-id>
|
|
76
|
-
assethub production execute --order-id <id> --mission-id <id> --task-id <id> [--task-id <id>...] [--part-extractor <name>] [--part-extraction-mode fast|high_quality]
|
|
64
|
+
assethub mesh generate (--file <path> | --source-url <url> | --source-resource-id <id> | --file-ref-json <json> | --stdin | --data-uri <uri>) [--model-id <id>] [--name <name>] [--wait] [--download --out-dir <dir>]
|
|
65
|
+
assethub mesh retopo (--source-resource-id <id> | --source-url <url> | --file-ref-json <json> | --stdin | --data-uri <uri>) --model-id <id> [--wait] [--download --out-dir <dir>]
|
|
66
|
+
assethub jobs get <job-id> [--download --out-dir <dir>]
|
|
67
|
+
assethub jobs watch <job-id> [--interval-ms <ms>] [--timeout-ms <ms>] [--download --out-dir <dir>]
|
|
68
|
+
assethub production analyze (--file <path> | --source-url <url> | --source-id <id> | --image-url <url> | --file-ref-json <json> | --stdin | --data-uri <uri>) [--part-extractor <name>] [--name <name>] [--wait] [--download --out-dir <dir>]
|
|
69
|
+
assethub production status <order-id> [--wait] [--download --out-dir <dir>]
|
|
70
|
+
assethub production execute --order-id <id> --mission-id <id> --task-id <id> [--task-id <id>...] [--part-extractor <name>] [--part-extraction-mode fast|high_quality] [--wait] [--download --out-dir <dir>]
|
|
71
|
+
assethub parts split (--file <path> | --source-url <url> | --source-id <id> | --image-url <url> | --file-ref-json <json> | --stdin | --data-uri <uri> | --order-id <id>) [--part-extractor <name>] [--wait] [--task-id <id>...] [--mission-id <id>] [--all-ready] [--download --out-dir <dir>]
|
|
77
72
|
|
|
78
73
|
Global options:
|
|
79
74
|
--api-key <key> Overrides saved auth and ASSETHUB_API_KEY.
|
|
@@ -87,9 +82,12 @@ Examples:
|
|
|
87
82
|
assethub auth login --api-key-stdin
|
|
88
83
|
assethub models list --domain meshGen --ids-only
|
|
89
84
|
assethub files upload ./input.png --media-type image
|
|
85
|
+
assethub source create --file ./input.png --media-type image
|
|
86
|
+
cat ./input.png | assethub parts split --stdin --file-name input.png --part-extractor "V1.5" --wait
|
|
90
87
|
assethub image generate --prompt "stylized prop concept" --wait
|
|
91
88
|
assethub mesh generate --file ./input.png --model-id meshGen.hunyuan31 --wait
|
|
92
|
-
assethub production analyze --
|
|
89
|
+
assethub production analyze --file ./input.png --part-extractor "V1.5" --wait
|
|
90
|
+
assethub parts split --file ./input.png --part-extractor "V1.5" --wait --all-ready
|
|
93
91
|
`;
|
|
94
92
|
const print = (value) => {
|
|
95
93
|
stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
@@ -162,23 +160,22 @@ const requireFlag = (flags, name) => {
|
|
|
162
160
|
const getPartExtractorFlag = (flags) => getFlag(flags, 'part-extractor') ??
|
|
163
161
|
getFlag(flags, 'part-extractor-version') ??
|
|
164
162
|
getFlag(flags, 'agent-version');
|
|
165
|
-
const
|
|
163
|
+
const resolvePartExtractorPublicName = (value) => {
|
|
166
164
|
if (isInternalPartExtractorId(value)) {
|
|
167
165
|
throw new Error(`Internal part extractor IDs are not accepted by the public CLI. Use one of: ${partExtractorPublicNames}.`);
|
|
168
166
|
}
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
167
|
+
const publicName = partExtractorPublicNameByInput.get(normalizePartExtractorName(value));
|
|
168
|
+
if (publicName == null) {
|
|
171
169
|
throw new Error(`Unknown part extractor: ${value}. Use one of: ${partExtractorPublicNames}.`);
|
|
172
170
|
}
|
|
173
|
-
return
|
|
171
|
+
return publicName;
|
|
174
172
|
};
|
|
175
|
-
const resolvePartExtractorForAnalyze = (flags) =>
|
|
173
|
+
const resolvePartExtractorForAnalyze = (flags) => resolvePartExtractorPublicName(getPartExtractorFlag(flags) ?? defaultPartExtractorName);
|
|
176
174
|
const publicPartExtractorName = (value) => {
|
|
177
175
|
if (value == null) {
|
|
178
176
|
return null;
|
|
179
177
|
}
|
|
180
|
-
return (
|
|
181
|
-
(isInternalPartExtractorId(value) ? 'Unknown' : value));
|
|
178
|
+
return (isInternalPartExtractorId(value) ? 'Unknown' : value);
|
|
182
179
|
};
|
|
183
180
|
const sanitizeProductionOutput = (value) => {
|
|
184
181
|
if (Array.isArray(value)) {
|
|
@@ -224,6 +221,10 @@ const parseMediaType = (value) => {
|
|
|
224
221
|
}
|
|
225
222
|
throw new Error('--media-type must be "image" or "mesh"');
|
|
226
223
|
};
|
|
224
|
+
const defaultFileNameForMediaType = (mediaType) => mediaType === 'image' ? 'stdin.png' : 'stdin.glb';
|
|
225
|
+
const getSourceResourceIdFlag = (flags) => getFlag(flags, 'source-resource-id') ??
|
|
226
|
+
getFlag(flags, 'source-id') ??
|
|
227
|
+
getFlag(flags, 'resource-id');
|
|
227
228
|
const getProfileName = (flags) => getFlag(flags, 'profile') ?? defaultProfileName;
|
|
228
229
|
const getConfigPath = (flags) => getFlag(flags, 'config') ??
|
|
229
230
|
env.ASSETHUB_CLI_CONFIG ??
|
|
@@ -265,6 +266,18 @@ const readStdin = async () => await new Promise((resolve, reject) => {
|
|
|
265
266
|
stdin.on('end', () => resolve(value));
|
|
266
267
|
stdin.on('error', reject);
|
|
267
268
|
});
|
|
269
|
+
const readStdinBlob = async (type) => await new Promise((resolve, reject) => {
|
|
270
|
+
const chunks = [];
|
|
271
|
+
stdin.on('data', chunk => {
|
|
272
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
273
|
+
});
|
|
274
|
+
stdin.on('end', () => {
|
|
275
|
+
const buffer = Buffer.concat(chunks);
|
|
276
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
277
|
+
resolve(new Blob([arrayBuffer], type == null ? undefined : { type }));
|
|
278
|
+
});
|
|
279
|
+
stdin.on('error', reject);
|
|
280
|
+
});
|
|
268
281
|
const resolveAuth = async (flags) => {
|
|
269
282
|
const profile = getProfileName(flags);
|
|
270
283
|
const baseUrlFromFlags = getFlag(flags, 'base-url');
|
|
@@ -323,8 +336,46 @@ const readBlob = async (filePath) => {
|
|
|
323
336
|
const buffer = await readFile(filePath);
|
|
324
337
|
return new Blob([new Uint8Array(buffer)]);
|
|
325
338
|
};
|
|
339
|
+
const blobFromDataUri = (value) => {
|
|
340
|
+
const match = value.match(/^data:([^;,]+)?(;base64)?,(.*)$/s);
|
|
341
|
+
if (match == null) {
|
|
342
|
+
throw new Error('--data-uri must be a valid data URI.');
|
|
343
|
+
}
|
|
344
|
+
const contentType = match[1] === '' ? undefined : match[1];
|
|
345
|
+
const isBase64 = match[2] != null;
|
|
346
|
+
const payload = match[3] ?? '';
|
|
347
|
+
const buffer = isBase64
|
|
348
|
+
? Buffer.from(payload, 'base64')
|
|
349
|
+
: Buffer.from(decodeURIComponent(payload), 'utf8');
|
|
350
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
351
|
+
return {
|
|
352
|
+
blob: new Blob([arrayBuffer], contentType == null ? undefined : { type: contentType }),
|
|
353
|
+
contentType,
|
|
354
|
+
};
|
|
355
|
+
};
|
|
356
|
+
const extensionFromMimeType = (mimeType, mediaType) => {
|
|
357
|
+
if (mimeType === 'image/png')
|
|
358
|
+
return 'png';
|
|
359
|
+
if (mimeType === 'image/jpeg')
|
|
360
|
+
return 'jpg';
|
|
361
|
+
if (mimeType === 'image/webp')
|
|
362
|
+
return 'webp';
|
|
363
|
+
if (mimeType === 'image/gif')
|
|
364
|
+
return 'gif';
|
|
365
|
+
if (mimeType === 'model/gltf-binary')
|
|
366
|
+
return 'glb';
|
|
367
|
+
if (mimeType === 'model/gltf+json')
|
|
368
|
+
return 'gltf';
|
|
369
|
+
return mediaType === 'image' ? 'png' : 'glb';
|
|
370
|
+
};
|
|
371
|
+
const ensureFileNameExtension = (fileName, mediaType, mimeType) => {
|
|
372
|
+
if (extname(fileName) !== '') {
|
|
373
|
+
return fileName;
|
|
374
|
+
}
|
|
375
|
+
return `${fileName}.${extensionFromMimeType(mimeType, mediaType)}`;
|
|
376
|
+
};
|
|
326
377
|
const resolveSource = async ({ flags, client, fileMediaType, }) => {
|
|
327
|
-
const sourceResourceId =
|
|
378
|
+
const sourceResourceId = getSourceResourceIdFlag(flags);
|
|
328
379
|
if (sourceResourceId != null) {
|
|
329
380
|
return { resourceId: sourceResourceId };
|
|
330
381
|
}
|
|
@@ -345,12 +396,32 @@ const resolveSource = async ({ flags, client, fileMediaType, }) => {
|
|
|
345
396
|
});
|
|
346
397
|
return { fileRef: fileRef.fileRef };
|
|
347
398
|
}
|
|
399
|
+
const dataUri = getFlag(flags, 'data-uri');
|
|
400
|
+
if (dataUri != null) {
|
|
401
|
+
const { blob, contentType } = blobFromDataUri(dataUri);
|
|
402
|
+
const fileName = ensureFileNameExtension(getFlag(flags, 'file-name') ?? 'data-uri', fileMediaType, contentType);
|
|
403
|
+
const fileRef = await client.v2.uploadFile({
|
|
404
|
+
file: blob,
|
|
405
|
+
fileName,
|
|
406
|
+
mediaType: fileMediaType,
|
|
407
|
+
});
|
|
408
|
+
return { fileRef: fileRef.fileRef };
|
|
409
|
+
}
|
|
410
|
+
if (hasFlag(flags, 'stdin')) {
|
|
411
|
+
const fileName = ensureFileNameExtension(getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType), fileMediaType);
|
|
412
|
+
const fileRef = await client.v2.uploadFile({
|
|
413
|
+
file: await readStdinBlob(),
|
|
414
|
+
fileName,
|
|
415
|
+
mediaType: fileMediaType,
|
|
416
|
+
});
|
|
417
|
+
return { fileRef: fileRef.fileRef };
|
|
418
|
+
}
|
|
348
419
|
return undefined;
|
|
349
420
|
};
|
|
350
421
|
const requireSource = async (options) => {
|
|
351
422
|
const source = await resolveSource(options);
|
|
352
423
|
if (source == null) {
|
|
353
|
-
throw new Error('Missing source. Pass --file, --source-url, --source-resource-id,
|
|
424
|
+
throw new Error('Missing source. Pass --file, --source-url, --source-resource-id, --source-id, --file-ref-json, --stdin, or --data-uri.');
|
|
354
425
|
}
|
|
355
426
|
return source;
|
|
356
427
|
};
|
|
@@ -366,6 +437,44 @@ const sourceToV1ImageInput = (source) => {
|
|
|
366
437
|
}
|
|
367
438
|
throw new Error('v1 image generation does not accept source-resource-id. Use --source-url, --file, or --api-version v2.');
|
|
368
439
|
};
|
|
440
|
+
const resolveProductionAnalyzeSource = async (ctx) => {
|
|
441
|
+
const imageUrl = getFlag(ctx.flags, 'image-url');
|
|
442
|
+
if (imageUrl != null) {
|
|
443
|
+
return { imageUrl };
|
|
444
|
+
}
|
|
445
|
+
const source = await requireSource({
|
|
446
|
+
flags: ctx.flags,
|
|
447
|
+
client: ctx.client,
|
|
448
|
+
fileMediaType: 'image',
|
|
449
|
+
});
|
|
450
|
+
if (source.resourceId != null) {
|
|
451
|
+
return { imageAssetId: source.resourceId };
|
|
452
|
+
}
|
|
453
|
+
if (source.url != null) {
|
|
454
|
+
return { imageUrl: source.url };
|
|
455
|
+
}
|
|
456
|
+
if (source.fileRef == null) {
|
|
457
|
+
throw new Error('Missing source. Pass --file, --source-url, --source-id, --file-ref-json, --stdin, or --data-uri.');
|
|
458
|
+
}
|
|
459
|
+
return { imageBlobLocation: source.fileRef };
|
|
460
|
+
};
|
|
461
|
+
const productionAnalyzeTerminalStatuses = new Set(['ready', 'failed']);
|
|
462
|
+
const productionExecuteTerminalStatuses = new Set(['completed', 'failed']);
|
|
463
|
+
const pollProductionStatus = async ({ ctx, orderId, terminalStatuses, }) => {
|
|
464
|
+
const intervalMs = parsePositiveIntegerFlag(ctx.flags, 'interval-ms', 5000);
|
|
465
|
+
const timeoutMs = parsePositiveIntegerFlag(ctx.flags, 'timeout-ms', 900000);
|
|
466
|
+
const startedAt = Date.now();
|
|
467
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
468
|
+
const status = await ctx.client.v1.getProductionStatus(orderId);
|
|
469
|
+
const orderStatus = status.order.status;
|
|
470
|
+
stderr.write(`[poll] order=${orderId} status=${orderStatus}\n`);
|
|
471
|
+
if (terminalStatuses.has(orderStatus)) {
|
|
472
|
+
return status;
|
|
473
|
+
}
|
|
474
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
475
|
+
}
|
|
476
|
+
throw new Error(`Polling timeout: order=${orderId} did not reach terminal state within ${timeoutMs}ms`);
|
|
477
|
+
};
|
|
369
478
|
const pollIfRequested = async ({ ctx, jobId, apiVersion = 'v2', }) => {
|
|
370
479
|
if (!hasFlag(ctx.flags, 'wait')) {
|
|
371
480
|
return undefined;
|
|
@@ -388,6 +497,60 @@ const requirePositional = (positionals, index, label) => {
|
|
|
388
497
|
}
|
|
389
498
|
return value;
|
|
390
499
|
};
|
|
500
|
+
const commandSource = async (subcommand, ctx) => {
|
|
501
|
+
if (subcommand !== 'create') {
|
|
502
|
+
throw new Error('Unknown source command. Use "source create".');
|
|
503
|
+
}
|
|
504
|
+
const mediaType = parseMediaType(requireFlag(ctx.flags, 'media-type'));
|
|
505
|
+
const sourceUrl = getFlag(ctx.flags, 'source-url') ?? getFlag(ctx.flags, 'image-url');
|
|
506
|
+
const sourceResourceId = getSourceResourceIdFlag(ctx.flags);
|
|
507
|
+
const filePath = getFlag(ctx.flags, 'file');
|
|
508
|
+
const useStdin = hasFlag(ctx.flags, 'stdin');
|
|
509
|
+
const dataUri = getFlag(ctx.flags, 'data-uri');
|
|
510
|
+
const provided = [
|
|
511
|
+
sourceUrl != null,
|
|
512
|
+
sourceResourceId != null,
|
|
513
|
+
filePath != null,
|
|
514
|
+
useStdin,
|
|
515
|
+
dataUri != null,
|
|
516
|
+
].filter(Boolean);
|
|
517
|
+
if (provided.length !== 1) {
|
|
518
|
+
throw new Error('Pass exactly one source input: --file, --source-url, --source-id, --source-resource-id, --stdin, or --data-uri.');
|
|
519
|
+
}
|
|
520
|
+
if (sourceUrl != null) {
|
|
521
|
+
print({ source: { url: sourceUrl }, url: sourceUrl, mediaType });
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (sourceResourceId != null) {
|
|
525
|
+
print({
|
|
526
|
+
source: { resourceId: sourceResourceId },
|
|
527
|
+
resourceId: sourceResourceId,
|
|
528
|
+
mediaType,
|
|
529
|
+
});
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const upload = filePath != null
|
|
533
|
+
? await ctx.client.v2.uploadFile({
|
|
534
|
+
file: await readBlob(filePath),
|
|
535
|
+
fileName: basename(filePath),
|
|
536
|
+
mediaType,
|
|
537
|
+
})
|
|
538
|
+
: dataUri != null
|
|
539
|
+
? await (async () => {
|
|
540
|
+
const { blob, contentType } = blobFromDataUri(dataUri);
|
|
541
|
+
return await ctx.client.v2.uploadFile({
|
|
542
|
+
file: blob,
|
|
543
|
+
fileName: ensureFileNameExtension(getFlag(ctx.flags, 'file-name') ?? 'data-uri', mediaType, contentType),
|
|
544
|
+
mediaType,
|
|
545
|
+
});
|
|
546
|
+
})()
|
|
547
|
+
: await ctx.client.v2.uploadFile({
|
|
548
|
+
file: await readStdinBlob(),
|
|
549
|
+
fileName: ensureFileNameExtension(getFlag(ctx.flags, 'file-name') ?? defaultFileNameForMediaType(mediaType), mediaType),
|
|
550
|
+
mediaType,
|
|
551
|
+
});
|
|
552
|
+
print({ source: { fileRef: upload.fileRef }, fileRef: upload.fileRef, mediaType });
|
|
553
|
+
};
|
|
391
554
|
const commandModels = async (subcommand, positionals, ctx) => {
|
|
392
555
|
if (subcommand === 'list') {
|
|
393
556
|
const domain = getFlag(ctx.flags, 'domain');
|
|
@@ -533,7 +696,7 @@ const commandImage = async (subcommand, ctx) => {
|
|
|
533
696
|
source,
|
|
534
697
|
});
|
|
535
698
|
const job = await pollIfRequested({ ctx, jobId: queued.jobId, apiVersion });
|
|
536
|
-
print({ queued, job, apiVersion });
|
|
699
|
+
print(await withOptionalDownloads(ctx.flags, { queued, job, apiVersion }));
|
|
537
700
|
};
|
|
538
701
|
const commandMesh = async (subcommand, ctx) => {
|
|
539
702
|
if (subcommand === 'generate') {
|
|
@@ -551,7 +714,7 @@ const commandMesh = async (subcommand, ctx) => {
|
|
|
551
714
|
isLowPoly: parseBooleanFlag(ctx.flags, 'low-poly'),
|
|
552
715
|
});
|
|
553
716
|
const job = await pollIfRequested({ ctx, jobId: queued.jobId });
|
|
554
|
-
print({ queued, job });
|
|
717
|
+
print(await withOptionalDownloads(ctx.flags, { queued, job }));
|
|
555
718
|
return;
|
|
556
719
|
}
|
|
557
720
|
if (subcommand === 'retopo') {
|
|
@@ -567,7 +730,7 @@ const commandMesh = async (subcommand, ctx) => {
|
|
|
567
730
|
polygonType: getFlag(ctx.flags, 'polygon-type'),
|
|
568
731
|
});
|
|
569
732
|
const job = await pollIfRequested({ ctx, jobId: queued.jobId });
|
|
570
|
-
print({ queued, job });
|
|
733
|
+
print(await withOptionalDownloads(ctx.flags, { queued, job }));
|
|
571
734
|
return;
|
|
572
735
|
}
|
|
573
736
|
throw new Error('Unknown mesh command. Use "mesh generate" or "mesh retopo".');
|
|
@@ -575,7 +738,8 @@ const commandMesh = async (subcommand, ctx) => {
|
|
|
575
738
|
const commandJobs = async (subcommand, positionals, ctx) => {
|
|
576
739
|
const jobId = requirePositional(positionals, 2, 'job-id');
|
|
577
740
|
if (subcommand === 'get') {
|
|
578
|
-
|
|
741
|
+
const job = await ctx.client.v2.getJob(jobId);
|
|
742
|
+
print(await withOptionalDownloads(ctx.flags, { job }));
|
|
579
743
|
return;
|
|
580
744
|
}
|
|
581
745
|
if (subcommand === 'watch') {
|
|
@@ -586,7 +750,7 @@ const commandJobs = async (subcommand, positionals, ctx) => {
|
|
|
586
750
|
stderr.write(`[poll] job=${current.id} status=${current.status}\n`);
|
|
587
751
|
},
|
|
588
752
|
});
|
|
589
|
-
print({ job });
|
|
753
|
+
print(await withOptionalDownloads(ctx.flags, { job }));
|
|
590
754
|
return;
|
|
591
755
|
}
|
|
592
756
|
throw new Error('Unknown jobs command. Use "jobs get" or "jobs watch".');
|
|
@@ -599,22 +763,200 @@ const resolvePartExtractorFromMission = async (ctx, orderId, missionId) => {
|
|
|
599
763
|
partExtractorPublicNames);
|
|
600
764
|
}
|
|
601
765
|
if (isInternalPartExtractorId(mission.agentVersion)) {
|
|
602
|
-
|
|
766
|
+
throw new Error('Could not infer the public part extractor from the mission. Pass --part-extractor with one of: ' +
|
|
767
|
+
partExtractorPublicNames);
|
|
768
|
+
}
|
|
769
|
+
return resolvePartExtractorPublicName(mission.agentVersion);
|
|
770
|
+
};
|
|
771
|
+
const redactUrl = (value) => {
|
|
772
|
+
try {
|
|
773
|
+
const url = new URL(value);
|
|
774
|
+
url.search = '';
|
|
775
|
+
url.hash = '';
|
|
776
|
+
return url.toString();
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
return value;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
const collectHttpUrls = (value, urls = new Set()) => {
|
|
783
|
+
if (typeof value === 'string') {
|
|
784
|
+
try {
|
|
785
|
+
const url = new URL(value);
|
|
786
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
787
|
+
urls.add(value);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
catch {
|
|
791
|
+
// Ignore non-URL strings.
|
|
792
|
+
}
|
|
793
|
+
return urls;
|
|
794
|
+
}
|
|
795
|
+
if (Array.isArray(value)) {
|
|
796
|
+
for (const item of value) {
|
|
797
|
+
collectHttpUrls(item, urls);
|
|
798
|
+
}
|
|
799
|
+
return urls;
|
|
800
|
+
}
|
|
801
|
+
if (value != null && typeof value === 'object') {
|
|
802
|
+
for (const item of Object.values(value)) {
|
|
803
|
+
collectHttpUrls(item, urls);
|
|
804
|
+
}
|
|
603
805
|
}
|
|
604
|
-
return
|
|
806
|
+
return urls;
|
|
807
|
+
};
|
|
808
|
+
const extensionFromContentType = (contentType) => {
|
|
809
|
+
if (contentType == null) {
|
|
810
|
+
return '';
|
|
811
|
+
}
|
|
812
|
+
const clean = contentType.split(';')[0]?.trim().toLowerCase();
|
|
813
|
+
switch (clean) {
|
|
814
|
+
case 'image/png':
|
|
815
|
+
return '.png';
|
|
816
|
+
case 'image/jpeg':
|
|
817
|
+
return '.jpg';
|
|
818
|
+
case 'image/webp':
|
|
819
|
+
return '.webp';
|
|
820
|
+
case 'image/gif':
|
|
821
|
+
return '.gif';
|
|
822
|
+
case 'model/gltf-binary':
|
|
823
|
+
return '.glb';
|
|
824
|
+
case 'model/gltf+json':
|
|
825
|
+
return '.gltf';
|
|
826
|
+
case 'application/octet-stream':
|
|
827
|
+
default:
|
|
828
|
+
return '';
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
const safeDownloadFileName = (value, index, contentType) => {
|
|
832
|
+
let name = `asset-${index + 1}`;
|
|
833
|
+
try {
|
|
834
|
+
const url = new URL(value);
|
|
835
|
+
const fromPath = basename(url.pathname);
|
|
836
|
+
if (fromPath !== '' && fromPath !== '/' && fromPath !== '.') {
|
|
837
|
+
name = fromPath;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
catch {
|
|
841
|
+
// Keep fallback name.
|
|
842
|
+
}
|
|
843
|
+
name = name.replace(/[^A-Za-z0-9._-]/g, '_').replace(/^_+/, '');
|
|
844
|
+
if (name === '' || name === '.' || name === '..') {
|
|
845
|
+
name = `asset-${index + 1}`;
|
|
846
|
+
}
|
|
847
|
+
if (extname(name) === '') {
|
|
848
|
+
name = `${name}${extensionFromContentType(contentType)}`;
|
|
849
|
+
}
|
|
850
|
+
return `${String(index + 1).padStart(3, '0')}-${name}`;
|
|
851
|
+
};
|
|
852
|
+
const maybeDownloadUrls = async (flags, value) => {
|
|
853
|
+
if (!hasFlag(flags, 'download')) {
|
|
854
|
+
return undefined;
|
|
855
|
+
}
|
|
856
|
+
const outDir = getFlag(flags, 'out-dir') ?? getFlag(flags, 'out');
|
|
857
|
+
if (outDir == null || outDir === 'true') {
|
|
858
|
+
throw new Error('Missing download output directory. Pass --out-dir <dir> or --out <dir>.');
|
|
859
|
+
}
|
|
860
|
+
await mkdir(outDir, { recursive: true });
|
|
861
|
+
const urls = [...collectHttpUrls(value)];
|
|
862
|
+
const files = [];
|
|
863
|
+
for (const [index, url] of urls.entries()) {
|
|
864
|
+
const response = await fetch(url);
|
|
865
|
+
if (!response.ok) {
|
|
866
|
+
throw new Error(`Download failed: ${redactUrl(url)} returned HTTP ${response.status}`);
|
|
867
|
+
}
|
|
868
|
+
const contentType = response.headers.get('content-type');
|
|
869
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
870
|
+
const fileName = safeDownloadFileName(url, index, contentType);
|
|
871
|
+
const filePath = join(outDir, fileName);
|
|
872
|
+
await writeFile(filePath, bytes);
|
|
873
|
+
files.push({
|
|
874
|
+
url: redactUrl(url),
|
|
875
|
+
path: filePath,
|
|
876
|
+
contentType,
|
|
877
|
+
bytes: bytes.byteLength,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
const manifest = { generatedAt: new Date().toISOString(), files };
|
|
881
|
+
await writeFile(join(outDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
882
|
+
return { outDir, files };
|
|
883
|
+
};
|
|
884
|
+
const withOptionalDownloads = async (flags, value) => {
|
|
885
|
+
const downloads = await maybeDownloadUrls(flags, value);
|
|
886
|
+
return downloads == null ? value : { ...value, downloads };
|
|
887
|
+
};
|
|
888
|
+
const readyTaskIdsFromStatus = (status) => {
|
|
889
|
+
const missions = status != null && typeof status === 'object' && 'missions' in status
|
|
890
|
+
? status.missions
|
|
891
|
+
: undefined;
|
|
892
|
+
if (!Array.isArray(missions)) {
|
|
893
|
+
return [];
|
|
894
|
+
}
|
|
895
|
+
return missions.flatMap(mission => {
|
|
896
|
+
if (mission == null || typeof mission !== 'object' || !('tasks' in mission)) {
|
|
897
|
+
return [];
|
|
898
|
+
}
|
|
899
|
+
const tasks = mission.tasks;
|
|
900
|
+
if (!Array.isArray(tasks)) {
|
|
901
|
+
return [];
|
|
902
|
+
}
|
|
903
|
+
return tasks.flatMap(task => {
|
|
904
|
+
if (task == null || typeof task !== 'object') {
|
|
905
|
+
return [];
|
|
906
|
+
}
|
|
907
|
+
const id = task.id;
|
|
908
|
+
const taskStatus = task.status;
|
|
909
|
+
return typeof id === 'string' &&
|
|
910
|
+
typeof taskStatus === 'string' &&
|
|
911
|
+
['ready', 'pending', 'pending_review'].includes(taskStatus)
|
|
912
|
+
? [id]
|
|
913
|
+
: [];
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
};
|
|
917
|
+
const singleMissionIdFromStatus = (status) => {
|
|
918
|
+
const missions = status != null && typeof status === 'object' && 'missions' in status
|
|
919
|
+
? status.missions
|
|
920
|
+
: undefined;
|
|
921
|
+
if (!Array.isArray(missions) || missions.length !== 1) {
|
|
922
|
+
throw new Error('Pass --mission-id when the production order does not have exactly one mission.');
|
|
923
|
+
}
|
|
924
|
+
const missionId = missions[0].id;
|
|
925
|
+
if (typeof missionId !== 'string' || missionId.trim() === '') {
|
|
926
|
+
throw new Error('Could not infer mission id. Pass --mission-id.');
|
|
927
|
+
}
|
|
928
|
+
return missionId;
|
|
605
929
|
};
|
|
606
930
|
const commandProduction = async (subcommand, positionals, ctx) => {
|
|
607
931
|
if (subcommand === 'analyze') {
|
|
608
|
-
|
|
609
|
-
|
|
932
|
+
const analyzed = await ctx.client.v1.analyzeProduction({
|
|
933
|
+
...(await resolveProductionAnalyzeSource(ctx)),
|
|
610
934
|
agentVersion: resolvePartExtractorForAnalyze(ctx.flags),
|
|
611
935
|
name: getFlag(ctx.flags, 'name'),
|
|
612
|
-
})
|
|
936
|
+
});
|
|
937
|
+
const status = hasFlag(ctx.flags, 'wait')
|
|
938
|
+
? await pollProductionStatus({
|
|
939
|
+
ctx,
|
|
940
|
+
orderId: analyzed.orderId,
|
|
941
|
+
terminalStatuses: productionAnalyzeTerminalStatuses,
|
|
942
|
+
})
|
|
943
|
+
: undefined;
|
|
944
|
+
print(await withOptionalDownloads(ctx.flags, sanitizeProductionOutput({ analyzed, status })));
|
|
613
945
|
return;
|
|
614
946
|
}
|
|
615
947
|
if (subcommand === 'status') {
|
|
616
948
|
const orderId = requirePositional(positionals, 2, 'order-id');
|
|
617
|
-
|
|
949
|
+
const status = hasFlag(ctx.flags, 'wait')
|
|
950
|
+
? await pollProductionStatus({
|
|
951
|
+
ctx,
|
|
952
|
+
orderId,
|
|
953
|
+
terminalStatuses: new Set([
|
|
954
|
+
...productionAnalyzeTerminalStatuses,
|
|
955
|
+
...productionExecuteTerminalStatuses,
|
|
956
|
+
]),
|
|
957
|
+
})
|
|
958
|
+
: await ctx.client.v1.getProductionStatus(orderId);
|
|
959
|
+
print(await withOptionalDownloads(ctx.flags, sanitizeProductionOutput({ status })));
|
|
618
960
|
return;
|
|
619
961
|
}
|
|
620
962
|
if (subcommand === 'execute') {
|
|
@@ -633,18 +975,105 @@ const commandProduction = async (subcommand, positionals, ctx) => {
|
|
|
633
975
|
const partExtractorFlag = getPartExtractorFlag(ctx.flags);
|
|
634
976
|
const agentVersion = partExtractorFlag == null
|
|
635
977
|
? await resolvePartExtractorFromMission(ctx, orderId, missionId)
|
|
636
|
-
:
|
|
637
|
-
|
|
978
|
+
: resolvePartExtractorPublicName(partExtractorFlag);
|
|
979
|
+
const executed = await ctx.client.v1.executeProduction({
|
|
638
980
|
orderId,
|
|
639
981
|
missionId,
|
|
640
982
|
confirmedTaskIds,
|
|
641
983
|
agentVersion,
|
|
642
984
|
partExtractionMode,
|
|
643
|
-
})
|
|
985
|
+
});
|
|
986
|
+
const status = hasFlag(ctx.flags, 'wait')
|
|
987
|
+
? await pollProductionStatus({
|
|
988
|
+
ctx,
|
|
989
|
+
orderId,
|
|
990
|
+
terminalStatuses: productionExecuteTerminalStatuses,
|
|
991
|
+
})
|
|
992
|
+
: undefined;
|
|
993
|
+
print(await withOptionalDownloads(ctx.flags, sanitizeProductionOutput({ executed, status })));
|
|
644
994
|
return;
|
|
645
995
|
}
|
|
646
996
|
throw new Error('Unknown production command. Use "production analyze", "production status", or "production execute".');
|
|
647
997
|
};
|
|
998
|
+
const commandParts = async (subcommand, ctx) => {
|
|
999
|
+
if (subcommand !== 'split') {
|
|
1000
|
+
throw new Error('Unknown parts command. Use "parts split".');
|
|
1001
|
+
}
|
|
1002
|
+
let orderId = getFlag(ctx.flags, 'order-id');
|
|
1003
|
+
let analyzed;
|
|
1004
|
+
let status;
|
|
1005
|
+
if (orderId == null) {
|
|
1006
|
+
const result = await ctx.client.v1.analyzeProduction({
|
|
1007
|
+
...(await resolveProductionAnalyzeSource(ctx)),
|
|
1008
|
+
agentVersion: resolvePartExtractorForAnalyze(ctx.flags),
|
|
1009
|
+
name: getFlag(ctx.flags, 'name'),
|
|
1010
|
+
});
|
|
1011
|
+
analyzed = result;
|
|
1012
|
+
orderId = result.orderId;
|
|
1013
|
+
if (hasFlag(ctx.flags, 'wait') || hasFlag(ctx.flags, 'all-ready')) {
|
|
1014
|
+
status = await pollProductionStatus({
|
|
1015
|
+
ctx,
|
|
1016
|
+
orderId,
|
|
1017
|
+
terminalStatuses: productionAnalyzeTerminalStatuses,
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
else if (hasFlag(ctx.flags, 'wait') || hasFlag(ctx.flags, 'all-ready')) {
|
|
1022
|
+
status = await pollProductionStatus({
|
|
1023
|
+
ctx,
|
|
1024
|
+
orderId,
|
|
1025
|
+
terminalStatuses: productionAnalyzeTerminalStatuses,
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
let taskIds = getFlagValues(ctx.flags, 'task-id');
|
|
1029
|
+
if (hasFlag(ctx.flags, 'all-ready')) {
|
|
1030
|
+
if (status == null) {
|
|
1031
|
+
status = await ctx.client.v1.getProductionStatus(orderId);
|
|
1032
|
+
}
|
|
1033
|
+
taskIds = readyTaskIdsFromStatus(status);
|
|
1034
|
+
if (taskIds.length === 0) {
|
|
1035
|
+
throw new Error('No ready tasks were found for --all-ready.');
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
let executed;
|
|
1039
|
+
let executionStatus;
|
|
1040
|
+
if (taskIds.length > 0) {
|
|
1041
|
+
if (status == null) {
|
|
1042
|
+
status = await ctx.client.v1.getProductionStatus(orderId);
|
|
1043
|
+
}
|
|
1044
|
+
const missionId = getFlag(ctx.flags, 'mission-id') ?? singleMissionIdFromStatus(status);
|
|
1045
|
+
const partExtractorFlag = getPartExtractorFlag(ctx.flags);
|
|
1046
|
+
const agentVersion = partExtractorFlag == null
|
|
1047
|
+
? await resolvePartExtractorFromMission(ctx, orderId, missionId)
|
|
1048
|
+
: resolvePartExtractorPublicName(partExtractorFlag);
|
|
1049
|
+
const partExtractionMode = getFlag(ctx.flags, 'part-extraction-mode');
|
|
1050
|
+
if (partExtractionMode != null &&
|
|
1051
|
+
partExtractionMode !== 'fast' &&
|
|
1052
|
+
partExtractionMode !== 'high_quality') {
|
|
1053
|
+
throw new Error('--part-extraction-mode must be "fast" or "high_quality"');
|
|
1054
|
+
}
|
|
1055
|
+
executed = await ctx.client.v1.executeProduction({
|
|
1056
|
+
orderId,
|
|
1057
|
+
missionId,
|
|
1058
|
+
confirmedTaskIds: taskIds,
|
|
1059
|
+
agentVersion,
|
|
1060
|
+
partExtractionMode,
|
|
1061
|
+
});
|
|
1062
|
+
if (hasFlag(ctx.flags, 'wait')) {
|
|
1063
|
+
executionStatus = await pollProductionStatus({
|
|
1064
|
+
ctx,
|
|
1065
|
+
orderId,
|
|
1066
|
+
terminalStatuses: productionExecuteTerminalStatuses,
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
print(await withOptionalDownloads(ctx.flags, sanitizeProductionOutput({
|
|
1071
|
+
analyzed,
|
|
1072
|
+
status,
|
|
1073
|
+
executed,
|
|
1074
|
+
executionStatus,
|
|
1075
|
+
})));
|
|
1076
|
+
};
|
|
648
1077
|
const run = async () => {
|
|
649
1078
|
const parsed = parseArgs(argv.slice(2));
|
|
650
1079
|
if (hasFlag(parsed.flags, 'help') || parsed.positionals.length === 0) {
|
|
@@ -664,6 +1093,9 @@ const run = async () => {
|
|
|
664
1093
|
case 'files':
|
|
665
1094
|
await commandFiles(subcommand, parsed.positionals, ctx);
|
|
666
1095
|
return;
|
|
1096
|
+
case 'source':
|
|
1097
|
+
await commandSource(subcommand, ctx);
|
|
1098
|
+
return;
|
|
667
1099
|
case 'image':
|
|
668
1100
|
await commandImage(subcommand, ctx);
|
|
669
1101
|
return;
|
|
@@ -676,6 +1108,9 @@ const run = async () => {
|
|
|
676
1108
|
case 'production':
|
|
677
1109
|
await commandProduction(subcommand, parsed.positionals, ctx);
|
|
678
1110
|
return;
|
|
1111
|
+
case 'parts':
|
|
1112
|
+
await commandParts(subcommand, ctx);
|
|
1113
|
+
return;
|
|
679
1114
|
default:
|
|
680
1115
|
throw new Error(`Unknown command: ${command}`);
|
|
681
1116
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assethub/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Command line interface for AssetHub.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"assethub": "
|
|
7
|
+
"assethub": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -20,19 +20,19 @@
|
|
|
20
20
|
"README.md",
|
|
21
21
|
"LICENSE"
|
|
22
22
|
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/index.js",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit"
|
|
26
|
+
},
|
|
23
27
|
"license": "MIT",
|
|
24
28
|
"publishConfig": {
|
|
25
29
|
"access": "public"
|
|
26
30
|
},
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@assethub/api-client": "^0.1.
|
|
32
|
+
"@assethub/api-client": "^0.1.1"
|
|
29
33
|
},
|
|
30
34
|
"devDependencies": {
|
|
31
35
|
"@types/node": "24.10.1",
|
|
32
36
|
"typescript": "5.9.3"
|
|
33
|
-
},
|
|
34
|
-
"scripts": {
|
|
35
|
-
"build": "tsc -p tsconfig.json && chmod +x dist/index.js",
|
|
36
|
-
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit"
|
|
37
37
|
}
|
|
38
|
-
}
|
|
38
|
+
}
|