@ttmg/cli 0.4.6-vibe-beta.6 → 0.4.6-vibe-beta.7

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 CHANGED
@@ -21,9 +21,21 @@ npm install @ttmg/cli --save-dev
21
21
 
22
22
  #### Login 登录
23
23
 
24
- The experimental `upload --vibe` local prototype does not require platform login.
25
- See [Vibe binding/upload](vibe-upload.md) for the schema-v2 Skill contract, real local preparation and task-based Mock. Set `TTMG_VIBE_BACKEND=mock` outside the Skill; success ends at upload completion, without preview or release.
26
- 实验性 `upload --vibe` 不要求平台登录。通过 `TTMG_VIBE_BACKEND=mock` Skill 外配置模拟模式;真实本地准备、schema v2 状态、绑定上传 Mock [Vibe 绑定上传](vibe-upload.md)。本次结束于上传完成,不含预览和发布。
24
+ For the beta.7 trial, run `ttmg upload --vibe --mock` without environment setup.
25
+ Task creation/polling use the built-in PPE lane; authorization and uploads remain
26
+ simulated. Unlike beta.6, this is not offline: backend access is required and task
27
+ creation is a real platform write. See [Vibe binding/upload](vibe-upload.md).
28
+ beta.7 试用直接运行 `ttmg upload --vibe --mock`,不用登录或配置环境变量。
29
+ 创建/查询任务走 CLI 内置 PPE,授权/上传继续模拟;与 beta.6 不同,不再是全程离线。
30
+ 需要后端可访问,会创建真实任务,不上传游戏、不预览、不审核或发布。
31
+
32
+ ```sh
33
+ npm install -g @ttmg/cli@0.4.6-vibe-beta.7
34
+ ttmg upload --vibe --mock --dir ./game --title "My game" --icon ./avatar.png --format ndjson
35
+ ```
36
+
37
+ PPE routing will be removed in a production release without Skill environment setup.
38
+ 上线时由 CLI 去掉 PPE 路由,不让 Skill 配置或切换环境。
27
39
 
28
40
  For the existing authenticated platform workflow, log in first; this does not apply to the local Vibe prototype above.
29
41
  原有需要平台鉴权的流程仍先登录;上述 Vibe 本地原型不需要此步骤。
package/dist/index.js CHANGED
@@ -7277,12 +7277,12 @@ const messages = {
7277
7277
  'cli.command.upload.servePreviewPage': 'Serve a temporary local preview page with test-account and preview QR codes',
7278
7278
  'cli.command.upload.noOpen': 'Do not open the local preview page in the default browser',
7279
7279
  'cli.command.upload.format': 'Output format: text | json | ndjson',
7280
- 'cli.command.upload.vibe': 'Skill-guided binding and upload (schema v2; mock/local testing only)',
7280
+ 'cli.command.upload.vibe': 'Skill-guided binding and upload (schema v2; use --mock for the integration trial)',
7281
7281
  'cli.command.upload.vibeTitle': 'Game title supplied by the Skill (--vibe)',
7282
7282
  'cli.command.upload.vibeIcon': 'Local PNG/JPEG/WebP selected by the user (--vibe)',
7283
7283
  'cli.command.upload.vibePackage': 'Prepared ZIP instead of --dir (--vibe)',
7284
7284
  'cli.command.upload.vibeDryRun': 'Validate and package locally without authorization or upload (--vibe)',
7285
- 'cli.command.upload.vibeMock': 'Auto-simulate authorization and upload; no backend or phone scan (--vibe)',
7285
+ 'cli.command.upload.vibeMock': 'Integration trial: real PPE task creation/polling, simulated authorization/upload; no environment setup or phone authorization (--vibe)',
7286
7286
  'cli.command.upload.vibeScenario': 'Mock: success | denied | expired | timeout | upload-failed | processing | icon-failed | info-failed',
7287
7287
  'cli.command.upload.vibeApi': 'Minis Import API origin on a loopback IP only (--vibe)',
7288
7288
  'cli.command.upload.vibeAuthorizeUrl': 'Obsolete: remove this option; task creation returns landing_url (--vibe)',
@@ -7537,12 +7537,12 @@ const messages = {
7537
7537
  'cli.command.upload.servePreviewPage': '启动包含测试账号与预览二维码的临时本地页面',
7538
7538
  'cli.command.upload.noOpen': '不使用默认浏览器打开本地预览页面',
7539
7539
  'cli.command.upload.format': '输出格式:text | json | ndjson',
7540
- 'cli.command.upload.vibe': 'Skill 引导绑定上传(schema v2,当前仅 Mock/本地测试)',
7540
+ 'cli.command.upload.vibe': 'Skill 引导绑定上传(schema v2;使用 --mock 联调试用)',
7541
7541
  'cli.command.upload.vibeTitle': 'Skill 提供的游戏标题(--vibe)',
7542
7542
  'cli.command.upload.vibeIcon': '用户指定的本地 PNG/JPEG/WebP 图片(--vibe)',
7543
7543
  'cli.command.upload.vibePackage': '已准备的 ZIP,与 --dir 二选一(--vibe)',
7544
7544
  'cli.command.upload.vibeDryRun': '仅本地校验与打包,不授权、不上传(--vibe)',
7545
- 'cli.command.upload.vibeMock': '自动模拟授权与上传,无需后端和手机扫码(--vibe)',
7545
+ 'cli.command.upload.vibeMock': '联调试用:真实 PPE 创建/查询,模拟授权/上传;无需环境配置或完成手机授权(--vibe)',
7546
7546
  'cli.command.upload.vibeScenario': '模拟:success | denied | expired | timeout | upload-failed | processing | icon-failed | info-failed',
7547
7547
  'cli.command.upload.vibeApi': 'Minis Import 接口地址,仅接受回环 IP(--vibe)',
7548
7548
  'cli.command.upload.vibeAuthorizeUrl': '已废弃,请移除此参数;授权地址由创建任务返回(--vibe)',
@@ -15993,7 +15993,7 @@ async function authStatus(options = {}) {
15993
15993
  return result;
15994
15994
  }
15995
15995
 
15996
- var version = "0.4.6-vibe-beta.6";
15996
+ var version = "0.4.6-vibe-beta.7";
15997
15997
  var engines = {
15998
15998
  node: ">=20.19.1 <21 || >=22"
15999
15999
  };
@@ -16017,11 +16017,28 @@ function getCapabilities() {
16017
16017
  command: 'ttmg upload --vibe --mock --format ndjson',
16018
16018
  option: '--vibe',
16019
16019
  available: true,
16020
- platformWrite: false,
16021
- readiness: 'local-prototype',
16020
+ platformWrite: true,
16021
+ readiness: 'ppe-authorization-integration',
16022
16022
  schemaVersion: '2',
16023
16023
  backendEnvironment: 'TTMG_VIBE_BACKEND',
16024
- backendModes: ['mock', 'local-http'],
16024
+ backendModes: ['mock', 'local-http', 'ppe', 'ppe-mock'],
16025
+ ppeMock: {
16026
+ environment: 'TTMG_VIBE_PPE',
16027
+ realOperations: ['taskCreation', 'statusPolling'],
16028
+ mockOperations: ['authorization', 'avatar', 'app_info', 'package'],
16029
+ simulateAfterPolls: 3,
16030
+ realUploadEnabled: false,
16031
+ simulationStartsAt: 'authorization',
16032
+ },
16033
+ ppe: {
16034
+ environment: 'TTMG_VIBE_PPE',
16035
+ host: 'developers.tiktok.com',
16036
+ taskCreation: true,
16037
+ statusPolling: true,
16038
+ authorizationScheme: 'aweme://webview?url=<encoded landing_url>',
16039
+ uploadEnabled: false,
16040
+ uploadBlockedError: 'VIBE_REAL_UPLOAD_NOT_READY',
16041
+ },
16025
16042
  terminalSuccess: 'uploaded',
16026
16043
  stages: ['precheck', 'packaging', 'prepared', 'creating_task', 'waiting_authorization', 'uploading', 'uploaded', 'failed', 'cancelled'],
16027
16044
  uploadSteps: ['avatar', 'app_info', 'package'],
@@ -16034,6 +16051,9 @@ function getCapabilities() {
16034
16051
  metadataForm: false,
16035
16052
  dryRun: true,
16036
16053
  mock: true,
16054
+ mockRequiresBackend: true,
16055
+ mockBackendRouting: 'built-in-ppe-task-only',
16056
+ mockRequiresEnvironmentConfiguration: false,
16037
16057
  mockAutoAuthorization: true,
16038
16058
  mockRequiresClientScan: false,
16039
16059
  mockSimulatedProgress: true,
@@ -16980,7 +17000,7 @@ class VibeError extends Error {
16980
17000
  }
16981
17001
  }
16982
17002
 
16983
- // BFF IDL: feat-minis-import, verified 2026-09-12. No production adapter enabled.
17003
+ // BFF IDL: feat-minis-import. PPE task creation/polling only; no production adapter.
16984
17004
  const prefix = '/bff/app_info/minis_import/';
16985
17005
  const MINIS_IMPORT_PATHS = {
16986
17006
  create: `${prefix}create_minis_import_task_for_agent/`,
@@ -17020,7 +17040,7 @@ function status(value) {
17020
17040
  }
17021
17041
  function envelope(data) {
17022
17042
  if (!data || data.error_info?.code !== 0) {
17023
- if (data?.error_info?.code === 400001)
17043
+ if (data?.error_info?.code === 400001 || data?.error_info?.code === 420001)
17024
17044
  throw new VibeError('VIBE_AUTH_EXPIRED');
17025
17045
  if (typeof data?.error_info?.code === 'number')
17026
17046
  throw new VibeError('VIBE_BACKEND_REJECTED');
@@ -17136,13 +17156,87 @@ function createLocalHttpBackend(baseUrl) {
17136
17156
  base.search ||
17137
17157
  base.hash)
17138
17158
  throw new VibeError('VIBE_INVALID_LOCAL_ENDPOINT');
17159
+ return createHttpBackend('local-http', base.origin);
17160
+ }
17161
+ function createPpeBackend(lane, adapter) {
17162
+ // No default lane and no production fallback. Keep PPE requests on the known host.
17163
+ if (!lane || !/^ppe_[a-zA-Z0-9_-]{1,64}$/.test(lane))
17164
+ throw new VibeError('VIBE_INVALID_PPE_ENV');
17165
+ const backend = createHttpBackend('ppe', 'https://developers.tiktok.com', {
17166
+ 'x-use-ppe': '1',
17167
+ 'x-tt-env': lane,
17168
+ }, adapter);
17169
+ const uploadUnavailable = async () => {
17170
+ throw new VibeError('VIBE_REAL_UPLOAD_NOT_READY');
17171
+ };
17172
+ return {
17173
+ ...backend,
17174
+ uploadAvailable: false,
17175
+ uploadIcon: uploadUnavailable,
17176
+ submitInfo: uploadUnavailable,
17177
+ uploadPackage: uploadUnavailable,
17178
+ };
17179
+ }
17180
+ /** Opt-in integration demo: only creation and polling can reach PPE. */
17181
+ // Release-owned configuration, not a developer setup step. Remove PPE routing
17182
+ // here when the production chain is ready; do not change the Skill command.
17183
+ function createVibeTrialBackend(scenario, adapter) {
17184
+ return createPpeMockBackend('ppe_op_vc', adapter, scenario);
17185
+ }
17186
+ function createPpeMockBackend(lane, adapter, scenario = 'success') {
17187
+ const real = createPpeBackend(lane, adapter);
17188
+ const mock = createMockBackend(scenario);
17189
+ let realTask;
17190
+ let mockTask;
17191
+ let polls = 0;
17192
+ let simulatedReady = false;
17193
+ const uploadTask = (task) => {
17194
+ if (task !== realTask || !mockTask || !simulatedReady)
17195
+ throw new VibeError('VIBE_TASK_NOT_ACCEPTING_INPUT');
17196
+ return mockTask;
17197
+ };
17198
+ return {
17199
+ mode: 'ppe-mock',
17200
+ uploadAvailable: true,
17201
+ async createTask(input, signal) {
17202
+ if (realTask)
17203
+ throw new VibeError('VIBE_TASK_NOT_ACCEPTING_INPUT');
17204
+ realTask = await real.createTask(input, signal);
17205
+ // Separate in-memory credentials: real import tokens never reach mock uploads.
17206
+ mockTask = await mock.createTask(input, signal);
17207
+ return realTask;
17208
+ },
17209
+ async getTaskStatus(task, signal) {
17210
+ if (task !== realTask)
17211
+ throw new VibeError('VIBE_TASK_NOT_ACCEPTING_INPUT');
17212
+ // Errors remain errors; this is not an automatic fallback for failed requests.
17213
+ const observed = await real.getTaskStatus(task, signal);
17214
+ if (++polls >= 3) {
17215
+ const simulated = await mock.getTaskStatus(mockTask, signal);
17216
+ // The mock normal path needs three observations before accepting uploads.
17217
+ if (!simulated.readyForInput) {
17218
+ await mock.getTaskStatus(mockTask, signal);
17219
+ simulatedReady = (await mock.getTaskStatus(mockTask, signal)).readyForInput;
17220
+ }
17221
+ else
17222
+ simulatedReady = true;
17223
+ }
17224
+ return { ...observed, simulatedReadyForInput: simulatedReady };
17225
+ },
17226
+ uploadIcon: async (task, input, signal) => mock.uploadIcon(uploadTask(task), input, signal),
17227
+ submitInfo: async (task, title, iconUri, signal) => mock.submitInfo(uploadTask(task), title, iconUri, signal),
17228
+ uploadPackage: async (task, input, signal, onProgress) => mock.uploadPackage(uploadTask(task), input, signal, onProgress),
17229
+ };
17230
+ }
17231
+ function createHttpBackend(mode, baseURL, environmentHeaders = {}, adapter) {
17139
17232
  const client = axios.create({
17140
- baseURL: base.origin,
17233
+ baseURL,
17141
17234
  timeout: 30000,
17142
17235
  maxRedirects: 0,
17143
17236
  proxy: false,
17237
+ adapter,
17144
17238
  });
17145
- return createBackend('local-http', async (request) => {
17239
+ return createBackend(mode, async (request) => {
17146
17240
  try {
17147
17241
  let body;
17148
17242
  let headers = {};
@@ -17169,7 +17263,7 @@ function createLocalHttpBackend(baseUrl) {
17169
17263
  url: request.path,
17170
17264
  params: request.method === 'GET' ? request.data : undefined,
17171
17265
  data: body,
17172
- headers,
17266
+ headers: { ...environmentHeaders, ...headers },
17173
17267
  signal: request.signal,
17174
17268
  maxBodyLength: Infinity,
17175
17269
  onUploadProgress: p => request.onProgress?.(p.loaded, p.total),
@@ -17273,6 +17367,11 @@ function createMockBackend(scenario = 'success') {
17273
17367
  });
17274
17368
  }
17275
17369
 
17370
+ function createAuthorizationScheme(landingUrl) {
17371
+ // Encode the entire landing URL once so its token/query stays inside the WebView.
17372
+ return `aweme://webview?url=${encodeURIComponent(safeWebUrl(landingUrl))}`;
17373
+ }
17374
+
17276
17375
  const DEV_PORT = 3000;
17277
17376
  const DEV_WS_PORT = 4000;
17278
17377
  path.join(os.homedir(), '__TTMG__');
@@ -17573,52 +17672,60 @@ async function renderVibePage(input) {
17573
17672
  const copy = zh
17574
17673
  ? {
17575
17674
  steps: ['扫码授权', '上传游戏', '完成'],
17576
- mock: '模拟',
17675
+ mock: input.hybrid ? 'PPE + Mock' : '模拟',
17577
17676
  game: '当前游戏',
17578
17677
  gameIcon: '游戏头像',
17579
17678
  bindingTitle: '上传到 TikTok',
17580
- bindingAction: '使用 TikTok 扫描二维码',
17581
- bindingSubtitle: '在手机上确认授权后,将继续上传这个游戏。',
17582
- bindingStatus: input.simulation ? '模拟等待扫码' : '等待扫码',
17679
+ bindingAction: input.hybrid ? '真实授权二维码 · 混合联调' : '使用 TikTok 扫描二维码',
17680
+ bindingSubtitle: input.hybrid
17681
+ ? '可扫码查看端内授权页。查询三次真实状态后,模拟授权和上传,无需完成手机授权。'
17682
+ : input.uploadAvailable === false
17683
+ ? '请在手机上完成授权。当前为真实 PPE 授权联调,尚未开放上传。'
17684
+ : '在手机上确认授权后,将继续上传这个游戏。',
17685
+ bindingStatus: input.hybrid ? '正在查询 PPE 任务' : input.simulation ? '模拟等待扫码' : '等待扫码',
17583
17686
  uploadingTitle: '正在上传小游戏',
17584
- uploadingSubtitle: '授权已完成,正在上传本次游戏内容。',
17687
+ uploadingSubtitle: input.simulation ? '正在模拟授权后的上传流程,不会上传游戏内容。' : '授权已完成,正在上传本次游戏内容。',
17585
17688
  uploadLabel: input.simulation ? '模拟上传进度' : '上传进度',
17586
17689
  uploadStatus: '正在上传游戏,请稍候',
17587
- completeTitle: '上传完成',
17690
+ completeTitle: input.simulation ? '模拟上传完成' : '上传完成',
17588
17691
  completeSubtitle: input.simulation
17589
17692
  ? '绑定和上传流程模拟完成,未向 TikTok 上传游戏。'
17590
17693
  : '游戏内容已上传,本次操作已完成。',
17591
17694
  failedTitle: '本次上传未完成',
17592
17695
  bindingCode: '授权绑定二维码',
17593
- accepted: '已上传',
17594
- mockNote: '模拟运行 · 无需扫码,不会真实上传',
17696
+ accepted: input.simulation ? '模拟完成' : '已上传',
17697
+ mockNote: input.hybrid ? '真实创建 / 查询任务 · 授权 / 上传为模拟 · 不改变真实任务状态' : '模拟运行 · 无需扫码,不会真实上传',
17595
17698
  footer: '临时页面 · 15 分钟后关闭',
17596
17699
  }
17597
17700
  : {
17598
17701
  steps: ['Authorize', 'Upload', 'Done'],
17599
- mock: 'MOCK',
17702
+ mock: input.hybrid ? 'PPE + Mock' : 'MOCK',
17600
17703
  game: 'YOUR GAME',
17601
17704
  gameIcon: 'Game icon',
17602
17705
  bindingTitle: 'Upload to TikTok',
17603
- bindingAction: 'Scan the QR code with TikTok',
17604
- bindingSubtitle: 'Authorize on your phone to continue uploading this game.',
17605
- bindingStatus: input.simulation
17706
+ bindingAction: input.hybrid ? 'Real authorization QR · integration demo' : 'Scan the QR code with TikTok',
17707
+ bindingSubtitle: input.hybrid
17708
+ ? 'Scan to view the in-app page. After three real status polls, authorization and upload are simulated. Phone authorization is not required.'
17709
+ : input.uploadAvailable === false
17710
+ ? 'Authorize on your phone. This is a real PPE authorization test; upload is not enabled yet.'
17711
+ : 'Authorize on your phone to continue uploading this game.',
17712
+ bindingStatus: input.hybrid ? 'Querying the PPE task' : input.simulation
17606
17713
  ? 'Simulating scan request'
17607
17714
  : 'Waiting for you to scan',
17608
17715
  uploadingTitle: 'Uploading your game',
17609
- uploadingSubtitle: 'Account authorized. Your game is being uploaded.',
17716
+ uploadingSubtitle: input.simulation ? 'Simulating the post-authorization upload. No game content is sent.' : 'Account authorized. Your game is being uploaded.',
17610
17717
  uploadLabel: input.simulation
17611
17718
  ? 'Simulated upload progress'
17612
17719
  : 'Upload progress',
17613
17720
  uploadStatus: 'Uploading your game. Please wait.',
17614
- completeTitle: 'Upload complete',
17721
+ completeTitle: input.simulation ? 'Simulated upload complete' : 'Upload complete',
17615
17722
  completeSubtitle: input.simulation
17616
17723
  ? 'Binding and upload simulation complete. Nothing was uploaded to TikTok.'
17617
17724
  : 'Your game content has been uploaded. This operation is complete.',
17618
17725
  failedTitle: 'Upload not completed',
17619
17726
  bindingCode: 'Authorization QR',
17620
- accepted: 'Uploaded',
17621
- mockNote: 'Mock only · no scan needed, nothing is uploaded',
17727
+ accepted: input.simulation ? 'Simulation complete' : 'Uploaded',
17728
+ mockNote: input.hybrid ? 'Real task creation / polling · Mock authorization / upload · Real task status unchanged' : 'Mock only · no scan needed, nothing is uploaded',
17622
17729
  footer: 'Temporary page · closes after 15 minutes',
17623
17730
  };
17624
17731
  const step = phase === 'binding' ? 0 : phase === 'uploading' ? 1 : 2;
@@ -17887,6 +17994,7 @@ function abortableDelay(ms, signal) {
17887
17994
  });
17888
17995
  }
17889
17996
  const defaults = {
17997
+ createTrialBackend: createVibeTrialBackend,
17890
17998
  prepare: prepareVibeInput,
17891
17999
  startPage: startVibePage,
17892
18000
  sleep: abortableDelay,
@@ -17905,6 +18013,9 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
17905
18013
  let page;
17906
18014
  let detached = false;
17907
18015
  let progressUpdates = Promise.resolve();
18016
+ let ppeLane;
18017
+ let ppeSelected = false;
18018
+ let hybridSelected = false;
17908
18019
  let result = {
17909
18020
  schemaVersion: '2',
17910
18021
  command: 'upload',
@@ -17939,14 +18050,24 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
17939
18050
  throw new VibeError('VIBE_CANCELLED');
17940
18051
  };
17941
18052
  try {
17942
- // Backend selection is environment configuration, not a Skill workflow branch.
17943
- // Explicit command-line selection wins; never silently fall back to mock.
18053
+ // --mock is the stable trial entry: working steps use the built-in PPE lane,
18054
+ // blocked steps remain simulated. No environment setup is required.
18055
+ // Explicit command-line selection wins; real failures never fall back to mock.
17944
18056
  if (!options.mock && !options.vibeApi && process.env.TTMG_VIBE_BACKEND) {
17945
18057
  const configured = process.env.TTMG_VIBE_BACKEND;
17946
18058
  if (configured === 'mock')
17947
18059
  options = { ...options, mock: true };
17948
18060
  else if (configured === 'local-http' && process.env.TTMG_VIBE_API)
17949
18061
  options = { ...options, vibeApi: process.env.TTMG_VIBE_API };
18062
+ else if (configured === 'ppe' || configured === 'ppe-mock') {
18063
+ if (process.env.TTMG_VIBE_API)
18064
+ throw new VibeError('VIBE_BACKEND_OPTION_CONFLICT');
18065
+ ppeSelected = true;
18066
+ hybridSelected = configured === 'ppe-mock';
18067
+ ppeLane = process.env.TTMG_VIBE_PPE;
18068
+ if (!ppeLane || !/^ppe_[a-zA-Z0-9_-]{1,64}$/.test(ppeLane))
18069
+ throw new VibeError('VIBE_INVALID_PPE_ENV');
18070
+ }
17950
18071
  else
17951
18072
  throw new VibeError('VIBE_BACKEND_NOT_CONFIGURED');
17952
18073
  }
@@ -17960,7 +18081,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
17960
18081
  if (options.vibeAuthorizeUrl)
17961
18082
  throw new VibeError('VIBE_DEPRECATED_OPTION');
17962
18083
  if ((options.mock && (options.vibeApi || options.vibeAuthorizeUrl)) ||
17963
- (!options.mock && options.mockScenario))
18084
+ (!options.mock && !hybridSelected && options.mockScenario))
17964
18085
  throw new VibeError('VIBE_BACKEND_OPTION_CONFLICT');
17965
18086
  if (options.pollInterval !== undefined)
17966
18087
  seconds(options.pollInterval, 1);
@@ -17972,7 +18093,11 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
17972
18093
  let backend = deps.backend;
17973
18094
  if (!options.dryRun && !backend) {
17974
18095
  if (options.mock)
17975
- backend = createMockBackend(options.mockScenario);
18096
+ backend = deps.createTrialBackend(options.mockScenario);
18097
+ else if (hybridSelected)
18098
+ backend = createPpeMockBackend(ppeLane, undefined, options.mockScenario);
18099
+ else if (ppeSelected)
18100
+ backend = createPpeBackend(ppeLane);
17976
18101
  else if (options.vibeApi)
17977
18102
  backend = createLocalHttpBackend(options.vibeApi);
17978
18103
  else
@@ -17980,7 +18105,8 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
17980
18105
  }
17981
18106
  emit('precheck', {
17982
18107
  backend: backend?.mode || 'none',
17983
- simulation: backend?.mode === 'mock',
18108
+ simulation: backend?.mode === 'mock' || backend?.mode === 'ppe-mock',
18109
+ ...(backend?.mode === 'ppe-mock' ? { simulationStartsAt: 'authorization' } : {}),
17984
18110
  });
17985
18111
  ensureActive();
17986
18112
  const input = await deps.prepare(options, () => emit('packaging'), controller.signal);
@@ -18000,9 +18126,11 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
18000
18126
  const pageGame = {
18001
18127
  title: input.title,
18002
18128
  iconDataUrl: `data:${input.icon.contentType};base64,${input.icon.bytes.toString('base64')}`,
18129
+ uploadAvailable: backend.uploadAvailable !== false,
18130
+ hybrid: backend.mode === 'ppe-mock',
18003
18131
  };
18004
18132
  const sourceProjectId = deps.projectId(options);
18005
- emit('creating_task');
18133
+ emit('creating_task', { platformWrite: backend.mode === 'ppe' || backend.mode === 'ppe-mock' });
18006
18134
  const task = await backend.createTask({
18007
18135
  source_type: 1,
18008
18136
  source_project_id: sourceProjectId,
@@ -18010,7 +18138,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
18010
18138
  minis_type: 'MINI_GAME',
18011
18139
  }, controller.signal);
18012
18140
  ensureActive();
18013
- const authorizationUrl = safeWebUrl(task.landingUrl);
18141
+ const authorizationUrl = createAuthorizationScheme(task.landingUrl);
18014
18142
  const pollMs = seconds(options.pollInterval, task.pollIntervalSeconds);
18015
18143
  result = {
18016
18144
  ...result,
@@ -18019,17 +18147,25 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
18019
18147
  };
18020
18148
  page = await deps.startPage();
18021
18149
  ensureActive();
18150
+ const bindingMessage = backend.mode === 'ppe-mock'
18151
+ ? (getCurrentLanguage() === 'zh-CN'
18152
+ ? '真实 PPE 任务和授权二维码;查询三次后模拟授权、上传,无需完成手机授权。'
18153
+ : 'Real PPE task and authorization QR. After three polls, authorization and upload are simulated; phone authorization is not required.')
18154
+ : backend.uploadAvailable === false
18155
+ ? (getCurrentLanguage() === 'zh-CN' ? '请扫码完成真实 PPE 授权;上传尚未开放。' : 'Scan to authorize in PPE. Upload is not enabled yet.')
18156
+ : text('waiting_authorization', result.simulation);
18022
18157
  await page.update({
18023
18158
  ...pageGame,
18024
18159
  language: getCurrentLanguage(),
18025
18160
  phase: 'binding',
18026
18161
  simulation: result.simulation,
18027
- message: text('waiting_authorization', result.simulation),
18162
+ message: bindingMessage,
18028
18163
  qrUrl: authorizationUrl,
18029
18164
  purpose: 'authorization_binding',
18030
18165
  });
18031
18166
  ensureActive();
18032
18167
  emit('waiting_authorization', {
18168
+ message: bindingMessage,
18033
18169
  pageUrl: page.url,
18034
18170
  qrPurpose: 'authorization_binding',
18035
18171
  nextAction: result.simulation ? 'wait' : 'scan_qr',
@@ -18046,11 +18182,17 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
18046
18182
  ...result,
18047
18183
  taskStatusRaw: response.statusRaw,
18048
18184
  readyForInput: response.readyForInput,
18185
+ ...(backend.mode === 'ppe-mock' ? {
18186
+ simulatedReadyForInput: response.simulatedReadyForInput === true,
18187
+ simulatedTaskStatusRaw: response.simulatedReadyForInput ? 2 : 1,
18188
+ } : {}),
18049
18189
  };
18050
- if (response.readyForInput)
18190
+ if (backend.mode === 'ppe-mock' ? response.simulatedReadyForInput : response.readyForInput)
18051
18191
  break;
18052
18192
  await deps.sleep(pollMs, controller.signal);
18053
18193
  }
18194
+ if (backend.uploadAvailable === false)
18195
+ throw new VibeError('VIBE_REAL_UPLOAD_NOT_READY');
18054
18196
  await page.update({
18055
18197
  ...pageGame,
18056
18198
  language: getCurrentLanguage(),
@@ -18105,7 +18247,9 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
18105
18247
  task.importToken = '';
18106
18248
  task.landingUrl = '';
18107
18249
  // A validated upload receipt is authoritative even if result-page rendering fails.
18108
- result = { ...result, uploaded: true, taskStatusRaw: receipt.statusRaw };
18250
+ result = { ...result, uploaded: true, ...(backend.mode === 'ppe-mock'
18251
+ ? { simulatedTaskStatusRaw: receipt.statusRaw }
18252
+ : { taskStatusRaw: receipt.statusRaw }) };
18109
18253
  await progressUpdates;
18110
18254
  if (progressPageError)
18111
18255
  throw progressPageError;
@@ -18160,6 +18304,14 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
18160
18304
  '尚未配置服务;本地模拟请使用 --mock。',
18161
18305
  'No backend configured. Use --mock for local simulation.',
18162
18306
  ],
18307
+ VIBE_INVALID_PPE_ENV: [
18308
+ 'PPE 模式需要显式设置 TTMG_VIBE_PPE,例如 ppe_op_vc。',
18309
+ 'PPE mode requires TTMG_VIBE_PPE, for example ppe_op_vc.',
18310
+ ],
18311
+ VIBE_REAL_UPLOAD_NOT_READY: [
18312
+ '任务授权已完成,尚未上传:真实上传所需的完整基础信息尚未接入。不会模拟上传成功。',
18313
+ 'Task authorized; nothing uploaded. The complete app-info input contract is not integrated yet. No simulated success.',
18314
+ ],
18163
18315
  VIBE_BACKEND_REQUEST_FAILED: [
18164
18316
  '服务请求失败,本次操作已停止;已开始上传时请先核对任务,勿直接重传。',
18165
18317
  'The service request failed. If upload started, check the task before retrying.',
@@ -18200,6 +18352,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
18200
18352
  title: result.title || '',
18201
18353
  language: getCurrentLanguage(),
18202
18354
  simulation: result.simulation,
18355
+ hybrid: result.backend === 'ppe-mock',
18203
18356
  phase: result.uploaded ? 'complete' : 'failed',
18204
18357
  message,
18205
18358
  });
@@ -18255,7 +18408,7 @@ async function vibeUpload(options) {
18255
18408
  else if (format === 'text')
18256
18409
  console.log(`${event.simulation ? '[MOCK] ' : ''}${event.message}${event.status === 'uploading' && percent(event.loadedBytes, event.totalBytes) !== undefined ? ` ${percent(event.loadedBytes, event.totalBytes)}%` : ''}${event.pageUrl && ['waiting_authorization', 'uploaded'].includes(event.status) ? `\n${event.pageUrl}` : ''}${event.errorCode ? ` (${event.errorCode})` : ''}`);
18257
18410
  else if (event.status === 'waiting_authorization')
18258
- console.error(`${event.simulation ? '[MOCK] ' : ''}${text(event.status, event.simulation)}\n${event.pageUrl}`);
18411
+ console.error(`${event.simulation ? '[MOCK] ' : ''}${event.message}\n${event.pageUrl}`);
18259
18412
  }, {}, controller.signal);
18260
18413
  if (format === 'json' || !['text', 'json', 'ndjson'].includes(format))
18261
18414
  console.log(JSON.stringify(result));