@playcraft/cli 0.1.68 → 0.1.69

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.
Files changed (26) hide show
  1. package/dist/asset-library/provenance.js +169 -0
  2. package/dist/commands/library-messages.js +27 -9
  3. package/dist/commands/library.js +62 -10
  4. package/dist/model-adaptation/project-template.js +56 -3
  5. package/dist/model-adaptation/project.js +15 -1
  6. package/dist/workspace-runtime/batch/freeze.js +15 -2
  7. package/dist/workspace-runtime/batch/git.js +16 -4
  8. package/dist/workspace-runtime/batch/publication.js +96 -0
  9. package/dist/workspace-runtime/batch/source-candidate.js +2 -1
  10. package/dist/workspace-runtime/batch/source-publish.js +26 -15
  11. package/dist/workspace-runtime/persistence/sqlite-worker.js +2 -1
  12. package/dist/workspace-runtime/serve.js +50 -18
  13. package/dist/workspace-runtime/server/dispatch.js +42 -9
  14. package/dist/workspace-runtime/server/http.js +12 -2
  15. package/dist/workspace-runtime/verify-artifact.js +2 -1
  16. package/dist/workspace-runtime/workspaces/archive.js +28 -3
  17. package/dist/workspace-runtime/workspaces/asset-library-import.js +33 -2
  18. package/dist/workspace-runtime/workspaces/asset-provenance-verifier.js +36 -0
  19. package/dist/workspace-runtime/workspaces/registry.js +3 -5
  20. package/dist/workspace-runtime/workspaces/remote-attach-marker.js +55 -0
  21. package/package.json +3 -3
  22. package/project-template/.claude/skills/playcraft-workspace-assets/SKILL.md +15 -4
  23. package/project-template-v2/.claude/skills/playcraft-workspace-assets/SKILL.md +15 -4
  24. package/project-template-v2/.playcraft/skills.lock.json +2 -2
  25. package/project-templates/phaser/.claude/skills/playcraft-workspace-assets/SKILL.md +15 -4
  26. package/project-templates/threejs/.claude/skills/playcraft-workspace-assets/SKILL.md +15 -4
@@ -6,6 +6,7 @@ import { BINARY_WORKSPACE_FILE_MAX_BYTES, WORKSPACE_LIBRARY_ATTACHMENT_MAX_BYTES
6
6
  import { observeWorkspaceBinaryFile, writeWorkspaceBinaryFile } from './binary-write.js';
7
7
  import { shareWorkspaceAssetDirectory } from './asset-publication-access.js';
8
8
  import { ensureSharedDirectory } from './shared-write.js';
9
+ import { publishLibraryModels } from '../../asset-library/provenance.js';
9
10
  import { isEditableAudioLibraryAsset, prepareAudioLibraryImport } from './audio-assets/library-import.js';
10
11
  import { readAudioArtifactPackage } from './audio-assets/artifact-package.js';
11
12
  const RESERVED = new Set(['.git', 'node_modules', '.playcraft-temp', '.tmp']);
@@ -341,6 +342,8 @@ export async function importWorkspaceLibraryAssets(root, selection, snapshot, as
341
342
  await createPublicDirectory(rootReal, payload.targetDirectory);
342
343
  const result = { paths: [], skippedPaths: [], failures: [...transport.failures] };
343
344
  const processed = new Set(transport.failures.map(failure => failure.assetId));
345
+ const curated = [];
346
+ const plannedTargets = new Set();
344
347
  const progress = () => options.onProgress?.({ ...result, failures: [...result.failures,
345
348
  ...payload.assetIds.filter(id => !processed.has(id)).map(assetId => ({ assetId, error: 'asset_library_import_interrupted' }))] });
346
349
  await progress();
@@ -361,7 +364,7 @@ export async function importWorkspaceLibraryAssets(root, selection, snapshot, as
361
364
  let name = originalName;
362
365
  let target = validPath([payload.targetDirectory, name].filter(Boolean).join('/'));
363
366
  let existing = await statTarget(rootReal, target);
364
- if (existing && payload.collision === 'skip') {
367
+ if ((existing || plannedTargets.has(target)) && payload.collision === 'skip') {
365
368
  result.skippedPaths.push(target);
366
369
  processed.add(assetId);
367
370
  await progress();
@@ -370,7 +373,7 @@ export async function importWorkspaceLibraryAssets(root, selection, snapshot, as
370
373
  if (payload.collision === 'rename') {
371
374
  const extension = asset.kind === 'bundle' ? '' : extname(originalName);
372
375
  const stem = originalName.slice(0, originalName.length - extension.length);
373
- for (let suffix = 1; existing; suffix++) {
376
+ for (let suffix = 1; existing || plannedTargets.has(target); suffix++) {
374
377
  if (suffix > 10_000)
375
378
  throw new AssetLibraryImportError('content_conflict');
376
379
  name = `${stem} (${suffix})${extension}`;
@@ -378,6 +381,14 @@ export async function importWorkspaceLibraryAssets(root, selection, snapshot, as
378
381
  existing = await statTarget(rootReal, target);
379
382
  }
380
383
  }
384
+ if (asset.provenance) {
385
+ const observed = existing ? await observeWorkspaceBinaryFile(rootReal, target) : undefined;
386
+ curated.push({ assetId, path: target, bytes: Buffer.from(asset.files[0].content, 'base64'),
387
+ provenance: asset.provenance, expectedContentHash: observed?.contentHash ?? null });
388
+ plannedTargets.add(target);
389
+ await progress();
390
+ continue;
391
+ }
381
392
  if (asset.kind === 'bundle') {
382
393
  const audio = isEditableAudioLibraryAsset(asset) ? await prepareAudioLibraryImport(rootReal, target, asset, assertCurrent) : undefined;
383
394
  const incoming = audio?.asset ?? asset;
@@ -396,6 +407,8 @@ export async function importWorkspaceLibraryAssets(root, selection, snapshot, as
396
407
  }
397
408
  }
398
409
  catch (error) {
410
+ if (error instanceof Error && ['stale_lease', 'batch_workspace_readonly'].includes(error.message))
411
+ throw error;
399
412
  const code = error instanceof AssetLibraryImportError ? error.message
400
413
  : error instanceof Error && ['invalid_audio_library_package', 'audio_library_engine_unavailable', 'audio_library_source_unverified', 'audio_representation_conflict'].includes(error.message) ? error.message
401
414
  : error?.code === 'content_conflict' ? 'content_conflict'
@@ -408,5 +421,23 @@ export async function importWorkspaceLibraryAssets(root, selection, snapshot, as
408
421
  if (publishedTransaction)
409
422
  await removeTransaction(rootReal, publishedTransaction).catch(() => undefined);
410
423
  }
424
+ if (curated.length) {
425
+ try {
426
+ const receipt = await publishLibraryModels(rootReal, curated, { operationId: options.operationId,
427
+ scope: options.scope, assertCurrent });
428
+ result.bundleReceipt = receipt;
429
+ result.paths.push(...receipt.files.map(file => file.path));
430
+ }
431
+ catch (error) {
432
+ if (error instanceof Error && ['stale_lease', 'batch_workspace_readonly'].includes(error.message))
433
+ throw error;
434
+ const code = error.code ?? (error instanceof Error ? error.message : 'asset_library_import_failed');
435
+ for (const model of curated)
436
+ result.failures.push({ assetId: model.assetId, error: code.slice(0, 256) });
437
+ }
438
+ for (const model of curated)
439
+ processed.add(model.assetId);
440
+ await progress();
441
+ }
411
442
  return workspaceAssetLibraryImportResultSchema.parse(result);
412
443
  }
@@ -0,0 +1,36 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { assetLibraryModelProvenanceSchema, canonicalAssetLibraryModelProvenance } from '@playcraft/common/cloud-workspace/runtime';
4
+ import { businessEndpoint } from '../../utils/environment-endpoints.js';
5
+ import { ensureCliCredentials } from './cli-credentials.js';
6
+ /** Compare the portable source record with the current user's immutable server revision. */
7
+ export async function verifyRuntimeAssetProvenance(state, provenance) {
8
+ await ensureCliCredentials(state);
9
+ const credential = JSON.parse(await readFile(join(state.config.workspaceRoot, '.playcraft.json'), 'utf8'));
10
+ if (typeof credential.apiUrl !== 'string' || typeof credential.token !== 'string' || !credential.token)
11
+ throw new Error('asset_provenance_authority_unavailable');
12
+ const url = businessEndpoint(credential.apiUrl, `/api/agent/tools/asset-library/${encodeURIComponent(provenance.assetId)}`);
13
+ url.searchParams.set('revisionId', provenance.revisionId);
14
+ const response = await fetch(url, { redirect: 'error', signal: AbortSignal.timeout(15_000),
15
+ headers: { 'X-Sandbox-Token': credential.token, 'playcraft-env': state.config.environment } });
16
+ if (!response.ok) {
17
+ await response.body?.cancel();
18
+ throw new Error('asset_provenance_authority_unavailable');
19
+ }
20
+ const maximum = 256 * 1024, chunks = [];
21
+ let length = 0;
22
+ if (!response.body)
23
+ throw new Error('asset_provenance_authority_unavailable');
24
+ for await (const chunk of response.body) {
25
+ length += chunk.byteLength;
26
+ if (length > maximum) {
27
+ await response.body.cancel().catch(() => undefined);
28
+ throw new Error('asset_provenance_authority_response_bound');
29
+ }
30
+ chunks.push(chunk);
31
+ }
32
+ const result = JSON.parse(Buffer.concat(chunks).toString('utf8'));
33
+ const authoritative = assetLibraryModelProvenanceSchema.parse(result.provenance);
34
+ if (canonicalAssetLibraryModelProvenance(authoritative) !== canonicalAssetLibraryModelProvenance(provenance))
35
+ throw new Error('asset_provenance_authority_mismatch');
36
+ }
@@ -1,5 +1,5 @@
1
1
  import { execFile } from 'node:child_process';
2
- import { lstat, mkdir, realpath, rm, writeFile } from 'node:fs/promises';
2
+ import { lstat, mkdir, realpath, rm } from 'node:fs/promises';
3
3
  import { dirname, join, relative } from 'node:path';
4
4
  import { promisify } from 'node:util';
5
5
  import { isLocalWorkspaceGitAuthMode, sameWorkspaceRegistration, workspaceRegistrationSchema, batchSourceSnapshotSchema, } from '@playcraft/common/cloud-workspace/runtime';
@@ -10,6 +10,7 @@ import { ensureWorkspaceSkills, readWorkspaceSkills } from './skills.js';
10
10
  import { loadWorkspaceSkillArtifact, workspacePlatformSkillAccess } from './skill-artifact-loader.js';
11
11
  import { writeWorkspaceCliContext } from './cli-context.js';
12
12
  import { attachWorkspaceRemote } from './git.js';
13
+ import { markRemoteAttachPending } from './remote-attach-marker.js';
13
14
  import { ensureWorkspaceArtifactInstructions } from './instructions.js';
14
15
  import { repairWorkspaceShares } from './workspace-shares.js';
15
16
  export { WorkspaceContextError } from './context-error.js';
@@ -183,10 +184,7 @@ export async function registerWorkspace(state, input, leaseFence, preparation, r
183
184
  && (needsRemote || remoteUpgrade || await lstat(attachMarker).then(() => true, () => false))) {
184
185
  if (preparation?.mode !== 'clone' || !state.gitCredentials)
185
186
  throw new WorkspaceContextError('workspace_git_gateway_unavailable');
186
- // private
187
- await mkdir(join(state.config.stateDir, 'workspace-remote-attach'), { recursive: true, mode: 0o700 });
188
- // private
189
- await writeFile(attachMarker, JSON.stringify({ repositoryUrl: binding.repositoryUrl, branchName: binding.branchName }), { mode: 0o600 });
187
+ await markRemoteAttachPending(state, binding.workspaceId, binding.repositoryUrl, binding.branchName);
190
188
  // The credential helper only serves a workspace the journal knows to be bound.
191
189
  await state.journal.putWorkspace({ ...binding, directories });
192
190
  try {
@@ -0,0 +1,55 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { link, mkdir, open, readFile, unlink } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ function markerPath(state, workspaceId) {
5
+ return join(state.config.stateDir, 'workspace-remote-attach', `${workspaceId}.json`);
6
+ }
7
+ /** Caller holds the workspace journal lock. Never replace a different unfinished attach. */
8
+ export async function markRemoteAttachPending(state, workspaceId, repositoryUrl, branchName) {
9
+ const path = markerPath(state, workspaceId);
10
+ const directory = join(state.config.stateDir, 'workspace-remote-attach');
11
+ // private: the attach marker is controller state outside the shared workspace tree.
12
+ await mkdir(directory, { recursive: true, mode: 0o700 });
13
+ const expected = { repositoryUrl, branchName };
14
+ const temporary = `${path}.${randomUUID()}.tmp`;
15
+ // private: only the controller reads an unfinished attach back.
16
+ const handle = await open(temporary, 'wx', 0o600);
17
+ try {
18
+ await handle.writeFile(JSON.stringify(expected));
19
+ await handle.sync();
20
+ await handle.close();
21
+ try {
22
+ await link(temporary, path);
23
+ }
24
+ catch (error) {
25
+ if (error.code !== 'EEXIST')
26
+ throw error;
27
+ const previous = JSON.parse(await readFile(path, 'utf8'));
28
+ if (previous.repositoryUrl !== repositoryUrl || previous.branchName !== branchName)
29
+ throw new Error('batch_source_identity_mismatch');
30
+ }
31
+ const folder = await open(directory, 'r');
32
+ try {
33
+ await folder.sync();
34
+ }
35
+ finally {
36
+ await folder.close();
37
+ }
38
+ }
39
+ finally {
40
+ await handle.close();
41
+ await unlink(temporary).catch(() => undefined);
42
+ }
43
+ }
44
+ /** Caller holds the workspace journal lock to serialize with attach and marker removal. */
45
+ export async function remoteAttachPending(state, workspaceId) {
46
+ try {
47
+ await readFile(markerPath(state, workspaceId));
48
+ return true;
49
+ }
50
+ catch (error) {
51
+ if (error.code === 'ENOENT')
52
+ return false;
53
+ throw error;
54
+ }
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/cli",
3
- "version": "0.1.68",
3
+ "version": "0.1.69",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -56,8 +56,8 @@
56
56
  "@gltf-transform/core": "^4.3.0",
57
57
  "@gltf-transform/extensions": "^4.3.0",
58
58
  "@gltf-transform/functions": "^4.3.0",
59
- "@playcraft/build": "0.1.68",
60
- "@playcraft/common": "0.1.68",
59
+ "@playcraft/build": "0.1.69",
60
+ "@playcraft/common": "0.1.69",
61
61
  "better-sqlite3-pinned": "npm:better-sqlite3@13.0.3",
62
62
  "chokidar": "^4.0.3",
63
63
  "commander": "^13.1.0",
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: playcraft-workspace-assets
3
- description: Use when finding or downloading shared library assets, or creating and managing durable image, audio, sprite, animation, model, or video generation tasks in a PlayCraft cloud workspace.
3
+ description: Use when selecting 3D asset kits, finding or downloading shared library assets, or managing durable media generation tasks in a PlayCraft cloud workspace.
4
4
  compatibility: agent,opencode
5
- version: 1.0.0
5
+ version: 1.1.0
6
6
  surfaces:
7
7
  - sandbox-agent
8
8
  ---
@@ -13,6 +13,17 @@ surfaces:
13
13
 
14
14
  ## 查找并复用素材
15
15
 
16
+ 一组三维物件的检索入口为素材包;包内覆盖结果给出逐项可用模型及缺口:
17
+
18
+ ```bash
19
+ playcraft library kits --search "家具" --json
20
+ playcraft library kit kenney-furniture-kit --needs "椅子,桌子,打印机" --json
21
+ ```
22
+
23
+ 素材包是主题与风格的逻辑集合,区别于一起下载的文件集合及版权来源归档。`coverageScope: partial` 表示仅部分收录;`members` 才是当前可用模型及确切版本,`needCoverage` 返回同包每项需求的候选或缺口。`inventoryRevision` 只标识当前库存,不是持久保存的历史整包版本。`styleGuidance` 提供设计参照,`compatibility` 未验证的单位、朝向和连接规则不能用于兼容性承诺;生成供应商不会自动匹配该风格。
24
+
25
+ 包内模型和独立素材都使用以下单素材入口。模型下载加 `--revision-id <revisionId>`,保留命令生成的 `.playcraft/asset-provenance.json`、许可和来源说明;工程文件路径、模型版本与来源记录共同标识实际使用实例。
26
+
16
27
  ```bash
17
28
  playcraft library search "角色" --type texture --tags character --limit 20 --json
18
29
  playcraft library facets --json
@@ -21,9 +32,9 @@ playcraft library files <assetId> --json
21
32
  playcraft library download <assetId> --output ./assets/character
22
33
  ```
23
34
 
24
- 检索字段是名称、素材库路径和标签;还可按分类、子分类、团队和分页筛选。使用检索结果返回的素材库编号,与 `tools list-assets` 的项目资产编号分别处理。不要把下载预览图当作完整素材包。
35
+ 检索字段是名称、素材库路径和标签,已审核模型还支持别名、用途、许可及能力筛选;也可按分类、子分类、团队和分页筛选。使用检索结果返回的素材库编号,与 `tools list-assets` 的项目资产编号分别处理。预览图不能代替模型或其依赖。
25
36
 
26
- `--output` 对单文件表示文件路径,对素材包表示目录;`--output-dir` 始终表示目录。目标路径必须尚不存在。素材包包含清单、嵌套目录和模型贴图等依赖,下载后保留其相对路径。用户要求修改已有素材时,先下载到新路径并核对,再按用户要求替换。
37
+ `--output` 对单文件表示文件路径,对文件集合表示目录;`--output-dir` 始终表示目录。目标路径必须尚不存在。文件集合包含清单、嵌套目录和模型贴图等依赖,下载后保留其相对路径。用户要求修改已有素材时,先下载到新路径并核对,再按用户要求替换。
27
38
 
28
39
  ## 创建持久化生成任务
29
40
 
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: playcraft-workspace-assets
3
- description: Use when finding or downloading shared library assets, or creating and managing durable image, audio, sprite, animation, model, or video generation tasks in a PlayCraft cloud workspace.
3
+ description: Use when selecting 3D asset kits, finding or downloading shared library assets, or managing durable media generation tasks in a PlayCraft cloud workspace.
4
4
  compatibility: agent,opencode
5
- version: 1.0.0
5
+ version: 1.1.0
6
6
  surfaces:
7
7
  - sandbox-agent
8
8
  ---
@@ -13,6 +13,17 @@ surfaces:
13
13
 
14
14
  ## 查找并复用素材
15
15
 
16
+ 一组三维物件的检索入口为素材包;包内覆盖结果给出逐项可用模型及缺口:
17
+
18
+ ```bash
19
+ playcraft library kits --search "家具" --json
20
+ playcraft library kit kenney-furniture-kit --needs "椅子,桌子,打印机" --json
21
+ ```
22
+
23
+ 素材包是主题与风格的逻辑集合,区别于一起下载的文件集合及版权来源归档。`coverageScope: partial` 表示仅部分收录;`members` 才是当前可用模型及确切版本,`needCoverage` 返回同包每项需求的候选或缺口。`inventoryRevision` 只标识当前库存,不是持久保存的历史整包版本。`styleGuidance` 提供设计参照,`compatibility` 未验证的单位、朝向和连接规则不能用于兼容性承诺;生成供应商不会自动匹配该风格。
24
+
25
+ 包内模型和独立素材都使用以下单素材入口。模型下载加 `--revision-id <revisionId>`,保留命令生成的 `.playcraft/asset-provenance.json`、许可和来源说明;工程文件路径、模型版本与来源记录共同标识实际使用实例。
26
+
16
27
  ```bash
17
28
  playcraft library search "角色" --type texture --tags character --limit 20 --json
18
29
  playcraft library facets --json
@@ -21,9 +32,9 @@ playcraft library files <assetId> --json
21
32
  playcraft library download <assetId> --output ./assets/character
22
33
  ```
23
34
 
24
- 检索字段是名称、素材库路径和标签;还可按分类、子分类、团队和分页筛选。使用检索结果返回的素材库编号,与 `tools list-assets` 的项目资产编号分别处理。不要把下载预览图当作完整素材包。
35
+ 检索字段是名称、素材库路径和标签,已审核模型还支持别名、用途、许可及能力筛选;也可按分类、子分类、团队和分页筛选。使用检索结果返回的素材库编号,与 `tools list-assets` 的项目资产编号分别处理。预览图不能代替模型或其依赖。
25
36
 
26
- `--output` 对单文件表示文件路径,对素材包表示目录;`--output-dir` 始终表示目录。目标路径必须尚不存在。素材包包含清单、嵌套目录和模型贴图等依赖,下载后保留其相对路径。用户要求修改已有素材时,先下载到新路径并核对,再按用户要求替换。
37
+ `--output` 对单文件表示文件路径,对文件集合表示目录;`--output-dir` 始终表示目录。目标路径必须尚不存在。文件集合包含清单、嵌套目录和模型贴图等依赖,下载后保留其相对路径。用户要求修改已有素材时,先下载到新路径并核对,再按用户要求替换。
27
38
 
28
39
  ## 创建持久化生成任务
29
40
 
@@ -171,8 +171,8 @@
171
171
  },
172
172
  {
173
173
  "skillRef": "remix/playcraft-workspace-assets",
174
- "version": "1.0.0",
175
- "artifactHash": "f098949b043a229e561d92a34c3a65b44ffbd83d1758ef30ac09d2d0d5ea2570",
174
+ "version": "1.1.0",
175
+ "artifactHash": "bb8d3c17953e85e006f5de75bd267d29ea9cef329298d169ba57e11f59f4f21e",
176
176
  "surface": "sandbox-agent",
177
177
  "mountPath": ".playcraft/skills/remix__playcraft-workspace-assets"
178
178
  }
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: playcraft-workspace-assets
3
- description: Use when finding or downloading shared library assets, or creating and managing durable image, audio, sprite, animation, model, or video generation tasks in a PlayCraft cloud workspace.
3
+ description: Use when selecting 3D asset kits, finding or downloading shared library assets, or managing durable media generation tasks in a PlayCraft cloud workspace.
4
4
  compatibility: agent,opencode
5
- version: 1.0.0
5
+ version: 1.1.0
6
6
  surfaces:
7
7
  - sandbox-agent
8
8
  ---
@@ -13,6 +13,17 @@ surfaces:
13
13
 
14
14
  ## 查找并复用素材
15
15
 
16
+ 一组三维物件的检索入口为素材包;包内覆盖结果给出逐项可用模型及缺口:
17
+
18
+ ```bash
19
+ playcraft library kits --search "家具" --json
20
+ playcraft library kit kenney-furniture-kit --needs "椅子,桌子,打印机" --json
21
+ ```
22
+
23
+ 素材包是主题与风格的逻辑集合,区别于一起下载的文件集合及版权来源归档。`coverageScope: partial` 表示仅部分收录;`members` 才是当前可用模型及确切版本,`needCoverage` 返回同包每项需求的候选或缺口。`inventoryRevision` 只标识当前库存,不是持久保存的历史整包版本。`styleGuidance` 提供设计参照,`compatibility` 未验证的单位、朝向和连接规则不能用于兼容性承诺;生成供应商不会自动匹配该风格。
24
+
25
+ 包内模型和独立素材都使用以下单素材入口。模型下载加 `--revision-id <revisionId>`,保留命令生成的 `.playcraft/asset-provenance.json`、许可和来源说明;工程文件路径、模型版本与来源记录共同标识实际使用实例。
26
+
16
27
  ```bash
17
28
  playcraft library search "角色" --type texture --tags character --limit 20 --json
18
29
  playcraft library facets --json
@@ -21,9 +32,9 @@ playcraft library files <assetId> --json
21
32
  playcraft library download <assetId> --output ./assets/character
22
33
  ```
23
34
 
24
- 检索字段是名称、素材库路径和标签;还可按分类、子分类、团队和分页筛选。使用检索结果返回的素材库编号,与 `tools list-assets` 的项目资产编号分别处理。不要把下载预览图当作完整素材包。
35
+ 检索字段是名称、素材库路径和标签,已审核模型还支持别名、用途、许可及能力筛选;也可按分类、子分类、团队和分页筛选。使用检索结果返回的素材库编号,与 `tools list-assets` 的项目资产编号分别处理。预览图不能代替模型或其依赖。
25
36
 
26
- `--output` 对单文件表示文件路径,对素材包表示目录;`--output-dir` 始终表示目录。目标路径必须尚不存在。素材包包含清单、嵌套目录和模型贴图等依赖,下载后保留其相对路径。用户要求修改已有素材时,先下载到新路径并核对,再按用户要求替换。
37
+ `--output` 对单文件表示文件路径,对文件集合表示目录;`--output-dir` 始终表示目录。目标路径必须尚不存在。文件集合包含清单、嵌套目录和模型贴图等依赖,下载后保留其相对路径。用户要求修改已有素材时,先下载到新路径并核对,再按用户要求替换。
27
38
 
28
39
  ## 创建持久化生成任务
29
40
 
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: playcraft-workspace-assets
3
- description: Use when finding or downloading shared library assets, or creating and managing durable image, audio, sprite, animation, model, or video generation tasks in a PlayCraft cloud workspace.
3
+ description: Use when selecting 3D asset kits, finding or downloading shared library assets, or managing durable media generation tasks in a PlayCraft cloud workspace.
4
4
  compatibility: agent,opencode
5
- version: 1.0.0
5
+ version: 1.1.0
6
6
  surfaces:
7
7
  - sandbox-agent
8
8
  ---
@@ -13,6 +13,17 @@ surfaces:
13
13
 
14
14
  ## 查找并复用素材
15
15
 
16
+ 一组三维物件的检索入口为素材包;包内覆盖结果给出逐项可用模型及缺口:
17
+
18
+ ```bash
19
+ playcraft library kits --search "家具" --json
20
+ playcraft library kit kenney-furniture-kit --needs "椅子,桌子,打印机" --json
21
+ ```
22
+
23
+ 素材包是主题与风格的逻辑集合,区别于一起下载的文件集合及版权来源归档。`coverageScope: partial` 表示仅部分收录;`members` 才是当前可用模型及确切版本,`needCoverage` 返回同包每项需求的候选或缺口。`inventoryRevision` 只标识当前库存,不是持久保存的历史整包版本。`styleGuidance` 提供设计参照,`compatibility` 未验证的单位、朝向和连接规则不能用于兼容性承诺;生成供应商不会自动匹配该风格。
24
+
25
+ 包内模型和独立素材都使用以下单素材入口。模型下载加 `--revision-id <revisionId>`,保留命令生成的 `.playcraft/asset-provenance.json`、许可和来源说明;工程文件路径、模型版本与来源记录共同标识实际使用实例。
26
+
16
27
  ```bash
17
28
  playcraft library search "角色" --type texture --tags character --limit 20 --json
18
29
  playcraft library facets --json
@@ -21,9 +32,9 @@ playcraft library files <assetId> --json
21
32
  playcraft library download <assetId> --output ./assets/character
22
33
  ```
23
34
 
24
- 检索字段是名称、素材库路径和标签;还可按分类、子分类、团队和分页筛选。使用检索结果返回的素材库编号,与 `tools list-assets` 的项目资产编号分别处理。不要把下载预览图当作完整素材包。
35
+ 检索字段是名称、素材库路径和标签,已审核模型还支持别名、用途、许可及能力筛选;也可按分类、子分类、团队和分页筛选。使用检索结果返回的素材库编号,与 `tools list-assets` 的项目资产编号分别处理。预览图不能代替模型或其依赖。
25
36
 
26
- `--output` 对单文件表示文件路径,对素材包表示目录;`--output-dir` 始终表示目录。目标路径必须尚不存在。素材包包含清单、嵌套目录和模型贴图等依赖,下载后保留其相对路径。用户要求修改已有素材时,先下载到新路径并核对,再按用户要求替换。
37
+ `--output` 对单文件表示文件路径,对文件集合表示目录;`--output-dir` 始终表示目录。目标路径必须尚不存在。文件集合包含清单、嵌套目录和模型贴图等依赖,下载后保留其相对路径。用户要求修改已有素材时,先下载到新路径并核对,再按用户要求替换。
27
38
 
28
39
  ## 创建持久化生成任务
29
40