@ttmg/cli 0.4.6-vibe-beta.14 → 0.4.6-vibe-beta.15
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 +68 -3
- package/dist/index.js +251 -13
- package/dist/index.js.map +1 -1
- package/dist/package.json +2 -2
- package/dist/scripts/verify-agent-contract.js +13 -0
- package/dist/scripts/vibe-upload-smoke.js +28 -3
- package/dist/scripts/wasm-collect-smoke.js +78 -0
- package/dist/scripts/wasm-collect-test-preload.cjs +13 -0
- package/package.json +2 -2
- package/vibe-upload.md +43 -3
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttmg/cli",
|
|
3
|
-
"version": "0.4.6-vibe-beta.
|
|
3
|
+
"version": "0.4.6-vibe-beta.15",
|
|
4
4
|
"description": "TikTok Mini Game Command Line Tool",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"bin": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"test:check-ai": "node scripts/run-ts-test.js src/commands/native/check/ai.test.ts",
|
|
49
49
|
"test:build-structured": "node scripts/run-ts-test.js src/commands/native/build/build.test.ts",
|
|
50
50
|
"test:doctor": "node scripts/run-ts-test.js src/commands/native/doctor/doctor.test.ts",
|
|
51
|
-
"test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/acceptance-fixture.test.js",
|
|
51
|
+
"test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/run-ts-test.js src/commands/native/wasm/collect.test.ts && node scripts/acceptance-fixture.test.js",
|
|
52
52
|
"test:agent-contract": "npm run test:login && npm run test:auth && npm run test:capabilities && npm run test:test-account && npm run test:upload-preview && npm run test:preview-page && npm run test:check && npm run test:check-ai && npm run test:build-structured && npm run test:doctor && npm run test:wasm && npm run test:dev-session && npm run test:dev-session-command && npm run test:dev-runtime-event && npm run test:dev-runtime-client && npm run test:dev-session-control && npm run test:dev-runtime-session-follow && npm run test:dev-client-preview-orchestrator && npm run test:dev-watch-change && npm run test:network && npm run test:vibe && npx tsc --noEmit && npm run build && npm run test:vibe-process && npm run test:preview-page-process && npm run test:preview-page-detached && npm run verify:dist-version && npm run verify:agent-contract",
|
|
53
53
|
"test:agent-contract:full": "npm run test:agent-contract && npm run test:dev-default-process && npm run test:dev-game-ready-process && npm run test:dev-client-preview-control-process && node scripts/dev-workflow-smoke.js",
|
|
54
54
|
"publish:beta": "npm publish --registry=https://registry.npmjs.org/ --access public --tag beta",
|
|
@@ -404,6 +404,8 @@ assert.match(uploadHelp.stdout, /--vibe --web --dir/);
|
|
|
404
404
|
assert.match(uploadHelp.stdout, /authorizationOpenCommand/);
|
|
405
405
|
assert.match(uploadHelp.stdout, /simulation=false/);
|
|
406
406
|
assert.match(uploadHelp.stdout, /--category <category>/);
|
|
407
|
+
assert.match(uploadHelp.stdout, /--description <description>/);
|
|
408
|
+
assert.match(uploadHelp.stdout, /Discover the fun of TikTok Mini Games\./);
|
|
407
409
|
assert.match(uploadHelp.stdout, /--subcategory <subcategory>/);
|
|
408
410
|
assert.match(uploadHelp.stdout, /--preview\b/);
|
|
409
411
|
assert.match(uploadHelp.stdout, /--serve-preview-page\b/);
|
|
@@ -451,6 +453,17 @@ const wasmSmoke = spawnSync(process.execPath, [path.join(cliRoot, 'scripts/wasm-
|
|
|
451
453
|
});
|
|
452
454
|
assertExit(wasmSmoke, 0, 'Wasm built-artifact process smoke');
|
|
453
455
|
console.log(wasmSmoke.stdout.trim());
|
|
456
|
+
for (const operation of ['start', 'export']) {
|
|
457
|
+
assertExit(runWithoutWasmBinding(['wasm', 'collect', operation, '--help']), 0, `collect ${operation} help without binding`);
|
|
458
|
+
const invalid = runCli(['wasm', 'collect', operation, '--format', 'json']);
|
|
459
|
+
assertExit(invalid, 1, `collect ${operation} missing input`);
|
|
460
|
+
assert.equal(parseSingleJson(invalid, `collect ${operation}`).errorCode, 'WASM_COLLECT_INVALID_OPTIONS');
|
|
461
|
+
}
|
|
462
|
+
const collectSmoke = spawnSync(process.execPath, [path.join(cliRoot, 'scripts/wasm-collect-smoke.js')], {
|
|
463
|
+
cwd: cliRoot, encoding: 'utf8', timeout: 60000,
|
|
464
|
+
});
|
|
465
|
+
assertExit(collectSmoke, 0, 'Wasm collection built-artifact process smoke');
|
|
466
|
+
console.log(collectSmoke.stdout.trim());
|
|
454
467
|
const captureHelp = runCli(['dev', 'capture', '--help']);
|
|
455
468
|
assertExit(captureHelp, 0, 'capture help');
|
|
456
469
|
assert.match(captureHelp.stdout, /--timeout/);
|
|
@@ -31,6 +31,7 @@ let webMode = false;
|
|
|
31
31
|
let webPolls = 0;
|
|
32
32
|
let webFailureEndpoint;
|
|
33
33
|
let expectedWebCategory = { category: 'Other', subcategory: 'Other' };
|
|
34
|
+
let expectedWebDescription = 'Discover the fun of TikTok Mini Games.';
|
|
34
35
|
const env = {
|
|
35
36
|
...process.env,
|
|
36
37
|
HOME: temp,
|
|
@@ -194,7 +195,7 @@ async function main() {
|
|
|
194
195
|
assert.equal(info.app_info.icon_uri, 'web-icon-receipt');
|
|
195
196
|
assert.equal(info.app_info.app_name, input[input.indexOf('--title') + 1]);
|
|
196
197
|
assert.deepEqual(info.app_info, {
|
|
197
|
-
description:
|
|
198
|
+
description: expectedWebDescription,
|
|
198
199
|
...expectedWebCategory,
|
|
199
200
|
terms_of_service: 'https://www.tiktok.com',
|
|
200
201
|
privacy_policy: 'https://www.tiktok.com',
|
|
@@ -265,6 +266,19 @@ async function main() {
|
|
|
265
266
|
assert.equal(events(completeDry)[0].classification.category, 'Casual');
|
|
266
267
|
assert.equal(events(completeDry)[0].classification.subcategory, 'Puzzle');
|
|
267
268
|
assert.equal(events(completeDry)[0].classification.source, 'app-info');
|
|
269
|
+
assert.equal(events(completeDry)[0].description, 'Process contract fixture');
|
|
270
|
+
assert.equal(events(completeDry)[0].descriptionSource, 'app-info');
|
|
271
|
+
const describedDry = await run([...input, '--description', ' Stack cats 🐈 ', '--dry-run', '--format', 'json']);
|
|
272
|
+
assert.equal(describedDry.code, 0, describedDry.stderr);
|
|
273
|
+
assert.equal(events(describedDry)[0].description, 'Stack cats 🐈');
|
|
274
|
+
assert.equal(events(describedDry)[0].descriptionSource, 'arguments');
|
|
275
|
+
assert.equal(events(describedDry)[0].classification.source, 'defaults');
|
|
276
|
+
assert.equal(events(describedDry)[0].platformWrite, false);
|
|
277
|
+
const descriptionOverrideDry = await run([...input, '--app-info', appInfoPath, '--description', 'New description', '--dry-run', '--format', 'json']);
|
|
278
|
+
assert.equal(descriptionOverrideDry.code, 0, descriptionOverrideDry.stderr);
|
|
279
|
+
assert.equal(events(descriptionOverrideDry)[0].description, 'New description');
|
|
280
|
+
assert.equal(events(descriptionOverrideDry)[0].descriptionSource, 'arguments');
|
|
281
|
+
assert.equal(events(descriptionOverrideDry)[0].classification.source, 'app-info');
|
|
268
282
|
const overrideDry = await run([...input, '--app-info', appInfoPath, '--category', 'Puzzle', '--subcategory', 'Physics', '--dry-run', '--format', 'json']);
|
|
269
283
|
assert.equal(overrideDry.code, 0, overrideDry.stderr);
|
|
270
284
|
assert.equal(events(overrideDry)[0].classification.category, 'Puzzle');
|
|
@@ -380,6 +394,8 @@ async function main() {
|
|
|
380
394
|
assert.equal(events(legacyWeb)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
|
|
381
395
|
assert.equal(requests.length, beforeWeb);
|
|
382
396
|
for (const [args, code] of [
|
|
397
|
+
[['--description', ''], 'VIBE_INVALID_DESCRIPTION'],
|
|
398
|
+
[['--description', ' \n '], 'VIBE_INVALID_DESCRIPTION'],
|
|
383
399
|
[['--category', 'Puzzle'], 'VIBE_CATEGORY_PAIR_REQUIRED'],
|
|
384
400
|
[['--subcategory', 'Physics'], 'VIBE_CATEGORY_PAIR_REQUIRED'],
|
|
385
401
|
[['--category', 'Other', '--subcategory', 'Physics'], 'VIBE_INVALID_CATEGORY'],
|
|
@@ -394,14 +410,17 @@ async function main() {
|
|
|
394
410
|
assert.equal(result.taskId, undefined);
|
|
395
411
|
assert.equal(result.pageUrl, undefined);
|
|
396
412
|
}
|
|
397
|
-
assert.equal(requests.length, beforeWeb, 'Invalid
|
|
413
|
+
assert.equal(requests.length, beforeWeb, 'Invalid metadata must not create tasks or upload');
|
|
398
414
|
webMode = true;
|
|
399
415
|
for (const format of ['json', 'ndjson']) {
|
|
416
|
+
expectedWebDescription = format === 'json' ? 'Discover the fun of TikTok Mini Games.' : 'Stack cats on a rooftop 🐈';
|
|
417
|
+
const descriptionArgs = format === 'json' ? [] : ['--description', expectedWebDescription];
|
|
400
418
|
expectedWebCategory = format === 'json' ? {category: 'Other', subcategory: 'Other'} : {category: 'Puzzle', subcategory: 'Physics'};
|
|
401
419
|
const categoryArgs = format === 'json' ? [] : ['--category', 'Puzzle', '--subcategory', 'Physics'];
|
|
402
420
|
const receipt = path.join(temp, `upload-web-${format}.ndjson`);
|
|
403
421
|
const requestStart = requests.length;
|
|
404
|
-
const web = await run([...input.filter(v => v !== '--no-open'), '--web', ...categoryArgs, '--format', format, '--poll-interval', '0.2'], async event => {
|
|
422
|
+
const web = await run([...input.filter(v => v !== '--no-open'), '--web', ...categoryArgs, ...descriptionArgs, '--format', format, '--poll-interval', '0.2'], async event => {
|
|
423
|
+
if (event.status === 'prepared') assert.equal(event.description, expectedWebDescription);
|
|
405
424
|
if (event.status === 'prepared') assert.deepEqual(
|
|
406
425
|
{category: event.classification.category, subcategory: event.classification.subcategory},
|
|
407
426
|
expectedWebCategory);
|
|
@@ -437,6 +456,8 @@ async function main() {
|
|
|
437
456
|
{ category: output.at(-1).classification.category, subcategory: output.at(-1).classification.subcategory },
|
|
438
457
|
expectedWebCategory);
|
|
439
458
|
assert.equal(output.at(-1).classification.source, format === 'json' ? 'defaults' : 'arguments');
|
|
459
|
+
assert.equal(output.at(-1).description, expectedWebDescription);
|
|
460
|
+
assert.equal(output.at(-1).descriptionSource, format === 'json' ? 'defaults' : 'arguments');
|
|
440
461
|
assert.equal(output.at(-1).taskStatusRaw, 3);
|
|
441
462
|
assert.equal(output.filter(e => e.terminal).length, 1);
|
|
442
463
|
assert.equal(webPolls, 5, 'Only true backend readiness unlocks authorization');
|
|
@@ -451,6 +472,7 @@ async function main() {
|
|
|
451
472
|
assert.doesNotMatch(JSON.stringify(opens) + web.stdout + web.stderr, /process-test-secret|import_token|web-icon-receipt|web-package-receipt|aweme:\/\//);
|
|
452
473
|
}
|
|
453
474
|
expectedWebCategory = {category: 'Other', subcategory: 'Other'};
|
|
475
|
+
expectedWebDescription = 'Discover the fun of TikTok Mini Games.';
|
|
454
476
|
webFailureEndpoint = '/bff/app_info/minis_import/submit_minis_import_info_for_agent/';
|
|
455
477
|
const failedStart = requests.length;
|
|
456
478
|
const webUploadFailure = await run([...input.filter(v => v !== '--no-open'), '--web', '--format', 'json'], undefined, {
|
|
@@ -588,6 +610,9 @@ async function main() {
|
|
|
588
610
|
assert.equal(badMode.code, 1);
|
|
589
611
|
assert.equal(events(badMode)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
|
|
590
612
|
const badCategoryMode = await run(['upload', '--category', 'Puzzle', '--subcategory', 'Physics', '--format', 'json']);
|
|
613
|
+
const badDescriptionMode = await run(['upload', '--description', 'Game description', '--format', 'json']);
|
|
614
|
+
assert.equal(badDescriptionMode.code, 1);
|
|
615
|
+
assert.equal(events(badDescriptionMode)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
|
|
591
616
|
assert.equal(events(badCategoryMode)[0].errorCode, 'VIBE_OPTION_REQUIRES_VIBE');
|
|
592
617
|
const beforeCatalog = requests.length;
|
|
593
618
|
const catalog = await run(['game', 'categories', '--format', 'json']);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
const http = require('node:http');
|
|
7
|
+
const { spawn } = require('node:child_process');
|
|
8
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ttmg-collect-process-'));
|
|
9
|
+
const entry = path.resolve(__dirname, '../dist/index.js');
|
|
10
|
+
const preload = path.resolve(__dirname, 'wasm-collect-test-preload.cjs');
|
|
11
|
+
const input = path.join(root, 'original.wasm');
|
|
12
|
+
const wasm = Buffer.from('0061736d010000000105016000017f0304030000000404017000030707010372756e00000909010041000b030001020a1003040010010b0400412a0b040041070b', 'hex');
|
|
13
|
+
fs.writeFileSync(input, wasm);
|
|
14
|
+
const md5 = crypto.createHash('md5').update(wasm).digest('hex');
|
|
15
|
+
const rc = path.join(root, 'rc.json');
|
|
16
|
+
const secret = 'sessionid=TEST_ONLY_COLLECT_SECRET';
|
|
17
|
+
fs.writeFileSync(rc, JSON.stringify({ cookie: secret }));
|
|
18
|
+
let mode = 'success'; const calls = [];
|
|
19
|
+
const server = http.createServer(async (req, res) => {
|
|
20
|
+
let body = ''; for await (const b of req) body += b;
|
|
21
|
+
calls.push({ url: req.url, method: req.method, body: body ? JSON.parse(body) : null, cookie: req.headers.cookie });
|
|
22
|
+
res.setHeader('content-type', 'application/json');
|
|
23
|
+
const data = mode === 'auth' ? { code: -401, message: secret }
|
|
24
|
+
: req.url.includes('/start') ? { code: 0, collect_state: 'open', cookie: secret }
|
|
25
|
+
: { code: 0, func_ids: [1, 0, 1], boot_func_ids: [0], cookie: secret };
|
|
26
|
+
res.end(JSON.stringify(data));
|
|
27
|
+
});
|
|
28
|
+
async function run(args) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const child = spawn(process.execPath, ['-r', preload, entry, ...args], { cwd: root,
|
|
31
|
+
env: { ...process.env, TTMGRC_PATH: rc, TTMG_COLLECT_TEST_PORT: String(server.address().port),
|
|
32
|
+
HTTP_PROXY: '', HTTPS_PROXY: '', ALL_PROXY: '', http_proxy: '', https_proxy: '', all_proxy: '',
|
|
33
|
+
NO_COLOR: '1', FORCE_COLOR: '0' } });
|
|
34
|
+
let stdout = '', stderr = '';
|
|
35
|
+
child.stdout.on('data', b => stdout += b); child.stderr.on('data', b => stderr += b);
|
|
36
|
+
const timer = setTimeout(() => { child.kill('SIGKILL'); reject(Error('CLI timeout')); }, 15000);
|
|
37
|
+
child.on('error', reject); child.on('close', code => { clearTimeout(timer); resolve({ code, stdout, stderr }); });
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function result(r, code = 0) {
|
|
41
|
+
assert.equal(r.code, code, r.stderr || r.stdout);
|
|
42
|
+
assert.equal(r.stdout.trim().split('\n').length, 1, r.stdout);
|
|
43
|
+
assert.equal((r.stdout+r.stderr).includes(secret), false);
|
|
44
|
+
return JSON.parse(r.stdout);
|
|
45
|
+
}
|
|
46
|
+
(async () => {
|
|
47
|
+
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
48
|
+
const args = ['--client-key','fixture','--wasm-md5',md5,'--format','json'];
|
|
49
|
+
const a = result(await run(['wasm','collect','start', ...args, '--verbose']));
|
|
50
|
+
assert.equal(a.collectState,'open'); assert.equal(calls[0].cookie,secret);
|
|
51
|
+
assert.equal(calls[0].body.reset,false);
|
|
52
|
+
result(await run(['wasm','collect','start',...args,'--reset']));
|
|
53
|
+
assert.equal(calls[1].body.reset,true);
|
|
54
|
+
const beforeDefault = calls.length;
|
|
55
|
+
const defaultJson = result(await run(['--verbose', 'wasm','collect','start','--client-key','fixture','--wasm-md5',md5]));
|
|
56
|
+
assert.equal(defaultJson.collectState,'open');assert.equal(calls.length,beforeDefault+1);
|
|
57
|
+
const output=path.join(root,'collection.json');
|
|
58
|
+
const exported=result(await run(['wasm','collect','export',...args,'--input',input,'--output',output,'--verbose']));
|
|
59
|
+
assert.equal(exported.collection.path,output);assert.equal(calls[3].method,'GET');
|
|
60
|
+
assert.equal(calls[3].cookie,secret);assert.match(calls[3].url,/strategy=union/);
|
|
61
|
+
const collection=JSON.parse(fs.readFileSync(output,'utf8'));
|
|
62
|
+
assert.deepEqual(collection.funcIds,[0,1]);assert.deepEqual(collection.bootFuncIds,[0]);
|
|
63
|
+
assert.equal(fs.readFileSync(output,'utf8').includes(secret),false);
|
|
64
|
+
const split=result(await run(['wasm','split','--input',input,'--collection',output,
|
|
65
|
+
'--output',path.join(root,'split'),'--dry-run','--format','json']));
|
|
66
|
+
assert.equal(split.status,'ready');
|
|
67
|
+
mode='auth';
|
|
68
|
+
assert.equal(result(await run(['wasm','collect','start',...args,'--verbose']),1).errorCode,'WASM_AUTH_REQUIRED');
|
|
69
|
+
const count=calls.length;fs.writeFileSync(rc,'{}');
|
|
70
|
+
assert.equal(result(await run(['wasm','collect','start',...args]),1).errorCode,'WASM_AUTH_REQUIRED');
|
|
71
|
+
assert.equal(calls.length,count);
|
|
72
|
+
const unknown=await run(['wasm','collect','export',...args,'--reset']);assert.notEqual(unknown.code,0);
|
|
73
|
+
assert.equal(calls.length,count);
|
|
74
|
+
assert.deepEqual(fs.readFileSync(input),wasm);
|
|
75
|
+
console.log('Wasm collect process contract passed: local login transport, reset, export-to-split, clean verbose output; fixture only');
|
|
76
|
+
})().catch(e => { console.error(e); process.exitCode=1; }).finally(() => {
|
|
77
|
+
server.closeAllConnections();server.close();fs.rmSync(root,{recursive:true,force:true});
|
|
78
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Test-only transport: credentials and platform responses are synthetic.
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const https = require('node:https');
|
|
4
|
+
const http = require('node:http');
|
|
5
|
+
const port = Number(process.env.TTMG_COLLECT_TEST_PORT);
|
|
6
|
+
assert.ok(Number.isInteger(port) && port > 0 && port < 65536);
|
|
7
|
+
https.request = (options, callback) => {
|
|
8
|
+
assert.equal(options.hostname, 'developers.tiktok.com');
|
|
9
|
+
assert.match(options.path, /^\/api\/wasm-collect\/v1\/(start|export)(\?|$)/);
|
|
10
|
+
return http.request({ ...options, protocol: 'http:', hostname: '127.0.0.1', host: undefined,
|
|
11
|
+
port, agent: undefined, servername: undefined }, callback);
|
|
12
|
+
};
|
|
13
|
+
https.get = (options, callback) => { const req = https.request(options, callback); req.end(); return req; };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttmg/cli",
|
|
3
|
-
"version": "0.4.6-vibe-beta.
|
|
3
|
+
"version": "0.4.6-vibe-beta.15",
|
|
4
4
|
"description": "TikTok Mini Game Command Line Tool",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"bin": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"test:check-ai": "node scripts/run-ts-test.js src/commands/native/check/ai.test.ts",
|
|
49
49
|
"test:build-structured": "node scripts/run-ts-test.js src/commands/native/build/build.test.ts",
|
|
50
50
|
"test:doctor": "node scripts/run-ts-test.js src/commands/native/doctor/doctor.test.ts",
|
|
51
|
-
"test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/acceptance-fixture.test.js",
|
|
51
|
+
"test:wasm": "node scripts/run-ts-test.js src/commands/native/wasm/wasm.test.ts && node scripts/run-ts-test.js src/commands/native/wasm/collect.test.ts && node scripts/acceptance-fixture.test.js",
|
|
52
52
|
"test:agent-contract": "npm run test:login && npm run test:auth && npm run test:capabilities && npm run test:test-account && npm run test:upload-preview && npm run test:preview-page && npm run test:check && npm run test:check-ai && npm run test:build-structured && npm run test:doctor && npm run test:wasm && npm run test:dev-session && npm run test:dev-session-command && npm run test:dev-runtime-event && npm run test:dev-runtime-client && npm run test:dev-session-control && npm run test:dev-runtime-session-follow && npm run test:dev-client-preview-orchestrator && npm run test:dev-watch-change && npm run test:network && npm run test:vibe && npx tsc --noEmit && npm run build && npm run test:vibe-process && npm run test:preview-page-process && npm run test:preview-page-detached && npm run verify:dist-version && npm run verify:agent-contract",
|
|
53
53
|
"test:agent-contract:full": "npm run test:agent-contract && npm run test:dev-default-process && npm run test:dev-game-ready-process && npm run test:dev-client-preview-control-process && node scripts/dev-workflow-smoke.js",
|
|
54
54
|
"publish:beta": "npm publish --registry=https://registry.npmjs.org/ --access public --tag beta",
|
package/vibe-upload.md
CHANGED
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## 中文
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
适用版本:`0.4.6-vibe-beta.15`。用户只提供标题和头像;Skill 可选填描述与分类,其他应用资料由 CLI 自动补齐。
|
|
6
6
|
|
|
7
7
|
本次范围:本地准备 → 授权绑定 → 上传头像 → 提交应用信息 → 上传代码包 → 上传完成。不包含预览、审核或正式发布。`--web` 使用真实 PPE 授权和上传;`--mock` 的授权与素材上传仍为模拟。网页授权后自动上传的修复已在 `0.4.6-vibe-beta.9` 发布;用户输入要求已在 `0.4.6-vibe-beta.10` 修正。
|
|
8
8
|
|
|
9
|
+
### Quick Start
|
|
10
|
+
|
|
11
|
+
1. 安装并检查 `ttmg --version`、`ttmg capabilities --format json`。
|
|
12
|
+
2. Skill 根据实际玩法,用 `ttmg game categories --format json` 查询并选择分类;可生成描述并传 `--description`。未传时默认描述为 `Discover the fun of TikTok Mini Games.`,默认分类为 `Other/Other`。
|
|
13
|
+
3. 执行下方 Web 上传命令并保持原进程运行,在自动打开的浏览器完成授权。需要核对描述和分类时,先用 `--dry-run --format json` 本地检查。
|
|
14
|
+
4. 等待 `terminal=true`、`status=uploaded`、`uploaded=true`、`simulation=false` 和退出码 0,再报告上传完成。失败读取 `errorCode` 与 `nextAction`,不自动重传。
|
|
15
|
+
|
|
9
16
|
### Web 上传入口
|
|
10
17
|
|
|
11
18
|
真实授权和上传使用 `--web`。安装 `vibe-beta` 标签对应的版本,再用 `ttmg --version` 核对实际执行版本。`beta` 是另一个发布标签,不能用它判断 Web 链路是否包含最新修复。无需设置 PPE 环境变量或预先执行 `ttmg login`;用户在本次浏览器授权页完成授权。
|
|
@@ -15,7 +22,7 @@ Skill 只引导开发者准备游戏名称、本地头像和工程,然后调
|
|
|
15
22
|
Skill 固定调用:
|
|
16
23
|
|
|
17
24
|
```sh
|
|
18
|
-
npm install -g @ttmg/cli@vibe-beta
|
|
25
|
+
npm install -g @ttmg/cli@0.4.6-vibe-beta.15
|
|
19
26
|
ttmg --version
|
|
20
27
|
ttmg capabilities --format json
|
|
21
28
|
ttmg upload --vibe --web --dir ./game --title "星环守卫" --icon ./avatar.png --format ndjson
|
|
@@ -39,6 +46,19 @@ ttmg upload --vibe --web --dir ./game --title "星环守卫" --icon ./avatar.png
|
|
|
39
46
|
|
|
40
47
|
CLI 在 `app_info` 请求中自动带上 `description`、`category`、`subcategory`、`terms_of_service`、`privacy_policy`、`copyright_confirmation`、`contact_email`,再填入用户标题和真实上传头像 URI。默认资料标识为 `ppe-trial`;命令中没有这些参数,不代表请求里没传。能力查询的 `appInfoFields.defaultedByCli` 列出默认字段。
|
|
41
48
|
|
|
49
|
+
### 可选游戏描述
|
|
50
|
+
|
|
51
|
+
默认描述为 `Discover the fun of TikTok Mini Games.`,不包含游戏名称。Skill 可根据实际玩法生成描述并传入 `--description`,无需用户额外填写:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
ttmg upload --vibe --web --dir ./game --title "My game" --icon ./avatar.png --description "Stack cats on a rooftop and keep the tower balanced." --format ndjson
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- 优先级:`--description` > 旧 `--app-info` 文件中的描述 > 默认描述;只覆盖描述,其他字段保持原有规则。
|
|
58
|
+
- 参数首尾空白会被去除;空白文本或无效 Unicode 返回 `VIBE_INVALID_DESCRIPTION`,不会创建任务或上传。
|
|
59
|
+
- `prepared` 和后续终态返回 `description` 与 `descriptionSource`(`arguments / app-info / defaults`);可用 `--dry-run` 检查。
|
|
60
|
+
- 描述是可选 Skill 输入,省略仍可上传,不新增用户必填项。
|
|
61
|
+
|
|
42
62
|
### 查询并推荐游戏分类
|
|
43
63
|
|
|
44
64
|
以下命令和参数从 `0.4.6-vibe-beta.14` 开始提供。查询离线执行,不登录、不创建任务、不写平台:
|
|
@@ -199,6 +219,13 @@ TTMG_VIBE_BACKEND=local-http TTMG_VIBE_API=http://127.0.0.1:3100 ttmg upload --v
|
|
|
199
219
|
|
|
200
220
|
The default-metadata fix shipped in `0.4.6-vibe-beta.11`. The CLI fills the existing PPE trial metadata automatically; users provide only title and icon. Beta.10 changed input guidance without filling the fields, and its real app-info submission was rejected.
|
|
201
221
|
|
|
222
|
+
### Quick Start
|
|
223
|
+
|
|
224
|
+
1. Install, then check `ttmg --version` and `ttmg capabilities --format json`.
|
|
225
|
+
2. The Skill queries `ttmg game categories --format json` and selects a pair from the actual gameplay. It may generate a description and pass `--description`. Omitting these options keeps `Discover the fun of TikTok Mini Games.` and `Other/Other`.
|
|
226
|
+
3. Run the Web upload command below, keep the same process alive and authorize in the opened browser. To inspect description and classification first, use `--dry-run --format json` for local validation.
|
|
227
|
+
4. Confirm `terminal=true`, `status=uploaded`, `uploaded=true`, `simulation=false` and exit code 0 before reporting upload completion. On failure, read `errorCode` and `nextAction`; do not automatically reupload.
|
|
228
|
+
|
|
202
229
|
### Web upload entry
|
|
203
230
|
|
|
204
231
|
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.
|
|
@@ -208,7 +235,7 @@ Automatic upload was fixed in `0.4.6-vibe-beta.9`; the user-input clarification
|
|
|
208
235
|
Use `--web` for real authorization and upload. Install `vibe-beta` and check the executable with `ttmg --version`. The separate `beta` tag does not identify the latest Web fixes. No PPE environment variables or prior `ttmg login` are required; the user authorizes in the browser for this task.
|
|
209
236
|
|
|
210
237
|
```sh
|
|
211
|
-
npm install -g @ttmg/cli@vibe-beta
|
|
238
|
+
npm install -g @ttmg/cli@0.4.6-vibe-beta.15
|
|
212
239
|
ttmg --version
|
|
213
240
|
ttmg capabilities --format json
|
|
214
241
|
ttmg upload --vibe --web --dir ./game --title "My game" --icon ./avatar.png --format ndjson
|
|
@@ -230,6 +257,19 @@ The CLI submits `description`, `category`, `subcategory`, `terms_of_service`, `p
|
|
|
230
257
|
|
|
231
258
|
Source checked on 2026-09-14: BFF enforces PNG/JPEG, 1024×1024, 5 MiB icons and a 100 MiB Native ZIP/uncompressed-size limit. Full local ZIP-content/uncompressed-size validation is not implemented. On 2026-09-15, a corrected build completed real PPE web authorization and icon, default app-info and package uploads with owned test fixtures. This does not cover every game, phone authorization or production.
|
|
232
259
|
|
|
260
|
+
### Optional game description
|
|
261
|
+
|
|
262
|
+
The default is `Discover the fun of TikTok Mini Games.`, without the game title. A Skill may generate a description from the actual gameplay and pass `--description` without asking the user for another field:
|
|
263
|
+
|
|
264
|
+
```sh
|
|
265
|
+
ttmg upload --vibe --web --dir ./game --title "My game" --icon ./avatar.png --description "Stack cats on a rooftop and keep the tower balanced." --format ndjson
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- Precedence: `--description` > legacy `--app-info` description > default. Only the description is overridden; other metadata follows existing rules.
|
|
269
|
+
- Leading and trailing whitespace is trimmed. Blank text or invalid Unicode returns `VIBE_INVALID_DESCRIPTION` before task creation or upload.
|
|
270
|
+
- `prepared` and subsequent terminal events expose `description` and `descriptionSource` (`arguments / app-info / defaults`). Inspect them with `--dry-run`.
|
|
271
|
+
- Description is an optional Skill input. Omitting it still permits upload and adds no required user input.
|
|
272
|
+
|
|
233
273
|
### Query and recommend game categories
|
|
234
274
|
|
|
235
275
|
These commands and options are available from `0.4.6-vibe-beta.14`. Queries run offline without login, task creation or platform writes:
|