@ttmg/cli 0.4.6-vibe-beta.11 → 0.4.6-vibe-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttmg/cli",
3
- "version": "0.4.6-vibe-beta.11",
3
+ "version": "0.4.6-vibe-beta.14",
4
4
  "description": "TikTok Mini Game Command Line Tool",
5
5
  "license": "ISC",
6
6
  "bin": {
@@ -16,7 +16,8 @@
16
16
  },
17
17
  "scripts": {
18
18
  "demo:vibe": "npm run build && node scripts/vibe-upload-demo.js",
19
- "test:vibe": "node scripts/run-ts-test.js src/commands/common/vibe/vibe.test.ts",
19
+ "test:game-categories": "node scripts/run-ts-test.js src/commands/common/game/categories.test.ts",
20
+ "test:vibe": "npm run test:game-categories && node scripts/run-ts-test.js src/commands/common/vibe/vibe.test.ts",
20
21
  "test:vibe-process": "node scripts/vibe-upload-smoke.js",
21
22
  "prepack": "npm run build && npm run verify:dist-version && npm run verify:agent-contract",
22
23
  "build": "node scripts/build.js",
@@ -50,7 +51,7 @@
50
51
  "test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/acceptance-fixture.test.js",
51
52
  "test:agent-contract": "npm run test:login && npm run test:auth && npm run test:capabilities && npm run test:test-account && npm run test:upload-preview && npm run test:preview-page && npm run test:check && npm run test:check-ai && npm run test:build-structured && npm run test:doctor && npm run test:wasm && npm run test:dev-session && npm run test:dev-session-command && npm run test:dev-runtime-event && npm run test:dev-runtime-client && npm run test:dev-session-control && npm run test:dev-runtime-session-follow && npm run test:dev-client-preview-orchestrator && npm run test:dev-watch-change && npm run test:network && npm run test:vibe && npx tsc --noEmit && npm run build && npm run test:vibe-process && npm run test:preview-page-process && npm run test:preview-page-detached && npm run verify:dist-version && npm run verify:agent-contract",
52
53
  "test:agent-contract:full": "npm run test:agent-contract && npm run test:dev-default-process && npm run test:dev-game-ready-process && npm run test:dev-client-preview-control-process && node scripts/dev-workflow-smoke.js",
53
- "publish:beta": "npm publish --registry=https://registry.npmjs.org/ --access public --tag vibe-beta",
54
+ "publish:beta": "npm publish --registry=https://registry.npmjs.org/ --access public --tag beta",
54
55
  "setup": "node scripts/setup.js",
55
56
  "reset:local-state": "node scripts/resetLocalState.js",
56
57
  "dev:link": "npm run build && npm link",
@@ -126,6 +126,18 @@ const capabilitiesResult = runCli(['capabilities', '--format', 'json']);
126
126
  assertExit(capabilitiesResult, 0, 'capabilities');
127
127
  const capabilities = parseSingleJson(capabilitiesResult, 'capabilities');
128
128
  assert.equal(capabilities.command, 'capabilities');
129
+ const gameCategories = runCli(['game', 'categories', '--format', 'json']);
130
+ assertExit(gameCategories, 0, 'game categories');
131
+ const categoryCatalog = parseSingleJson(gameCategories, 'game categories');
132
+ assert.equal(categoryCatalog.platformWrite, false);
133
+ assert.equal(categoryCatalog.categoryCount, 12);
134
+ assert.equal(categoryCatalog.subcategoryCount, 114);
135
+ assert.equal(capabilities.commands.gameCategories.catalogVersion, categoryCatalog.catalogVersion);
136
+ assert.match(capabilities.commands.vibeUpload.command, /--vibe --web/);
137
+ assert.equal(capabilities.commands.vibeUpload.skillWorkflow.keepUploadProcessRunning, true);
138
+ assert.equal(capabilities.commands.vibeUpload.skillWorkflow.realUploadSuccess.simulation, false);
139
+ assert.equal(capabilities.commands.vibeUpload.appInfoFields.defaultedByCli.includes('privacy_policy'), true);
140
+ assert.equal(packageJson.files.includes('vibe-upload.md'), true);
129
141
  assert.equal(capabilities.structuredOutput.stdoutProtocolOnly, true);
130
142
  assert.equal(capabilities.commands.check.platformWrite, false);
131
143
  assert.equal(capabilities.commands.check.ai.option, '--ai');
@@ -388,6 +400,11 @@ const uploadHelp = runCli(['upload', '--help']);
388
400
  assert.doesNotMatch(uploadHelp.stdout, /--app-info/);
389
401
  assert.match(uploadHelp.stdout.replace(/\s+/g, ' '), /only title and icon|只收集标题和头像/);
390
402
  assertExit(uploadHelp, 0, 'upload help');
403
+ assert.match(uploadHelp.stdout, /--vibe --web --dir/);
404
+ assert.match(uploadHelp.stdout, /authorizationOpenCommand/);
405
+ assert.match(uploadHelp.stdout, /simulation=false/);
406
+ assert.match(uploadHelp.stdout, /--category <category>/);
407
+ assert.match(uploadHelp.stdout, /--subcategory <subcategory>/);
391
408
  assert.match(uploadHelp.stdout, /--preview\b/);
392
409
  assert.match(uploadHelp.stdout, /--serve-preview-page\b/);
393
410
  assert.match(uploadHelp.stdout, /--no-open\b/);
@@ -30,6 +30,7 @@ let failStatus = false;
30
30
  let webMode = false;
31
31
  let webPolls = 0;
32
32
  let webFailureEndpoint;
33
+ let expectedWebCategory = { category: 'Other', subcategory: 'Other' };
33
34
  const env = {
34
35
  ...process.env,
35
36
  HOME: temp,
@@ -194,9 +195,9 @@ async function main() {
194
195
  assert.equal(info.app_info.app_name, input[input.indexOf('--title') + 1]);
195
196
  assert.deepEqual(info.app_info, {
196
197
  description: 'DevTool PPE integration test fixture. Not a production game.',
197
- category: 'Other', subcategory: 'Other',
198
- terms_of_service: 'https://example.invalid/devtool-ppe-test/terms',
199
- privacy_policy: 'https://example.invalid/devtool-ppe-test/privacy',
198
+ ...expectedWebCategory,
199
+ terms_of_service: 'https://www.tiktok.com',
200
+ privacy_policy: 'https://www.tiktok.com',
200
201
  copyright_confirmation: true, contact_email: 'devtool-test@example.invalid',
201
202
  app_name: input[input.indexOf('--title') + 1], icon_uri: 'web-icon-receipt',
202
203
  }, 'CLI must send complete PPE trial defaults without asking the user for them');
@@ -261,6 +262,14 @@ async function main() {
261
262
  const completeDry = await run([...input, '--app-info', appInfoPath, '--dry-run', '--format', 'json']);
262
263
  assert.equal(completeDry.code, 0, completeDry.stderr);
263
264
  assert.equal(events(completeDry)[0].status, 'prepared');
265
+ assert.equal(events(completeDry)[0].classification.category, 'Casual');
266
+ assert.equal(events(completeDry)[0].classification.subcategory, 'Puzzle');
267
+ assert.equal(events(completeDry)[0].classification.source, 'app-info');
268
+ const overrideDry = await run([...input, '--app-info', appInfoPath, '--category', 'Puzzle', '--subcategory', 'Physics', '--dry-run', '--format', 'json']);
269
+ assert.equal(overrideDry.code, 0, overrideDry.stderr);
270
+ assert.equal(events(overrideDry)[0].classification.category, 'Puzzle');
271
+ assert.equal(events(overrideDry)[0].classification.subcategory, 'Physics');
272
+ assert.equal(events(overrideDry)[0].classification.source, 'arguments');
264
273
  assert.equal(requests.length, 0);
265
274
  fs.writeFileSync(appInfoPath, '{private-token');
266
275
  const badInfo = await run([...input, '--mock', '--app-info', appInfoPath, '--format', 'json']);
@@ -370,11 +379,32 @@ async function main() {
370
379
  const legacyWeb = await run(['upload', '--web', '--format', 'json']);
371
380
  assert.equal(events(legacyWeb)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
372
381
  assert.equal(requests.length, beforeWeb);
382
+ for (const [args, code] of [
383
+ [['--category', 'Puzzle'], 'VIBE_CATEGORY_PAIR_REQUIRED'],
384
+ [['--subcategory', 'Physics'], 'VIBE_CATEGORY_PAIR_REQUIRED'],
385
+ [['--category', 'Other', '--subcategory', 'Physics'], 'VIBE_INVALID_CATEGORY'],
386
+ [['--category', 'Unknown', '--subcategory', 'Other'], 'VIBE_INVALID_CATEGORY'],
387
+ [['--category', '', '--subcategory', ''], 'VIBE_INVALID_CATEGORY'],
388
+ ]) {
389
+ const invalid = await run([...input.filter(v => v !== '--no-open'), '--web', ...args, '--format', 'json']);
390
+ assert.equal(invalid.code, 1);
391
+ const result = events(invalid)[0];
392
+ assert.equal(result.errorCode, code);
393
+ assert.equal(result.platformWrite, false);
394
+ assert.equal(result.taskId, undefined);
395
+ assert.equal(result.pageUrl, undefined);
396
+ }
397
+ assert.equal(requests.length, beforeWeb, 'Invalid classification must not create tasks or upload');
373
398
  webMode = true;
374
399
  for (const format of ['json', 'ndjson']) {
400
+ expectedWebCategory = format === 'json' ? {category: 'Other', subcategory: 'Other'} : {category: 'Puzzle', subcategory: 'Physics'};
401
+ const categoryArgs = format === 'json' ? [] : ['--category', 'Puzzle', '--subcategory', 'Physics'];
375
402
  const receipt = path.join(temp, `upload-web-${format}.ndjson`);
376
403
  const requestStart = requests.length;
377
- const web = await run([...input.filter(v => v !== '--no-open'), '--web', '--format', format, '--poll-interval', '0.2'], async event => {
404
+ const web = await run([...input.filter(v => v !== '--no-open'), '--web', ...categoryArgs, '--format', format, '--poll-interval', '0.2'], async event => {
405
+ if (event.status === 'prepared') assert.deepEqual(
406
+ {category: event.classification.category, subcategory: event.classification.subcategory},
407
+ expectedWebCategory);
378
408
  if (event.status === 'waiting_authorization') {
379
409
  assert.equal(event.authorizationMode, 'web');
380
410
  assert.equal(event.browserOpenRequested, true);
@@ -403,6 +433,10 @@ async function main() {
403
433
  assert.equal(output.at(-1).readyForInput, true);
404
434
  assert.equal(output.at(-1).uploadAttempted, true);
405
435
  assert.equal(output.at(-1).uploaded, true);
436
+ assert.deepEqual(
437
+ { category: output.at(-1).classification.category, subcategory: output.at(-1).classification.subcategory },
438
+ expectedWebCategory);
439
+ assert.equal(output.at(-1).classification.source, format === 'json' ? 'defaults' : 'arguments');
406
440
  assert.equal(output.at(-1).taskStatusRaw, 3);
407
441
  assert.equal(output.filter(e => e.terminal).length, 1);
408
442
  assert.equal(webPolls, 5, 'Only true backend readiness unlocks authorization');
@@ -416,6 +450,7 @@ async function main() {
416
450
  assert.ok(opens[0].args.some(arg => /^http:\/\/127\.0\.0\.1:\d+\/[a-f0-9]{48}\/authorize$/.test(arg)));
417
451
  assert.doesNotMatch(JSON.stringify(opens) + web.stdout + web.stderr, /process-test-secret|import_token|web-icon-receipt|web-package-receipt|aweme:\/\//);
418
452
  }
453
+ expectedWebCategory = {category: 'Other', subcategory: 'Other'};
419
454
  webFailureEndpoint = '/bff/app_info/minis_import/submit_minis_import_info_for_agent/';
420
455
  const failedStart = requests.length;
421
456
  const webUploadFailure = await run([...input.filter(v => v !== '--no-open'), '--web', '--format', 'json'], undefined, {
@@ -552,6 +587,29 @@ async function main() {
552
587
  const badMode = await run(['upload', '--title', 'x', '--format', 'json']);
553
588
  assert.equal(badMode.code, 1);
554
589
  assert.equal(events(badMode)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
590
+ const badCategoryMode = await run(['upload', '--category', 'Puzzle', '--subcategory', 'Physics', '--format', 'json']);
591
+ assert.equal(events(badCategoryMode)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
592
+ const beforeCatalog = requests.length;
593
+ const catalog = await run(['game', 'categories', '--format', 'json']);
594
+ assert.equal(catalog.code, 0, catalog.stderr);
595
+ assert.equal(events(catalog).length, 1);
596
+ assert.equal(events(catalog)[0].categoryCount, 12);
597
+ assert.equal(events(catalog)[0].subcategoryCount, 114);
598
+ assert.equal(events(catalog)[0].platformWrite, false);
599
+ const filtered = await run(['game', 'categories', '--category', 'Puzzle', '--format', 'ndjson']);
600
+ assert.equal(filtered.code, 0);
601
+ assert.deepEqual(events(filtered)[0].categories.map(item => item.value), ['Puzzle']);
602
+ assert.ok(events(filtered)[0].categories[0].subcategories.every(item => item.description));
603
+ for (const [args, code] of [
604
+ [['--category', 'unknown'], 'GAME_CATEGORY_UNKNOWN'],
605
+ [['--format', 'xml'], 'GAME_CATEGORIES_INVALID_FORMAT'],
606
+ ]) {
607
+ const invalid = await run(['game', 'categories', '--format', 'json', ...args]);
608
+ assert.equal(invalid.code, 1);
609
+ assert.equal(events(invalid)[0].errorCode, code);
610
+ assert.equal(events(invalid)[0].platformWrite, false);
611
+ }
612
+ assert.equal(requests.length, beforeCatalog, 'Catalog commands must remain offline');
555
613
  const help = await run(['upload', '--help']);
556
614
  assert.equal(help.code, 0);
557
615
  assert.match(help.stdout, /--vibe/);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttmg/cli",
3
- "version": "0.4.6-vibe-beta.11",
3
+ "version": "0.4.6-vibe-beta.14",
4
4
  "description": "TikTok Mini Game Command Line Tool",
5
5
  "license": "ISC",
6
6
  "bin": {
@@ -16,7 +16,8 @@
16
16
  },
17
17
  "scripts": {
18
18
  "demo:vibe": "npm run build && node scripts/vibe-upload-demo.js",
19
- "test:vibe": "node scripts/run-ts-test.js src/commands/common/vibe/vibe.test.ts",
19
+ "test:game-categories": "node scripts/run-ts-test.js src/commands/common/game/categories.test.ts",
20
+ "test:vibe": "npm run test:game-categories && node scripts/run-ts-test.js src/commands/common/vibe/vibe.test.ts",
20
21
  "test:vibe-process": "node scripts/vibe-upload-smoke.js",
21
22
  "prepack": "npm run build && npm run verify:dist-version && npm run verify:agent-contract",
22
23
  "build": "node scripts/build.js",
@@ -50,7 +51,7 @@
50
51
  "test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/acceptance-fixture.test.js",
51
52
  "test:agent-contract": "npm run test:login && npm run test:auth && npm run test:capabilities && npm run test:test-account && npm run test:upload-preview && npm run test:preview-page && npm run test:check && npm run test:check-ai && npm run test:build-structured && npm run test:doctor && npm run test:wasm && npm run test:dev-session && npm run test:dev-session-command && npm run test:dev-runtime-event && npm run test:dev-runtime-client && npm run test:dev-session-control && npm run test:dev-runtime-session-follow && npm run test:dev-client-preview-orchestrator && npm run test:dev-watch-change && npm run test:network && npm run test:vibe && npx tsc --noEmit && npm run build && npm run test:vibe-process && npm run test:preview-page-process && npm run test:preview-page-detached && npm run verify:dist-version && npm run verify:agent-contract",
52
53
  "test:agent-contract:full": "npm run test:agent-contract && npm run test:dev-default-process && npm run test:dev-game-ready-process && npm run test:dev-client-preview-control-process && node scripts/dev-workflow-smoke.js",
53
- "publish:beta": "npm publish --registry=https://registry.npmjs.org/ --access public --tag vibe-beta",
54
+ "publish:beta": "npm publish --registry=https://registry.npmjs.org/ --access public --tag beta",
54
55
  "setup": "node scripts/setup.js",
55
56
  "reset:local-state": "node scripts/resetLocalState.js",
56
57
  "dev:link": "npm run build && npm link",
package/vibe-upload.md CHANGED
@@ -6,34 +6,76 @@
6
6
 
7
7
  本次范围:本地准备 → 授权绑定 → 上传头像 → 提交应用信息 → 上传代码包 → 上传完成。不包含预览、审核或正式发布。`--web` 使用真实 PPE 授权和上传;`--mock` 的授权与素材上传仍为模拟。网页授权后自动上传的修复已在 `0.4.6-vibe-beta.9` 发布;用户输入要求已在 `0.4.6-vibe-beta.10` 修正。
8
8
 
9
- ### 博华如何接入
9
+ ### Web 上传入口
10
10
 
11
- 继续使用 `--mock`,无需本地环境配置。CLI 内置 `ppe_op_vc` 创建任务,展示授权二维码,并查询三次真实状态;之后模拟授权和头像/信息/代码包上传。二维码可用于查看端内授权页,不要求完成授权。真实请求失败仍报错,素材不会上传到 PPE。上线时由 CLI 版本去掉 PPE 路由,Skill 不负责切换环境。
11
+ 真实授权和上传使用 `--web`。安装 `vibe-beta` 标签对应的版本,再用 `ttmg --version` 核对实际执行版本。`beta` 是另一个发布标签,不能用它判断 Web 链路是否包含最新修复。无需设置 PPE 环境变量或预先执行 `ttmg login`;用户在本次浏览器授权页完成授权。
12
12
 
13
13
  Skill 只引导开发者准备游戏名称、本地头像和工程,然后调用 CLI、读取结构化状态。不调用平台接口、不保存 Token、不自行轮询、不增加名称头像表单。
14
14
 
15
15
  Skill 固定调用:
16
16
 
17
17
  ```sh
18
- ttmg upload --vibe --mock --dir ./game --title "星环守卫" --icon ./avatar.png --format ndjson
18
+ npm install -g @ttmg/cli@vibe-beta
19
+ ttmg --version
20
+ ttmg capabilities --format json
21
+ ttmg upload --vibe --web --dir ./game --title "星环守卫" --icon ./avatar.png --format ndjson
19
22
  ```
20
23
 
21
- 未安装本地构建时,以 `node /绝对路径/cli/dist/index.js` 替换 `ttmg`。已调通的步骤接真实接口、未调通的步骤继续模拟,由 CLI 内部维护;Skill 不跟着 HTTP 字段和联调环境变化。
24
+ Skill 启动一次命令并持续读取 NDJSON。宿主工具返回会话 ID 时,继续读取同一进程;等待用户授权期间不终止、不重启,也不再调用第二次上传。`--web` 全程使用真实授权和上传;`--mock` 仅用于下方单独列出的演示。
22
25
 
23
26
  可先检查素材,不创建 task、不打开页面:
24
27
 
25
28
  ```sh
26
- ttmg upload --vibe --dir ./game --title "星环守卫" --icon ./avatar.png --dry-run --format json
29
+ ttmg upload --vibe --web --dir ./game --title "星环守卫" --icon ./avatar.png --dry-run --format json
27
30
  ```
28
31
 
29
32
  已有 ZIP 时,用 `--package ./game.zip` 替换 `--dir ./game`。名称和头像必须由开发者提供;演示脚本中的临时样本不作为产品默认素材。
30
33
 
31
34
  ### 用户需要提供什么
32
35
 
33
- **只收集游戏标题和本地头像,并使用当前游戏目录或代码包。** 描述、分类、服务条款、隐私政策、版权确认及其他应用资料由 CLI 使用既有 PPE 试用默认值补齐,Skill 不向用户索要,也不生成资料表单或自行补值。
36
+ **只向用户收集游戏标题和本地头像,并使用当前游戏目录或代码包。** 描述、服务条款、隐私政策、版权确认及其他应用资料由 CLI 使用既有 PPE 试用默认值补齐。分类可由 Skill 按下方目录选择,未选择时仍使用默认值;不向用户索要额外资料或增加填写表单。
34
37
 
35
38
  能力查询和上传事件均返回 `inputRequirements`:`requiredUserInputs=["title","icon"]`、`additionalMetadataSource="cli-defaults"`、`requestAdditionalMetadata=false`。`--app-info` 仅兼容旧调用,当前流程不使用。
36
39
 
40
+ CLI 在 `app_info` 请求中自动带上 `description`、`category`、`subcategory`、`terms_of_service`、`privacy_policy`、`copyright_confirmation`、`contact_email`,再填入用户标题和真实上传头像 URI。默认资料标识为 `ppe-trial`;命令中没有这些参数,不代表请求里没传。能力查询的 `appInfoFields.defaultedByCli` 列出默认字段。
41
+
42
+ ### 查询并推荐游戏分类
43
+
44
+ 以下命令和参数从 `0.4.6-vibe-beta.14` 开始提供。查询离线执行,不登录、不创建任务、不写平台:
45
+
46
+ ```sh
47
+ ttmg game categories --format json
48
+ ttmg game categories --category Puzzle --format json
49
+ ```
50
+
51
+ 返回 `schemaVersion="1"`、`catalogVersion`、`source=bundled`、`platformWrite=false`,以及 `categories[].value` 和 `subcategories[].value/description`。当前为 12 个类型、114 个子类型,玩法说明为英文;按精确英文值筛选。`catalogVersion` 标识随 CLI 打包的快照,不表示实时拉取线上配置。
52
+
53
+ Skill 根据生成游戏的核心玩法和胜负条件选择组合,给用户一句推荐理由;用户已指定分类时优先保留。不增加必填表单;无法匹配时可使用 Other/Other。
54
+
55
+ ```sh
56
+ ttmg upload --vibe --web --dir ./game --title "星环守卫" --icon ./avatar.png --category Puzzle --subcategory Physics --format ndjson
57
+ ```
58
+
59
+
60
+ - 两个分类参数都不传:仍为 `Other / Other`,旧 Skill 命令无需修改。
61
+
62
+ - 两个都传:校验通过后仅覆盖分类,其他默认资料继续自动补齐。
63
+
64
+ - 只传一个:`VIBE_CATEGORY_PAIR_REQUIRED`;值无效或归属不匹配:`VIBE_INVALID_CATEGORY`。均在创建任务和打开浏览器前失败,不静默改成 Other。
65
+
66
+ - 旧 `--app-info` 调用未传新参数时,保留文件分类与原校验行为;显式分类参数覆盖文件分类。新流程不需要该文件。
67
+
68
+ - `prepared` 和后续终态返回 `classification={category, subcategory, source, catalogVersion}`,来源为 `arguments / defaults / app-info`。`--dry-run` 可核对最终选值。
69
+
70
+ - 查询类型不存在:`GAME_CATEGORY_UNKNOWN`;格式无效:`GAME_CATEGORIES_INVALID_FORMAT`。返回非零退出码。
71
+
72
+ ### Skill 执行顺序
73
+
74
+ 1. 调用上方 `--web --format ndjson` 命令;本地预检可先加 `--dry-run`。
75
+ 2. 收到 `waiting_authorization`,提示用户在系统浏览器完成授权,保持原进程运行。
76
+ 3. 收到 `uploading`,按 `uploadStep` 展示 `avatar`、`app_info`、`package` 进度。授权轮询和上传由 CLI 完成。
77
+ 4. 收到 `terminal=true`、`status=uploaded`、`uploaded=true`、`simulation=false`,确认真实上传完成。`failed`、`cancelled` 或进程中断按下方失败说明处理。
78
+
37
79
  ### Skill 读取什么
38
80
 
39
81
  #### 上传时直接使用网页授权
@@ -58,6 +100,8 @@ ttmg auth open --page-url <pageUrl> --format json
58
100
 
59
101
  命令通过临时本地页面跳转到后端原始 `landing_url`,不使用 `aweme://`,不新建任务或上传文件。凭证只留在上传进程与页面服务中,不输出到 Skill 或放进命令参数。原上传进程继续轮询,授权成功与否以其 `readyForInput` 为准。
60
102
 
103
+ `pageUrl` 是本机绑定/上传进度页,不是平台原始授权页。`authorizationOpenCommand` 是重新打开同一任务授权页的入口;正常情况下 `--web` 已经自动打开,无需重复执行。`browserOpenRequested=true` 只代表发起打开浏览器,不能据此判定授权成功。两种页面入口都依赖原上传进程,不能把本机链接发给另一台电脑使用。
104
+
61
105
  成功返回 `status=open_requested`、`browserOpenRequested=true`、`platformWrite=false`,只表示已请求系统浏览器打开。页面过期、流程结束或没有可用真实授权地址时,返回 `VIBE_AUTH_PAGE_UNAVAILABLE`;URL 输入错误返回 `VIBE_INVALID_PAGE_URL`,浏览器启动失败返回 `VIBE_BROWSER_OPEN_FAILED`。不得因打开失败而自动创建新任务。
62
106
 
63
107
  命令在 CLI 所在电脑执行;远程机器不会打开开发者电脑的浏览器。二维码原路径和 `--mock` 自动推进逻辑保持不变;真实网页授权与上传使用 `--web`。
@@ -105,13 +149,13 @@ ttmg upload --vibe --package ./game.zip --title "测试游戏" --icon ./avatar.p
105
149
  | icon-failed / info-failed / upload-failed | 在对应上传步骤失败,uploadStep 指明位置 |
106
150
  | processing | 包已上传,后端状态仍为 PROCESSING;CLI 返回 uploaded,不等 SUCCEEDED |
107
151
 
108
- Mock 的 denied 是交互测试场景,真实端内拒绝授权如何映射错误仍待后端确认。Mock 不请求任何远端,不更改平台登录态,不创建真实应用。
152
+ Mock 的 denied 是交互测试场景,真实端内拒绝授权如何映射错误仍待后端确认。独立离线测试通过注入传输实现;用户执行的 `--mock` 会真实创建并查询 PPE 任务,但不调用真实素材上传接口。
109
153
 
110
154
  轮询间隔使用创建任务返回的 `poll_interval_seconds`,第三次真实查询后模拟授权。`--poll-interval` 仅用于覆盖测试间隔。`--timeout` 是整体超时,默认 300 秒。不自动重试写接口、不实现跨进程续传。
111
155
 
112
156
  ### 页面与素材
113
157
 
114
- 同一电脑本地页依次显示授权绑定码、上传进度、上传完成;失败时清除授权码并显示失败原因。没有完成后的预览码、发布按钮或名称头像表单。结构化输出不自动打开浏览器,Skill 用 pageUrl 打开 Codex 右侧预览或浏览器;文本输出默认打开,`--no-open` 可关闭。
158
+ 同一电脑本地页显示绑定状态、上传进度、上传完成;二维码模式显示授权绑定码,Web 模式在系统浏览器完成授权。失败时清除授权码并显示失败原因。`--web` 在 text、JSON、NDJSON 格式下均自动打开授权页;其他模式只有 text 默认打开本地页,可用 `--no-open` 关闭。Skill 可用 `pageUrl` 展示本地进度页。
115
159
 
116
160
  页面只监听 127.0.0.1,二维码才由手机扫描,手机不访问 pageUrl。默认 15 分钟后关闭,内容驻留内存;测试可设置 `TTMG_VIBE_PAGE_TTL_MS`(1—900000)。取消进程会关闭页面;成功与可显示的失败页面保留至到期。不要分享本地页面地址。
117
161
 
@@ -143,7 +187,7 @@ TTMG_VIBE_BACKEND=local-http TTMG_VIBE_API=http://127.0.0.1:3100 ttmg upload --v
143
187
 
144
188
  **v1 → v2 兼容性变化**:旧 UUID 换 Token 的三接口协议废弃,`--vibe-authorize-url` 返回明确错误;授权地址来自 landing_url。不再输出 preview_ready、game_preview、uploadId、reviewStatus。Skill 必须校验 schemaVersion=2,并按新终态处理。普通不带 --vibe 的 upload 不变。
145
189
 
146
- 2026-09-14 已核对 BFF 源码:头像 PNG/JPEG、1024×1024、5 MiB;Native ZIP 和解压总大小上限 100 MiB。本地暂未实现完整 ZIP 内容及解压总大小校验。待真实联调验证:网关包体透传、TOS 权限、手机 OAuth 授权与上传回执。后端接口调整留在适配层处理;若用户动作或成功语义改变,应协商升级 CLI schema。
190
+ 2026-09-14 已核对 BFF 源码:头像 PNG/JPEG、1024×1024、5 MiB;Native ZIP 和解压总大小上限 100 MiB。本地暂未实现完整 ZIP 内容及解压总大小校验。2026-09-15 修复构建使用自有测试素材跑通真实 PPE 网页授权、头像、默认资料和代码包上传;该验证不覆盖全部游戏、手机授权或正式环境。后端接口调整留在适配层处理;若用户动作或成功语义改变,应协商升级 CLI schema。
147
191
 
148
192
  ### 验证
149
193
 
@@ -153,33 +197,77 @@ TTMG_VIBE_BACKEND=local-http TTMG_VIBE_API=http://127.0.0.1:3100 ttmg upload --v
153
197
 
154
198
  ## English
155
199
 
156
- The default-metadata fix ships in `0.4.6-vibe-beta.11`. CLI now fills the existing PPE trial metadata automatically; users provide only title and icon. Beta.10 changed input guidance without filling the fields, and its real app-info submission was rejected.
200
+ The default-metadata fix shipped in `0.4.6-vibe-beta.11`. The CLI fills the existing PPE trial metadata automatically; users provide only title and icon. Beta.10 changed input guidance without filling the fields, and its real app-info submission was rejected.
157
201
 
158
- ### Skill entry
202
+ ### Web upload entry
159
203
 
160
204
  Scope: local preparation → authorization binding → avatar upload → app-info submission → package upload → **upload complete**. No preview, review or production release. `--web` uses real PPE authorization and uploads; `--mock` still simulates authorization and material uploads.
161
205
 
162
206
  Automatic upload was fixed in `0.4.6-vibe-beta.9`; the user-input clarification ships in `0.4.6-vibe-beta.10`.
163
207
 
164
- Keep the existing --mock command; no environment setup is required:
208
+ Use `--web` for real authorization and upload. Install `vibe-beta` and check the executable with `ttmg --version`. The separate `beta` tag does not identify the latest Web fixes. No PPE environment variables or prior `ttmg login` are required; the user authorizes in the browser for this task.
165
209
 
166
210
  ```sh
167
- ttmg upload --vibe --mock --dir ./game --title "My game" --icon ./avatar.png --format ndjson
211
+ npm install -g @ttmg/cli@vibe-beta
212
+ ttmg --version
213
+ ttmg capabilities --format json
214
+ ttmg upload --vibe --web --dir ./game --title "My game" --icon ./avatar.png --format ndjson
168
215
  ```
169
216
 
170
- Use `--package ./game.zip` instead of --dir for a prepared ZIP. Use `--dry-run --format json` for local preparation only. When running a source build, replace ttmg with `node /absolute/path/cli/dist/index.js`. Explicit --mock or --vibe-api overrides environment selection; an unconfigured backend fails instead of silently simulating.
217
+ Use `--package ./game.zip` instead of `--dir` for an existing ZIP. Add `--dry-run --format json` to the Web command for local preparation only; it creates no task and opens no browser.
171
218
 
172
- The Skill gathers the title and user-selected local icon in conversation. It does not call backend APIs, store credentials, poll services, or introduce a metadata form. The CLI internally connects working steps to PPE and simulates the remaining steps. Removing PPE at production launch is a CLI release change, not a Skill configuration step.
219
+ Start one command and keep reading its NDJSON. If the host tool returns a session ID, keep reading that same process while the user authorizes. Do not stop, restart or launch another upload. `--web` uses real authorization and uploads throughout; `--mock` is the separate demo mode below. The Skill does not call platform APIs, store credentials or add a metadata form.
173
220
 
174
221
  ### User inputs
175
222
 
176
- **Collect only the game title and local icon, using the current game directory or ZIP.** The CLI fills other app metadata with existing PPE trial defaults. Do not request extra fields, add a form or invent values.
223
+ **Collect only the game title and local icon, using the current game directory or ZIP.** The CLI fills other app metadata with existing PPE trial defaults. The Skill may select a category pair from the catalog below; omission keeps the defaults. Do not ask the user for extra fields or add a form.
177
224
 
178
225
  Capabilities and upload events include `inputRequirements` with `requiredUserInputs=["title","icon"]`, `additionalMetadataSource="cli-defaults"` and `requestAdditionalMetadata=false`.
179
226
 
180
227
  The `--app-info` option remains for legacy callers only; the current workflow does not use it.
181
228
 
182
- Source checked on 2026-09-14: BFF enforces these icon limits and a 100 MiB Native ZIP/uncompressed-size limit. Full local ZIP-content/uncompressed-size validation is not implemented. Gateway transport, TOS access, phone OAuth and upload receipts still require real integration testing.
229
+ The CLI submits `description`, `category`, `subcategory`, `terms_of_service`, `privacy_policy`, `copyright_confirmation` and `contact_email` inside `app_info`, together with the user title and uploaded icon URI. The profile is `ppe-trial`; missing command-line arguments do not mean missing request fields. Capabilities list the defaults under `appInfoFields.defaultedByCli`.
230
+
231
+ Source checked on 2026-09-14: BFF enforces PNG/JPEG, 1024×1024, 5 MiB icons and a 100 MiB Native ZIP/uncompressed-size limit. Full local ZIP-content/uncompressed-size validation is not implemented. On 2026-09-15, a corrected build completed real PPE web authorization and icon, default app-info and package uploads with owned test fixtures. This does not cover every game, phone authorization or production.
232
+
233
+ ### Query and recommend game categories
234
+
235
+ These commands and options are available from `0.4.6-vibe-beta.14`. Queries run offline without login, task creation or platform writes:
236
+
237
+ ```sh
238
+ ttmg game categories --format json
239
+ ttmg game categories --category Puzzle --format json
240
+ ```
241
+
242
+ The result includes `schemaVersion="1"`, `catalogVersion`, `source=bundled`, `platformWrite=false`, `categories[].value` and `subcategories[].value/description`.
243
+
244
+ There are 12 categories and 114 subcategories with English gameplay descriptions. Filtering uses the exact category value. The version identifies a bundled snapshot, not a live platform fetch.
245
+
246
+ The Skill selects a valid pair from the generated game's core gameplay and win conditions and gives a short reason. Preserve a user's explicit choice. Do not add a required form; use Other/Other when no category matches.
247
+
248
+ ```sh
249
+ ttmg upload --vibe --web --dir ./game --title "My game" --icon ./avatar.png --category Puzzle --subcategory Physics --format ndjson
250
+ ```
251
+
252
+
253
+ - Omit both: keep `Other / Other`; existing Skill commands work unchanged.
254
+
255
+ - Supply both: override only category fields after validation; other metadata defaults remain.
256
+
257
+ - Supply one: `VIBE_CATEGORY_PAIR_REQUIRED`. Invalid values or a mismatched pair: `VIBE_INVALID_CATEGORY`. Fail before task creation or browser opening, without a silent fallback.
258
+
259
+ - Legacy `--app-info` calls retain file values and existing validation when new options are absent. Explicit category options override file category fields. New callers need no metadata file.
260
+
261
+ - The `prepared` and subsequent terminal events return `classification={category, subcategory, source, catalogVersion}`, with source `arguments / defaults / app-info`. Use `--dry-run` to inspect the effective selection.
262
+
263
+ - Unknown query category: `GAME_CATEGORY_UNKNOWN`. Invalid format: `GAME_CATEGORIES_INVALID_FORMAT`. Both return nonzero exit codes.
264
+
265
+ ### Skill execution order
266
+
267
+ 1. Run the `--web --format ndjson` command above; optionally precheck with `--dry-run`.
268
+ 2. At `waiting_authorization`, ask the user to authorize in the system browser and keep the process running.
269
+ 3. At `uploading`, display `uploadStep`: `avatar`, `app_info`, then `package`. The CLI owns polling and uploads.
270
+ 4. Confirm real completion only when `terminal=true`, `status=uploaded`, `uploaded=true`, `simulation=false`. Handle failure, cancellation or interruption as described below.
183
271
 
184
272
  ### Output and decisions
185
273
 
@@ -210,6 +298,8 @@ Credentials stay inside the uploader/page worker, not in Skill output or command
210
298
  arguments. The original uploader keeps polling; its `readyForInput` determines
211
299
  authorization readiness.
212
300
 
301
+ `pageUrl` is the local binding/upload-progress page, not the original platform authorization URL. To reopen authorization for the same task, execute `authorizationOpenCommand`. Normally `--web` has already opened it. `browserOpenRequested=true` only confirms the open request, not authorization. Both page entry points depend on the original uploader and the CLI computer; do not send localhost links to another computer.
302
+
213
303
  Success reports `status=open_requested`, `browserOpenRequested=true`, and
214
304
  `platformWrite=false`; it does not mean authorization succeeded. Expired,
215
305
  completed, or unavailable authorization pages return `VIBE_AUTH_PAGE_UNAVAILABLE`;
@@ -265,4 +355,4 @@ The normal --mock trial internally uses ppe_op_vc without environment variables.
265
355
 
266
356
  Schema-v1 UUID endpoints, --vibe-authorize-url, preview_ready, game_preview, uploadId and reviewStatus are retired. The deprecated option returns an explicit error. Ordinary upload without --vibe is unchanged.
267
357
 
268
- Unconfirmed: platform file limits, token expiry/retry rules, actual denial mapping and in-client PPE Cookie behavior. Run test:vibe, built test:vibe-process, and test:agent-contract. Local contract tests do not establish real platform or device results.
358
+ Unconfirmed: token expiry/retry rules, actual denial mapping and in-client PPE Cookie behavior. Run test:vibe, built test:vibe-process, and test:agent-contract. Local contract tests do not establish real platform or device results; the PPE test-fixture result above is recorded separately.