@ttmg/cli 0.4.6-vibe-beta.8 → 0.4.6-vibe-beta.10
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 +4 -8
- package/dist/index.js +54 -33
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/scripts/verify-agent-contract.js +2 -0
- package/dist/scripts/vibe-ppe-test-preload.cjs +16 -2
- package/dist/scripts/vibe-upload-smoke.js +78 -11
- package/package.json +1 -1
- package/vibe-upload.md +24 -47
package/dist/package.json
CHANGED
|
@@ -385,6 +385,8 @@ assert.notEqual(
|
|
|
385
385
|
);
|
|
386
386
|
|
|
387
387
|
const uploadHelp = runCli(['upload', '--help']);
|
|
388
|
+
assert.doesNotMatch(uploadHelp.stdout, /--app-info/);
|
|
389
|
+
assert.match(uploadHelp.stdout.replace(/\s+/g, ' '), /only title and icon|只收集标题和头像/);
|
|
388
390
|
assertExit(uploadHelp, 0, 'upload help');
|
|
389
391
|
assert.match(uploadHelp.stdout, /--preview\b/);
|
|
390
392
|
assert.match(uploadHelp.stdout, /--serve-preview-page\b/);
|
|
@@ -3,18 +3,32 @@
|
|
|
3
3
|
const assert = require('node:assert/strict');
|
|
4
4
|
const http = require('node:http');
|
|
5
5
|
const https = require('node:https');
|
|
6
|
+
const net = require('node:net');
|
|
6
7
|
const port = Number(process.env.TTMG_VIBE_TEST_PORT);
|
|
7
8
|
assert.ok(Number.isInteger(port) && port > 0 && port < 65536);
|
|
8
9
|
https.request = (options, callback) => {
|
|
9
10
|
assert.equal(typeof options, 'object');
|
|
10
11
|
assert.equal(options.hostname, 'developers.tiktok.com');
|
|
11
12
|
assert.equal(options.protocol, 'https:');
|
|
12
|
-
assert.match(options.path, /^\/bff\/app_info\/minis_import\/(create_minis_import_task_for_agent|get_minis_import_task_status_for_agent)\//);
|
|
13
|
-
|
|
13
|
+
assert.match(options.path, /^\/bff\/app_info\/minis_import\/(create_minis_import_task_for_agent|get_minis_import_task_status_for_agent|upload_minis_import_app_icon_for_agent|submit_minis_import_info_for_agent|upload_minis_import_package_for_agent)\//);
|
|
14
|
+
const request = http.request({
|
|
14
15
|
...options,
|
|
15
16
|
protocol: 'http:', hostname: '127.0.0.1', host: undefined, port,
|
|
16
17
|
agent: undefined, servername: undefined,
|
|
17
18
|
}, callback);
|
|
19
|
+
if (process.env.TTMG_VIBE_TEST_RETAIN_UPLOAD_SOCKET === '1' &&
|
|
20
|
+
/upload_minis_import_(app_icon|package)_for_agent/.test(options.path)) {
|
|
21
|
+
// Reproduce the live gateway's completed response with a retained upload
|
|
22
|
+
// socket. The socket is real but loopback-only; operation cleanup must close it.
|
|
23
|
+
assert.ok(options.agent && options.agent !== https.globalAgent);
|
|
24
|
+
request.once('response', response => response.once('end', () => {
|
|
25
|
+
const socket = net.connect({host: '127.0.0.1', port});
|
|
26
|
+
const key = 'retained-vibe-test-sockets';
|
|
27
|
+
(options.agent.sockets[key] ||= []).push(socket);
|
|
28
|
+
socket.on('error', () => {});
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
return request;
|
|
18
32
|
};
|
|
19
33
|
https.get = (options, callback) => {
|
|
20
34
|
const request = https.request(options, callback);
|
|
@@ -29,6 +29,7 @@ const requests = [];
|
|
|
29
29
|
let failStatus = false;
|
|
30
30
|
let webMode = false;
|
|
31
31
|
let webPolls = 0;
|
|
32
|
+
let webFailureEndpoint;
|
|
32
33
|
const env = {
|
|
33
34
|
...process.env,
|
|
34
35
|
HOME: temp,
|
|
@@ -98,7 +99,15 @@ function events(result) {
|
|
|
98
99
|
return result.stdout
|
|
99
100
|
.trim()
|
|
100
101
|
.split('\n')
|
|
101
|
-
.map(line =>
|
|
102
|
+
.map(line => {
|
|
103
|
+
const event = JSON.parse(line);
|
|
104
|
+
if (event.mode === 'vibe') {
|
|
105
|
+
assert.deepEqual(event.inputRequirements.requiredUserInputs, ['title', 'icon']);
|
|
106
|
+
assert.equal(event.inputRequirements.requestAdditionalMetadata, false);
|
|
107
|
+
assert.equal(event.inputRequirements.additionalMetadataSource, 'platform-defaults');
|
|
108
|
+
}
|
|
109
|
+
return event;
|
|
110
|
+
});
|
|
102
111
|
}
|
|
103
112
|
async function main() {
|
|
104
113
|
// Exercise the built worker IPC and new command without creating a platform task.
|
|
@@ -152,15 +161,20 @@ async function main() {
|
|
|
152
161
|
assert.equal(fs.readFileSync(browserReceipt, 'utf8').trim().split('\n').length, 2);
|
|
153
162
|
authWorker.kill();
|
|
154
163
|
children.delete(authWorker);
|
|
155
|
-
fixtureServer = http.createServer((req, res) => {
|
|
164
|
+
fixtureServer = http.createServer(async (req, res) => {
|
|
156
165
|
const url = new URL(req.url, 'http://127.0.0.1');
|
|
157
166
|
requests.push({ path: url.pathname, method: req.method, headers: req.headers });
|
|
158
|
-
|
|
167
|
+
const chunks = [];
|
|
168
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
169
|
+
const body = Buffer.concat(chunks);
|
|
159
170
|
const create = url.pathname.endsWith('/create_minis_import_task_for_agent/');
|
|
160
171
|
const status = url.pathname.endsWith('/get_minis_import_task_status_for_agent/');
|
|
172
|
+
const iconUpload = url.pathname.endsWith('/upload_minis_import_app_icon_for_agent/');
|
|
173
|
+
const infoUpload = url.pathname.endsWith('/submit_minis_import_info_for_agent/');
|
|
174
|
+
const packageUpload = url.pathname.endsWith('/upload_minis_import_package_for_agent/');
|
|
161
175
|
if (create) webPolls = 0;
|
|
162
176
|
if (status && webMode) webPolls++;
|
|
163
|
-
assert.ok(create || status, '
|
|
177
|
+
assert.ok(create || status || (webMode && (iconUpload || infoUpload || packageUpload)), 'Mock must not call material-upload endpoints');
|
|
164
178
|
assert.equal(req.headers['x-use-ppe'], '1');
|
|
165
179
|
assert.equal(req.headers['x-tt-env'], 'ppe_op_vc');
|
|
166
180
|
assert.equal(req.headers.cookie, undefined);
|
|
@@ -168,13 +182,35 @@ async function main() {
|
|
|
168
182
|
assert.equal(url.searchParams.get('task_id'), '7684370037737553941');
|
|
169
183
|
assert.equal(url.searchParams.get('import_token'), 'process-test-secret');
|
|
170
184
|
}
|
|
185
|
+
if (iconUpload || infoUpload || packageUpload) {
|
|
186
|
+
assert.equal(webPolls, 5, 'Do not upload before server readiness');
|
|
187
|
+
assert.equal(req.method, 'POST');
|
|
188
|
+
if (infoUpload) {
|
|
189
|
+
assert.match(body.toString(), /"task_id":7684370037737553941/);
|
|
190
|
+
const info = JSON.parse(body.toString());
|
|
191
|
+
assert.equal(info.import_token, 'process-test-secret');
|
|
192
|
+
assert.equal(info.app_info.icon_uri, 'web-icon-receipt');
|
|
193
|
+
assert.equal(info.app_info.app_name, input[input.indexOf('--title') + 1]);
|
|
194
|
+
assert.deepEqual(Object.keys(info.app_info).sort(), ['app_name', 'icon_uri'],
|
|
195
|
+
'Default uploads must leave other metadata to platform defaults');
|
|
196
|
+
} else {
|
|
197
|
+
assert.match(req.headers['content-type'], /^multipart\/form-data; boundary=/);
|
|
198
|
+
assert.match(body.toString(), /name="task_id"\r\n\r\n7684370037737553941\r\n/);
|
|
199
|
+
assert.match(body.toString(), /name="import_token"\r\n\r\nprocess-test-secret\r\n/);
|
|
200
|
+
assert.ok(body.includes(fs.readFileSync(iconUpload ? icon : zip)));
|
|
201
|
+
if (packageUpload) assert.match(body.toString(), /name="asset_type"\r\n\r\n2\r\n/);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
171
204
|
const data = create ? {
|
|
172
205
|
task_id: '7684370037737553941', import_token: 'process-test-secret',
|
|
173
206
|
landing_url: 'https://developers.tiktok.com/portal_h5/minis_import?task_id=7684370037737553941&import_token=process-test-secret',
|
|
174
207
|
poll_interval_seconds: 0.01,
|
|
175
|
-
} :
|
|
208
|
+
} : iconUpload ? {icon_uri: 'web-icon-receipt'}
|
|
209
|
+
: infoUpload ? {}
|
|
210
|
+
: packageUpload ? {status: 3, package_uri: 'web-package-receipt'}
|
|
211
|
+
: { status: webMode && webPolls >= 5 ? 2 : 1, ready_for_input: webMode && webPolls >= 5 };
|
|
176
212
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
177
|
-
res.end(JSON.stringify({ ...data, error_info: { code: failStatus && status ? 420500 : 0 } }));
|
|
213
|
+
res.end(JSON.stringify({ ...data, error_info: { code: (failStatus && status) || url.pathname === webFailureEndpoint ? 420500 : 0 } }));
|
|
178
214
|
});
|
|
179
215
|
await new Promise(resolve => fixtureServer.listen(0, '127.0.0.1', resolve));
|
|
180
216
|
env.TTMG_VIBE_TEST_PORT = String(fixtureServer.address().port);
|
|
@@ -193,6 +229,13 @@ async function main() {
|
|
|
193
229
|
|
|
194
230
|
let bindingHtml;
|
|
195
231
|
const appInfoPath = path.join(temp, 'app-info.json');
|
|
232
|
+
const invalidLegacy = await run([...input, '--app-info', appInfoPath, '--dry-run', '--format', 'json']);
|
|
233
|
+
assert.equal(invalidLegacy.code, 1);
|
|
234
|
+
const legacyFailure = events(invalidLegacy)[0];
|
|
235
|
+
assert.equal(legacyFailure.errorCode, 'VIBE_INVALID_APP_INFO');
|
|
236
|
+
assert.equal(legacyFailure.platformWrite, false);
|
|
237
|
+
assert.match(legacyFailure.message, /Remove --app-info|移除 --app-info/);
|
|
238
|
+
assert.doesNotMatch(legacyFailure.message, /are required|需要简介/);
|
|
196
239
|
fs.writeFileSync(appInfoPath, JSON.stringify({
|
|
197
240
|
description: 'Process contract fixture', category: 'Casual', subcategory: 'Puzzle',
|
|
198
241
|
terms_of_service: 'https://example.invalid/terms', privacy_policy: 'https://example.invalid/privacy',
|
|
@@ -323,6 +366,7 @@ async function main() {
|
|
|
323
366
|
webMode = true;
|
|
324
367
|
for (const format of ['json', 'ndjson']) {
|
|
325
368
|
const receipt = path.join(temp, `upload-web-${format}.ndjson`);
|
|
369
|
+
const requestStart = requests.length;
|
|
326
370
|
const web = await run([...input.filter(v => v !== '--no-open'), '--web', '--format', format, '--poll-interval', '0.2'], async event => {
|
|
327
371
|
if (event.status === 'waiting_authorization') {
|
|
328
372
|
assert.equal(event.authorizationMode, 'web');
|
|
@@ -342,22 +386,45 @@ async function main() {
|
|
|
342
386
|
TTMG_AUTH_OPEN_TEST_RECEIPT: receipt,
|
|
343
387
|
// Explicit --web must not inherit an old Mock backend setting.
|
|
344
388
|
TTMG_VIBE_BACKEND: 'mock',
|
|
389
|
+
TTMG_VIBE_TEST_RETAIN_UPLOAD_SOCKET: '1',
|
|
345
390
|
});
|
|
346
|
-
assert.equal(web.code,
|
|
391
|
+
assert.equal(web.code, 0, web.stderr);
|
|
347
392
|
const output = events(web);
|
|
348
|
-
assert.equal(output.at(-1).
|
|
393
|
+
assert.equal(output.at(-1).status, 'uploaded');
|
|
349
394
|
assert.equal(output.at(-1).simulation, false);
|
|
350
395
|
assert.equal(output.at(-1).backend, 'ppe');
|
|
351
396
|
assert.equal(output.at(-1).readyForInput, true);
|
|
352
|
-
assert.equal(output.at(-1).uploadAttempted,
|
|
353
|
-
assert.equal(output.at(-1).uploaded,
|
|
397
|
+
assert.equal(output.at(-1).uploadAttempted, true);
|
|
398
|
+
assert.equal(output.at(-1).uploaded, true);
|
|
399
|
+
assert.equal(output.at(-1).taskStatusRaw, 3);
|
|
400
|
+
assert.equal(output.filter(e => e.terminal).length, 1);
|
|
354
401
|
assert.equal(webPolls, 5, 'Only true backend readiness unlocks authorization');
|
|
402
|
+
assert.deepEqual(requests.slice(requestStart).map(r => r.path.split('/').filter(Boolean).at(-1)), [
|
|
403
|
+
'create_minis_import_task_for_agent', ...Array(5).fill('get_minis_import_task_status_for_agent'),
|
|
404
|
+
'upload_minis_import_app_icon_for_agent', 'submit_minis_import_info_for_agent', 'upload_minis_import_package_for_agent',
|
|
405
|
+
]);
|
|
355
406
|
if (format === 'json') assert.equal(output.length, 1);
|
|
356
407
|
const opens = fs.readFileSync(receipt, 'utf8').trim().split('\n').map(line => JSON.parse(line));
|
|
357
408
|
assert.equal(opens.length, 1);
|
|
358
409
|
assert.ok(opens[0].args.some(arg => /^http:\/\/127\.0\.0\.1:\d+\/[a-f0-9]{48}\/authorize$/.test(arg)));
|
|
359
|
-
assert.doesNotMatch(JSON.stringify(opens) + web.stdout + web.stderr, /process-test-secret|import_token|aweme:\/\//);
|
|
410
|
+
assert.doesNotMatch(JSON.stringify(opens) + web.stdout + web.stderr, /process-test-secret|import_token|web-icon-receipt|web-package-receipt|aweme:\/\//);
|
|
360
411
|
}
|
|
412
|
+
webFailureEndpoint = '/bff/app_info/minis_import/submit_minis_import_info_for_agent/';
|
|
413
|
+
const failedStart = requests.length;
|
|
414
|
+
const webUploadFailure = await run([...input.filter(v => v !== '--no-open'), '--web', '--format', 'json'], undefined, {
|
|
415
|
+
NODE_OPTIONS: env.NODE_OPTIONS + ' --require=' + path.join(__dirname, 'vibe-auth-open-test-preload.cjs'),
|
|
416
|
+
TTMG_AUTH_OPEN_TEST_RECEIPT: path.join(temp, 'web-failure-open.ndjson'),
|
|
417
|
+
TTMG_VIBE_TEST_RETAIN_UPLOAD_SOCKET: '1',
|
|
418
|
+
});
|
|
419
|
+
assert.equal(webUploadFailure.code, 1);
|
|
420
|
+
const webFailure = events(webUploadFailure)[0];
|
|
421
|
+
assert.equal(webFailure.errorCode, 'VIBE_BACKEND_REJECTED');
|
|
422
|
+
assert.equal(webFailure.uploadAttempted, true);
|
|
423
|
+
assert.equal(webFailure.uploaded, false);
|
|
424
|
+
assert.equal(webFailure.simulation, false);
|
|
425
|
+
assert.equal(requests.at(-1).path, webFailureEndpoint);
|
|
426
|
+
assert.equal(requests.slice(failedStart).filter(r => r.path === webFailureEndpoint).length, 1);
|
|
427
|
+
webFailureEndpoint = undefined;
|
|
361
428
|
webMode = false;
|
|
362
429
|
|
|
363
430
|
const json = await run(
|
package/package.json
CHANGED
package/vibe-upload.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## 中文
|
|
4
4
|
|
|
5
|
-
本次范围:本地准备 →
|
|
5
|
+
本次范围:本地准备 → 授权绑定 → 上传头像 → 提交应用信息 → 上传代码包 → 上传完成。不包含预览、审核或正式发布。`--web` 使用真实 PPE 授权和上传;`--mock` 的授权与素材上传仍为模拟。网页授权后自动上传的修复已在 `0.4.6-vibe-beta.9` 发布;用户输入要求已在 `0.4.6-vibe-beta.10` 修正。
|
|
6
6
|
|
|
7
7
|
### 博华如何接入
|
|
8
8
|
|
|
@@ -26,36 +26,23 @@ ttmg upload --vibe --dir ./game --title "星环守卫" --icon ./avatar.png --dry
|
|
|
26
26
|
|
|
27
27
|
已有 ZIP 时,用 `--package ./game.zip` 替换 `--dir ./game`。名称和头像必须由开发者提供;演示脚本中的临时样本不作为产品默认素材。
|
|
28
28
|
|
|
29
|
-
###
|
|
29
|
+
### 用户需要提供什么
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
**只收集游戏标题和本地头像,并使用当前游戏目录或代码包。** 描述、分类、服务条款、隐私政策、版权确认及其他应用资料由平台默认处理,Skill 不向用户索要,也不生成资料表单或自行补值。
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
ttmg upload --vibe --package ./game.zip --title "测试游戏" --icon ./avatar.png --app-info ./app-info.json --dry-run --format json
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
JSON 文件为以下字段组成的对象,最大 64 KiB;名称由 `--title` 提供,头像 URI 由上传接口返回,不在文件中填写。
|
|
38
|
-
|
|
39
|
-
| 字段 | 要求 |
|
|
40
|
-
| --- | --- |
|
|
41
|
-
| description、category、subcategory | 必填非空字符串;分类与子分类应使用平台分类表中的配对值,本地暂不校验分类枚举 |
|
|
42
|
-
| terms_of_service、privacy_policy | 必填 HTTP(S) 链接,不含用户名和密码;须使用游戏自己的真实条款与隐私链接 |
|
|
43
|
-
| copyright_confirmation | 必须显式为 true;只有用户已确认版权时才填写,Skill 不得代为默认确认 |
|
|
44
|
-
| apple_team_id、contact_email、region、min_sdk_version | 可选字符串;填写邮箱时检查基本格式 |
|
|
45
|
-
|
|
46
|
-
提供该文件后,创建任务前检查名称最多 30 个字符,以及头像为 PNG/JPEG、1024×1024、最大 5 MiB。头像仅做文件头预检,完整解码由后端校验。错误分别返回 `VIBE_INVALID_APP_INFO`、`VIBE_TITLE_TOO_LONG`、`VIBE_PLATFORM_ICON_INVALID`,`nextAction=fix_input`。JSON 不接受额外字段,内容不会进入结构化输出;修改后重新运行 dry-run。
|
|
33
|
+
能力查询和上传事件均返回 `inputRequirements`:`requiredUserInputs=["title","icon"]`、`additionalMetadataSource="platform-defaults"`、`requestAdditionalMetadata=false`。`--app-info` 仅兼容旧调用,当前流程不使用。
|
|
47
34
|
|
|
48
35
|
### Skill 读取什么
|
|
49
36
|
|
|
50
37
|
#### 上传时直接使用网页授权
|
|
51
38
|
|
|
52
|
-
使用 `--web`:CLI 通过内置 `ppe_op_vc`
|
|
39
|
+
使用 `--web`:CLI 通过内置 `ppe_op_vc` 真实创建任务,在执行机的系统浏览器打开授权页,并持续查询真实绑定状态。服务端返回 `ready_for_input=true` 后,原进程使用同一任务依次上传头像、提交应用信息、上传代码包。Skill 不需要配置环境、再调用 `auth open` 或另发上传命令。
|
|
53
40
|
|
|
54
41
|
```sh
|
|
55
42
|
ttmg upload --vibe --web --dir ./game --title "星环守卫" --icon ./avatar.png --format ndjson
|
|
56
43
|
```
|
|
57
44
|
|
|
58
|
-
原 `upload --vibe --mock` 链路不变。`--web` 与 `--mock`、`--mock-scenario`、`--vibe-api`、`--no-open` 互斥,在创建任务前报错;显式 `--web` 不继承旧 Mock 后端设置。`--dry-run` 不创建任务或打开浏览器。JSON/NDJSON
|
|
45
|
+
原 `upload --vibe --mock` 链路不变。`--web` 与 `--mock`、`--mock-scenario`、`--vibe-api`、`--no-open` 互斥,在创建任务前报错;显式 `--web` 不继承旧 Mock 后端设置。`--dry-run` 不创建任务或打开浏览器。JSON/NDJSON 模式也会打开浏览器,但不污染结构化输出。真实上传失败时返回对应错误,不自动重试写入或降级 Mock。
|
|
59
46
|
|
|
60
47
|
等待事件返回 `authorizationMode=web` 和 `browserOpenRequested`。真实授权场景的 `nextAction=open_browser` 表示在浏览器完成授权,不代表已授权。打开失败时返回 `browserOpenErrorCode=VIBE_BROWSER_OPEN_FAILED`,仍轮询原任务,可用下面的辅助命令重试,不重新建任务。
|
|
61
48
|
|
|
@@ -71,7 +58,7 @@ ttmg auth open --page-url <pageUrl> --format json
|
|
|
71
58
|
|
|
72
59
|
成功返回 `status=open_requested`、`browserOpenRequested=true`、`platformWrite=false`,只表示已请求系统浏览器打开。页面过期、流程结束或没有可用真实授权地址时,返回 `VIBE_AUTH_PAGE_UNAVAILABLE`;URL 输入错误返回 `VIBE_INVALID_PAGE_URL`,浏览器启动失败返回 `VIBE_BROWSER_OPEN_FAILED`。不得因打开失败而自动创建新任务。
|
|
73
60
|
|
|
74
|
-
命令在 CLI 所在电脑执行;远程机器不会打开开发者电脑的浏览器。二维码原路径和 `--mock`
|
|
61
|
+
命令在 CLI 所在电脑执行;远程机器不会打开开发者电脑的浏览器。二维码原路径和 `--mock` 自动推进逻辑保持不变;真实网页授权与上传使用 `--web`。
|
|
75
62
|
|
|
76
63
|
输出 schema 为 `"2"`。每行 NDJSON 是同一 `operationId` 的状态快照;只有最后一行 `terminal=true`。`--format json` 仅在结束时向 stdout 输出一个对象,等待授权的本地页面地址写到 stderr。不要解析中文/英文文案判断状态。
|
|
77
64
|
|
|
@@ -79,7 +66,7 @@ ttmg auth open --page-url <pageUrl> --format json
|
|
|
79
66
|
| ------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
80
67
|
| precheck / packaging / prepared | 正在检查或准备素材;dry-run 的 prepared 为终态 |
|
|
81
68
|
| creating_task | 正在准备授权绑定 |
|
|
82
|
-
| waiting_authorization |
|
|
69
|
+
| waiting_authorization | nextAction=open_browser 时在已打开的系统浏览器完成授权;scan_qr 时提示 TikTok 扫码;wait 时等待模拟推进 |
|
|
83
70
|
| uploading | 根据 uploadStep 显示 avatar、app_info、package 的进度,不再询问授权 |
|
|
84
71
|
| uploaded | terminal=true 且 uploaded=true:本次上传完成;不是审核通过或正式发布 |
|
|
85
72
|
| failed / cancelled | 根据 errorCode、message 和 nextAction 提示处理,不自动重传 |
|
|
@@ -92,7 +79,7 @@ ttmg auth open --page-url <pageUrl> --format json
|
|
|
92
79
|
|
|
93
80
|
按阶段补充:`taskId`(字符串,禁止转成 JS Number)、`pollIntervalSeconds`、`readyForInput`、`taskStatusRaw`(仅诊断)、`uploadStep`、`loadedBytes`、`totalBytes`、`pageUrl`、`qrPurpose=authorization_binding`、素材大小和包 SHA256。代码包字节进度达到 100% 不代表成功,必须等待最终上传回执。
|
|
94
81
|
|
|
95
|
-
`nextAction` 取值:`wait`、`scan_qr`、`fix_input`、`restart_authorization`、`check_task`、`none`。已开始上传后发生失败/超时,先检查 task,不能直接再次创建并重传。若上传已完成但页面失败,仍保留 `uploaded=true`,同时返回错误;Skill 提示上传已完成但展示失败。
|
|
82
|
+
`nextAction` 取值:`wait`、`open_browser`、`scan_qr`、`fix_input`、`restart_authorization`、`check_task`、`none`。已开始上传后发生失败/超时,先检查 task,不能直接再次创建并重传。若上传已完成但页面失败,仍保留 `uploaded=true`,同时返回错误;Skill 提示上传已完成但展示失败。
|
|
96
83
|
|
|
97
84
|
退出码:成功/dry-run 为 0,失败为 1,Ctrl+C 为 130。进程或宿主意外终止导致没有终态时,不得认定上传成功。模拟必须显示为模拟:即使 `uploaded=true`,`simulation=true` 时也只表示模拟步骤完成。纯 Mock 的 `platformWrite=false`;混合联调创建过真实任务时为 true,但都不代表真实上传。
|
|
98
85
|
|
|
@@ -166,7 +153,9 @@ TTMG_VIBE_BACKEND=local-http TTMG_VIBE_API=http://127.0.0.1:3100 ttmg upload --v
|
|
|
166
153
|
|
|
167
154
|
### Skill entry
|
|
168
155
|
|
|
169
|
-
Scope: local preparation → authorization binding → avatar upload → app-info submission → package upload → **upload complete**. No preview, review or production release.
|
|
156
|
+
Scope: local preparation → authorization binding → avatar upload → app-info submission → package upload → **upload complete**. No preview, review or production release. `--web` uses real PPE authorization and uploads; `--mock` still simulates authorization and material uploads.
|
|
157
|
+
|
|
158
|
+
Automatic upload was fixed in `0.4.6-vibe-beta.9`; the user-input clarification ships in `0.4.6-vibe-beta.10`.
|
|
170
159
|
|
|
171
160
|
Keep the existing --mock command; no environment setup is required:
|
|
172
161
|
|
|
@@ -178,24 +167,13 @@ Use `--package ./game.zip` instead of --dir for a prepared ZIP. Use `--dry-run -
|
|
|
178
167
|
|
|
179
168
|
The Skill gathers the title and user-selected local icon in conversation. It does not call backend APIs, store credentials, poll services, or introduce a metadata form. The CLI internally connects working steps to PPE and simulates the remaining steps. Removing PPE at production launch is a CLI release change, not a Skill configuration step.
|
|
180
169
|
|
|
181
|
-
###
|
|
182
|
-
|
|
183
|
-
Maintainers can add `--app-info ./app-info.json` to prepare complete upload parameters. It does not change the existing --mock entry or enable PPE material uploads.
|
|
184
|
-
|
|
185
|
-
```sh
|
|
186
|
-
ttmg upload --vibe --package ./game.zip --title "Test game" --icon ./avatar.png --app-info ./app-info.json --dry-run --format json
|
|
187
|
-
```
|
|
170
|
+
### User inputs
|
|
188
171
|
|
|
189
|
-
|
|
172
|
+
**Collect only the game title and local icon, using the current game directory or ZIP.** The platform defaults all other app metadata. Do not request extra fields, add a form or invent values.
|
|
190
173
|
|
|
191
|
-
|
|
192
|
-
| --- | --- |
|
|
193
|
-
| description, category, subcategory | Required nonempty strings; use a valid platform category/subcategory pair. Local enum validation is not implemented yet |
|
|
194
|
-
| terms_of_service, privacy_policy | Required HTTP(S) URLs without embedded credentials; provide the game's actual legal links |
|
|
195
|
-
| copyright_confirmation | Explicit true only after the user confirms ownership; the Skill must not infer consent |
|
|
196
|
-
| apple_team_id, contact_email, region, min_sdk_version | Optional strings; email receives a basic syntax check |
|
|
174
|
+
Capabilities and upload events include `inputRequirements` with `requiredUserInputs=["title","icon"]`, `additionalMetadataSource="platform-defaults"` and `requestAdditionalMetadata=false`.
|
|
197
175
|
|
|
198
|
-
|
|
176
|
+
The `--app-info` option remains for legacy callers only; the current workflow does not use it.
|
|
199
177
|
|
|
200
178
|
Source checked on 2026-09-14: BFF enforces these icon limits and a 100 MiB Native ZIP/uncompressed-size limit. Full local ZIP-content/uncompressed-size validation is not implemented. Gateway transport, TOS access, phone OAuth and upload receipts still require real integration testing.
|
|
201
179
|
|
|
@@ -203,13 +181,13 @@ Source checked on 2026-09-14: BFF enforces these icon limits and a 100 MiB Nativ
|
|
|
203
181
|
|
|
204
182
|
#### Use browser authorization during upload
|
|
205
183
|
|
|
206
|
-
Use `--web` for real authorization. The CLI creates a task through the built-in `ppe_op_vc` lane, opens authorization in the execution machine's system browser and polls real binding status
|
|
184
|
+
Use `--web` for real authorization. The CLI creates a task through the built-in `ppe_op_vc` lane, opens authorization in the execution machine's system browser and polls real binding status. Once the server returns `ready_for_input=true`, the same process uploads the icon, submits app information and uploads the package using the same task. No environment setup, `auth open` helper or second upload command is required.
|
|
207
185
|
|
|
208
186
|
```sh
|
|
209
187
|
ttmg upload --vibe --web --dir ./game --title "My game" --icon ./avatar.png --format ndjson
|
|
210
188
|
```
|
|
211
189
|
|
|
212
|
-
The original `upload --vibe --mock` workflow is unchanged. `--web` conflicts with `--mock`, `--mock-scenario`, `--vibe-api` and `--no-open` before task creation, and does not inherit an old Mock backend environment setting. Dry-run creates no task and opens no browser. JSON/NDJSON also open the browser without contaminating stdout. Real
|
|
190
|
+
The original `upload --vibe --mock` workflow is unchanged. `--web` conflicts with `--mock`, `--mock-scenario`, `--vibe-api` and `--no-open` before task creation, and does not inherit an old Mock backend environment setting. Dry-run creates no task and opens no browser. JSON/NDJSON also open the browser without contaminating stdout. Real upload failures return an error; writes are not automatically retried or replaced by Mock results.
|
|
213
191
|
|
|
214
192
|
Waiting events add `authorizationMode=web` and `browserOpenRequested`. For real authorization, `nextAction=open_browser` requests browser approval; it does not prove approval. If opening fails, `browserOpenErrorCode=VIBE_BROWSER_OPEN_FAILED` is returned while polling continues. Retry the helper below against the same task instead of creating another one.
|
|
215
193
|
|
|
@@ -235,17 +213,16 @@ bad input returns `VIBE_INVALID_PAGE_URL`, and browser launch failures return
|
|
|
235
213
|
`VIBE_BROWSER_OPEN_FAILED`. Do not create another task automatically on failure.
|
|
236
214
|
|
|
237
215
|
This opens the browser on the CLI computer, not a remote user's computer. Existing
|
|
238
|
-
QR behavior and `--mock` auto-advance stay unchanged
|
|
239
|
-
|
|
240
|
-
Use a build containing the new command; existing released versions are unchanged.
|
|
216
|
+
QR behavior and `--mock` auto-advance stay unchanged. Use `--web` for real browser
|
|
217
|
+
authorization followed by upload.
|
|
241
218
|
|
|
242
|
-
Schema **2** provides NDJSON snapshots under one operationId, with exactly one final `terminal=true` event. JSON mode emits one final object on stdout and the authorization page address on stderr.
|
|
219
|
+
Schema **2** provides NDJSON snapshots under one operationId, with exactly one final `terminal=true` event. JSON mode emits one final object on stdout and the authorization page address on stderr. With `--web`, all output formats open the system browser automatically. Other modes open pageUrl automatically only in text mode unless --no-open is set.
|
|
243
220
|
|
|
244
221
|
| status | Skill action |
|
|
245
222
|
| ------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
246
223
|
| precheck / packaging / prepared | Wait for input preparation; prepared is terminal only for dry-run |
|
|
247
224
|
| creating_task | Preparing binding |
|
|
248
|
-
| waiting_authorization |
|
|
225
|
+
| waiting_authorization | open_browser requests approval in the opened system browser; scan_qr requests a TikTok scan; wait means simulation advances automatically |
|
|
249
226
|
| uploading | Follow uploadStep: avatar, app_info, package |
|
|
250
227
|
| uploaded | terminal=true and uploaded=true mean upload complete, not release |
|
|
251
228
|
| failed / cancelled | Read errorCode, message and nextAction; do not blindly repeat uploads |
|
|
@@ -256,7 +233,7 @@ platformWrite becomes true immediately before attempting real task creation, not
|
|
|
256
233
|
|
|
257
234
|
For backend=ppe-mock, simulation=true and simulationStartsAt=authorization. platformWrite becomes true before real task creation. taskStatusRaw and readyForInput retain the last real observation; simulatedTaskStatusRaw and simulatedReadyForInput describe the local demo separately. The uploaded terminal state means simulated completion only; the Skill must retain the simulation label.
|
|
258
235
|
|
|
259
|
-
nextAction is wait, scan_qr, fix_input, restart_authorization, check_task or none. If upload was attempted, verify the task before retrying. If a result page fails after confirmed upload, uploaded remains true alongside the error. Progress at 100% is not a receipt. Missing terminal output after a killed process is not success. Exit codes: 0 success/dry-run, 1 failure, 130 cancellation.
|
|
236
|
+
nextAction is wait, open_browser, scan_qr, fix_input, restart_authorization, check_task or none. If upload was attempted, verify the task before retrying. If a result page fails after confirmed upload, uploaded remains true alongside the error. Progress at 100% is not a receipt. Missing terminal output after a killed process is not success. Exit codes: 0 success/dry-run, 1 failure, 130 cancellation.
|
|
260
237
|
|
|
261
238
|
Pure Mock reports simulation=true and platformWrite=false. Hybrid PPE + Mock reports platformWrite=true once real task creation is attempted, but uploaded=true still means simulated completion only. No backend exposes credentials or raw HTTP errors. Backend integration must preserve this public contract; changes to user actions or completion semantics require an explicit schema migration.
|
|
262
239
|
|
|
@@ -278,7 +255,7 @@ The five endpoint contracts are listed in the table above. Mock and loopback HTT
|
|
|
278
255
|
|
|
279
256
|
A business-success response requires error_info.code=0. Package completion additionally requires package_uri and a valid non-failure status; no polling until SUCCEEDED. No platform Cookies or redirects are used. Configure TTMG_VIBE_BACKEND=local-http plus TTMG_VIBE_API=http://127.0.0.1:3100, or pass --vibe-api explicitly; this option rejects remote hosts.
|
|
280
257
|
|
|
281
|
-
For internal PPE integration, set TTMG_VIBE_BACKEND=ppe and TTMG_VIBE_PPE=ppe_op_vc without changing the Skill command. This uses real creation/polling on developers.tiktok.com and encodes the QR as aweme://webview?url=<encodeURIComponent(landing_url)>. The phone must use the same lane. OAuth is not bypassed or simulated. After authorization,
|
|
258
|
+
For internal PPE integration, set TTMG_VIBE_BACKEND=ppe and TTMG_VIBE_PPE=ppe_op_vc without changing the Skill command. This uses real creation/polling on developers.tiktok.com and encodes the QR as aweme://webview?url=<encodeURIComponent(landing_url)>. The phone must use the same lane. OAuth is not bypassed or simulated. After authorization, the CLI uploads the icon, submits the title and icon URI, and uploads the package. Other app metadata uses platform defaults. The regular --web entry configures PPE internally and opens the system browser.
|
|
282
259
|
|
|
283
260
|
The normal --mock trial internally uses ppe_op_vc without environment variables. Task creation, QR and three polls are real; authorization, icon, app-info and package uploads are simulated with separate in-memory credentials. This changes --mock from an offline run into a PPE integration trial: platformWrite becomes true when task creation is attempted, while simulation remains true. No real material-upload endpoint is called, and real request failures still fail. --mock-scenario controls only simulated steps. Offline unit/process tests use injected transports, not a separate developer configuration.
|
|
284
261
|
|