@playcraft/cli 0.1.81 → 0.1.83

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.
@@ -10,7 +10,6 @@ export function getCliTopicsHelpText() {
10
10
  ' Remix 参考 references list/search/import(发现参考并读取当前工程中的固定快照)',
11
11
  ' references prepare-starter-template(准备默认起始模板)',
12
12
  ' 视频理解 video models | video understand | video jobs get/cancel',
13
- ' APK 分析 apk analyze --attachment <引用文件> | apk analyze --resume <runId> | apk jobs get/cancel',
14
13
  ' 平台 build, build-base, build-playable, build-all, analyze, inspect, upgrade',
15
14
  ' tools 除 generate-* 外(Git / 沙箱构建 / 发布 / 列表 / Prefab 等,需后端 API)',
16
15
  ' 沙箱常驻 workspace-runtime serve(Cloud Workspace,由 Runtime 引导配置启动)',
@@ -52,8 +52,9 @@ function shared(command) {
52
52
  .option('--workspace <id>', t('workspace'))
53
53
  .option('--timeout <seconds>', t('timeout'));
54
54
  }
55
+ /** Hidden until DevRun is reachable from the platform clusters; the command still works when called directly. */
55
56
  export function registerApkCommands(program) {
56
- const apk = program.command('apk').description(t('topic'));
57
+ const apk = program.command('apk', { hidden: true }).description(t('topic'));
57
58
  shared(apk.command('analyze').description(t('analyze')))
58
59
  .option('--attachment <path>', t('attachment'))
59
60
  .option('--out <directory>', t('out'))
@@ -16,7 +16,8 @@ export function registerReferenceCommands(program) {
16
16
  ...(options.tag?.length ? { tagCategory: options.tagCategory ?? 'Gameplay', tagValues: options.tag }
17
17
  : options.tagCategory === undefined ? {} : { tagCategory: options.tagCategory }),
18
18
  page: Number(options.page), limit: Number(options.limit) } : {}),
19
- ...(action === 'import' ? { projectUuid: options.project, branchName: options.branch } : {}) });
19
+ ...(action === 'import' ? { projectUuid: options.project, branchName: options.branch } : {}),
20
+ ...(action === 'prepare-starter-template' ? { templateId: options.template } : {}) });
20
21
  const output = { schemaVersion: 'playcraft.references.v1', command: `references.${action}`, ...result };
21
22
  if (options.json)
22
23
  console.log(JSON.stringify(output));
@@ -45,7 +46,8 @@ export function registerReferenceCommands(program) {
45
46
  const scope = (command) => command.option('--workspace <id>', message('workspace'))
46
47
  .option('--directory <name>', message('directory')).option('--json', message('json'));
47
48
  scope(references.command('list').description(message('list'))).action(options => run('list', options));
48
- scope(references.command('prepare-starter-template').description(message('prepareStarterTemplate'))).action(options => run('prepare-starter-template', options));
49
+ scope(references.command('prepare-starter-template').description(message('prepareStarterTemplate'))
50
+ .option('--template <id>', message('template'))).action(options => run('prepare-starter-template', options));
49
51
  scope(references.command('search [query]').description(message('search'))
50
52
  .option('--source <source>', message('source'), 'recommended')
51
53
  .option('--tag <value>', message('tag'), (value, previous) => [...previous, value], [])
@@ -4,6 +4,7 @@ const messages = {
4
4
  list: 'List verified local Remix references and their paths', search: 'Search accessible recommended or community Remixes',
5
5
  import: 'Import a fixed Remix source snapshot into the current engineering directory',
6
6
  prepareStarterTemplate: 'Prepare a verified starter template in a temporary directory',
7
+ template: 'Starter template: cocos (default) or threejs',
7
8
  query: 'Search names, branches, tags, creators or templates', source: 'recommended or community', project: 'Source project identifier', branch: 'Exact source branch',
8
9
  tag: 'Match a tag value; repeat to match any of the values', tagCategory: 'Tag category; defaults to Gameplay when --tag is used',
9
10
  workspace: 'Current cloud workspace identifier (must match this conversation)', directory: 'Current engineering directory (must match this conversation)',
@@ -24,6 +25,7 @@ const messages = {
24
25
  description: '为当前云工作区发现并导入 Remix 源码参考',
25
26
  list: '列出已验证的本地 Remix 参考及路径', search: '搜索可访问的推荐或社区 Remix', import: '把固定版本的 Remix 源码参考导入当前工程',
26
27
  prepareStarterTemplate: '将经过校验的起始模板准备到临时目录',
28
+ template: '起始模板:cocos(默认)或 threejs',
27
29
  query: '搜索名称、分支、标签、创建者或模板', source: 'recommended(推荐)或 community(社区)', project: '来源项目标识', branch: '准确的来源分支名',
28
30
  tag: '匹配的标签值;可重复指定,匹配任意一个值', tagCategory: '标签分类;使用 --tag 时默认为 Gameplay',
29
31
  workspace: '当前云工作区标识(必须与本对话一致)', directory: '当前工程目录(必须与本对话一致)',
@@ -1,5 +1,6 @@
1
1
  import { join } from 'node:path';
2
2
  import { z } from 'zod';
3
+ import { WORKSPACE_STARTER_TEMPLATE_IDS } from '@playcraft/common/cloud-workspace/runtime';
3
4
  const scope = {
4
5
  cwd: z.string().min(1).max(4096),
5
6
  workspaceId: z.string().uuid().optional(),
@@ -11,7 +12,9 @@ const scope = {
11
12
  export const remixReferenceToolRequestSchema = z
12
13
  .discriminatedUnion('action', [
13
14
  z.object({ ...scope, action: z.literal('list') }).strict(),
14
- z.object({ ...scope, action: z.literal('prepare-starter-template') }).strict(),
15
+ z
16
+ .object({ ...scope, action: z.literal('prepare-starter-template'), templateId: z.enum(WORKSPACE_STARTER_TEMPLATE_IDS).optional() })
17
+ .strict(),
15
18
  z
16
19
  .object({
17
20
  ...scope,
@@ -33,7 +33,7 @@ function withModelShell(config, modelConfigId) {
33
33
  const ENGINEERING_BATCH_INSTRUCTIONS = [
34
34
  'For a single replica, work directly in the current repository and preserve the user-provided references. Do not create a batch for a single replica.',
35
35
  'If the repository is empty, initialize it using the default starter template before implementing the replica; preserve existing workspace metadata and files. If the template is unavailable, report the failure instead of substituting another template.',
36
- 'A repository is empty only when it has no user project files, including ignored files; workspace metadata, skills, references and attachments do not count as a project. For an empty repository, run playcraft references prepare-starter-template --json and merge the returned staged template into the current repository. Preserve existing files and workspace configuration, merge .gitignore without replacing it, preserve executable permissions, and report conflicts or preparation failures instead of overwriting files or creating a batch. Once finished, remove only the exact staging directory returned by the command.',
36
+ 'A repository is empty only when it has no user project files, including ignored files; workspace metadata, skills, references and attachments do not count as a project. For an empty repository, run playcraft references prepare-starter-template --template <cocos|threejs> --json and merge the returned staged template into the current repository. Always declare --template explicitly: use threejs when the user asks for a Three.js project, and cocos when the user asks for Cocos or does not specify an engine. Preserve existing files and workspace configuration, merge .gitignore without replacing it, preserve executable permissions, and report conflicts or preparation failures instead of overwriting files or creating a batch. Once finished, remove only the exact staging directory returned by the command.',
37
37
  'When the user requests multiple independent engineering variants, create a real platform batch using playcraft tools create-batch --prompt <the complete request, including constraints established in this conversation> --workspace-id <the trusted batch request workspaceId> --source-directory <the trusted batch request sourceDirectoryName> --client-request-id <the trusted batch request clientRequestId> --json.',
38
38
  'Distinguish independent variants from multiple assets, levels or themes within one engineering project. Use the current session directory supplied by the runtime as the batch source; do not infer it from a preview or another conversation.',
39
39
  'The platform freezes the current source and generates each planned variant in its own Remix directory. Submission authorizes automatic generation without another prompt approval. Keep the source unchanged for this batch; do not replace independent variants with theme switching in the source.',
@@ -94,29 +94,9 @@ export class WorkspaceModelBroker {
94
94
  activate: (readOnly = false) => {
95
95
  if (!this.valid(entry) || this.routes.get(routeKey) !== route)
96
96
  throw new Error('model_access_denied');
97
- const priorFeedback = route.selected?.feedback;
98
- let refsBlocked = Boolean(entry.refsBlocked || priorFeedback?.refsBlocked && priorFeedback.clientRequestId === clientRequestId);
99
- if (sessionId && clientRequestId) {
100
- const overlaps = (this.state.journal.listOperations?.() ?? []).some(({ operation }) => ['session.turn', 'session.review', 'session.compact'].includes(operation.kind)
101
- && ['dispatched', 'accepted', 'uncertain'].includes(operation.status)
102
- && operation.generation === this.state.config.generation && operation.runtimeEpoch === this.state.runtimeEpoch
103
- && (operation.sessionId !== sessionId || operation.clientRequestId !== clientRequestId));
104
- if (overlaps) {
105
- refsBlocked = true;
106
- // A completed overlapping turn may leave shells alive. Only a later turn gets a new safe boundary.
107
- for (const prepared of this.entries.values()) {
108
- if (prepared.epoch === this.state.runtimeEpoch)
109
- prepared.refsBlocked = true;
110
- }
111
- for (const existing of this.routes.values()) {
112
- if (existing.epoch === this.state.runtimeEpoch && existing.selected?.feedback)
113
- existing.selected.feedback.refsBlocked = true;
114
- }
115
- }
116
- }
117
97
  route.lastAdmissionReason = undefined;
118
98
  route.selected = { entry, model: credential.model,
119
- ...(sessionId && clientRequestId ? { feedback: { sessionId, clientRequestId, cwd, boundaryId, readOnly, refsBlocked,
99
+ ...(sessionId && clientRequestId ? { feedback: { sessionId, clientRequestId, cwd, boundaryId, readOnly,
120
100
  ...(originCwd ? { originCwd } : {}) } } : {}) };
121
101
  },
122
102
  model: credential.model,
@@ -43,11 +43,12 @@ export class WorkspaceRemixReferencesBroker {
43
43
  .map((item) => item.operation)
44
44
  .filter((operation) => ['session.turn', 'session.review', 'session.compact'].includes(operation.kind) &&
45
45
  ACTIVE.has(operation.status) &&
46
+ operation.sessionId === context.sessionId &&
47
+ operation.clientRequestId === context.clientRequestId &&
46
48
  operation.generation === state.config.generation &&
47
49
  operation.runtimeEpoch === state.runtimeEpoch);
48
50
  const operation = operations.length === 1 ? operations[0] : undefined;
49
51
  if (!valid() ||
50
- context.refsBlocked ||
51
52
  state.journal.destroyed() ||
52
53
  !operation ||
53
54
  operation.kind !== 'session.turn' ||
@@ -182,7 +183,7 @@ export class WorkspaceRemixReferencesBroker {
182
183
  sessionId: context.sessionId, wait: false,
183
184
  }, async () => {
184
185
  await this.current(context, valid, true);
185
- const raw = await this.upstream(scope, { action: 'starter-template' }, WORKSPACE_REMIX_REFERENCE_BODY_MAX_BYTES);
186
+ const raw = await this.upstream(scope, { action: 'starter-template', templateId: input.templateId }, WORKSPACE_REMIX_REFERENCE_BODY_MAX_BYTES);
186
187
  await this.current(context, valid, true);
187
188
  return prepareStarterTemplate(context.cwd, raw, () => this.operation(context, valid, true));
188
189
  });
@@ -24,7 +24,7 @@ export class WorkspaceSkillDiscoveryBroker {
24
24
  && operation.generation === state.config.generation && operation.runtimeEpoch === state.runtimeEpoch
25
25
  && ['dispatched', 'accepted'].includes(operation.status));
26
26
  const operation = operations.length === 1 ? operations[0] : undefined;
27
- if (!valid() || context.refsBlocked || state.journal.destroyed() || !operation?.workspaceId || !operation.directoryName
27
+ if (!valid() || state.journal.destroyed() || !operation?.workspaceId || !operation.directoryName
28
28
  || operation.directoryName === 'root' || !operation.nativeThreadId || !operation.nativeTurnId
29
29
  || !session || session.threadId !== operation.nativeThreadId || session.activeTurnId !== operation.nativeTurnId
30
30
  || session.cwd !== context.cwd || session.workspaceId !== operation.workspaceId
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/cli",
3
- "version": "0.1.81",
3
+ "version": "0.1.83",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -57,8 +57,8 @@
57
57
  "@gltf-transform/core": "^4.3.0",
58
58
  "@gltf-transform/extensions": "^4.3.0",
59
59
  "@gltf-transform/functions": "^4.3.0",
60
- "@playcraft/build": "0.1.81",
61
- "@playcraft/common": "0.1.81",
60
+ "@playcraft/build": "0.1.83",
61
+ "@playcraft/common": "0.1.83",
62
62
  "better-sqlite3-pinned": "npm:better-sqlite3@13.0.3",
63
63
  "chokidar": "^4.0.3",
64
64
  "commander": "^13.1.0",
@@ -106,13 +106,6 @@
106
106
  "surface": "sandbox-agent",
107
107
  "mountPath": ".playcraft/skills/remix__playcraft-adsdk"
108
108
  },
109
- {
110
- "skillRef": "remix/playcraft-apk-replication",
111
- "version": "1.1.0",
112
- "artifactHash": "a7c16b036e4cbce07c5c03d1b4e98a4d42312e997a1ea0ad3ceb2d1e85ecebb9",
113
- "surface": "sandbox-agent",
114
- "mountPath": ".playcraft/skills/remix__playcraft-apk-replication"
115
- },
116
109
  {
117
110
  "skillRef": "remix/playcraft-audio-generation",
118
111
  "version": "1.4.0",