@ttmg/cli 0.4.6-vibe-beta.21 → 0.4.6-vibe-beta.23
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/CHANGELOG.md +516 -0
- package/README.md +5 -13
- package/dist/index.js +198 -530
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/scripts/vibe-upload-smoke.js +30 -22
- package/package.json +1 -1
- package/src/commands/common/game/categories.test.ts +38 -0
- package/src/commands/common/game/categories.ts +48 -0
- package/src/commands/common/game/categoryCatalog.json +522 -0
- package/vibe-upload.md +19 -28
package/dist/index.js
CHANGED
|
@@ -7248,7 +7248,6 @@ const messages = {
|
|
|
7248
7248
|
'cli.command.game.categories.category': 'Filter by an exact category value, e.g. Puzzle',
|
|
7249
7249
|
'cli.command.game.categories.unknown': 'Unknown category. Run ttmg game categories --format json for valid values.',
|
|
7250
7250
|
'cli.command.game.categories.invalidFormat': 'Use text, json, or ndjson output.',
|
|
7251
|
-
'cli.command.upload.minisType': 'MINI_GAME (default) or MINI_TOOL (local PPE trial; Native ZIP, Efficiency & Office)',
|
|
7252
7251
|
'cli.command.upload.vibeCategory': 'Optional game category; provide with --subcategory (--vibe). Query ttmg game categories --format json',
|
|
7253
7252
|
'cli.command.upload.vibeDescription': 'Optional game description (--vibe); default: Discover the fun of TikTok Mini Games.',
|
|
7254
7253
|
'cli.command.upload.vibeSubcategory': 'Optional subcategory belonging to --category; omit both to keep Other/Other (--vibe)',
|
|
@@ -7264,9 +7263,8 @@ Vibe QR upload (real PPE trial):
|
|
|
7264
7263
|
Keep this process running. At waiting_authorization, scan the QR on pageUrl with TikTok on your phone.
|
|
7265
7264
|
pageUrl is the local QR/progress page on the CLI computer. Reopen it with authorizationOpenCommand.
|
|
7266
7265
|
The CLI polls and uploads avatar, app_info and package automatically after authorization. Do not start a second upload.
|
|
7267
|
-
After upload, waiting_preview keeps polling the same task until previewUrl is available
|
|
7268
|
-
|
|
7269
|
-
The operation timeout defaults to 1800 seconds (maximum 3600). The page stays available during the operation and for 30 minutes after the CLI finishes.
|
|
7266
|
+
After upload, waiting_preview keeps polling the same task until previewUrl is available; pageUrl then shows the game preview QR.
|
|
7267
|
+
Preview ready: previewReady=true and qrPurpose=game_preview. A preview timeout preserves uploaded=true; check the task, do not reupload.
|
|
7270
7268
|
Real success: terminal=true, status=uploaded, uploaded=true, simulation=false. On failure, read errorCode and nextAction.
|
|
7271
7269
|
The local QR page opens for every output format; --no-open disables automatic opening.
|
|
7272
7270
|
The page displays the submitted title, icon, description, category and subcategory.
|
|
@@ -7485,7 +7483,7 @@ Vibe QR upload (real PPE trial):
|
|
|
7485
7483
|
'cli.command.upload.vibeApi': 'Minis Import API origin on a loopback IP only (--vibe)',
|
|
7486
7484
|
'cli.command.upload.vibeAuthorizeUrl': 'Obsolete: remove this option; task creation returns landing_url (--vibe)',
|
|
7487
7485
|
'cli.command.upload.vibePollInterval': 'Override task polling interval; otherwise use poll_interval_seconds (--vibe)',
|
|
7488
|
-
'cli.command.upload.vibeTimeout': 'Operation timeout in seconds (--vibe; default
|
|
7486
|
+
'cli.command.upload.vibeTimeout': 'Operation timeout in seconds (--vibe; default 300)',
|
|
7489
7487
|
'cli.command.upload.vibeRequired': 'These options require --vibe; the existing platform upload has not been executed.',
|
|
7490
7488
|
'cli.command.testAccount.desc': 'Manage Mini Game test-account access',
|
|
7491
7489
|
'cli.command.testAccount.add.desc': 'Generate the authorization entry for adding a TikTok test account',
|
|
@@ -7700,7 +7698,6 @@ Vibe QR upload (real PPE trial):
|
|
|
7700
7698
|
'cli.command.game.categories.category': '按类型的精确值筛选,例如 Puzzle',
|
|
7701
7699
|
'cli.command.game.categories.unknown': '类型不存在。使用 ttmg game categories --format json 查询有效值。',
|
|
7702
7700
|
'cli.command.game.categories.invalidFormat': '输出格式请使用 text、json 或 ndjson。',
|
|
7703
|
-
'cli.command.upload.minisType': 'MINI_GAME(默认)或 MINI_TOOL(本地 PPE 试用;Native ZIP、效率办公类)',
|
|
7704
7701
|
'cli.command.upload.vibeCategory': '可选游戏类型,须与 --subcategory 同传(--vibe);用 ttmg game categories --format json 查询',
|
|
7705
7702
|
'cli.command.upload.vibeDescription': '可选游戏描述(--vibe);默认值:Discover the fun of TikTok Mini Games.',
|
|
7706
7703
|
'cli.command.upload.vibeSubcategory': '所属类型的可选子类型;两者均不传保留 Other/Other(--vibe)',
|
|
@@ -7716,9 +7713,8 @@ Vibe 扫码上传(真实 PPE 试用):
|
|
|
7716
7713
|
保持原进程运行。在 waiting_authorization 阶段,使用手机 TikTok 扫描 pageUrl 页面中的二维码。
|
|
7717
7714
|
pageUrl 是 CLI 所在电脑的二维码/进度页;重新打开时,执行返回的 authorizationOpenCommand。
|
|
7718
7715
|
CLI 负责轮询,授权后自动上传 avatar、app_info、package,不再启动第二次上传。
|
|
7719
|
-
|
|
7720
|
-
previewReady=true
|
|
7721
|
-
整体超时默认 1800 秒,最大 3600 秒;页面在任务进行中保持可用,CLI 结束后再保留 30 分钟。
|
|
7716
|
+
上传后进入 waiting_preview,继续查询同一任务;拿到 previewUrl 后,原 pageUrl 页面展示游戏预览二维码。
|
|
7717
|
+
previewReady=true 且 qrPurpose=game_preview 表示可扫码预览;等待超时仍保留 uploaded=true,请核对任务,不要重传。
|
|
7722
7718
|
真实成功:terminal=true、status=uploaded、uploaded=true、simulation=false。失败时读取 errorCode 和 nextAction。
|
|
7723
7719
|
所有输出格式都会打开本地二维码页;--no-open 可关闭自动打开。
|
|
7724
7720
|
页面展示本次提交的标题、头像、描述、类型和子类型。
|
|
@@ -7937,7 +7933,7 @@ Vibe 扫码上传(真实 PPE 试用):
|
|
|
7937
7933
|
'cli.command.upload.vibeApi': 'Minis Import 接口地址,仅接受回环 IP(--vibe)',
|
|
7938
7934
|
'cli.command.upload.vibeAuthorizeUrl': '已废弃,请移除此参数;授权地址由创建任务返回(--vibe)',
|
|
7939
7935
|
'cli.command.upload.vibePollInterval': '覆盖 task 轮询间隔;默认使用接口 poll_interval_seconds(--vibe)',
|
|
7940
|
-
'cli.command.upload.vibeTimeout': '操作超时秒数(--vibe,默认
|
|
7936
|
+
'cli.command.upload.vibeTimeout': '操作超时秒数(--vibe,默认 300)',
|
|
7941
7937
|
'cli.command.upload.vibeRequired': '这些参数需要 --vibe;没有执行原有平台上传。',
|
|
7942
7938
|
'cli.command.testAccount.desc': '管理小游戏测试账号访问',
|
|
7943
7939
|
'cli.command.testAccount.add.desc': '生成添加 TikTok 测试账号的授权入口',
|
|
@@ -16386,7 +16382,7 @@ async function authStatus(options = {}) {
|
|
|
16386
16382
|
return result;
|
|
16387
16383
|
}
|
|
16388
16384
|
|
|
16389
|
-
var version = "0.4.6-vibe-beta.
|
|
16385
|
+
var version = "0.4.6-vibe-beta.23";
|
|
16390
16386
|
var engines = {
|
|
16391
16387
|
node: ">=20.19.1 <21 || >=22"
|
|
16392
16388
|
};
|
|
@@ -16409,23 +16405,6 @@ const VIBE_INPUT_REQUIREMENTS = {
|
|
|
16409
16405
|
categoryPairRequiredWhenProvided: true,
|
|
16410
16406
|
defaultCategory: { category: 'Other', subcategory: 'Other' },
|
|
16411
16407
|
};
|
|
16412
|
-
const VIBE_TOOL_INPUT_REQUIREMENTS = {
|
|
16413
|
-
...VIBE_INPUT_REQUIREMENTS,
|
|
16414
|
-
defaultMetadataProfile: 'ppe-tool-trial',
|
|
16415
|
-
optionalSkillInputs: ['description', 'minisType'],
|
|
16416
|
-
defaultDescription: 'A simple productivity tool for everyday tasks.',
|
|
16417
|
-
categoryQuery: undefined,
|
|
16418
|
-
categoryPairRequiredWhenProvided: false,
|
|
16419
|
-
defaultCategory: { category: 'EFFICIENCY_AND_OFFICE', subcategory: '' },
|
|
16420
|
-
region: 'US', minSDKVersion: '1.0.0',
|
|
16421
|
-
};
|
|
16422
|
-
function resolveMinisType(value) {
|
|
16423
|
-
if (value === undefined)
|
|
16424
|
-
return 'MINI_GAME';
|
|
16425
|
-
if (value !== 'MINI_GAME' && value !== 'MINI_TOOL')
|
|
16426
|
-
throw new VibeError('VIBE_INVALID_MINIS_TYPE');
|
|
16427
|
-
return value;
|
|
16428
|
-
}
|
|
16429
16408
|
class VibeError extends Error {
|
|
16430
16409
|
constructor(code) {
|
|
16431
16410
|
super(code);
|
|
@@ -17163,10 +17142,6 @@ function getCapabilities() {
|
|
|
17163
17142
|
networkRequired: false,
|
|
17164
17143
|
},
|
|
17165
17144
|
vibeUpload: {
|
|
17166
|
-
minisType: {
|
|
17167
|
-
option: '--minis-type', default: 'MINI_GAME', values: ['MINI_GAME', 'MINI_TOOL'],
|
|
17168
|
-
tool: { readiness: 'local-ppe-trial', deviceVerified: false, category: 'EFFICIENCY_AND_OFFICE', region: 'US', minSDKVersion: '1.0.0', assetType: 'NativeRuntime', metadataOverrides: false },
|
|
17169
|
-
},
|
|
17170
17145
|
command: 'ttmg upload --vibe --dir <gameDir> --title <title> --icon <iconPath> --format ndjson',
|
|
17171
17146
|
mockCommand: 'ttmg upload --vibe --mock --dir <gameDir> --title <title> --icon <iconPath> --format ndjson',
|
|
17172
17147
|
guide: 'vibe-upload.md',
|
|
@@ -17300,9 +17275,6 @@ function getCapabilities() {
|
|
|
17300
17275
|
resultFields: ['previewUrl', 'previewReady', 'clientKey', 'appId', 'codeAssetId', 'minisCenterHistoryId'],
|
|
17301
17276
|
readiness: 'nonempty-valid-preview-url',
|
|
17302
17277
|
qrPurpose: 'game_preview',
|
|
17303
|
-
toolQrPurpose: 'tool_preview',
|
|
17304
|
-
pageQrSource: 'preview-url',
|
|
17305
|
-
retainsBindingQr: true,
|
|
17306
17278
|
sameTask: true,
|
|
17307
17279
|
timeout: 'shared-operation-timeout',
|
|
17308
17280
|
failurePreservesUploaded: true,
|
|
@@ -18066,7 +18038,6 @@ withVerboseOption(program
|
|
|
18066
18038
|
.option('--serve-preview-page', t('cli.command.upload.servePreviewPage'))
|
|
18067
18039
|
.option('--no-open', t('cli.command.upload.noOpen'))
|
|
18068
18040
|
.option('--vibe', t('cli.command.upload.vibe'))
|
|
18069
|
-
.option('--minis-type <type>', t('cli.command.upload.minisType'))
|
|
18070
18041
|
.addOption(new commander.Option('--web', t('cli.command.upload.vibeWeb')).hideHelp())
|
|
18071
18042
|
// Vibe v2 backend selection (including opt-in ppe-mock) stays outside the Skill workflow.
|
|
18072
18043
|
.option('--title <title>', t('cli.command.upload.vibeTitle'))
|
|
@@ -18084,15 +18055,15 @@ withVerboseOption(program
|
|
|
18084
18055
|
.option('--poll-interval <seconds>', t('cli.command.upload.vibePollInterval'))
|
|
18085
18056
|
.option('--timeout <seconds>', t('cli.command.upload.vibeTimeout'))
|
|
18086
18057
|
.option('--format <format>', t('cli.command.upload.format'), 'text')
|
|
18087
|
-
.addHelpText('after', t('cli.command.upload.vibeWebGuide'))).action(async (cmd) => {
|
|
18088
|
-
|
|
18058
|
+
.addHelpText('after', t('cli.command.upload.vibeWebGuide'))).action(async (cmd, command) => {
|
|
18059
|
+
const uploadOptions = { ...command.optsWithGlobals(), ...cmd };
|
|
18060
|
+
if (uploadOptions.vibe) {
|
|
18089
18061
|
const { vibeUpload } = await Promise.resolve().then(function () { return index$1; });
|
|
18090
|
-
await vibeUpload(
|
|
18062
|
+
await vibeUpload(uploadOptions);
|
|
18091
18063
|
return;
|
|
18092
18064
|
}
|
|
18093
18065
|
if ([
|
|
18094
18066
|
cmd.title,
|
|
18095
|
-
cmd.minisType,
|
|
18096
18067
|
cmd.description,
|
|
18097
18068
|
cmd.icon,
|
|
18098
18069
|
cmd.appInfo,
|
|
@@ -18668,6 +18639,50 @@ const MINIS_IMPORT_PATHS = {
|
|
|
18668
18639
|
info: `${prefix}submit_minis_import_info_for_agent/`,
|
|
18669
18640
|
package: `${prefix}upload_minis_import_package_for_agent/`,
|
|
18670
18641
|
};
|
|
18642
|
+
const sensitiveKey = /authorization|cookie|password|secret|token|email/i;
|
|
18643
|
+
function safeTraceUrl(value) {
|
|
18644
|
+
try {
|
|
18645
|
+
const url = new URL(value);
|
|
18646
|
+
for (const key of url.searchParams.keys()) {
|
|
18647
|
+
if (sensitiveKey.test(key))
|
|
18648
|
+
url.searchParams.set(key, '[REDACTED]');
|
|
18649
|
+
}
|
|
18650
|
+
return url.toString();
|
|
18651
|
+
}
|
|
18652
|
+
catch {
|
|
18653
|
+
return value;
|
|
18654
|
+
}
|
|
18655
|
+
}
|
|
18656
|
+
function safeTraceError(value) {
|
|
18657
|
+
return String(value ?? '')
|
|
18658
|
+
.replace(/([?&](?:import_token|access_token|token)=)[^&#\s]*/gi, '$1[REDACTED]')
|
|
18659
|
+
.replace(/((?:import_token|access_token|token)[=:]\s*)[^,;\s]*/gi, '$1[REDACTED]');
|
|
18660
|
+
}
|
|
18661
|
+
function safeTraceValue(value, key = '', seen = new WeakSet()) {
|
|
18662
|
+
if (sensitiveKey.test(key))
|
|
18663
|
+
return '[REDACTED]';
|
|
18664
|
+
if (Buffer.isBuffer(value))
|
|
18665
|
+
return { type: 'Buffer', sizeBytes: value.length };
|
|
18666
|
+
if (typeof value === 'string')
|
|
18667
|
+
return /url$/i.test(key) || /^https?:\/\//i.test(value) ? safeTraceUrl(value) : value;
|
|
18668
|
+
if (!value || typeof value !== 'object')
|
|
18669
|
+
return value;
|
|
18670
|
+
if (seen.has(value))
|
|
18671
|
+
return '[Circular]';
|
|
18672
|
+
seen.add(value);
|
|
18673
|
+
if (Array.isArray(value))
|
|
18674
|
+
return value.map(item => safeTraceValue(item, '', seen));
|
|
18675
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, childValue]) => [
|
|
18676
|
+
childKey,
|
|
18677
|
+
safeTraceValue(childValue, childKey, seen),
|
|
18678
|
+
]));
|
|
18679
|
+
}
|
|
18680
|
+
function emitTrace(sink, trace) {
|
|
18681
|
+
try {
|
|
18682
|
+
sink?.(trace);
|
|
18683
|
+
}
|
|
18684
|
+
catch { /* Diagnostics must never affect uploads. */ }
|
|
18685
|
+
}
|
|
18671
18686
|
function safeWebUrl(value) {
|
|
18672
18687
|
if (typeof value !== 'string')
|
|
18673
18688
|
throw new VibeError('VIBE_INVALID_RESPONSE');
|
|
@@ -18693,10 +18708,7 @@ function taskId(value) {
|
|
|
18693
18708
|
return value;
|
|
18694
18709
|
}
|
|
18695
18710
|
function status(value) {
|
|
18696
|
-
|
|
18697
|
-
// Keep polling it just like other non-failure states and use preview_url to
|
|
18698
|
-
// determine preview readiness, rather than treating review as a prerequisite.
|
|
18699
|
-
if (!Number.isInteger(value) || Number(value) < 1 || Number(value) > 9)
|
|
18711
|
+
if (!Number.isInteger(value) || Number(value) < 1 || Number(value) > 8)
|
|
18700
18712
|
throw new VibeError('VIBE_INVALID_RESPONSE');
|
|
18701
18713
|
return Number(value);
|
|
18702
18714
|
}
|
|
@@ -18758,7 +18770,6 @@ function createBackend(mode, transport) {
|
|
|
18758
18770
|
signal,
|
|
18759
18771
|
});
|
|
18760
18772
|
if (!nonempty(data.import_token) ||
|
|
18761
|
-
!nonempty(data.landing_url) ||
|
|
18762
18773
|
!Number.isFinite(data.poll_interval_seconds) ||
|
|
18763
18774
|
data.poll_interval_seconds <= 0 ||
|
|
18764
18775
|
data.poll_interval_seconds > 3600)
|
|
@@ -18766,7 +18777,7 @@ function createBackend(mode, transport) {
|
|
|
18766
18777
|
return {
|
|
18767
18778
|
taskId: taskId(data.task_id),
|
|
18768
18779
|
importToken: data.import_token,
|
|
18769
|
-
landingUrl: data.landing_url,
|
|
18780
|
+
landingUrl: safeWebUrl(data.landing_url),
|
|
18770
18781
|
pollIntervalSeconds: data.poll_interval_seconds,
|
|
18771
18782
|
};
|
|
18772
18783
|
},
|
|
@@ -18808,7 +18819,7 @@ function createBackend(mode, transport) {
|
|
|
18808
18819
|
path: MINIS_IMPORT_PATHS.info,
|
|
18809
18820
|
data: {
|
|
18810
18821
|
...credentials(task),
|
|
18811
|
-
app_info: { ...
|
|
18822
|
+
app_info: { ...(appInfo || VIBE_PPE_DEFAULT_APP_INFO), app_name: title, icon_uri: iconUri },
|
|
18812
18823
|
},
|
|
18813
18824
|
signal,
|
|
18814
18825
|
});
|
|
@@ -18833,7 +18844,7 @@ function createBackend(mode, transport) {
|
|
|
18833
18844
|
},
|
|
18834
18845
|
};
|
|
18835
18846
|
}
|
|
18836
|
-
function createLocalHttpBackend(baseUrl) {
|
|
18847
|
+
function createLocalHttpBackend(baseUrl, adapter, trace) {
|
|
18837
18848
|
let base;
|
|
18838
18849
|
try {
|
|
18839
18850
|
base = new URL(safeWebUrl(baseUrl));
|
|
@@ -18846,16 +18857,16 @@ function createLocalHttpBackend(baseUrl) {
|
|
|
18846
18857
|
base.search ||
|
|
18847
18858
|
base.hash)
|
|
18848
18859
|
throw new VibeError('VIBE_INVALID_LOCAL_ENDPOINT');
|
|
18849
|
-
return createHttpBackend('local-http', base.origin);
|
|
18860
|
+
return createHttpBackend('local-http', base.origin, {}, adapter, trace);
|
|
18850
18861
|
}
|
|
18851
|
-
function createPpeBackend(lane, adapter) {
|
|
18862
|
+
function createPpeBackend(lane, adapter, trace) {
|
|
18852
18863
|
// No default lane and no production fallback. Keep PPE requests on the known host.
|
|
18853
18864
|
if (!lane || !/^ppe_[a-zA-Z0-9_-]{1,64}$/.test(lane))
|
|
18854
18865
|
throw new VibeError('VIBE_INVALID_PPE_ENV');
|
|
18855
18866
|
const backend = createHttpBackend('ppe', 'https://developers.tiktok.com', {
|
|
18856
18867
|
'x-use-ppe': '1',
|
|
18857
18868
|
'x-tt-env': lane,
|
|
18858
|
-
}, adapter);
|
|
18869
|
+
}, adapter, trace);
|
|
18859
18870
|
return {
|
|
18860
18871
|
...backend,
|
|
18861
18872
|
uploadAvailable: true,
|
|
@@ -18864,11 +18875,11 @@ function createPpeBackend(lane, adapter) {
|
|
|
18864
18875
|
/** Opt-in integration demo: only creation and polling can reach PPE. */
|
|
18865
18876
|
// Release-owned configuration, not a developer setup step. Remove PPE routing
|
|
18866
18877
|
// here when the production chain is ready; do not change the Skill command.
|
|
18867
|
-
function createVibeTrialBackend(scenario, adapter) {
|
|
18868
|
-
return createPpeMockBackend('ppe_op_vc', adapter, scenario);
|
|
18878
|
+
function createVibeTrialBackend(scenario, adapter, trace) {
|
|
18879
|
+
return createPpeMockBackend('ppe_op_vc', adapter, scenario, trace);
|
|
18869
18880
|
}
|
|
18870
|
-
function createPpeMockBackend(lane, adapter, scenario = 'success') {
|
|
18871
|
-
const real = createPpeBackend(lane, adapter);
|
|
18881
|
+
function createPpeMockBackend(lane, adapter, scenario = 'success', trace) {
|
|
18882
|
+
const real = createPpeBackend(lane, adapter, trace);
|
|
18872
18883
|
const mock = createMockBackend(scenario);
|
|
18873
18884
|
let realTask;
|
|
18874
18885
|
let mockTask;
|
|
@@ -18914,7 +18925,7 @@ function createPpeMockBackend(lane, adapter, scenario = 'success') {
|
|
|
18914
18925
|
uploadPackage: async (task, input, signal, onProgress) => mock.uploadPackage(uploadTask(task), input, signal, onProgress),
|
|
18915
18926
|
};
|
|
18916
18927
|
}
|
|
18917
|
-
function createHttpBackend(mode, baseURL, environmentHeaders = {}, adapter) {
|
|
18928
|
+
function createHttpBackend(mode, baseURL, environmentHeaders = {}, adapter, trace) {
|
|
18918
18929
|
// Own the agents so an early multipart response cannot leave upload sockets
|
|
18919
18930
|
// keeping the CLI alive after its terminal result. Never destroy global agents.
|
|
18920
18931
|
const httpAgent = new http.Agent();
|
|
@@ -18928,7 +18939,11 @@ function createHttpBackend(mode, baseURL, environmentHeaders = {}, adapter) {
|
|
|
18928
18939
|
httpAgent,
|
|
18929
18940
|
httpsAgent,
|
|
18930
18941
|
});
|
|
18942
|
+
let requestSequence = 0;
|
|
18931
18943
|
const backend = createBackend(mode, async (request) => {
|
|
18944
|
+
const requestId = ++requestSequence;
|
|
18945
|
+
const startedAt = Date.now();
|
|
18946
|
+
const url = new URL(request.path, baseURL).toString();
|
|
18932
18947
|
try {
|
|
18933
18948
|
let body;
|
|
18934
18949
|
let headers = {};
|
|
@@ -18950,18 +18965,42 @@ function createHttpBackend(mode, baseURL, environmentHeaders = {}, adapter) {
|
|
|
18950
18965
|
body = JSON.stringify(request.data).replace(/"task_id":"([1-9]\d*)"/, '"task_id":$1');
|
|
18951
18966
|
headers = { 'Content-Type': 'application/json' };
|
|
18952
18967
|
}
|
|
18953
|
-
|
|
18968
|
+
const requestHeaders = { ...environmentHeaders, ...headers };
|
|
18969
|
+
emitTrace(trace, {
|
|
18970
|
+
event: 'http_request', requestId, method: request.method, url,
|
|
18971
|
+
headers: safeTraceValue(requestHeaders),
|
|
18972
|
+
request: safeTraceValue(request.data),
|
|
18973
|
+
});
|
|
18974
|
+
const response = await client.request({
|
|
18954
18975
|
method: request.method,
|
|
18955
18976
|
url: request.path,
|
|
18956
18977
|
params: request.method === 'GET' ? request.data : undefined,
|
|
18957
18978
|
data: body,
|
|
18958
|
-
headers:
|
|
18979
|
+
headers: requestHeaders,
|
|
18959
18980
|
signal: request.signal,
|
|
18960
18981
|
maxBodyLength: Infinity,
|
|
18961
18982
|
onUploadProgress: p => request.onProgress?.(p.loaded, p.total),
|
|
18962
|
-
})
|
|
18983
|
+
});
|
|
18984
|
+
emitTrace(trace, {
|
|
18985
|
+
event: 'http_response', requestId, method: request.method, url,
|
|
18986
|
+
statusCode: response.status,
|
|
18987
|
+
headers: safeTraceValue(response.headers),
|
|
18988
|
+
response: safeTraceValue(response.data),
|
|
18989
|
+
elapsedMs: Date.now() - startedAt,
|
|
18990
|
+
});
|
|
18991
|
+
return response.data;
|
|
18963
18992
|
}
|
|
18964
|
-
catch {
|
|
18993
|
+
catch (error) {
|
|
18994
|
+
emitTrace(trace, {
|
|
18995
|
+
event: 'http_error', requestId, method: request.method, url,
|
|
18996
|
+
statusCode: error?.response?.status,
|
|
18997
|
+
headers: safeTraceValue(error?.response?.headers),
|
|
18998
|
+
response: safeTraceValue(error?.response?.data),
|
|
18999
|
+
elapsedMs: Date.now() - startedAt,
|
|
19000
|
+
error: request.signal.aborted
|
|
19001
|
+
? 'Request cancelled'
|
|
19002
|
+
: safeTraceError(error?.message || error),
|
|
19003
|
+
});
|
|
18965
19004
|
throw new VibeError(request.signal.aborted
|
|
18966
19005
|
? 'VIBE_CANCELLED'
|
|
18967
19006
|
: 'VIBE_BACKEND_REQUEST_FAILED');
|
|
@@ -19000,7 +19039,7 @@ function createMockBackend(scenario = 'success') {
|
|
|
19000
19039
|
if (path === MINIS_IMPORT_PATHS.create) {
|
|
19001
19040
|
if (created ||
|
|
19002
19041
|
data.source_type !== 1 ||
|
|
19003
|
-
|
|
19042
|
+
data.minis_type !== 'MINI_GAME' ||
|
|
19004
19043
|
!nonempty(data.source_project_id) ||
|
|
19005
19044
|
!nonempty(data.task_idempotency_id))
|
|
19006
19045
|
throw new VibeError('VIBE_INVALID_RESPONSE');
|
|
@@ -19069,7 +19108,7 @@ function createMockBackend(scenario = 'success') {
|
|
|
19069
19108
|
function createAuthorizationScheme(landingUrl) {
|
|
19070
19109
|
// QR and browser authorization use the same backend-provided landing URL.
|
|
19071
19110
|
// Preserve its query exactly; do not wrap it in an app scheme or re-encode it.
|
|
19072
|
-
return landingUrl;
|
|
19111
|
+
return safeWebUrl(landingUrl);
|
|
19073
19112
|
}
|
|
19074
19113
|
|
|
19075
19114
|
const DEV_PORT = 3000;
|
|
@@ -19304,19 +19343,9 @@ function readFile(file, limit, code) {
|
|
|
19304
19343
|
}
|
|
19305
19344
|
async function prepareVibeInput(options, onPackaging, signal, deps = { check: executeCheck, zip: zipCwdToBuffer }) {
|
|
19306
19345
|
const title = options.title?.trim();
|
|
19307
|
-
const minisType = resolveMinisType(options.minisType);
|
|
19308
19346
|
if (!title)
|
|
19309
19347
|
throw new VibeError('VIBE_TITLE_REQUIRED');
|
|
19310
|
-
|
|
19311
|
-
throw new VibeError('VIBE_TOOL_TRIAL_METADATA');
|
|
19312
|
-
const appInfo = minisType === 'MINI_TOOL'
|
|
19313
|
-
? applyVibeDescription(options.description, {
|
|
19314
|
-
description: 'A simple productivity tool for everyday tasks.',
|
|
19315
|
-
category: 'EFFICIENCY_AND_OFFICE', subcategory: '',
|
|
19316
|
-
terms_of_service: 'https://www.tiktok.com', privacy_policy: 'https://www.tiktok.com',
|
|
19317
|
-
copyright_confirmation: true, region: 'US', min_sdk_version: '1.0.0',
|
|
19318
|
-
})
|
|
19319
|
-
: applyVibeCategory(options, applyVibeDescription(options.description, readVibeAppInfo(options.appInfo)));
|
|
19348
|
+
const appInfo = applyVibeCategory(options, applyVibeDescription(options.description, readVibeAppInfo(options.appInfo)));
|
|
19320
19349
|
if (options.package && options.dir)
|
|
19321
19350
|
throw new VibeError('VIBE_INPUT_CONFLICT');
|
|
19322
19351
|
const image = readFile(options.icon, VIBE_ICON_LIMIT, 'VIBE_INVALID_ICON');
|
|
@@ -19350,7 +19379,6 @@ async function prepareVibeInput(options, onPackaging, signal, deps = { check: ex
|
|
|
19350
19379
|
if (signal.aborted)
|
|
19351
19380
|
throw new VibeError('VIBE_CANCELLED');
|
|
19352
19381
|
const input = {
|
|
19353
|
-
minisType,
|
|
19354
19382
|
title,
|
|
19355
19383
|
...(appInfo ? { appInfo } : {}),
|
|
19356
19384
|
icon: {
|
|
@@ -19370,294 +19398,33 @@ const vibePageStyles = `
|
|
|
19370
19398
|
:root{font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;color:#24282f;background:#fff;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}
|
|
19371
19399
|
*{box-sizing:border-box}
|
|
19372
19400
|
body{margin:0;min-height:100vh;min-height:100svh;display:flex;align-items:center;justify-content:center;padding:64px 24px;font-size:14px;line-height:1.5}
|
|
19373
|
-
.shell{position:relative;width:
|
|
19374
|
-
.steps{display:flex;align-items:center;justify-content:center;gap:10px;margin:
|
|
19401
|
+
.shell{position:relative;width:100%;max-width:400px}
|
|
19402
|
+
.steps{display:flex;align-items:center;justify-content:center;gap:10px;margin:0 0 24px;padding:0;list-style:none}
|
|
19375
19403
|
.step{display:flex;align-items:center;gap:6px;font-size:11px;color:#9298a2;white-space:nowrap}
|
|
19376
19404
|
.step:after{content:"";height:1px;width:25px;margin-left:4px;background:#e9ebef}.step:last-child:after{display:none}
|
|
19377
19405
|
.step-number{height:18px;min-width:18px;border-radius:50%;border:1px solid #e2e5e9;display:grid;place-items:center;font-size:10px}
|
|
19378
|
-
.step.active{color:#272c34;font-weight:600}.step.active .step-number{background
|
|
19379
|
-
.step.done{color:#
|
|
19380
|
-
.
|
|
19381
|
-
|
|
19382
|
-
.
|
|
19383
|
-
.
|
|
19384
|
-
.preview-loading{width:min(240px,100%);height:4px;margin:18px auto 0;border-radius:4px;overflow:hidden;background:#eceef1}
|
|
19385
|
-
.preview-loading span{display:block;width:40%;height:100%;border-radius:inherit;background:#858c96;animation:preview-loading-slide 1.8s ease-in-out infinite}
|
|
19386
|
-
@keyframes preview-loading-slide{from{transform:translateX(-100%)}to{transform:translateX(250%)}}
|
|
19387
|
-
@media(prefers-reduced-motion:reduce){.preview-loading span{animation:none;transform:translateX(75%)}}
|
|
19388
|
-
.game{display:grid;grid-template-columns:44px minmax(0,1fr);column-gap:14px;align-items:start;margin:24px 22px 0;padding:16px 18px;border-radius:16px;background:#f7f8fa;text-align:left}
|
|
19389
|
-
.game-summary{display:contents}.game-title{grid-column:2;max-width:100%;margin:0;font-size:15px;font-weight:600;overflow-wrap:anywhere;min-width:0}
|
|
19406
|
+
.step.active{color:#272c34;font-weight:600}.step.active .step-number{background:#272c34;color:#fff;border-color:#272c34}
|
|
19407
|
+
.step.done{color:#537565}.step.done .step-number{background:#edf6f0;color:#287c54;border-color:#edf6f0}
|
|
19408
|
+
.heading{text-align:center}h1{font-size:26px;font-weight:650;line-height:1.35;letter-spacing:-.6px;margin:0 0 10px}
|
|
19409
|
+
.subtitle{color:#7a818c;font-size:12px;line-height:1.7;margin:0;overflow-wrap:anywhere}
|
|
19410
|
+
.game{margin:24px 0 0;padding-top:20px;border-top:1px solid #eceef1;text-align:center}
|
|
19411
|
+
.game-summary{display:flex;align-items:center;justify-content:center;gap:12px}.game-title{max-width:100%;margin:0;font-size:15px;font-weight:600;overflow-wrap:anywhere;min-width:0}
|
|
19390
19412
|
.game-icon,.game-avatar{width:44px;height:44px;border-radius:10px;flex-shrink:0;background:#f0f2f5}.game-avatar{object-fit:cover}.game-icon{display:grid;place-items:center;color:#707b8b}
|
|
19391
|
-
.game-
|
|
19392
|
-
.game-
|
|
19393
|
-
.
|
|
19394
|
-
/* 32px preserves at least four quiet modules even for a 21-module QR at 160px. */
|
|
19395
|
-
.scan-action{text-align:center;font-size:13px;font-weight:500;margin:26px 0 10px}.qr-area{display:flex;justify-content:center}.qr-frame{padding:32px;width:224px;height:224px;background:#fff}.qr-frame img{display:block;width:160px;height:160px;max-width:100%}
|
|
19396
|
-
.game+.qr-area{margin-top:20px}.scan-hint{text-align:center;font-size:11px;color:#7a818c;line-height:1.7;margin:8px 0 0;min-height:19px}
|
|
19397
|
-
.qr-area{flex-wrap:wrap;gap:20px;min-height:288px;align-content:flex-start}.qr-code{margin:0;width:224px;text-align:center}.qr-code figcaption{font-size:13px;font-weight:600;margin-top:4px;min-height:60px}.qr-code figcaption span{display:block;font-size:11px;font-weight:400;color:#7a818c;margin-top:4px;min-height:35px}
|
|
19398
|
-
.snake{display:grid;grid-template-columns:repeat(var(--cells),var(--module));color:#24282f;width:160px;height:160px}
|
|
19399
|
-
.snake i{width:var(--module);height:var(--module)}.snake i.lit,.snake i.on{background:currentColor}.snake i.fresh{animation:snake-settle .5s ease-out}.snake i.apple{background:#a3e635;animation:snake-apple 1.2s ease-in-out infinite}
|
|
19400
|
-
@keyframes snake-settle{from{background:#a3e635}to{background:currentColor}}@keyframes snake-apple{50%{opacity:.4}}
|
|
19401
|
-
@media(prefers-reduced-motion:reduce){.swirl,.snake i.fresh,.snake i.apple{animation:none}}
|
|
19413
|
+
.game-details{margin:16px 0 0;font-size:13px}.game-details>div:first-child,.game-categories>div{display:flex;align-items:baseline;justify-content:center;gap:8px}.game-details dt{flex-shrink:0;font-size:11px;color:#9298a2;margin:0}.game-details dd{min-width:0;margin:0;text-align:left;overflow-wrap:anywhere}
|
|
19414
|
+
.game-description{white-space:pre-wrap;line-height:1.65}.game-categories{display:grid;grid-template-columns:1fr;gap:16px;margin-top:16px}
|
|
19415
|
+
.scan-action{text-align:center;font-size:13px;font-weight:500;margin:26px 0 10px}.qr-area{display:flex;justify-content:center}.qr-frame{padding:8px;width:208px;height:208px}.qr-frame img{display:block;width:192px;height:192px;max-width:100%}
|
|
19402
19416
|
.upload-area{display:flex;align-items:center;flex-direction:column;gap:16px;padding-top:26px}.percentage{font-size:34px;line-height:1;font-variant-numeric:tabular-nums;font-weight:550}.percentage span{font-size:16px;color:#858e9d;padding-left:3px}
|
|
19403
19417
|
.progress-wrap{width:100%}progress{display:block;width:100%;height:4px;border:none;border-radius:20px;overflow:hidden;background:#edf0f4;accent-color:#4c6378}progress::-webkit-progress-bar{background:#edf0f4;border-radius:20px}progress::-webkit-progress-value{background:#4c6378;border-radius:20px}progress::-moz-progress-bar{background:#4c6378;border-radius:20px}.transfer-label{text-align:center;font-size:11px;color:#8b939f;margin:8px 0 0}
|
|
19404
19418
|
.footer{text-align:center;font-size:10px;line-height:1.7;color:#a0a5ae;margin:24px 0 0}.footer .dot{display:inline-block;height:5px;width:5px;border-radius:50%;background:#c1995e;vertical-align:middle;margin-right:5px}.mock-badge{width:fit-content;margin:0 0 10px auto;font-size:10px;padding:2px 7px;border:1px solid #dfe2e7;border-radius:5px;color:#707783}
|
|
19405
|
-
@media(max-width:380px){body{padding-left:20px;padding-right:20px}.steps{gap:7px}.step:after{width:16px;margin-left:1px}}
|
|
19406
|
-
@media(max-width:520px){.game{margin-left:0;margin-right:0}.scan-hint{min-height:38px}}
|
|
19419
|
+
@media(max-width:380px){body{padding-left:20px;padding-right:20px}.steps{gap:7px}.step:after{width:16px;margin-left:1px}h1{font-size:24px}}
|
|
19407
19420
|
`;
|
|
19408
19421
|
|
|
19409
|
-
// Adapted from tiktok-minis-skills publish_tool.js at 132cd91.
|
|
19410
|
-
// Decorative waiting animation only; real preview QRs always use previewUrl.
|
|
19411
|
-
const qrPreviewLoader = `(() => {
|
|
19412
|
-
const neighbours = (p, w) => {
|
|
19413
|
-
const row = (p / w) | 0, col = p % w, out = [];
|
|
19414
|
-
if (row > 0) out.push(p - w);
|
|
19415
|
-
if (row < w - 1) out.push(p + w);
|
|
19416
|
-
if (col > 0) out.push(p - 1);
|
|
19417
|
-
if (col < w - 1) out.push(p + 1);
|
|
19418
|
-
return out;
|
|
19419
|
-
};
|
|
19420
|
-
|
|
19421
|
-
/** First step of a shortest path from 'from' to 'to', body cells excluded. */
|
|
19422
|
-
function stepToward(from, to, blocked, w) {
|
|
19423
|
-
if (from === to) return from;
|
|
19424
|
-
const prev = new Map([[from, from]]);
|
|
19425
|
-
let front = [from];
|
|
19426
|
-
while (front.length) {
|
|
19427
|
-
const next = [];
|
|
19428
|
-
for (const cell of front)
|
|
19429
|
-
for (const nb of neighbours(cell, w)) {
|
|
19430
|
-
if (prev.has(nb) || (blocked.has(nb) && nb !== to)) continue;
|
|
19431
|
-
prev.set(nb, cell);
|
|
19432
|
-
if (nb === to) {
|
|
19433
|
-
let node = nb;
|
|
19434
|
-
while (prev.get(node) !== from) node = prev.get(node);
|
|
19435
|
-
return node;
|
|
19436
|
-
}
|
|
19437
|
-
next.push(nb);
|
|
19438
|
-
}
|
|
19439
|
-
front = next;
|
|
19440
|
-
}
|
|
19441
|
-
return -1;
|
|
19442
|
-
}
|
|
19443
|
-
|
|
19444
|
-
/** Nearest still-unlit module, by plain grid distance from the head. */
|
|
19445
|
-
function nearestTarget(head, pending, w) {
|
|
19446
|
-
const hr = (head / w) | 0, hc = head % w;
|
|
19447
|
-
let best = -1, bestD = Infinity;
|
|
19448
|
-
for (const cell of pending) {
|
|
19449
|
-
const d = Math.abs(((cell / w) | 0) - hr) + Math.abs((cell % w) - hc);
|
|
19450
|
-
if (d < bestD) { bestD = d; best = cell; }
|
|
19451
|
-
}
|
|
19452
|
-
return best;
|
|
19453
|
-
}
|
|
19454
|
-
|
|
19455
|
-
function play(matrix, w, maxLength) {
|
|
19456
|
-
const pending = new Set();
|
|
19457
|
-
for (let i = 0; i < w * w; i++) if (matrix[i]) pending.add(i);
|
|
19458
|
-
|
|
19459
|
-
// The snake starts on a dark module, and only moving lights one, so the
|
|
19460
|
-
// starting square has to be lit here or the finished code is a module
|
|
19461
|
-
// short -- a hole the error correction hides rather than a code that is
|
|
19462
|
-
// actually right.
|
|
19463
|
-
const start = [...pending][0];
|
|
19464
|
-
pending.delete(start);
|
|
19465
|
-
const snake = [start];
|
|
19466
|
-
const body = new Set(snake);
|
|
19467
|
-
const lit = new Set([start]);
|
|
19468
|
-
const frames = [];
|
|
19469
|
-
let budget = w * w * 6;
|
|
19470
|
-
|
|
19471
|
-
while (pending.size && --budget > 0) {
|
|
19472
|
-
const head = snake[snake.length - 1];
|
|
19473
|
-
const target = nearestTarget(head, pending, w);
|
|
19474
|
-
if (target < 0) break;
|
|
19475
|
-
const move = stepToward(head, target, body, w);
|
|
19476
|
-
if (move < 0) break;
|
|
19477
|
-
|
|
19478
|
-
snake.push(move);
|
|
19479
|
-
body.add(move);
|
|
19480
|
-
while (snake.length > maxLength) body.delete(snake.shift());
|
|
19481
|
-
if (matrix[move]) { lit.add(move); pending.delete(move); }
|
|
19482
|
-
|
|
19483
|
-
frames.push({ snake: [...snake], lit: [...lit], apple: target });
|
|
19484
|
-
}
|
|
19485
|
-
return frames;
|
|
19486
|
-
}
|
|
19487
|
-
|
|
19488
|
-
function mount(host) {
|
|
19489
|
-
if (host.dataset.mounted) return;
|
|
19490
|
-
host.dataset.mounted = '1';
|
|
19491
|
-
const matrix = JSON.parse(host.dataset.matrix);
|
|
19492
|
-
const w = Number(host.dataset.size);
|
|
19493
|
-
const SPEED = Number(host.dataset.speed) || 20; // ms a step
|
|
19494
|
-
host.style.setProperty('--cells', w);
|
|
19495
|
-
host.replaceChildren(...Array.from({ length: w * w }, () => document.createElement('i')));
|
|
19496
|
-
const dots = Array.from(host.children);
|
|
19497
|
-
|
|
19498
|
-
const frames = play(matrix, w, 14);
|
|
19499
|
-
const last = frames.length - 1;
|
|
19500
|
-
if (last < 1) return;
|
|
19501
|
-
const all = frames[last].lit;
|
|
19502
|
-
|
|
19503
|
-
// A lap stops short of the last modules and fades, rather than holding a
|
|
19504
|
-
// finished code that would read as the preview having arrived.
|
|
19505
|
-
const DRAW = Math.round(0.95 * last);
|
|
19506
|
-
const FADE = 20;
|
|
19507
|
-
const lap = DRAW + FADE;
|
|
19508
|
-
// Below this a lap is too unfinished for a sprint to be worth watching.
|
|
19509
|
-
const SPRINTABLE = 0.6;
|
|
19510
|
-
|
|
19511
|
-
// One clock for the whole wait. <main> is replaced as the upload advances,
|
|
19512
|
-
// so a board may be a later stand-in for an earlier one; it has to carry
|
|
19513
|
-
// on from where that one stopped instead of starting the code over.
|
|
19514
|
-
window.__snakeStart = window.__snakeStart || Date.now();
|
|
19515
|
-
|
|
19516
|
-
let sprint = null;
|
|
19517
|
-
|
|
19518
|
-
/**
|
|
19519
|
-
* Only the cells that changed. Repainting all 841 every frame meant ~2500
|
|
19520
|
-
* class writes 50 times a second to move a 14-cell snake; this touches
|
|
19521
|
-
* about 30. The lit list only ever grows within a pass, so the new
|
|
19522
|
-
* modules are its tail, and the body and the apple are small.
|
|
19523
|
-
*
|
|
19524
|
-
* The settle flag is off while the snake is only wandering: the flash
|
|
19525
|
-
* means a module was just revealed, and nothing is revealed then.
|
|
19526
|
-
*/
|
|
19527
|
-
const litSet = new Set();
|
|
19528
|
-
let litCount = 0;
|
|
19529
|
-
let prevBody = [];
|
|
19530
|
-
let prevApple = -1;
|
|
19531
|
-
|
|
19532
|
-
const cell = (k, className, opacity) => {
|
|
19533
|
-
const dot = dots[k];
|
|
19534
|
-
if (dot.className !== className) dot.className = className;
|
|
19535
|
-
if (dot.style.opacity !== opacity) dot.style.opacity = opacity;
|
|
19536
|
-
};
|
|
19537
|
-
const restingClass = (k) => (litSet.has(k) ? 'lit' : '');
|
|
19538
|
-
|
|
19539
|
-
const paint = ({ snake, lit, apple }, settle = true) => {
|
|
19540
|
-
if (lit.length < litCount) { // a fresh pass: start from an empty board
|
|
19541
|
-
litSet.clear(); litCount = 0; prevBody = []; prevApple = -1;
|
|
19542
|
-
for (const dot of dots) { dot.className = ''; dot.style.opacity = ''; }
|
|
19543
|
-
}
|
|
19544
|
-
// Paint every new module here rather than relying on the body to
|
|
19545
|
-
// uncover it: frames are skipped whenever the board moves faster than
|
|
19546
|
-
// one step a tick -- the sprint skips dozens -- and a module the body
|
|
19547
|
-
// never sat on would otherwise stay dark in a finished code.
|
|
19548
|
-
for (let i = litCount; i < lit.length; i++) { litSet.add(lit[i]); cell(lit[i], 'lit', ''); }
|
|
19549
|
-
litCount = lit.length;
|
|
19550
|
-
|
|
19551
|
-
// The module the snake lit is under its head; it shows when the tail
|
|
19552
|
-
// clears it, so the flash belongs to the cell the body just left.
|
|
19553
|
-
for (const k of prevBody)
|
|
19554
|
-
if (!snake.includes(k))
|
|
19555
|
-
cell(k, litSet.has(k) && settle ? 'lit fresh' : restingClass(k), '');
|
|
19556
|
-
|
|
19557
|
-
const length = snake.length;
|
|
19558
|
-
snake.forEach((k, i) => cell(k, 'on', String(0.2 + 0.55 * ((i + 1) / length))));
|
|
19559
|
-
prevBody = snake;
|
|
19560
|
-
|
|
19561
|
-
if (prevApple !== apple) {
|
|
19562
|
-
if (prevApple >= 0 && !snake.includes(prevApple))
|
|
19563
|
-
cell(prevApple, restingClass(prevApple), '');
|
|
19564
|
-
if (apple >= 0) cell(apple, 'apple', '');
|
|
19565
|
-
prevApple = apple;
|
|
19566
|
-
}
|
|
19567
|
-
};
|
|
19568
|
-
|
|
19569
|
-
const step = () => Math.floor((Date.now() - window.__snakeStart) / SPEED) % lap;
|
|
19570
|
-
/** How far into its draw this lap is, 0 to 1. */
|
|
19571
|
-
const lapProgress = () => Math.min(1, step() / DRAW);
|
|
19572
|
-
|
|
19573
|
-
if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {
|
|
19574
|
-
paint(frames[Math.floor(DRAW / 3)], false);
|
|
19575
|
-
host.finish = () => Promise.resolve();
|
|
19576
|
-
return;
|
|
19577
|
-
}
|
|
19578
|
-
|
|
19579
|
-
const timer = setInterval(() => {
|
|
19580
|
-
// The page swaps <main> as the upload advances; stop with the old board.
|
|
19581
|
-
if (!host.isConnected) { clearInterval(timer); return; }
|
|
19582
|
-
if (sprint) {
|
|
19583
|
-
const k = Math.min(1, (Date.now() - sprint.at) / sprint.ms);
|
|
19584
|
-
const p = sprint.from + (1 - sprint.from) * k;
|
|
19585
|
-
if (p >= 1) {
|
|
19586
|
-
paint({ snake: [], lit: all, apple: -1 });
|
|
19587
|
-
clearInterval(timer);
|
|
19588
|
-
sprint.done();
|
|
19589
|
-
return;
|
|
19590
|
-
}
|
|
19591
|
-
paint(frames[Math.round(p * last)]);
|
|
19592
|
-
return;
|
|
19593
|
-
}
|
|
19594
|
-
const at = step();
|
|
19595
|
-
if (at > DRAW) { // fading out before the next lap
|
|
19596
|
-
host.style.opacity = String(1 - (at - DRAW) / FADE);
|
|
19597
|
-
return;
|
|
19598
|
-
}
|
|
19599
|
-
if (host.style.opacity) host.style.opacity = '';
|
|
19600
|
-
paint(frames[at]);
|
|
19601
|
-
}, SPEED);
|
|
19602
|
-
|
|
19603
|
-
host.finish = () => {
|
|
19604
|
-
if (sprint) return Promise.resolve();
|
|
19605
|
-
const from = lapProgress();
|
|
19606
|
-
if (from < SPRINTABLE) return Promise.resolve();
|
|
19607
|
-
host.style.opacity = '';
|
|
19608
|
-
sprint = { from, at: Date.now(), ms: 300 + 500 * (1 - from), done: () => {} };
|
|
19609
|
-
return new Promise((resolve) => { sprint.done = resolve; });
|
|
19610
|
-
};
|
|
19611
|
-
}
|
|
19612
|
-
|
|
19613
|
-
const boards = () => [...document.querySelectorAll('.snake')];
|
|
19614
|
-
const start = () => boards().forEach(mount);
|
|
19615
|
-
window.__mountSnakes = start;
|
|
19616
|
-
window.__snakeFinish = () => Promise.all(boards().map((b) => b.finish?.()));
|
|
19617
|
-
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
|
|
19618
|
-
else start();
|
|
19619
|
-
})();`;
|
|
19620
|
-
|
|
19621
19422
|
const VIBE_PAGE_WORKER = '__vibe-page-server';
|
|
19622
19423
|
// Includes the base64 form of the locally validated image (up to 10 MiB).
|
|
19623
19424
|
const VIBE_PAGE_HTML_LIMIT = 16 * 1024 * 1024;
|
|
19624
19425
|
const pageUpdates = `(() => {
|
|
19625
19426
|
let etag;
|
|
19626
19427
|
const terminal = () => ['complete', 'preview', 'failed'].includes(document.querySelector('main')?.dataset.phase);
|
|
19627
|
-
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
19628
|
-
async function replace(current, updated) {
|
|
19629
|
-
const animate = current.dataset.phase !== updated.dataset.phase && current.animate &&
|
|
19630
|
-
!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
19631
|
-
const height = current.getBoundingClientRect().height;
|
|
19632
|
-
if (animate) await current.animate([{opacity: 1}, {opacity: 0}], {duration: 140, fill: 'forwards'}).finished.catch(() => {});
|
|
19633
|
-
current.replaceWith(updated);
|
|
19634
|
-
window.__mountSnakes?.();
|
|
19635
|
-
if (animate) await updated.animate([
|
|
19636
|
-
{opacity: 0, height: height + 'px', overflow: 'hidden'},
|
|
19637
|
-
{opacity: 1, height: updated.getBoundingClientRect().height + 'px', overflow: 'hidden'}
|
|
19638
|
-
], {duration: 280, easing: 'ease-out'}).finished.catch(() => {});
|
|
19639
|
-
}
|
|
19640
|
-
async function show(updated) {
|
|
19641
|
-
let current = document.querySelector('main');
|
|
19642
|
-
if (!current || current.dataset.rev === updated.dataset.rev) return;
|
|
19643
|
-
if (updated.dataset.phase === 'preview' && current.querySelector('.snake'))
|
|
19644
|
-
await window.__snakeFinish?.();
|
|
19645
|
-
if (updated.dataset.uploaded === 'true' && current.dataset.uploaded !== 'true') {
|
|
19646
|
-
// Show the upload receipt before either the binding or ready preview QR,
|
|
19647
|
-
// even when polling skips intermediate upload states.
|
|
19648
|
-
const success = updated.cloneNode(true);
|
|
19649
|
-
success.dataset.phase = 'upload_success';
|
|
19650
|
-
delete success.dataset.uploaded;
|
|
19651
|
-
const heading = success.querySelector('.heading');
|
|
19652
|
-
success.querySelector('h1').textContent = heading.dataset.uploadTitle;
|
|
19653
|
-
success.querySelector('.subtitle').textContent = heading.dataset.uploadSubtitle;
|
|
19654
|
-
success.querySelectorAll('.qr-area, .scan-action, .scan-hint, .preview-loading').forEach(node => node.remove());
|
|
19655
|
-
await replace(current, success);
|
|
19656
|
-
current = success;
|
|
19657
|
-
await delay(1200);
|
|
19658
|
-
}
|
|
19659
|
-
await replace(current, updated);
|
|
19660
|
-
}
|
|
19661
19428
|
async function refresh() {
|
|
19662
19429
|
if (terminal()) return;
|
|
19663
19430
|
try {
|
|
@@ -19665,10 +19432,9 @@ const pageUpdates = `(() => {
|
|
|
19665
19432
|
if (response.ok) {
|
|
19666
19433
|
etag = response.headers.get('etag');
|
|
19667
19434
|
const next = new DOMParser().parseFromString(await response.text(), 'text/html');
|
|
19668
|
-
const updated = next.querySelector('main');
|
|
19669
|
-
if (updated)
|
|
19435
|
+
const current = document.querySelector('main'), updated = next.querySelector('main');
|
|
19436
|
+
if (current && updated && current.outerHTML !== updated.outerHTML) current.replaceWith(updated);
|
|
19670
19437
|
document.title = next.title;
|
|
19671
|
-
document.documentElement.lang = next.documentElement.lang;
|
|
19672
19438
|
}
|
|
19673
19439
|
} catch { /* Keep the current page readable during a temporary connection failure. */ }
|
|
19674
19440
|
if (!terminal()) setTimeout(refresh, 2000);
|
|
@@ -19678,20 +19444,12 @@ const pageUpdates = `(() => {
|
|
|
19678
19444
|
const escape = (s) => s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
|
19679
19445
|
async function renderVibePage(input) {
|
|
19680
19446
|
const zh = input.language !== 'en-US';
|
|
19681
|
-
const message = input.localizedMessages?.[zh ? 'zh-CN' : 'en-US'] ?? input.message;
|
|
19682
|
-
const tool = input.minisType === 'MINI_TOOL';
|
|
19683
19447
|
const phase = input.phase ||
|
|
19684
19448
|
(input.purpose === 'authorization_binding' ? 'binding' : 'uploading');
|
|
19685
|
-
const uploadedBinding = !input.simulation && input.purpose === 'authorization_binding' &&
|
|
19686
|
-
(phase === 'waiting_preview' || phase === 'preview' || phase === 'complete');
|
|
19687
|
-
const preview = !input.simulation && phase === 'preview' &&
|
|
19688
|
-
(input.purpose === 'game_preview' || input.purpose === 'tool_preview');
|
|
19689
|
-
const uploaded = uploadedBinding || preview;
|
|
19690
19449
|
const copy = zh
|
|
19691
19450
|
? {
|
|
19692
|
-
steps: ['扫码授权', '上传游戏', '
|
|
19451
|
+
steps: ['扫码授权', '上传游戏', '完成'],
|
|
19693
19452
|
mock: input.hybrid ? 'PPE + Mock' : '模拟',
|
|
19694
|
-
game: '当前游戏',
|
|
19695
19453
|
gameIcon: '游戏头像',
|
|
19696
19454
|
bindingTitle: '上传到 TikTok',
|
|
19697
19455
|
bindingAction: input.hybrid ? '真实授权二维码 · 混合联调' : '使用 TikTok 扫描二维码',
|
|
@@ -19704,7 +19462,6 @@ async function renderVibePage(input) {
|
|
|
19704
19462
|
uploadingTitle: '正在上传小游戏',
|
|
19705
19463
|
uploadingSubtitle: input.simulation ? '正在模拟授权后的上传流程,不会上传游戏内容。' : '授权已完成,正在上传本次游戏内容。',
|
|
19706
19464
|
uploadLabel: input.simulation ? '模拟上传进度' : '上传进度',
|
|
19707
|
-
uploadStatus: '正在上传游戏,请稍候',
|
|
19708
19465
|
completeTitle: input.simulation ? '模拟上传完成' : '上传完成',
|
|
19709
19466
|
completeSubtitle: input.simulation
|
|
19710
19467
|
? '绑定和上传流程模拟完成,未向 TikTok 上传游戏。'
|
|
@@ -19713,11 +19470,11 @@ async function renderVibePage(input) {
|
|
|
19713
19470
|
bindingCode: '授权绑定二维码',
|
|
19714
19471
|
accepted: input.simulation ? '模拟完成' : '已上传',
|
|
19715
19472
|
mockNote: input.hybrid ? '真实创建 / 查询任务 · 授权 / 上传为模拟 · 不改变真实任务状态' : '模拟运行 · 无需扫码,不会真实上传',
|
|
19473
|
+
footer: '临时页面 · 15 分钟后关闭',
|
|
19716
19474
|
}
|
|
19717
19475
|
: {
|
|
19718
|
-
steps: ['
|
|
19476
|
+
steps: ['Authorize', 'Upload', 'Done'],
|
|
19719
19477
|
mock: input.hybrid ? 'PPE + Mock' : 'MOCK',
|
|
19720
|
-
game: 'YOUR GAME',
|
|
19721
19478
|
gameIcon: 'Game icon',
|
|
19722
19479
|
bindingTitle: 'Upload to TikTok',
|
|
19723
19480
|
bindingAction: input.hybrid ? 'Real authorization QR · integration demo' : 'Scan the QR code with TikTok',
|
|
@@ -19734,7 +19491,6 @@ async function renderVibePage(input) {
|
|
|
19734
19491
|
uploadLabel: input.simulation
|
|
19735
19492
|
? 'Simulated upload progress'
|
|
19736
19493
|
: 'Upload progress',
|
|
19737
|
-
uploadStatus: 'Uploading your game. Please wait.',
|
|
19738
19494
|
completeTitle: input.simulation ? 'Simulated upload complete' : 'Upload complete',
|
|
19739
19495
|
completeSubtitle: input.simulation
|
|
19740
19496
|
? 'Binding and upload simulation complete. Nothing was uploaded to TikTok.'
|
|
@@ -19743,17 +19499,8 @@ async function renderVibePage(input) {
|
|
|
19743
19499
|
bindingCode: 'Authorization QR',
|
|
19744
19500
|
accepted: input.simulation ? 'Simulation complete' : 'Uploaded',
|
|
19745
19501
|
mockNote: input.hybrid ? 'Real task creation / polling · Mock authorization / upload · Real task status unchanged' : 'Mock only · no scan needed, nothing is uploaded',
|
|
19502
|
+
footer: 'Temporary page · closes after 15 minutes',
|
|
19746
19503
|
};
|
|
19747
|
-
if (tool) {
|
|
19748
|
-
for (const key of Object.keys(copy)) {
|
|
19749
|
-
const value = copy[key];
|
|
19750
|
-
if (typeof value === 'string')
|
|
19751
|
-
copy[key] = value.replace(/小游戏|游戏/g, '工具').replace(/game/gi, 'tool');
|
|
19752
|
-
}
|
|
19753
|
-
copy.steps[1] = zh ? '上传工具' : 'Upload';
|
|
19754
|
-
}
|
|
19755
|
-
if (input.simulation)
|
|
19756
|
-
copy.steps[2] = zh ? '完成' : 'Done';
|
|
19757
19504
|
if (input.web) {
|
|
19758
19505
|
copy.steps[0] = zh ? '网页授权' : 'Web authorization';
|
|
19759
19506
|
copy.bindingAction = zh ? '在浏览器完成授权绑定' : 'Authorize in your browser';
|
|
@@ -19762,106 +19509,70 @@ async function renderVibePage(input) {
|
|
|
19762
19509
|
: (zh ? '请在已打开的 TikTok 页面确认授权,当前命令会继续查询绑定状态。' : 'Confirm authorization on the opened TikTok page. The command continues polling.');
|
|
19763
19510
|
copy.bindingStatus = input.simulation ? (zh ? '模拟等待授权' : 'Simulating authorization') : (zh ? '等待网页授权' : 'Waiting for browser authorization');
|
|
19764
19511
|
}
|
|
19765
|
-
const step = phase === 'binding' ? 0 : phase === 'uploading' ? 1 :
|
|
19512
|
+
const step = phase === 'binding' ? 0 : phase === 'uploading' ? 1 : 2;
|
|
19766
19513
|
// Only embed the chosen raster image; never fetch a URL or expose its local path.
|
|
19767
19514
|
const icon = input.iconDataUrl &&
|
|
19768
19515
|
input.iconDataUrl.length <= 14 * 1024 * 1024 &&
|
|
19769
19516
|
/^data:image\/(?:png|jpeg|webp);base64,[A-Za-z0-9+/]+=*$/.test(input.iconDataUrl)
|
|
19770
19517
|
? `<img class="game-avatar" width="44" height="44" alt="${copy.gameIcon}" src="${input.iconDataUrl}">`
|
|
19771
19518
|
: '<span class="game-icon" aria-hidden="true"><svg width="22" height="22" viewBox="0 0 24 24" fill="none"><path d="M7 7h10c2 0 3 2 3 4l1 5c.4 2-1 3-2.5 2l-3-2h-7l-3 2C4 19 2.6 18 3 16l1-5c0-2 1-4 3-4Z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M8 10v4m-2-2h4m6-1h.01M18 13h.01" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></span>';
|
|
19772
|
-
const game = `<div class="game"><div class="game-summary">${icon}<p class="game-title">${escape(input.title)}</p></div><dl class="game-details"><div><dt>${zh ? '描述' : 'Description'}</dt><dd class="game-description">${escape(input.description ?? VIBE_PPE_DEFAULT_APP_INFO.description)}</dd></div><div class="game-categories"><div><dt>${zh ? '类型' : 'Category'}</dt><dd>${escape(
|
|
19519
|
+
const game = `<div class="game"><div class="game-summary">${icon}<p class="game-title">${escape(input.title)}</p></div><dl class="game-details"><div><dt>${zh ? '描述' : 'Description'}</dt><dd class="game-description">${escape(input.description ?? VIBE_PPE_DEFAULT_APP_INFO.description)}</dd></div><div class="game-categories"><div><dt>${zh ? '类型' : 'Category'}</dt><dd>${escape(input.category ?? VIBE_PPE_DEFAULT_APP_INFO.category)}</dd></div><div><dt>${zh ? '子类型' : 'Subcategory'}</dt><dd>${escape(input.subcategory ?? VIBE_PPE_DEFAULT_APP_INFO.subcategory)}</dd></div></div></dl></div>`;
|
|
19773
19520
|
const checkIcon = '<svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="m3 8 3 3 7-7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
19774
|
-
const
|
|
19775
|
-
|
|
19776
|
-
|
|
19777
|
-
|
|
19778
|
-
|
|
19779
|
-
|
|
19780
|
-
|
|
19521
|
+
const title = phase === 'waiting_preview' ? (zh ? '正在准备预览' : 'Preparing preview')
|
|
19522
|
+
: phase === 'preview' ? (zh ? '扫码预览游戏' : 'Scan to preview')
|
|
19523
|
+
: phase === 'binding'
|
|
19524
|
+
? copy.bindingTitle
|
|
19525
|
+
: phase === 'uploading'
|
|
19526
|
+
? copy.uploadingTitle
|
|
19527
|
+
: phase === 'failed'
|
|
19528
|
+
? copy.failedTitle
|
|
19529
|
+
: copy.completeTitle;
|
|
19530
|
+
const subtitle = phase === 'waiting_preview' ? (zh ? '游戏内容已上传,预览就绪后会自动显示二维码。' : 'Game content uploaded. The preview QR will appear when ready.')
|
|
19531
|
+
: phase === 'preview' ? (zh ? '上传完成,使用手机 TikTok 扫描下方二维码。' : 'Upload complete. Scan the QR below with TikTok on your phone.')
|
|
19532
|
+
: phase === 'complete' && !input.simulation ? escape(input.message)
|
|
19781
19533
|
: phase === 'binding'
|
|
19782
|
-
? copy.
|
|
19534
|
+
? copy.bindingSubtitle
|
|
19783
19535
|
: phase === 'uploading'
|
|
19784
|
-
? copy.
|
|
19536
|
+
? copy.uploadingSubtitle
|
|
19785
19537
|
: phase === 'failed'
|
|
19786
|
-
?
|
|
19787
|
-
: copy.
|
|
19788
|
-
const
|
|
19789
|
-
? (zh ? '代码包已上传,正在生成预览。准备好后会自动显示预览二维码。' : 'Package uploaded. Your preview is being prepared; its QR will appear when ready.') : zh
|
|
19790
|
-
? `本次${tool ? '工具' : '游戏'}代码包已上传完成,再次扫码即可查看预览版本并提交审核。`
|
|
19791
|
-
: `Your ${tool ? 'tool' : 'game'} package has been uploaded. Scan again to view the preview version and submit it for review.`)
|
|
19792
|
-
: phase === 'waiting_preview' ? (zh ? '内容已上传,预览就绪后会自动显示二维码。' : 'Content uploaded. The preview QR will appear when ready.')
|
|
19793
|
-
: phase === 'preview' ? ''
|
|
19794
|
-
: phase === 'complete' && !input.simulation ? escape(message)
|
|
19795
|
-
: phase === 'binding'
|
|
19796
|
-
? copy.bindingSubtitle
|
|
19797
|
-
: phase === 'uploading'
|
|
19798
|
-
? copy.uploadingSubtitle
|
|
19799
|
-
: phase === 'failed'
|
|
19800
|
-
? escape(message)
|
|
19801
|
-
: copy.completeSubtitle;
|
|
19802
|
-
const qr = (phase === 'binding' || phase === 'uploading' || phase === 'preview' || uploadedBinding) && !input.web && input.qrUrl
|
|
19538
|
+
? escape(input.message)
|
|
19539
|
+
: copy.completeSubtitle;
|
|
19540
|
+
const qr = (phase === 'binding' || phase === 'preview') && !input.web && input.qrUrl
|
|
19803
19541
|
? await QRCode.toDataURL(input.qrUrl, {
|
|
19804
19542
|
width: 384,
|
|
19805
|
-
|
|
19806
|
-
margin: 0,
|
|
19543
|
+
margin: 4,
|
|
19807
19544
|
errorCorrectionLevel: 'M',
|
|
19808
19545
|
})
|
|
19809
19546
|
: '';
|
|
19810
|
-
const
|
|
19811
|
-
const
|
|
19812
|
-
const bindingQr = preview && !input.web && input.bindingQrUrl && input.bindingQrUrl !== input.qrUrl
|
|
19813
|
-
? await QRCode.toDataURL(input.bindingQrUrl, { width: 384, margin: 0, errorCorrectionLevel: 'M' }) : '';
|
|
19814
|
-
const done = phase === 'preview' || phase === 'complete' && input.simulation;
|
|
19815
|
-
const active = phase === 'binding' || phase === 'uploading' || phase === 'waiting_preview';
|
|
19816
|
-
const waiting = phase === 'waiting_preview';
|
|
19817
|
-
const matrix = waiting ? QRCode.create('https://www.tiktok.com/minis/').modules : undefined;
|
|
19818
|
-
const pendingQr = matrix ? `<figure class="qr-code qr-pending"><div class="qr-frame" role="img" aria-label="${zh ? '预览正在生成' : 'Preview is being prepared'}"><div class="snake" aria-hidden="true" data-size="${matrix.size}" data-speed="20" data-matrix='${JSON.stringify(Array.from(matrix.data))}' style="--module:${160 / matrix.size}px"></div></div><figcaption>${zh ? '正在生成预览' : 'Preparing preview'}</figcaption></figure>` : '';
|
|
19547
|
+
const purpose = phase === 'preview' ? (zh ? '游戏预览二维码' : 'Game preview QR') : copy.bindingCode;
|
|
19548
|
+
const done = phase === 'complete' || phase === 'preview';
|
|
19819
19549
|
const progress = typeof input.progressPercent === 'number' &&
|
|
19820
19550
|
Number.isFinite(input.progressPercent)
|
|
19821
19551
|
? Math.max(0, Math.min(100, Math.round(input.progressPercent)))
|
|
19822
19552
|
: undefined;
|
|
19823
|
-
const progressQr = bindingQr ? `<figure class="qr-code"><div class="qr-frame"><img width="160" height="160" alt="${progressCode}" src="${bindingQr}"></div><figcaption>${zh ? '查看进度' : 'Check progress'}<span>${zh ? '查看应用信息并提交审核' : 'View app details and submit for review'}</span></figcaption></figure>` : '';
|
|
19824
|
-
// Keep the original scan entry on the left while the right side becomes a preview.
|
|
19825
19553
|
const content = qr
|
|
19826
|
-
? `<div class="qr-area"
|
|
19554
|
+
? `<div class="qr-area"><div class="qr-frame"><img width="192" height="192" alt="${purpose}" src="${qr}"></div></div>`
|
|
19827
19555
|
: phase === 'uploading' && progress !== undefined
|
|
19828
19556
|
? `<div class="upload-area"><div class="percentage">${progress}<span>%</span></div><div class="progress-wrap"><progress aria-label="${copy.uploadLabel}" max="100" value="${progress}"></progress><p class="transfer-label">${copy.uploadLabel}</p></div></div>`
|
|
19829
19557
|
: '';
|
|
19830
|
-
const previewLoading = phase === 'waiting_preview'
|
|
19831
|
-
? `<div class="preview-loading" role="progressbar" aria-label="${zh ? '正在生成预览' : 'Preparing preview'}"><span aria-hidden="true"></span></div>`
|
|
19832
|
-
: '';
|
|
19833
|
-
const revision = crypto.createHash('sha256').update(JSON.stringify(input)).digest('hex');
|
|
19834
|
-
const steps = phase === 'failed' ? '' : `<ol class="steps" aria-label="${zh ? '上传步骤' : 'Upload steps'}">${copy.steps.map((label, i) => `<li class="step ${done || i < step ? 'done' : active && i === step ? 'active' : ''}"${!done && active && i === step ? ' aria-current="step"' : ''}><span class="step-number">${done || i < step ? checkIcon : active && i === step ? '<span class="swirl" aria-hidden="true"></span>' : i + 1}</span>${label}</li>`).join('')}</ol>`;
|
|
19835
19558
|
return `<!doctype html><html lang="${zh ? 'zh-CN' : 'en'}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escape(title)} · ${escape(input.title)}</title><style>${vibePageStyles}</style><script src="?updates" defer></script></head>
|
|
19836
|
-
<body><main class="shell
|
|
19837
|
-
<section class="card" aria-labelledby="page-title"
|
|
19838
|
-
<div class="heading"
|
|
19559
|
+
<body><main class="shell" data-phase="${phase}">${input.simulation ? `<p class="mock-badge">${copy.mock}</p>` : ''}
|
|
19560
|
+
<section class="card" aria-labelledby="page-title">${phase === 'failed' ? '' : `<ol class="steps" aria-label="${zh ? '上传步骤' : 'Upload steps'}">${copy.steps.map((label, i) => `<li class="step ${done || i < step ? 'done' : i === step ? 'active' : ''}"${!done && i === step ? ' aria-current="step"' : ''}><span class="step-number">${done || i < step ? checkIcon : i + 1}</span>${label}</li>`).join('')}</ol>`}
|
|
19561
|
+
<div class="heading"><h1 id="page-title">${title}</h1><p class="subtitle" role="status">${subtitle}</p></div>${game}${phase === 'binding' ? `<p class="scan-action">${copy.bindingAction}</p>` : ''}${content}
|
|
19839
19562
|
</section>
|
|
19840
|
-
|
|
19841
|
-
}
|
|
19842
|
-
// Follow the reader's preferred browser language, as in the Tool skill.
|
|
19843
|
-
function browserPageLanguage(header) {
|
|
19844
|
-
const preferred = (header || '').split(',').map(part => {
|
|
19845
|
-
const [tag, ...parameters] = part.trim().split(';');
|
|
19846
|
-
const quality = parameters.find(value => /^\s*q=/i.test(value));
|
|
19847
|
-
const q = quality ? Number(quality.split('=')[1]) : 1;
|
|
19848
|
-
return { tag: tag.toLowerCase(), q };
|
|
19849
|
-
}).filter(item => item.tag && Number.isFinite(item.q) && item.q > 0 && item.q <= 1)
|
|
19850
|
-
.sort((a, b) => b.q - a.q)[0];
|
|
19851
|
-
return preferred?.tag.split('-')[0] === 'zh' ? 'zh-CN' : 'en-US';
|
|
19563
|
+
<p class="footer">${input.simulation ? `<span class="dot" aria-hidden="true"></span>${copy.mockNote}` : copy.footer}</p></main></body></html>`;
|
|
19852
19564
|
}
|
|
19853
19565
|
async function createVibePageServer() {
|
|
19854
19566
|
const route = `/${crypto.randomBytes(24).toString('hex')}`;
|
|
19855
19567
|
let html = 'Starting…';
|
|
19856
19568
|
let etag = '';
|
|
19857
|
-
let variants = {};
|
|
19858
19569
|
let landingUrl;
|
|
19859
19570
|
const server = http.createServer((req, res) => {
|
|
19860
19571
|
const address = server.address();
|
|
19861
19572
|
const host = address && typeof address !== 'string' ? `127.0.0.1:${address.port}` : '';
|
|
19862
19573
|
if (req.method === 'GET' && req.url === `${route}?updates` && req.headers.host === host) {
|
|
19863
19574
|
res.writeHead(200, { 'Content-Type': 'text/javascript; charset=utf-8', 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff' });
|
|
19864
|
-
res.end(
|
|
19575
|
+
res.end(pageUpdates);
|
|
19865
19576
|
return;
|
|
19866
19577
|
}
|
|
19867
19578
|
if (req.url === `${route}/authorize` && req.headers.host === host &&
|
|
@@ -19884,23 +19595,19 @@ async function createVibePageServer() {
|
|
|
19884
19595
|
res.writeHead(404).end();
|
|
19885
19596
|
return;
|
|
19886
19597
|
}
|
|
19887
|
-
|
|
19888
|
-
|
|
19889
|
-
const responseEtag = localized?.etag ?? etag;
|
|
19890
|
-
if (responseEtag && req.headers['if-none-match'] === responseEtag) {
|
|
19891
|
-
res.writeHead(304, { 'ETag': responseEtag, 'Cache-Control': 'no-store', 'Vary': 'Accept-Language' }).end();
|
|
19598
|
+
if (etag && req.headers['if-none-match'] === etag) {
|
|
19599
|
+
res.writeHead(304, { 'ETag': etag, 'Cache-Control': 'no-store' }).end();
|
|
19892
19600
|
return;
|
|
19893
19601
|
}
|
|
19894
19602
|
res.writeHead(200, {
|
|
19895
|
-
'ETag':
|
|
19896
|
-
'Vary': 'Accept-Language',
|
|
19603
|
+
'ETag': etag,
|
|
19897
19604
|
'Content-Type': 'text/html; charset=utf-8',
|
|
19898
19605
|
'Cache-Control': 'no-store',
|
|
19899
19606
|
'Referrer-Policy': 'no-referrer',
|
|
19900
19607
|
'X-Content-Type-Options': 'nosniff',
|
|
19901
19608
|
'Content-Security-Policy': "default-src 'none'; img-src data:; style-src 'unsafe-inline'; script-src 'self'; connect-src 'self'; frame-ancestors 'none'",
|
|
19902
19609
|
});
|
|
19903
|
-
res.end(
|
|
19610
|
+
res.end(html);
|
|
19904
19611
|
});
|
|
19905
19612
|
await new Promise((resolve, reject) => {
|
|
19906
19613
|
server.once('error', reject);
|
|
@@ -19909,15 +19616,9 @@ async function createVibePageServer() {
|
|
|
19909
19616
|
const address = server.address();
|
|
19910
19617
|
return {
|
|
19911
19618
|
url: `http://127.0.0.1:${address.port}${route}`,
|
|
19912
|
-
update: (value, authorization
|
|
19619
|
+
update: (value, authorization) => {
|
|
19913
19620
|
html = value;
|
|
19914
19621
|
etag = '"' + crypto.createHash('sha256').update(value).digest('hex') + '"';
|
|
19915
|
-
variants = {};
|
|
19916
|
-
for (const language of ['zh-CN', 'en-US']) {
|
|
19917
|
-
const localized = localizedHtml?.[language];
|
|
19918
|
-
if (typeof localized === 'string' && localized.length < VIBE_PAGE_HTML_LIMIT)
|
|
19919
|
-
variants[language] = { html: localized, etag: '"' + crypto.createHash('sha256').update(localized).digest('hex') + '"' };
|
|
19920
|
-
}
|
|
19921
19622
|
landingUrl = browserLandingUrl(authorization);
|
|
19922
19623
|
},
|
|
19923
19624
|
close: () => server.close(),
|
|
@@ -19930,34 +19631,24 @@ async function runVibePageWorker() {
|
|
|
19930
19631
|
}
|
|
19931
19632
|
const server = await createVibePageServer();
|
|
19932
19633
|
const requestedTtl = Number(process.env.TTMG_VIBE_PAGE_TTL_MS);
|
|
19933
|
-
const ttl = requestedTtl > 0 && requestedTtl <=
|
|
19934
|
-
|
|
19935
|
-
let closing = false;
|
|
19936
|
-
const close = () => {
|
|
19937
|
-
closing = true;
|
|
19938
|
-
clearTimeout(timer);
|
|
19634
|
+
const ttl = requestedTtl > 0 && requestedTtl <= 900000 ? requestedTtl : 900000;
|
|
19635
|
+
const timer = setTimeout(() => {
|
|
19939
19636
|
server.close();
|
|
19940
19637
|
if (process.connected)
|
|
19941
19638
|
process.disconnect();
|
|
19942
|
-
};
|
|
19943
|
-
// The parent owns the operation deadline. Keep the page alive during polling,
|
|
19944
|
-
// then start its retention window when the parent detaches (or exits).
|
|
19945
|
-
const retain = () => {
|
|
19946
|
-
if (!closing && !timer)
|
|
19947
|
-
timer = setTimeout(close, ttl);
|
|
19948
|
-
};
|
|
19949
|
-
process.once('disconnect', retain);
|
|
19950
|
-
if (!process.connected)
|
|
19951
|
-
retain();
|
|
19639
|
+
}, ttl);
|
|
19952
19640
|
process.on('message', (message) => {
|
|
19953
19641
|
if (message?.type === 'render' &&
|
|
19954
19642
|
typeof message.html === 'string' &&
|
|
19955
19643
|
message.html.length < VIBE_PAGE_HTML_LIMIT) {
|
|
19956
|
-
server.update(message.html, message.browserLandingUrl
|
|
19644
|
+
server.update(message.html, message.browserLandingUrl);
|
|
19957
19645
|
process.send?.({ type: 'rendered', id: message.id });
|
|
19958
19646
|
}
|
|
19959
19647
|
if (message?.type === 'close') {
|
|
19960
|
-
|
|
19648
|
+
clearTimeout(timer);
|
|
19649
|
+
server.close();
|
|
19650
|
+
if (process.connected)
|
|
19651
|
+
process.disconnect();
|
|
19961
19652
|
}
|
|
19962
19653
|
});
|
|
19963
19654
|
process.send({ type: 'ready', url: server.url });
|
|
@@ -19995,15 +19686,11 @@ async function startVibePage() {
|
|
|
19995
19686
|
return {
|
|
19996
19687
|
url: ready.url,
|
|
19997
19688
|
async update(input) {
|
|
19998
|
-
const
|
|
19999
|
-
renderVibePage({ ...input, language: 'zh-CN' }),
|
|
20000
|
-
renderVibePage({ ...input, language: 'en-US' }),
|
|
20001
|
-
]);
|
|
20002
|
-
const html = input.language === 'en-US' ? en : zh;
|
|
19689
|
+
const html = await renderVibePage(input);
|
|
20003
19690
|
const current = ++id;
|
|
20004
19691
|
const ack = receive(m => m?.type === 'rendered' && m.id === current);
|
|
20005
19692
|
const landingUrl = input.phase === 'binding' ? browserLandingUrl(input.browserLandingUrl) : undefined;
|
|
20006
|
-
child.send({ type: 'render', id: current, html,
|
|
19693
|
+
child.send({ type: 'render', id: current, html, browserLandingUrl: landingUrl }, error => {
|
|
20007
19694
|
if (error)
|
|
20008
19695
|
child.emit('error', error);
|
|
20009
19696
|
});
|
|
@@ -20027,7 +19714,6 @@ async function startVibePage() {
|
|
|
20027
19714
|
|
|
20028
19715
|
// Source identity is project metadata, not a Skill input or a credential.
|
|
20029
19716
|
function getVibeProjectId(options) {
|
|
20030
|
-
const minisType = resolveMinisType(options.minisType);
|
|
20031
19717
|
const root = path.resolve(options.package ? path.dirname(options.package) : options.dir || '.');
|
|
20032
19718
|
const dir = path.join(root, '.ttmg');
|
|
20033
19719
|
const file = path.join(dir, 'vibe-project.json');
|
|
@@ -20035,7 +19721,7 @@ function getVibeProjectId(options) {
|
|
|
20035
19721
|
fs.mkdirSync(dir, { recursive: true });
|
|
20036
19722
|
const id = crypto.randomUUID();
|
|
20037
19723
|
try {
|
|
20038
|
-
fs.writeFileSync(file, JSON.stringify({ schemaVersion: '1', sourceProjectId: id
|
|
19724
|
+
fs.writeFileSync(file, JSON.stringify({ schemaVersion: '1', sourceProjectId: id }) + '\n', { flag: 'wx', mode: 0o600 });
|
|
20039
19725
|
}
|
|
20040
19726
|
catch (error) {
|
|
20041
19727
|
if (error.code !== 'EEXIST')
|
|
@@ -20046,13 +19732,9 @@ function getVibeProjectId(options) {
|
|
|
20046
19732
|
typeof saved.sourceProjectId !== 'string' ||
|
|
20047
19733
|
!/^[a-zA-Z0-9_-]{1,100}$/.test(saved.sourceProjectId))
|
|
20048
19734
|
throw new Error();
|
|
20049
|
-
if ((saved.minisType || 'MINI_GAME') !== minisType)
|
|
20050
|
-
throw new VibeError('VIBE_PROJECT_TYPE_CONFLICT');
|
|
20051
19735
|
return saved.sourceProjectId;
|
|
20052
19736
|
}
|
|
20053
|
-
catch
|
|
20054
|
-
if (error instanceof VibeError)
|
|
20055
|
-
throw error;
|
|
19737
|
+
catch {
|
|
20056
19738
|
throw new VibeError('VIBE_PROJECT_STATE_FAILED');
|
|
20057
19739
|
}
|
|
20058
19740
|
}
|
|
@@ -20140,7 +19822,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20140
19822
|
let page;
|
|
20141
19823
|
let backend = deps.backend;
|
|
20142
19824
|
let task;
|
|
20143
|
-
let authorizationUrl;
|
|
20144
19825
|
let detached = false;
|
|
20145
19826
|
let progressUpdates = Promise.resolve();
|
|
20146
19827
|
let ppeLane;
|
|
@@ -20166,6 +19847,16 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20166
19847
|
errorCode: null,
|
|
20167
19848
|
error: null,
|
|
20168
19849
|
};
|
|
19850
|
+
const httpTrace = options.verbose
|
|
19851
|
+
? trace => console.error(JSON.stringify({
|
|
19852
|
+
schemaVersion: '1',
|
|
19853
|
+
command: 'upload',
|
|
19854
|
+
mode: 'vibe',
|
|
19855
|
+
operationId: result.operationId,
|
|
19856
|
+
ts: Date.now(),
|
|
19857
|
+
...trace,
|
|
19858
|
+
}))
|
|
19859
|
+
: undefined;
|
|
20169
19860
|
const emit = (status, fields = {}) => {
|
|
20170
19861
|
result = {
|
|
20171
19862
|
...result,
|
|
@@ -20184,10 +19875,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20184
19875
|
try {
|
|
20185
19876
|
if (options.web)
|
|
20186
19877
|
throw new VibeError('VIBE_WEB_DISABLED');
|
|
20187
|
-
const minisType = resolveMinisType(options.minisType);
|
|
20188
|
-
result.minisType = minisType;
|
|
20189
|
-
if (minisType === 'MINI_TOOL')
|
|
20190
|
-
result.inputRequirements = VIBE_TOOL_INPUT_REQUIREMENTS;
|
|
20191
19878
|
// Real QR uploads use the release-owned PPE route.
|
|
20192
19879
|
// Explicit test backends remain opt-in; real failures never fall back to them.
|
|
20193
19880
|
if (!options.mock && !options.vibeApi && process.env.TTMG_VIBE_BACKEND) {
|
|
@@ -20228,20 +19915,20 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20228
19915
|
throw new VibeError('VIBE_BACKEND_OPTION_CONFLICT');
|
|
20229
19916
|
if (options.pollInterval !== undefined)
|
|
20230
19917
|
seconds(options.pollInterval, 1);
|
|
20231
|
-
const timeoutMs = seconds(options.timeout,
|
|
19918
|
+
const timeoutMs = seconds(options.timeout, 300);
|
|
20232
19919
|
timeout = setTimeout(() => {
|
|
20233
19920
|
timedOut = true;
|
|
20234
19921
|
controller.abort();
|
|
20235
19922
|
}, timeoutMs);
|
|
20236
19923
|
if (!options.dryRun && !backend) {
|
|
20237
19924
|
if (options.mock)
|
|
20238
|
-
backend = deps.createTrialBackend(options.mockScenario);
|
|
19925
|
+
backend = deps.createTrialBackend(options.mockScenario, undefined, httpTrace);
|
|
20239
19926
|
else if (hybridSelected)
|
|
20240
|
-
backend = createPpeMockBackend(ppeLane, undefined, options.mockScenario);
|
|
19927
|
+
backend = createPpeMockBackend(ppeLane, undefined, options.mockScenario, httpTrace);
|
|
20241
19928
|
else if (ppeSelected)
|
|
20242
|
-
backend = createPpeBackend(ppeLane);
|
|
19929
|
+
backend = createPpeBackend(ppeLane, undefined, httpTrace);
|
|
20243
19930
|
else if (options.vibeApi)
|
|
20244
|
-
backend = createLocalHttpBackend(options.vibeApi);
|
|
19931
|
+
backend = createLocalHttpBackend(options.vibeApi, undefined, httpTrace);
|
|
20245
19932
|
else
|
|
20246
19933
|
throw new VibeError('VIBE_BACKEND_NOT_CONFIGURED');
|
|
20247
19934
|
}
|
|
@@ -20263,7 +19950,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20263
19950
|
category: (input.appInfo || VIBE_PPE_DEFAULT_APP_INFO).category,
|
|
20264
19951
|
subcategory: (input.appInfo || VIBE_PPE_DEFAULT_APP_INFO).subcategory,
|
|
20265
19952
|
source: options.category !== undefined ? 'arguments' : options.appInfo !== undefined ? 'app-info' : 'defaults',
|
|
20266
|
-
catalogVersion:
|
|
19953
|
+
catalogVersion: GAME_CATEGORY_CATALOG_VERSION,
|
|
20267
19954
|
},
|
|
20268
19955
|
iconSizeBytes: input.icon.bytes.length,
|
|
20269
19956
|
packageSizeBytes: input.code.bytes.length,
|
|
@@ -20275,7 +19962,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20275
19962
|
return result;
|
|
20276
19963
|
const submittedInfo = input.appInfo || VIBE_PPE_DEFAULT_APP_INFO;
|
|
20277
19964
|
pageMetadata = {
|
|
20278
|
-
minisType,
|
|
20279
19965
|
iconDataUrl: `data:${input.icon.contentType};base64,${input.icon.bytes.toString('base64')}`,
|
|
20280
19966
|
description: submittedInfo.description,
|
|
20281
19967
|
category: submittedInfo.category,
|
|
@@ -20288,15 +19974,18 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20288
19974
|
hybrid: backend.mode === 'ppe-mock',
|
|
20289
19975
|
};
|
|
20290
19976
|
const sourceProjectId = deps.projectId(options);
|
|
19977
|
+
// The project ID identifies the game; the task request ID is new for each upload.
|
|
19978
|
+
// Keep it separate from the operation ID used in progress events.
|
|
19979
|
+
const taskIdempotencyId = crypto.randomUUID();
|
|
20291
19980
|
emit('creating_task', { platformWrite: backend.mode === 'ppe' || backend.mode === 'ppe-mock' });
|
|
20292
19981
|
task = await backend.createTask({
|
|
20293
19982
|
source_type: 1,
|
|
20294
19983
|
source_project_id: sourceProjectId,
|
|
20295
|
-
task_idempotency_id:
|
|
20296
|
-
minis_type:
|
|
19984
|
+
task_idempotency_id: taskIdempotencyId,
|
|
19985
|
+
minis_type: 'MINI_GAME',
|
|
20297
19986
|
}, controller.signal);
|
|
20298
19987
|
ensureActive();
|
|
20299
|
-
authorizationUrl = createAuthorizationScheme(task.landingUrl);
|
|
19988
|
+
const authorizationUrl = createAuthorizationScheme(task.landingUrl);
|
|
20300
19989
|
const pollMs = seconds(options.pollInterval, task.pollIntervalSeconds);
|
|
20301
19990
|
result = {
|
|
20302
19991
|
...result,
|
|
@@ -20375,8 +20064,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20375
20064
|
...pageGame,
|
|
20376
20065
|
language: getCurrentLanguage(),
|
|
20377
20066
|
phase: 'uploading',
|
|
20378
|
-
qrUrl: result.simulation ? undefined : authorizationUrl,
|
|
20379
|
-
purpose: result.simulation ? undefined : 'authorization_binding',
|
|
20380
20067
|
simulation: result.simulation,
|
|
20381
20068
|
message: text('uploading', result.simulation),
|
|
20382
20069
|
});
|
|
@@ -20415,8 +20102,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20415
20102
|
...pageGame,
|
|
20416
20103
|
language: getCurrentLanguage(),
|
|
20417
20104
|
phase: 'uploading',
|
|
20418
|
-
qrUrl: result.simulation ? undefined : authorizationUrl,
|
|
20419
|
-
purpose: result.simulation ? undefined : 'authorization_binding',
|
|
20420
20105
|
simulation: result.simulation,
|
|
20421
20106
|
message: text('uploading', result.simulation),
|
|
20422
20107
|
progressPercent: percent(loaded, validTotal),
|
|
@@ -20438,9 +20123,8 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20438
20123
|
throw progressPageError;
|
|
20439
20124
|
if (!result.simulation) {
|
|
20440
20125
|
emit('waiting_preview', { previewReady: false, terminal: false, nextAction: 'wait',
|
|
20441
|
-
qrPurpose:
|
|
20126
|
+
qrPurpose: undefined, authorizationOpenCommand: undefined });
|
|
20442
20127
|
await page.update({ ...pageGame, language: getCurrentLanguage(), phase: 'waiting_preview',
|
|
20443
|
-
qrUrl: authorizationUrl, purpose: 'authorization_binding',
|
|
20444
20128
|
simulation: false, message: text('waiting_preview') });
|
|
20445
20129
|
while (true) {
|
|
20446
20130
|
ensureActive();
|
|
@@ -20464,20 +20148,17 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20464
20148
|
...pageGame,
|
|
20465
20149
|
language: getCurrentLanguage(),
|
|
20466
20150
|
phase: result.previewReady ? 'preview' : 'complete',
|
|
20467
|
-
qrUrl: result.
|
|
20468
|
-
|
|
20469
|
-
purpose: result.simulation ? undefined : result.previewReady
|
|
20470
|
-
? result.minisType === 'MINI_TOOL' ? 'tool_preview' : 'game_preview'
|
|
20471
|
-
: 'authorization_binding',
|
|
20151
|
+
qrUrl: result.previewUrl,
|
|
20152
|
+
purpose: result.previewReady ? 'game_preview' : undefined,
|
|
20472
20153
|
simulation: result.simulation,
|
|
20473
20154
|
message: text('uploaded', result.simulation),
|
|
20474
20155
|
});
|
|
20475
20156
|
page.detach();
|
|
20476
20157
|
detached = true;
|
|
20477
20158
|
return emit('uploaded', { terminal: true, nextAction: result.previewReady ? 'scan_qr' : 'none',
|
|
20478
|
-
qrPurpose: result.previewReady ?
|
|
20159
|
+
qrPurpose: result.previewReady ? 'game_preview' : undefined,
|
|
20479
20160
|
...(result.previewReady ? { message: getCurrentLanguage() === 'zh-CN'
|
|
20480
|
-
? '
|
|
20161
|
+
? '上传完成,请使用 TikTok 扫码预览游戏。' : 'Upload complete. Scan with TikTok to preview your game.' } : {}) });
|
|
20481
20162
|
}
|
|
20482
20163
|
catch (error) {
|
|
20483
20164
|
const code = timedOut
|
|
@@ -20495,9 +20176,6 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20495
20176
|
const messages = {
|
|
20496
20177
|
VIBE_CATEGORY_PAIR_REQUIRED: ['--category 和 --subcategory 必须同时提供;均不传时保留默认分类。使用 ttmg game categories --format json 查询。', 'Provide --category and --subcategory together, or omit both to keep the default. Query ttmg game categories --format json.'],
|
|
20497
20178
|
VIBE_INVALID_CATEGORY: ['类型或子类型无效,或两者不匹配。使用 ttmg game categories --format json 查询有效组合。', 'Invalid category/subcategory pair. Query valid values with ttmg game categories --format json.'],
|
|
20498
|
-
VIBE_INVALID_MINIS_TYPE: ['应用类型须为 MINI_GAME 或 MINI_TOOL。', 'Minis type must be MINI_GAME or MINI_TOOL.'],
|
|
20499
|
-
VIBE_TOOL_TRIAL_METADATA: ['本地 Tool 试用固定使用效率办公类、US 和 SDK 1.0.0;暂不接受分类或 app-info 覆盖。', 'The local Tool trial uses Efficiency & Office, US and SDK 1.0.0; category and app-info overrides are not supported yet.'],
|
|
20500
|
-
VIBE_PROJECT_TYPE_CONFLICT: ['该工程已使用其他应用类型,请为新的 Tool 使用独立工程目录。', 'This project has a different Minis type. Use a separate project directory for a new Tool.'],
|
|
20501
20179
|
VIBE_INVALID_DESCRIPTION: ['描述必须是非空的有效 Unicode 文本;省略 --description 可使用默认描述。', 'Description must be non-empty valid Unicode text; omit --description to use the default.'],
|
|
20502
20180
|
VIBE_AUTH_DENIED: [
|
|
20503
20181
|
'已取消授权,本次未上传。',
|
|
@@ -20579,7 +20257,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20579
20257
|
text(status);
|
|
20580
20258
|
controller.abort();
|
|
20581
20259
|
await progressUpdates;
|
|
20582
|
-
//
|
|
20260
|
+
// Remove the binding QR on failure too. Keep a readable terminal result page.
|
|
20583
20261
|
if (page && (code !== 'VIBE_CANCELLED' || result.uploaded)) {
|
|
20584
20262
|
try {
|
|
20585
20263
|
await page.update({
|
|
@@ -20589,20 +20267,7 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
20589
20267
|
simulation: result.simulation,
|
|
20590
20268
|
hybrid: result.backend === 'ppe-mock',
|
|
20591
20269
|
phase: result.uploaded ? 'complete' : 'failed',
|
|
20592
|
-
|
|
20593
|
-
purpose: result.uploaded && !result.simulation ? 'authorization_binding' : undefined,
|
|
20594
|
-
localizedMessages: result.uploaded && !result.simulation ? {
|
|
20595
|
-
'zh-CN': '代码包已上传,本页已停止等待预览。可扫码查看进度,无需重复上传。',
|
|
20596
|
-
'en-US': 'Package uploaded. This page has stopped waiting for the preview. Scan to check progress; no need to upload again.',
|
|
20597
|
-
} : {
|
|
20598
|
-
'zh-CN': messages[code]?.[0] || '本次操作未完成,请检查 CLI 输出。',
|
|
20599
|
-
'en-US': messages[code]?.[1] || 'This operation did not complete. Check the CLI output.',
|
|
20600
|
-
},
|
|
20601
|
-
message: result.uploaded && !result.simulation
|
|
20602
|
-
? getCurrentLanguage() === 'zh-CN'
|
|
20603
|
-
? '代码包已上传,本页已停止等待预览。可扫码查看进度,无需重复上传。'
|
|
20604
|
-
: 'Package uploaded. This page has stopped waiting for the preview. Scan to check progress; no need to upload again.'
|
|
20605
|
-
: message,
|
|
20270
|
+
message,
|
|
20606
20271
|
});
|
|
20607
20272
|
page.detach();
|
|
20608
20273
|
detached = true;
|
|
@@ -20656,8 +20321,11 @@ async function vibeUpload(options) {
|
|
|
20656
20321
|
try {
|
|
20657
20322
|
const format = options.format || 'text';
|
|
20658
20323
|
const result = await executeVibeUpload(options, event => {
|
|
20659
|
-
if (format === 'ndjson')
|
|
20324
|
+
if (format === 'ndjson') {
|
|
20660
20325
|
console.log(JSON.stringify(event));
|
|
20326
|
+
if (event.status === 'waiting_authorization' && event.pageUrl)
|
|
20327
|
+
console.error(`page: ${event.pageUrl}`);
|
|
20328
|
+
}
|
|
20661
20329
|
else if (format === 'text')
|
|
20662
20330
|
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})` : ''}`);
|
|
20663
20331
|
else if (event.status === 'waiting_authorization')
|