@ttmg/cli 0.4.6-vibe-beta.11 → 0.4.6-vibe-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -11
- package/dist/index.js +818 -113
- package/dist/index.js.map +1 -1
- package/dist/package.json +4 -3
- package/dist/scripts/verify-agent-contract.js +17 -0
- package/dist/scripts/vibe-upload-smoke.js +62 -4
- package/package.json +4 -3
- package/vibe-upload.md +108 -18
package/dist/index.js
CHANGED
|
@@ -7233,6 +7233,28 @@ const messages = {
|
|
|
7233
7233
|
'cli.command.auth.desc': 'Inspect local authentication state',
|
|
7234
7234
|
'cli.command.auth.open.desc': 'Open an existing Vibe task authorization page in the system browser',
|
|
7235
7235
|
'cli.command.upload.vibeWeb': 'Authorize in the browser, then upload to PPE (--vibe; no Mock or --no-open)',
|
|
7236
|
+
'cli.command.game.desc': 'Inspect game metadata supported by the platform',
|
|
7237
|
+
'cli.command.game.categories.desc': 'List game categories and subcategories with gameplay descriptions (offline, read-only)',
|
|
7238
|
+
'cli.command.game.categories.category': 'Filter by an exact category value, e.g. Puzzle',
|
|
7239
|
+
'cli.command.game.categories.unknown': 'Unknown category. Run ttmg game categories --format json for valid values.',
|
|
7240
|
+
'cli.command.game.categories.invalidFormat': 'Use text, json, or ndjson output.',
|
|
7241
|
+
'cli.command.upload.vibeCategory': 'Optional game category; provide with --subcategory (--vibe). Query ttmg game categories --format json',
|
|
7242
|
+
'cli.command.upload.vibeSubcategory': 'Optional subcategory belonging to --category; omit both to keep Other/Other (--vibe)',
|
|
7243
|
+
'cli.command.upload.vibeWebGuide': `
|
|
7244
|
+
Vibe web upload (real PPE trial):
|
|
7245
|
+
ttmg upload --vibe --web --dir ./game --title "My game" --icon ./avatar.png --format ndjson
|
|
7246
|
+
Use --package ./game.zip instead of --dir for an existing ZIP.
|
|
7247
|
+
Collect only title and icon; the CLI fills other app_info fields with PPE trial defaults.
|
|
7248
|
+
Skill may query ttmg game categories --format json and pass --category Puzzle --subcategory Physics together.
|
|
7249
|
+
Omit both to keep Other/Other (or the legacy metadata-file classification). Invalid pairs fail before task creation.
|
|
7250
|
+
prepared and terminal events expose the submitted classification and its source.
|
|
7251
|
+
Keep this process running. At waiting_authorization, complete authorization in the system browser on the CLI computer.
|
|
7252
|
+
pageUrl is the local binding/progress page. To reopen authorization, execute the returned authorizationOpenCommand.
|
|
7253
|
+
The CLI polls and uploads avatar, app_info and package automatically after authorization. Do not start a second upload.
|
|
7254
|
+
Real success: terminal=true, status=uploaded, uploaded=true, simulation=false. On failure, read errorCode and nextAction.
|
|
7255
|
+
--web opens the browser for every output format. --mock is a separate simulation mode.
|
|
7256
|
+
Upload completion is not review approval or production release. See bundled vibe-upload.md and ttmg capabilities --format json.
|
|
7257
|
+
`,
|
|
7236
7258
|
'cli.command.auth.open.pageUrl': 'Local pageUrl returned by the running Vibe upload command',
|
|
7237
7259
|
'cli.command.auth.open.requested': 'Browser open requested. Complete authorization there; the existing upload process continues polling.',
|
|
7238
7260
|
'cli.command.auth.open.invalidPage': 'Pass the local pageUrl from the running Vibe upload command, not the platform URL.',
|
|
@@ -7646,6 +7668,28 @@ const messages = {
|
|
|
7646
7668
|
'cli.command.auth.desc': '查看本地登录状态',
|
|
7647
7669
|
'cli.command.auth.open.desc': '在系统浏览器打开已有 Vibe 任务的授权页',
|
|
7648
7670
|
'cli.command.upload.vibeWeb': '网页授权就绪后自动上传到 PPE(--vibe;不能与 Mock 或 --no-open 同用)',
|
|
7671
|
+
'cli.command.game.desc': '查询平台支持的游戏资料',
|
|
7672
|
+
'cli.command.game.categories.desc': '查询游戏类型、子类型及玩法说明(离线、只读)',
|
|
7673
|
+
'cli.command.game.categories.category': '按类型的精确值筛选,例如 Puzzle',
|
|
7674
|
+
'cli.command.game.categories.unknown': '类型不存在。使用 ttmg game categories --format json 查询有效值。',
|
|
7675
|
+
'cli.command.game.categories.invalidFormat': '输出格式请使用 text、json 或 ndjson。',
|
|
7676
|
+
'cli.command.upload.vibeCategory': '可选游戏类型,须与 --subcategory 同传(--vibe);用 ttmg game categories --format json 查询',
|
|
7677
|
+
'cli.command.upload.vibeSubcategory': '所属类型的可选子类型;两者均不传保留 Other/Other(--vibe)',
|
|
7678
|
+
'cli.command.upload.vibeWebGuide': `
|
|
7679
|
+
Vibe 网页上传(真实 PPE 试用):
|
|
7680
|
+
ttmg upload --vibe --web --dir ./game --title "游戏名称" --icon ./avatar.png --format ndjson
|
|
7681
|
+
已有 ZIP 时,用 --package ./game.zip 替换 --dir。
|
|
7682
|
+
只收集标题和头像;其他 app_info 字段由 CLI 使用 PPE 试用默认值补齐。
|
|
7683
|
+
Skill 可先查 ttmg game categories --format json,再成对传入 --category Puzzle --subcategory Physics。
|
|
7684
|
+
两者均不传保留 Other/Other(旧资料文件调用保留文件分类);无效组合在创建任务前报错。
|
|
7685
|
+
prepared 和终态事件返回最终 classification 及其来源。
|
|
7686
|
+
保持原进程运行。在 waiting_authorization 阶段,在 CLI 所在电脑的系统浏览器完成授权。
|
|
7687
|
+
pageUrl 是本机绑定/进度页;重新打开授权页时,执行返回的 authorizationOpenCommand。
|
|
7688
|
+
CLI 负责轮询,授权后自动上传 avatar、app_info、package,不再启动第二次上传。
|
|
7689
|
+
真实成功:terminal=true、status=uploaded、uploaded=true、simulation=false。失败时读取 errorCode 和 nextAction。
|
|
7690
|
+
--web 在所有输出格式下都会打开浏览器;--mock 是独立的模拟模式。
|
|
7691
|
+
上传完成不代表审核通过或正式上线。详见包内 vibe-upload.md 和 ttmg capabilities --format json。
|
|
7692
|
+
`,
|
|
7649
7693
|
'cli.command.auth.open.pageUrl': '正在运行的 Vibe 上传命令返回的本地 pageUrl',
|
|
7650
7694
|
'cli.command.auth.open.requested': '已请求浏览器打开授权页。请在页面完成授权,原上传进程继续轮询。',
|
|
7651
7695
|
'cli.command.auth.open.invalidPage': '请传入正在运行的 Vibe 上传命令返回的本地 pageUrl,不是平台授权链接。',
|
|
@@ -16308,7 +16352,7 @@ async function authStatus(options = {}) {
|
|
|
16308
16352
|
return result;
|
|
16309
16353
|
}
|
|
16310
16354
|
|
|
16311
|
-
var version = "0.4.6-vibe-beta.
|
|
16355
|
+
var version = "0.4.6-vibe-beta.14";
|
|
16312
16356
|
var engines = {
|
|
16313
16357
|
node: ">=20.19.1 <21 || >=22"
|
|
16314
16358
|
};
|
|
@@ -16324,6 +16368,10 @@ const VIBE_INPUT_REQUIREMENTS = {
|
|
|
16324
16368
|
additionalMetadataSource: 'cli-defaults',
|
|
16325
16369
|
defaultMetadataProfile: 'ppe-trial',
|
|
16326
16370
|
requestAdditionalMetadata: false,
|
|
16371
|
+
optionalSkillInputs: ['category', 'subcategory'],
|
|
16372
|
+
categoryQuery: 'ttmg game categories --format json',
|
|
16373
|
+
categoryPairRequiredWhenProvided: true,
|
|
16374
|
+
defaultCategory: { category: 'Other', subcategory: 'Other' },
|
|
16327
16375
|
};
|
|
16328
16376
|
class VibeError extends Error {
|
|
16329
16377
|
constructor(code) {
|
|
@@ -16332,6 +16380,703 @@ class VibeError extends Error {
|
|
|
16332
16380
|
}
|
|
16333
16381
|
}
|
|
16334
16382
|
|
|
16383
|
+
var catalogVersion = "sha256:f1d67b08cfbfb9396cba1ec246a84a4c1b6b2c9f077e236498c42e9c154b6900";
|
|
16384
|
+
var source = "bundled";
|
|
16385
|
+
var categories$1 = [
|
|
16386
|
+
{
|
|
16387
|
+
value: "Action",
|
|
16388
|
+
subcategories: [
|
|
16389
|
+
{
|
|
16390
|
+
value: "Artillery Shooter",
|
|
16391
|
+
description: "Turn based tactics game in which players aim and shoot other players"
|
|
16392
|
+
},
|
|
16393
|
+
{
|
|
16394
|
+
value: "Asymmetric Battle",
|
|
16395
|
+
description: "Game in which different players have different roles and capabilities as well as win condition (e.g. Identity 5)"
|
|
16396
|
+
},
|
|
16397
|
+
{
|
|
16398
|
+
value: "Brawl",
|
|
16399
|
+
description: "Multiplayer battle where player controls a character in team deathmatch battle without aiming a gun"
|
|
16400
|
+
},
|
|
16401
|
+
{
|
|
16402
|
+
value: "Fighting",
|
|
16403
|
+
description: "A game where the player controls a character to fight against other characters"
|
|
16404
|
+
},
|
|
16405
|
+
{
|
|
16406
|
+
value: "MOBA",
|
|
16407
|
+
description: "Multiplayer battle arena where the player controls a character on a team to destroy an enemy base"
|
|
16408
|
+
},
|
|
16409
|
+
{
|
|
16410
|
+
value: "Music & Rhythm",
|
|
16411
|
+
description: "Games that feature music as the primary gameplay driver"
|
|
16412
|
+
},
|
|
16413
|
+
{
|
|
16414
|
+
value: "Open World",
|
|
16415
|
+
description: "Action game in which players can explore the world and attempt objectives freely"
|
|
16416
|
+
},
|
|
16417
|
+
{
|
|
16418
|
+
value: "Platformer",
|
|
16419
|
+
description: "A game where players complete stages by avoiding obstacles and jumping between platforms"
|
|
16420
|
+
},
|
|
16421
|
+
{
|
|
16422
|
+
value: "Royale Battle",
|
|
16423
|
+
description: "Battle royale game (many players compete but with a single winner) that is action based and not a shooter"
|
|
16424
|
+
},
|
|
16425
|
+
{
|
|
16426
|
+
value: "Run & Gun Platformer",
|
|
16427
|
+
description: "Action games similar to side-scrolling platformers, but players are typically battling an onslaught of enemies appearing in front or behind them"
|
|
16428
|
+
},
|
|
16429
|
+
{
|
|
16430
|
+
value: "Runner",
|
|
16431
|
+
description: "A game where the player automatically progresses and must avoid obstacles to continue"
|
|
16432
|
+
},
|
|
16433
|
+
{
|
|
16434
|
+
value: "Shark",
|
|
16435
|
+
description: "Game in which the player eats other objects in the game to grow or stay alive"
|
|
16436
|
+
},
|
|
16437
|
+
{
|
|
16438
|
+
value: "Shoot'em Up",
|
|
16439
|
+
description: "A game where a lone unit (usually a spaceship) shoots projectiles against a large number of other enemies. Generally in a 2D movement space"
|
|
16440
|
+
},
|
|
16441
|
+
{
|
|
16442
|
+
value: "Slicing (Slasher)",
|
|
16443
|
+
description: "Game in which the main objective is to cut objects into pieces using a swiping motion"
|
|
16444
|
+
},
|
|
16445
|
+
{
|
|
16446
|
+
value: "Stealth",
|
|
16447
|
+
description: "Game where the player attempts to sneak around a game world and achieve objectives or escape"
|
|
16448
|
+
},
|
|
16449
|
+
{
|
|
16450
|
+
value: "Adventure",
|
|
16451
|
+
description: "Focuses on the actions themselves (shooting, twitch combat, etc.) or exploration of the world instead (finding non-gameplay collectables, solving puzzles, etc.) - Super Mario"
|
|
16452
|
+
},
|
|
16453
|
+
{
|
|
16454
|
+
value: "Beat 'em up",
|
|
16455
|
+
description: "Featuring hand-to-hand combat between the protagonist and an improbably large number of opponents."
|
|
16456
|
+
},
|
|
16457
|
+
{
|
|
16458
|
+
value: "Other Action",
|
|
16459
|
+
description: "Other general action gameplay or games that feature arcade-style gameplay"
|
|
16460
|
+
}
|
|
16461
|
+
]
|
|
16462
|
+
},
|
|
16463
|
+
{
|
|
16464
|
+
value: "Match",
|
|
16465
|
+
subcategories: [
|
|
16466
|
+
{
|
|
16467
|
+
value: "Blast-Meta",
|
|
16468
|
+
description: "Blast-style game with a heavy emphasis on an investable meta game system"
|
|
16469
|
+
},
|
|
16470
|
+
{
|
|
16471
|
+
value: "Blast-Saga",
|
|
16472
|
+
description: "Game where primary mechanic involves blasting/removing adjacent like items with saga based progression"
|
|
16473
|
+
},
|
|
16474
|
+
{
|
|
16475
|
+
value: "Bubble Shooter",
|
|
16476
|
+
description: "Game where bubbles are shot at different angles clearing groups of bubbles arranged on top of screen"
|
|
16477
|
+
},
|
|
16478
|
+
{
|
|
16479
|
+
value: "Connect Tiles",
|
|
16480
|
+
description: "Game in which two like tiles are linked via a pathway on a board and removed from the board"
|
|
16481
|
+
},
|
|
16482
|
+
{
|
|
16483
|
+
value: "Line-Saga",
|
|
16484
|
+
description: "Game where a line is created between like items to remove them from the board with saga based progression"
|
|
16485
|
+
},
|
|
16486
|
+
{
|
|
16487
|
+
value: "M3-Meta",
|
|
16488
|
+
description: "Match 3 swapping game with a focus on some form of building in its meta game"
|
|
16489
|
+
},
|
|
16490
|
+
{
|
|
16491
|
+
value: "M3-Saga",
|
|
16492
|
+
description: "Primary gameplay mechanism is matching 3 items on screen and swapping matched items"
|
|
16493
|
+
},
|
|
16494
|
+
{
|
|
16495
|
+
value: "Merge-Saga",
|
|
16496
|
+
description: "Game with focus on merging like items to create more powerful units or items and utilizing saga based progression"
|
|
16497
|
+
},
|
|
16498
|
+
{
|
|
16499
|
+
value: "Other-Meta",
|
|
16500
|
+
description: "Other match style game with a heavy emphasis on an investable meta game system"
|
|
16501
|
+
},
|
|
16502
|
+
{
|
|
16503
|
+
value: "Other Match",
|
|
16504
|
+
description: "Other kind of matching game"
|
|
16505
|
+
}
|
|
16506
|
+
]
|
|
16507
|
+
},
|
|
16508
|
+
{
|
|
16509
|
+
value: "Puzzle",
|
|
16510
|
+
subcategories: [
|
|
16511
|
+
{
|
|
16512
|
+
value: "Block Puzzle",
|
|
16513
|
+
description: "Game in which players need to fit different shaped block pieces to fit onto a game board efficiently"
|
|
16514
|
+
},
|
|
16515
|
+
{
|
|
16516
|
+
value: "Brain Puzzle",
|
|
16517
|
+
description: "Knowledge or intelligence-based puzzle games"
|
|
16518
|
+
},
|
|
16519
|
+
{
|
|
16520
|
+
value: "Brick-crusher",
|
|
16521
|
+
description: "A bubble shooter game featuring many balls shot, high action, and real time (not turn based) gameplay"
|
|
16522
|
+
},
|
|
16523
|
+
{
|
|
16524
|
+
value: "Detective",
|
|
16525
|
+
description: "Game in which players must solve puzzles based on various clues and involves solving a narrative arc"
|
|
16526
|
+
},
|
|
16527
|
+
{
|
|
16528
|
+
value: "Hidden Object",
|
|
16529
|
+
description: "Games where the primary objective is identifying visual elements"
|
|
16530
|
+
},
|
|
16531
|
+
{
|
|
16532
|
+
value: "Hybrid Puzzle",
|
|
16533
|
+
description: "Puzzle game involving multiple types of puzzles instead of focusing on just one"
|
|
16534
|
+
},
|
|
16535
|
+
{
|
|
16536
|
+
value: "Jigsaw",
|
|
16537
|
+
description: "Jigsaw puzzle game"
|
|
16538
|
+
},
|
|
16539
|
+
{
|
|
16540
|
+
value: "Klotski",
|
|
16541
|
+
description: "A sliding block puzzle game"
|
|
16542
|
+
},
|
|
16543
|
+
{
|
|
16544
|
+
value: "Maze",
|
|
16545
|
+
description: "Game in which players must determine paths out of a maze"
|
|
16546
|
+
},
|
|
16547
|
+
{
|
|
16548
|
+
value: "Minesweeper",
|
|
16549
|
+
description: "Games similar to the classic Minesweeper game"
|
|
16550
|
+
},
|
|
16551
|
+
{
|
|
16552
|
+
value: "Nonogram",
|
|
16553
|
+
description: "Game in which cells in a grid are colored or left blank according to numbers on the side of a grid to reveal a hidden picture"
|
|
16554
|
+
},
|
|
16555
|
+
{
|
|
16556
|
+
value: "Physics",
|
|
16557
|
+
description: "Games that utilize a physics engine-based primary gameplay mechanism"
|
|
16558
|
+
},
|
|
16559
|
+
{
|
|
16560
|
+
value: "Room Escape",
|
|
16561
|
+
description: "Puzzles focused on progressing a player to escape from a room or building"
|
|
16562
|
+
},
|
|
16563
|
+
{
|
|
16564
|
+
value: "Spot the Difference",
|
|
16565
|
+
description: "Matching images and determining the difference between the images"
|
|
16566
|
+
},
|
|
16567
|
+
{
|
|
16568
|
+
value: "Sudoku",
|
|
16569
|
+
description: "Sudoku based game"
|
|
16570
|
+
},
|
|
16571
|
+
{
|
|
16572
|
+
value: "Tile-Matching",
|
|
16573
|
+
description: "Game in which tiles are matched to remove them from the game board (like Tetris)"
|
|
16574
|
+
},
|
|
16575
|
+
{
|
|
16576
|
+
value: "Word",
|
|
16577
|
+
description: "Word Games"
|
|
16578
|
+
},
|
|
16579
|
+
{
|
|
16580
|
+
value: "Other Puzzle",
|
|
16581
|
+
description: "Puzzle-based games that do not fall into the above categories"
|
|
16582
|
+
}
|
|
16583
|
+
]
|
|
16584
|
+
},
|
|
16585
|
+
{
|
|
16586
|
+
value: "Racing",
|
|
16587
|
+
subcategories: [
|
|
16588
|
+
{
|
|
16589
|
+
value: "Arcade Racing",
|
|
16590
|
+
description: "Casual and fun racing arcade type of game"
|
|
16591
|
+
},
|
|
16592
|
+
{
|
|
16593
|
+
value: "Competitive Racing",
|
|
16594
|
+
description: "PVP Racing that can use a variety of control schemes"
|
|
16595
|
+
},
|
|
16596
|
+
{
|
|
16597
|
+
value: "Simulation Racing",
|
|
16598
|
+
description: "Simulated game of realistic racing"
|
|
16599
|
+
}
|
|
16600
|
+
]
|
|
16601
|
+
},
|
|
16602
|
+
{
|
|
16603
|
+
value: "RPG",
|
|
16604
|
+
subcategories: [
|
|
16605
|
+
{
|
|
16606
|
+
value: "Empire Simulation RPG",
|
|
16607
|
+
description: "RPG game in which the player rules an empire or kingdom of some kind"
|
|
16608
|
+
},
|
|
16609
|
+
{
|
|
16610
|
+
value: "Idle RPG",
|
|
16611
|
+
description: "Squad based RPG that includes idle progression mechanics"
|
|
16612
|
+
},
|
|
16613
|
+
{
|
|
16614
|
+
value: "Location RPG",
|
|
16615
|
+
description: "Game where location plays a critical component in the core gameplay"
|
|
16616
|
+
},
|
|
16617
|
+
{
|
|
16618
|
+
value: "MMORPG",
|
|
16619
|
+
description: "Massively multiplayer online role-playing game typically featuring worlds or servers with hundreds or thousands of players online simultaneously"
|
|
16620
|
+
},
|
|
16621
|
+
{
|
|
16622
|
+
value: "Open World RPG",
|
|
16623
|
+
description: "RPG game where players traverse an open world relatively freely"
|
|
16624
|
+
},
|
|
16625
|
+
{
|
|
16626
|
+
value: "Puzzle RPG",
|
|
16627
|
+
description: "RPG games that utilize a puzzle system as the primary combat mechanism"
|
|
16628
|
+
},
|
|
16629
|
+
{
|
|
16630
|
+
value: "Roguelike ARPG",
|
|
16631
|
+
description: "ARPG with roguelike (sub-genre of role-playing games characterized by the exploration of randomly-generated dungeons and permanent death) gameplay elements (e.g. Hades)"
|
|
16632
|
+
},
|
|
16633
|
+
{
|
|
16634
|
+
value: "Survival",
|
|
16635
|
+
description: "RPG based game with a strong focus on crafting mechanics"
|
|
16636
|
+
},
|
|
16637
|
+
{
|
|
16638
|
+
value: "Tactics",
|
|
16639
|
+
description: "RPG game involving turn based movement of different characters on a tile-based map"
|
|
16640
|
+
},
|
|
16641
|
+
{
|
|
16642
|
+
value: "Team Battle",
|
|
16643
|
+
description: "RPG where you control multiple characters to advance the gameplay and story"
|
|
16644
|
+
},
|
|
16645
|
+
{
|
|
16646
|
+
value: "Tower Defense RPG",
|
|
16647
|
+
description: "Tower defense game with a strong emphasis on character collection and stateful upgrading"
|
|
16648
|
+
},
|
|
16649
|
+
{
|
|
16650
|
+
value: "Other RPG",
|
|
16651
|
+
description: "Other RPG Game"
|
|
16652
|
+
}
|
|
16653
|
+
]
|
|
16654
|
+
},
|
|
16655
|
+
{
|
|
16656
|
+
value: "Shooting",
|
|
16657
|
+
subcategories: [
|
|
16658
|
+
{
|
|
16659
|
+
value: "Battle Royale",
|
|
16660
|
+
description: "Shooter game in which battle royale is the primary game mode"
|
|
16661
|
+
},
|
|
16662
|
+
{
|
|
16663
|
+
value: "Cover-fire",
|
|
16664
|
+
description: "Shooting game where the player takes cover and shoots out of cover"
|
|
16665
|
+
},
|
|
16666
|
+
{
|
|
16667
|
+
value: "PvE Shooting",
|
|
16668
|
+
description: "Shooter game in which players primarily battle against computer generated, player-vs-environment enemies"
|
|
16669
|
+
},
|
|
16670
|
+
{
|
|
16671
|
+
value: "Sniper",
|
|
16672
|
+
description: "Shooting game focused on small number of accurate shots through a scope"
|
|
16673
|
+
},
|
|
16674
|
+
{
|
|
16675
|
+
value: "Team Deathmatch",
|
|
16676
|
+
description: "Shooter game in which team deathmatch is the primary gameplay mode"
|
|
16677
|
+
}
|
|
16678
|
+
]
|
|
16679
|
+
},
|
|
16680
|
+
{
|
|
16681
|
+
value: "Simulation",
|
|
16682
|
+
subcategories: [
|
|
16683
|
+
{
|
|
16684
|
+
value: "Animal Avatar",
|
|
16685
|
+
description: "Game in which players take the form and play as an animal in a virtual world"
|
|
16686
|
+
},
|
|
16687
|
+
{
|
|
16688
|
+
value: "Avatar Life",
|
|
16689
|
+
description: "Simulated life based on avatars in an alternate reality world"
|
|
16690
|
+
},
|
|
16691
|
+
{
|
|
16692
|
+
value: "Build Breed",
|
|
16693
|
+
description: "Games in which the player grows a city used to breed creatures for combat"
|
|
16694
|
+
},
|
|
16695
|
+
{
|
|
16696
|
+
value: "Building",
|
|
16697
|
+
description: "Games focused on building up a city with limited/no focus on combat"
|
|
16698
|
+
},
|
|
16699
|
+
{
|
|
16700
|
+
value: "Creative Sandbox",
|
|
16701
|
+
description: "Players have tools to create user generated levels or games shared with the overall game community"
|
|
16702
|
+
},
|
|
16703
|
+
{
|
|
16704
|
+
value: "Dress Up & Lifestyle",
|
|
16705
|
+
description: "Games built around dressing up avatars and simulated life-like experiences with emphasis on lifestyle"
|
|
16706
|
+
},
|
|
16707
|
+
{
|
|
16708
|
+
value: "Driving",
|
|
16709
|
+
description: "Games where the primary gameplay loop is centered around simulating driving or parking (non-competitive)."
|
|
16710
|
+
},
|
|
16711
|
+
{
|
|
16712
|
+
value: "Farming",
|
|
16713
|
+
description: "Farming-based simulation titles"
|
|
16714
|
+
},
|
|
16715
|
+
{
|
|
16716
|
+
value: "Home Design",
|
|
16717
|
+
description: "Games involving decorating homes"
|
|
16718
|
+
},
|
|
16719
|
+
{
|
|
16720
|
+
value: "Idle",
|
|
16721
|
+
description: "Games where game progression continues even when a player leaves the game session"
|
|
16722
|
+
},
|
|
16723
|
+
{
|
|
16724
|
+
value: "Idol Training Sim",
|
|
16725
|
+
description: "Game in which players train and level up Japanese style idols against different types of activities or professions"
|
|
16726
|
+
},
|
|
16727
|
+
{
|
|
16728
|
+
value: "Life Simulator",
|
|
16729
|
+
description: "Simulation of a virtual character from birth to death in which the player makes different life choices"
|
|
16730
|
+
},
|
|
16731
|
+
{
|
|
16732
|
+
value: "Manager",
|
|
16733
|
+
description: "Games simulating the management of a team or business"
|
|
16734
|
+
},
|
|
16735
|
+
{
|
|
16736
|
+
value: "Pet",
|
|
16737
|
+
description: "Games where the primary gameplay loop is centered around raising a pet (non-combat)"
|
|
16738
|
+
},
|
|
16739
|
+
{
|
|
16740
|
+
value: "Time Management",
|
|
16741
|
+
description: "Simulation games where players are challenged to complete tasks or quests within a specific (usually short) time frame"
|
|
16742
|
+
},
|
|
16743
|
+
{
|
|
16744
|
+
value: "Weapon Simulator",
|
|
16745
|
+
description: "Video game that focuses on simulating the experience of using various weapons. These games aim to provide players with a realistic or immersive representation of different types of firearms, melee weapons, or other tools used in combat"
|
|
16746
|
+
},
|
|
16747
|
+
{
|
|
16748
|
+
value: "Other Sim",
|
|
16749
|
+
description: "Other Sim/Management games"
|
|
16750
|
+
}
|
|
16751
|
+
]
|
|
16752
|
+
},
|
|
16753
|
+
{
|
|
16754
|
+
value: "Sports",
|
|
16755
|
+
subcategories: [
|
|
16756
|
+
{
|
|
16757
|
+
value: "Arcade Sports",
|
|
16758
|
+
description: "Sports games where gameplay focuses on non-realistic simulation of gameplay"
|
|
16759
|
+
},
|
|
16760
|
+
{
|
|
16761
|
+
value: "Simulation Sports",
|
|
16762
|
+
description: "Sports games where the gameplay focuses on realistic simulation of gameplay"
|
|
16763
|
+
},
|
|
16764
|
+
{
|
|
16765
|
+
value: "Team Manager",
|
|
16766
|
+
description: "Sports games where the player focuses on manage a team"
|
|
16767
|
+
},
|
|
16768
|
+
{
|
|
16769
|
+
value: "Other Sports",
|
|
16770
|
+
description: "Other Sports Games"
|
|
16771
|
+
}
|
|
16772
|
+
]
|
|
16773
|
+
},
|
|
16774
|
+
{
|
|
16775
|
+
value: "Strategy",
|
|
16776
|
+
subcategories: [
|
|
16777
|
+
{
|
|
16778
|
+
value: "4X March-Battle",
|
|
16779
|
+
description: "Games where player grows a city, builds an army that attacks through timed marches on a map"
|
|
16780
|
+
},
|
|
16781
|
+
{
|
|
16782
|
+
value: "Auto Chess",
|
|
16783
|
+
description: "Tactical strategy game with drafting and in which play is conducted PVP in multiple rounds"
|
|
16784
|
+
},
|
|
16785
|
+
{
|
|
16786
|
+
value: "Build-Battle",
|
|
16787
|
+
description: "Games focused on building troops that attack enemies and can be lost. City building is usually used to upgrade troops."
|
|
16788
|
+
},
|
|
16789
|
+
{
|
|
16790
|
+
value: "CCG-Battle",
|
|
16791
|
+
description: "Strategy game based on card decks typically associated with mana costs to play"
|
|
16792
|
+
},
|
|
16793
|
+
{
|
|
16794
|
+
value: "Lane-Battle",
|
|
16795
|
+
description: "Strategy game involving multiple lanes where units are sent down a specific lane to battle"
|
|
16796
|
+
},
|
|
16797
|
+
{
|
|
16798
|
+
value: "Legion Battle",
|
|
16799
|
+
description: "Game with a focus on building different types of army units and battling other players' armies"
|
|
16800
|
+
},
|
|
16801
|
+
{
|
|
16802
|
+
value: "Summon-Battle",
|
|
16803
|
+
description: "Games where units are summoned via some means (e.g., cards or mana) and then auto-battle"
|
|
16804
|
+
},
|
|
16805
|
+
{
|
|
16806
|
+
value: "Tower Defense Strategy",
|
|
16807
|
+
description: "Games where players establish defenses against NPC enemies that attack the player at regular intervals"
|
|
16808
|
+
},
|
|
16809
|
+
{
|
|
16810
|
+
value: "RTS",
|
|
16811
|
+
description: "Real-time Strategy is a strategy game that allow all users to play simultaneously instead of processing incrementally in turns"
|
|
16812
|
+
},
|
|
16813
|
+
{
|
|
16814
|
+
value: "Other Strategy",
|
|
16815
|
+
description: "Other strategy games"
|
|
16816
|
+
}
|
|
16817
|
+
]
|
|
16818
|
+
},
|
|
16819
|
+
{
|
|
16820
|
+
value: "Party",
|
|
16821
|
+
subcategories: [
|
|
16822
|
+
{
|
|
16823
|
+
value: "Guess Something",
|
|
16824
|
+
description: "Social game in which players must guess what another player has in mind from different kinds of hints or drawings"
|
|
16825
|
+
},
|
|
16826
|
+
{
|
|
16827
|
+
value: "Karaoke",
|
|
16828
|
+
description: "A Karaoke game is a type of video or computer game that simulates the experience of karaoke singing"
|
|
16829
|
+
},
|
|
16830
|
+
{
|
|
16831
|
+
value: "Dancing",
|
|
16832
|
+
description: "Video game that involves gameplay elements centered around dance and rhythm. These games often require players to mimic dance moves or follow on-screen prompts in synchronization with music."
|
|
16833
|
+
},
|
|
16834
|
+
{
|
|
16835
|
+
value: "Mafia/Betrayal",
|
|
16836
|
+
description: "Mafia based game"
|
|
16837
|
+
},
|
|
16838
|
+
{
|
|
16839
|
+
value: "Mini Games Collection",
|
|
16840
|
+
description: "Multiplayer social game in which players can choose to play from a collection of multiple minigames"
|
|
16841
|
+
},
|
|
16842
|
+
{
|
|
16843
|
+
value: "Party Royale",
|
|
16844
|
+
description: "Party based battle royale game"
|
|
16845
|
+
},
|
|
16846
|
+
{
|
|
16847
|
+
value: "Quiz",
|
|
16848
|
+
description: "Game involving trivia or quizzes"
|
|
16849
|
+
},
|
|
16850
|
+
{
|
|
16851
|
+
value: "Other Party",
|
|
16852
|
+
description: "Other party game"
|
|
16853
|
+
}
|
|
16854
|
+
]
|
|
16855
|
+
},
|
|
16856
|
+
{
|
|
16857
|
+
value: "Tabletop",
|
|
16858
|
+
subcategories: [
|
|
16859
|
+
{
|
|
16860
|
+
value: "Board Game",
|
|
16861
|
+
description: "Board game is a game that involves the movement of counters or other objects round a board."
|
|
16862
|
+
},
|
|
16863
|
+
{
|
|
16864
|
+
value: "Card Games",
|
|
16865
|
+
description: "[Non gambling] A card game is any game using playing cards as the primary device with which the game is played, be they traditional or game-specific"
|
|
16866
|
+
},
|
|
16867
|
+
{
|
|
16868
|
+
value: "Chess",
|
|
16869
|
+
description: "Chess is a board game for two players, called White and Black. It is sometimes called international chess or Western chess"
|
|
16870
|
+
},
|
|
16871
|
+
{
|
|
16872
|
+
value: "Dice",
|
|
16873
|
+
description: "Dice games are games that use or incorporate one or more dice as their sole or central component, usually as a random device"
|
|
16874
|
+
},
|
|
16875
|
+
{
|
|
16876
|
+
value: "Ludo",
|
|
16877
|
+
description: "Ludo is a strategy board game for two to four players, in which the players race their four tokens from start to finish according to the rolls of a single die"
|
|
16878
|
+
},
|
|
16879
|
+
{
|
|
16880
|
+
value: "Othello",
|
|
16881
|
+
description: "Game based on the classic Othello game"
|
|
16882
|
+
},
|
|
16883
|
+
{
|
|
16884
|
+
value: "Solitaire",
|
|
16885
|
+
description: "Solitaire games are card based games that can be played by a single player."
|
|
16886
|
+
},
|
|
16887
|
+
{
|
|
16888
|
+
value: "Other Tabletop",
|
|
16889
|
+
description: "Other tabletop game"
|
|
16890
|
+
}
|
|
16891
|
+
]
|
|
16892
|
+
},
|
|
16893
|
+
{
|
|
16894
|
+
value: "Other",
|
|
16895
|
+
subcategories: [
|
|
16896
|
+
{
|
|
16897
|
+
value: "Other",
|
|
16898
|
+
description: "Game that doesn't fit in any of the other categories"
|
|
16899
|
+
}
|
|
16900
|
+
]
|
|
16901
|
+
}
|
|
16902
|
+
];
|
|
16903
|
+
var catalog = {
|
|
16904
|
+
catalogVersion: catalogVersion,
|
|
16905
|
+
source: source,
|
|
16906
|
+
categories: categories$1
|
|
16907
|
+
};
|
|
16908
|
+
|
|
16909
|
+
const GAME_CATEGORY_CATALOG_VERSION = catalog.catalogVersion;
|
|
16910
|
+
const GAME_CATEGORIES = catalog.categories;
|
|
16911
|
+
function isGameCategoryPair(category, subcategory) {
|
|
16912
|
+
return GAME_CATEGORIES.some(item => item.value === category &&
|
|
16913
|
+
item.subcategories.some(child => child.value === subcategory));
|
|
16914
|
+
}
|
|
16915
|
+
function getGameCategories(options = {}) {
|
|
16916
|
+
const errorCode = !['text', 'json', 'ndjson'].includes(options.format || 'text')
|
|
16917
|
+
? 'GAME_CATEGORIES_INVALID_FORMAT'
|
|
16918
|
+
: options.category !== undefined && !GAME_CATEGORIES.some(item => item.value === options.category)
|
|
16919
|
+
? 'GAME_CATEGORY_UNKNOWN' : null;
|
|
16920
|
+
const categories = errorCode ? [] : GAME_CATEGORIES.filter(item => options.category === undefined || item.value === options.category);
|
|
16921
|
+
return {
|
|
16922
|
+
schemaVersion: '1',
|
|
16923
|
+
command: 'game categories',
|
|
16924
|
+
operationId: `game_categories_${crypto.randomUUID()}`,
|
|
16925
|
+
status: errorCode ? 'failed' : 'ready',
|
|
16926
|
+
terminal: true,
|
|
16927
|
+
platformWrite: false,
|
|
16928
|
+
catalogVersion: GAME_CATEGORY_CATALOG_VERSION,
|
|
16929
|
+
source: catalog.source,
|
|
16930
|
+
descriptionLanguage: 'en',
|
|
16931
|
+
categoryCount: categories.length,
|
|
16932
|
+
subcategoryCount: categories.reduce((count, item) => count + item.subcategories.length, 0),
|
|
16933
|
+
categories,
|
|
16934
|
+
errorCode,
|
|
16935
|
+
error: errorCode ? t(errorCode === 'GAME_CATEGORY_UNKNOWN'
|
|
16936
|
+
? 'cli.command.game.categories.unknown' : 'cli.command.game.categories.invalidFormat') : null,
|
|
16937
|
+
};
|
|
16938
|
+
}
|
|
16939
|
+
function gameCategories(options) {
|
|
16940
|
+
const result = getGameCategories(options);
|
|
16941
|
+
if (options.format && options.format !== 'text')
|
|
16942
|
+
console.log(JSON.stringify(result));
|
|
16943
|
+
else if (result.error)
|
|
16944
|
+
console.error(result.error);
|
|
16945
|
+
else
|
|
16946
|
+
for (const category of result.categories) {
|
|
16947
|
+
console.log(category.value);
|
|
16948
|
+
for (const item of category.subcategories)
|
|
16949
|
+
console.log(` ${item.value}: ${item.description}`);
|
|
16950
|
+
}
|
|
16951
|
+
if (result.errorCode)
|
|
16952
|
+
process.exitCode = 1;
|
|
16953
|
+
}
|
|
16954
|
+
|
|
16955
|
+
var categories = /*#__PURE__*/Object.freeze({
|
|
16956
|
+
__proto__: null,
|
|
16957
|
+
GAME_CATEGORIES: GAME_CATEGORIES,
|
|
16958
|
+
GAME_CATEGORY_CATALOG_VERSION: GAME_CATEGORY_CATALOG_VERSION,
|
|
16959
|
+
gameCategories: gameCategories,
|
|
16960
|
+
getGameCategories: getGameCategories,
|
|
16961
|
+
isGameCategoryPair: isGameCategoryPair
|
|
16962
|
+
});
|
|
16963
|
+
|
|
16964
|
+
// Current PPE trial defaults: the owner-approved metadata used by the successful
|
|
16965
|
+
// real upload fixture on 2026-09-14, with owner-specified legal URLs updated on
|
|
16966
|
+
// 2026-09-15. This is not a production metadata profile.
|
|
16967
|
+
const VIBE_PPE_DEFAULT_APP_INFO = Object.freeze({
|
|
16968
|
+
description: 'DevTool PPE integration test fixture. Not a production game.',
|
|
16969
|
+
category: 'Other',
|
|
16970
|
+
subcategory: 'Other',
|
|
16971
|
+
terms_of_service: 'https://www.tiktok.com',
|
|
16972
|
+
privacy_policy: 'https://www.tiktok.com',
|
|
16973
|
+
copyright_confirmation: true,
|
|
16974
|
+
contact_email: 'devtool-test@example.invalid',
|
|
16975
|
+
});
|
|
16976
|
+
// BFF IDL + Import Service ValidateAppInfo, feat-minis-import (2026-09-14).
|
|
16977
|
+
// Keep credentials, app_name and icon_uri out of this file: CLI owns those fields.
|
|
16978
|
+
const requiredStrings = [
|
|
16979
|
+
'description', 'category', 'subcategory', 'terms_of_service', 'privacy_policy',
|
|
16980
|
+
];
|
|
16981
|
+
const optionalStrings = ['apple_team_id', 'contact_email', 'region', 'min_sdk_version'];
|
|
16982
|
+
function applyVibeCategory(options, appInfo) {
|
|
16983
|
+
if (options.category === undefined && options.subcategory === undefined)
|
|
16984
|
+
return appInfo;
|
|
16985
|
+
if (options.category === undefined || options.subcategory === undefined)
|
|
16986
|
+
throw new VibeError('VIBE_CATEGORY_PAIR_REQUIRED');
|
|
16987
|
+
if (!isGameCategoryPair(options.category, options.subcategory))
|
|
16988
|
+
throw new VibeError('VIBE_INVALID_CATEGORY');
|
|
16989
|
+
return { ...(appInfo || VIBE_PPE_DEFAULT_APP_INFO), category: options.category, subcategory: options.subcategory };
|
|
16990
|
+
}
|
|
16991
|
+
function readVibeAppInfo(file) {
|
|
16992
|
+
if (file === undefined)
|
|
16993
|
+
return undefined;
|
|
16994
|
+
try {
|
|
16995
|
+
const stat = fs.statSync(file);
|
|
16996
|
+
if (!stat.isFile() || stat.size > 64 * 1024)
|
|
16997
|
+
throw new Error();
|
|
16998
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
16999
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
17000
|
+
throw new Error();
|
|
17001
|
+
const allowed = new Set([...requiredStrings, ...optionalStrings, 'copyright_confirmation']);
|
|
17002
|
+
if (Object.keys(value).some(key => !allowed.has(key)))
|
|
17003
|
+
throw new Error();
|
|
17004
|
+
// JSON can encode lone surrogates that Go rejects as invalid UTF-8 input.
|
|
17005
|
+
for (const item of Object.values(value)) {
|
|
17006
|
+
if (typeof item === 'string' && [...item].some(char => /^[\uD800-\uDFFF]$/.test(char)))
|
|
17007
|
+
throw new Error();
|
|
17008
|
+
}
|
|
17009
|
+
for (const key of requiredStrings) {
|
|
17010
|
+
if (typeof value[key] !== 'string' || !value[key].trim())
|
|
17011
|
+
throw new Error();
|
|
17012
|
+
}
|
|
17013
|
+
for (const key of optionalStrings) {
|
|
17014
|
+
if (value[key] !== undefined && (typeof value[key] !== 'string' || !value[key].trim()))
|
|
17015
|
+
throw new Error();
|
|
17016
|
+
}
|
|
17017
|
+
// An explicit declaration is required; never infer consent or supply a default.
|
|
17018
|
+
if (value.copyright_confirmation !== true)
|
|
17019
|
+
throw new Error();
|
|
17020
|
+
for (const key of ['terms_of_service', 'privacy_policy']) {
|
|
17021
|
+
if (/\s/.test(value[key]))
|
|
17022
|
+
throw new Error();
|
|
17023
|
+
const url = new URL(value[key]);
|
|
17024
|
+
if (!['http:', 'https:'].includes(url.protocol) || !url.hostname || url.username || url.password)
|
|
17025
|
+
throw new Error();
|
|
17026
|
+
}
|
|
17027
|
+
if (value.contact_email !== undefined && !/^[^\s<>@]+@[^\s<>@]+$/.test(value.contact_email))
|
|
17028
|
+
throw new Error();
|
|
17029
|
+
return value;
|
|
17030
|
+
}
|
|
17031
|
+
catch {
|
|
17032
|
+
// Do not expose paths, file contents or parser errors in structured output.
|
|
17033
|
+
throw new VibeError('VIBE_INVALID_APP_INFO');
|
|
17034
|
+
}
|
|
17035
|
+
}
|
|
17036
|
+
const VIBE_PLATFORM_ICON_LIMIT = 5 * 1024 * 1024;
|
|
17037
|
+
// Header preflight only. The BFF remains responsible for decoding the image.
|
|
17038
|
+
function validateVibeUploadInput(input) {
|
|
17039
|
+
if ([...input.title].length > 30)
|
|
17040
|
+
throw new VibeError('VIBE_TITLE_TOO_LONG');
|
|
17041
|
+
const image = input.icon.bytes;
|
|
17042
|
+
if (image.length > VIBE_PLATFORM_ICON_LIMIT)
|
|
17043
|
+
throw new VibeError('VIBE_PLATFORM_ICON_INVALID');
|
|
17044
|
+
let width = 0, height = 0;
|
|
17045
|
+
if (image.length >= 33 && image.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex')) &&
|
|
17046
|
+
image.readUInt32BE(8) === 13 && image.toString('ascii', 12, 16) === 'IHDR') {
|
|
17047
|
+
width = image.readUInt32BE(16);
|
|
17048
|
+
height = image.readUInt32BE(20);
|
|
17049
|
+
}
|
|
17050
|
+
else if (image[0] === 0xff && image[1] === 0xd8) {
|
|
17051
|
+
let offset = 2;
|
|
17052
|
+
while (offset + 4 <= image.length) {
|
|
17053
|
+
if (image[offset++] !== 0xff)
|
|
17054
|
+
break;
|
|
17055
|
+
while (image[offset] === 0xff)
|
|
17056
|
+
offset++;
|
|
17057
|
+
const marker = image[offset++];
|
|
17058
|
+
if (marker === 0xda || marker === 0xd9)
|
|
17059
|
+
break;
|
|
17060
|
+
if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7))
|
|
17061
|
+
continue;
|
|
17062
|
+
if (offset + 2 > image.length)
|
|
17063
|
+
break;
|
|
17064
|
+
const length = image.readUInt16BE(offset);
|
|
17065
|
+
if (length < 2 || offset + length > image.length)
|
|
17066
|
+
break;
|
|
17067
|
+
// Go image/jpeg accepts baseline, extended sequential and progressive SOF.
|
|
17068
|
+
if ([0xc0, 0xc1, 0xc2].includes(marker) && length >= 8) {
|
|
17069
|
+
height = image.readUInt16BE(offset + 3);
|
|
17070
|
+
width = image.readUInt16BE(offset + 5);
|
|
17071
|
+
break;
|
|
17072
|
+
}
|
|
17073
|
+
offset += length;
|
|
17074
|
+
}
|
|
17075
|
+
}
|
|
17076
|
+
if (width !== 1024 || height !== 1024)
|
|
17077
|
+
throw new VibeError('VIBE_PLATFORM_ICON_INVALID');
|
|
17078
|
+
}
|
|
17079
|
+
|
|
16335
17080
|
function getCapabilities() {
|
|
16336
17081
|
return {
|
|
16337
17082
|
schemaVersion: '1',
|
|
@@ -16343,8 +17088,22 @@ function getCapabilities() {
|
|
|
16343
17088
|
stdoutProtocolOnly: true,
|
|
16344
17089
|
},
|
|
16345
17090
|
commands: {
|
|
17091
|
+
gameCategories: {
|
|
17092
|
+
command: 'ttmg game categories --format json',
|
|
17093
|
+
available: true,
|
|
17094
|
+
schemaVersion: '1',
|
|
17095
|
+
platformWrite: false,
|
|
17096
|
+
source: 'bundled',
|
|
17097
|
+
catalogVersion: GAME_CATEGORY_CATALOG_VERSION,
|
|
17098
|
+
filterOption: '--category',
|
|
17099
|
+
includesGameplayDescriptions: true,
|
|
17100
|
+
requiresLogin: false,
|
|
17101
|
+
networkRequired: false,
|
|
17102
|
+
},
|
|
16346
17103
|
vibeUpload: {
|
|
16347
|
-
command: 'ttmg upload --vibe --
|
|
17104
|
+
command: 'ttmg upload --vibe --web --dir <gameDir> --title <title> --icon <iconPath> --format ndjson',
|
|
17105
|
+
mockCommand: 'ttmg upload --vibe --mock --dir <gameDir> --title <title> --icon <iconPath> --format ndjson',
|
|
17106
|
+
guide: 'vibe-upload.md',
|
|
16348
17107
|
option: '--vibe',
|
|
16349
17108
|
available: true,
|
|
16350
17109
|
platformWrite: true,
|
|
@@ -16374,12 +17133,42 @@ function getCapabilities() {
|
|
|
16374
17133
|
sourceProjectIdentity: '.ttmg/vibe-project.json',
|
|
16375
17134
|
metadataInputs: ['title', 'icon', 'dir|package'],
|
|
16376
17135
|
inputRequirements: VIBE_INPUT_REQUIREMENTS,
|
|
17136
|
+
classification: {
|
|
17137
|
+
queryCommand: 'ttmg game categories --format json',
|
|
17138
|
+
options: ['--category', '--subcategory'],
|
|
17139
|
+
suppliedTogether: true,
|
|
17140
|
+
default: { category: 'Other', subcategory: 'Other' },
|
|
17141
|
+
invalidPair: 'reject-before-task-creation',
|
|
17142
|
+
legacyAppInfo: 'preserved-when-options-omitted; explicit-pair-overrides-category-fields',
|
|
17143
|
+
resultField: 'classification',
|
|
17144
|
+
},
|
|
16377
17145
|
webAuthorization: { option: '--web', backend: 'ppe', defaultLane: 'ppe_op_vc', simulation: false, opensSystemBrowser: true, continuesPolling: true, formatIndependent: true, conflictsWith: ['--mock', '--mock-scenario', '--vibe-api', '--no-open'], mockBehaviorUnchanged: true, materialUploadEnabled: true },
|
|
17146
|
+
skillWorkflow: {
|
|
17147
|
+
preferredFormat: 'ndjson',
|
|
17148
|
+
packageAlternative: '--package <zipPath> replaces --dir <gameDir>',
|
|
17149
|
+
keepUploadProcessRunning: true,
|
|
17150
|
+
pollingOwner: 'cli',
|
|
17151
|
+
uploadOwner: 'cli',
|
|
17152
|
+
waitingAuthorization: {
|
|
17153
|
+
status: 'waiting_authorization',
|
|
17154
|
+
userAction: 'complete authorization in the system browser on the CLI computer',
|
|
17155
|
+
pageUrlField: 'pageUrl',
|
|
17156
|
+
pageUrlPurpose: 'local binding and upload progress page',
|
|
17157
|
+
reopenCommandField: 'authorizationOpenCommand',
|
|
17158
|
+
browserOpenRequestedProvesAuthorization: false,
|
|
17159
|
+
createAnotherUploadTask: false,
|
|
17160
|
+
},
|
|
17161
|
+
realUploadSuccess: { terminal: true, status: 'uploaded', uploaded: true, simulation: false },
|
|
17162
|
+
failure: 'Read errorCode and nextAction; do not automatically create another task or reupload',
|
|
17163
|
+
completionMeaning: 'materials uploaded to PPE; review and production release are separate',
|
|
17164
|
+
sameHostRequired: true,
|
|
17165
|
+
},
|
|
16378
17166
|
optionalMetadataFile: '--app-info',
|
|
16379
17167
|
appInfoFields: {
|
|
16380
17168
|
required: [],
|
|
16381
17169
|
optional: [],
|
|
16382
17170
|
suppliedByCli: ['app_name', 'icon_uri'],
|
|
17171
|
+
defaultedByCli: Object.keys(VIBE_PPE_DEFAULT_APP_INFO),
|
|
16383
17172
|
additionalMetadataSource: 'cli-defaults',
|
|
16384
17173
|
defaultMetadataProfile: 'ppe-trial',
|
|
16385
17174
|
requestFromUser: false,
|
|
@@ -16410,8 +17199,9 @@ function getCapabilities() {
|
|
|
16410
17199
|
httpContract: 'minis-import-task',
|
|
16411
17200
|
authorizationQr: true,
|
|
16412
17201
|
previewQr: false,
|
|
16413
|
-
realAuthorizationVerified:
|
|
16414
|
-
realUploadVerified:
|
|
17202
|
+
realAuthorizationVerified: true,
|
|
17203
|
+
realUploadVerified: true,
|
|
17204
|
+
verificationScope: { backend: 'ppe', assets: 'test-fixtures', date: '2026-09-15', production: false },
|
|
16415
17205
|
autoUploadAfterAuthorization: true,
|
|
16416
17206
|
boundedPolling: true,
|
|
16417
17207
|
cancellation: true,
|
|
@@ -17109,6 +17899,14 @@ withVerboseOption(program.command('logout').description(t('cli.command.logout.de
|
|
|
17109
17899
|
await logout();
|
|
17110
17900
|
});
|
|
17111
17901
|
const authCmd = program.command('auth').description(t('cli.command.auth.desc'));
|
|
17902
|
+
const gameCmd = program.command('game').description(t('cli.command.game.desc'));
|
|
17903
|
+
withVerboseOption(gameCmd.command('categories')
|
|
17904
|
+
.description(t('cli.command.game.categories.desc'))
|
|
17905
|
+
.option('--category <category>', t('cli.command.game.categories.category'))
|
|
17906
|
+
.option('--format <format>', t('cli.command.capabilities.format'), 'text')).action(async (cmd) => {
|
|
17907
|
+
const { gameCategories } = await Promise.resolve().then(function () { return categories; });
|
|
17908
|
+
gameCategories(cmd);
|
|
17909
|
+
});
|
|
17112
17910
|
withVerboseOption(authCmd.command('open')
|
|
17113
17911
|
.description(t('cli.command.auth.open.desc'))
|
|
17114
17912
|
.option('--page-url <url>', t('cli.command.auth.open.pageUrl'))
|
|
@@ -17146,6 +17944,8 @@ withVerboseOption(program
|
|
|
17146
17944
|
// Vibe v2 backend selection (including opt-in ppe-mock) stays outside the Skill workflow.
|
|
17147
17945
|
.option('--title <title>', t('cli.command.upload.vibeTitle'))
|
|
17148
17946
|
.option('--icon <path>', t('cli.command.upload.vibeIcon'))
|
|
17947
|
+
.option('--category <category>', t('cli.command.upload.vibeCategory'))
|
|
17948
|
+
.option('--subcategory <subcategory>', t('cli.command.upload.vibeSubcategory'))
|
|
17149
17949
|
.addOption(new commander.Option('--app-info <path>', t('cli.command.upload.vibeAppInfo')).hideHelp())
|
|
17150
17950
|
.option('--package <path>', t('cli.command.upload.vibePackage'))
|
|
17151
17951
|
.option('--dry-run', t('cli.command.upload.vibeDryRun'))
|
|
@@ -17155,7 +17955,8 @@ withVerboseOption(program
|
|
|
17155
17955
|
.option('--vibe-authorize-url <url>', t('cli.command.upload.vibeAuthorizeUrl'))
|
|
17156
17956
|
.option('--poll-interval <seconds>', t('cli.command.upload.vibePollInterval'))
|
|
17157
17957
|
.option('--timeout <seconds>', t('cli.command.upload.vibeTimeout'))
|
|
17158
|
-
.option('--format <format>', t('cli.command.upload.format'), 'text')
|
|
17958
|
+
.option('--format <format>', t('cli.command.upload.format'), 'text')
|
|
17959
|
+
.addHelpText('after', t('cli.command.upload.vibeWebGuide'))).action(async (cmd) => {
|
|
17159
17960
|
if (cmd.vibe) {
|
|
17160
17961
|
const { vibeUpload } = await Promise.resolve().then(function () { return index$1; });
|
|
17161
17962
|
await vibeUpload(cmd);
|
|
@@ -17165,6 +17966,8 @@ withVerboseOption(program
|
|
|
17165
17966
|
cmd.title,
|
|
17166
17967
|
cmd.icon,
|
|
17167
17968
|
cmd.appInfo,
|
|
17969
|
+
cmd.category,
|
|
17970
|
+
cmd.subcategory,
|
|
17168
17971
|
cmd.web,
|
|
17169
17972
|
cmd.package,
|
|
17170
17973
|
cmd.dryRun,
|
|
@@ -17707,112 +18510,6 @@ var authOpen = /*#__PURE__*/Object.freeze({
|
|
|
17707
18510
|
vibeAuthOpen: vibeAuthOpen
|
|
17708
18511
|
});
|
|
17709
18512
|
|
|
17710
|
-
// Current PPE trial defaults: the owner-approved metadata used by the successful
|
|
17711
|
-
// real upload fixture on 2026-09-14. This is not a production metadata profile.
|
|
17712
|
-
const VIBE_PPE_DEFAULT_APP_INFO = Object.freeze({
|
|
17713
|
-
description: 'DevTool PPE integration test fixture. Not a production game.',
|
|
17714
|
-
category: 'Other',
|
|
17715
|
-
subcategory: 'Other',
|
|
17716
|
-
terms_of_service: 'https://example.invalid/devtool-ppe-test/terms',
|
|
17717
|
-
privacy_policy: 'https://example.invalid/devtool-ppe-test/privacy',
|
|
17718
|
-
copyright_confirmation: true,
|
|
17719
|
-
contact_email: 'devtool-test@example.invalid',
|
|
17720
|
-
});
|
|
17721
|
-
// BFF IDL + Import Service ValidateAppInfo, feat-minis-import (2026-09-14).
|
|
17722
|
-
// Keep credentials, app_name and icon_uri out of this file: CLI owns those fields.
|
|
17723
|
-
const requiredStrings = [
|
|
17724
|
-
'description', 'category', 'subcategory', 'terms_of_service', 'privacy_policy',
|
|
17725
|
-
];
|
|
17726
|
-
const optionalStrings = ['apple_team_id', 'contact_email', 'region', 'min_sdk_version'];
|
|
17727
|
-
function readVibeAppInfo(file) {
|
|
17728
|
-
if (file === undefined)
|
|
17729
|
-
return undefined;
|
|
17730
|
-
try {
|
|
17731
|
-
const stat = fs.statSync(file);
|
|
17732
|
-
if (!stat.isFile() || stat.size > 64 * 1024)
|
|
17733
|
-
throw new Error();
|
|
17734
|
-
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
17735
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
17736
|
-
throw new Error();
|
|
17737
|
-
const allowed = new Set([...requiredStrings, ...optionalStrings, 'copyright_confirmation']);
|
|
17738
|
-
if (Object.keys(value).some(key => !allowed.has(key)))
|
|
17739
|
-
throw new Error();
|
|
17740
|
-
// JSON can encode lone surrogates that Go rejects as invalid UTF-8 input.
|
|
17741
|
-
for (const item of Object.values(value)) {
|
|
17742
|
-
if (typeof item === 'string' && [...item].some(char => /^[\uD800-\uDFFF]$/.test(char)))
|
|
17743
|
-
throw new Error();
|
|
17744
|
-
}
|
|
17745
|
-
for (const key of requiredStrings) {
|
|
17746
|
-
if (typeof value[key] !== 'string' || !value[key].trim())
|
|
17747
|
-
throw new Error();
|
|
17748
|
-
}
|
|
17749
|
-
for (const key of optionalStrings) {
|
|
17750
|
-
if (value[key] !== undefined && (typeof value[key] !== 'string' || !value[key].trim()))
|
|
17751
|
-
throw new Error();
|
|
17752
|
-
}
|
|
17753
|
-
// An explicit declaration is required; never infer consent or supply a default.
|
|
17754
|
-
if (value.copyright_confirmation !== true)
|
|
17755
|
-
throw new Error();
|
|
17756
|
-
for (const key of ['terms_of_service', 'privacy_policy']) {
|
|
17757
|
-
if (/\s/.test(value[key]))
|
|
17758
|
-
throw new Error();
|
|
17759
|
-
const url = new URL(value[key]);
|
|
17760
|
-
if (!['http:', 'https:'].includes(url.protocol) || !url.hostname || url.username || url.password)
|
|
17761
|
-
throw new Error();
|
|
17762
|
-
}
|
|
17763
|
-
if (value.contact_email !== undefined && !/^[^\s<>@]+@[^\s<>@]+$/.test(value.contact_email))
|
|
17764
|
-
throw new Error();
|
|
17765
|
-
return value;
|
|
17766
|
-
}
|
|
17767
|
-
catch {
|
|
17768
|
-
// Do not expose paths, file contents or parser errors in structured output.
|
|
17769
|
-
throw new VibeError('VIBE_INVALID_APP_INFO');
|
|
17770
|
-
}
|
|
17771
|
-
}
|
|
17772
|
-
const VIBE_PLATFORM_ICON_LIMIT = 5 * 1024 * 1024;
|
|
17773
|
-
// Header preflight only. The BFF remains responsible for decoding the image.
|
|
17774
|
-
function validateVibeUploadInput(input) {
|
|
17775
|
-
if ([...input.title].length > 30)
|
|
17776
|
-
throw new VibeError('VIBE_TITLE_TOO_LONG');
|
|
17777
|
-
const image = input.icon.bytes;
|
|
17778
|
-
if (image.length > VIBE_PLATFORM_ICON_LIMIT)
|
|
17779
|
-
throw new VibeError('VIBE_PLATFORM_ICON_INVALID');
|
|
17780
|
-
let width = 0, height = 0;
|
|
17781
|
-
if (image.length >= 33 && image.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex')) &&
|
|
17782
|
-
image.readUInt32BE(8) === 13 && image.toString('ascii', 12, 16) === 'IHDR') {
|
|
17783
|
-
width = image.readUInt32BE(16);
|
|
17784
|
-
height = image.readUInt32BE(20);
|
|
17785
|
-
}
|
|
17786
|
-
else if (image[0] === 0xff && image[1] === 0xd8) {
|
|
17787
|
-
let offset = 2;
|
|
17788
|
-
while (offset + 4 <= image.length) {
|
|
17789
|
-
if (image[offset++] !== 0xff)
|
|
17790
|
-
break;
|
|
17791
|
-
while (image[offset] === 0xff)
|
|
17792
|
-
offset++;
|
|
17793
|
-
const marker = image[offset++];
|
|
17794
|
-
if (marker === 0xda || marker === 0xd9)
|
|
17795
|
-
break;
|
|
17796
|
-
if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7))
|
|
17797
|
-
continue;
|
|
17798
|
-
if (offset + 2 > image.length)
|
|
17799
|
-
break;
|
|
17800
|
-
const length = image.readUInt16BE(offset);
|
|
17801
|
-
if (length < 2 || offset + length > image.length)
|
|
17802
|
-
break;
|
|
17803
|
-
// Go image/jpeg accepts baseline, extended sequential and progressive SOF.
|
|
17804
|
-
if ([0xc0, 0xc1, 0xc2].includes(marker) && length >= 8) {
|
|
17805
|
-
height = image.readUInt16BE(offset + 3);
|
|
17806
|
-
width = image.readUInt16BE(offset + 5);
|
|
17807
|
-
break;
|
|
17808
|
-
}
|
|
17809
|
-
offset += length;
|
|
17810
|
-
}
|
|
17811
|
-
}
|
|
17812
|
-
if (width !== 1024 || height !== 1024)
|
|
17813
|
-
throw new VibeError('VIBE_PLATFORM_ICON_INVALID');
|
|
17814
|
-
}
|
|
17815
|
-
|
|
17816
18513
|
// BFF IDL: feat-minis-import. PPE import flow; no production adapter.
|
|
17817
18514
|
const prefix = '/bff/app_info/minis_import/';
|
|
17818
18515
|
const MINIS_IMPORT_PATHS = {
|
|
@@ -18429,7 +19126,7 @@ async function prepareVibeInput(options, onPackaging, signal, deps = { check: ex
|
|
|
18429
19126
|
const title = options.title?.trim();
|
|
18430
19127
|
if (!title)
|
|
18431
19128
|
throw new VibeError('VIBE_TITLE_REQUIRED');
|
|
18432
|
-
const appInfo = readVibeAppInfo(options.appInfo);
|
|
19129
|
+
const appInfo = applyVibeCategory(options, readVibeAppInfo(options.appInfo));
|
|
18433
19130
|
if (options.package && options.dir)
|
|
18434
19131
|
throw new VibeError('VIBE_INPUT_CONFLICT');
|
|
18435
19132
|
const image = readFile(options.icon, VIBE_ICON_LIMIT, 'VIBE_INVALID_ICON');
|
|
@@ -18472,7 +19169,7 @@ async function prepareVibeInput(options, onPackaging, signal, deps = { check: ex
|
|
|
18472
19169
|
},
|
|
18473
19170
|
code: { bytes: code, name: 'game.zip', contentType: 'application/zip' },
|
|
18474
19171
|
};
|
|
18475
|
-
if (appInfo)
|
|
19172
|
+
if (options.appInfo !== undefined)
|
|
18476
19173
|
validateVibeUploadInput(input);
|
|
18477
19174
|
return input;
|
|
18478
19175
|
}
|
|
@@ -18984,6 +19681,12 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
18984
19681
|
terminal: !!options.dryRun,
|
|
18985
19682
|
nextAction: options.dryRun ? 'none' : 'wait',
|
|
18986
19683
|
title: input.title,
|
|
19684
|
+
classification: {
|
|
19685
|
+
category: (input.appInfo || VIBE_PPE_DEFAULT_APP_INFO).category,
|
|
19686
|
+
subcategory: (input.appInfo || VIBE_PPE_DEFAULT_APP_INFO).subcategory,
|
|
19687
|
+
source: options.category !== undefined ? 'arguments' : input.appInfo ? 'app-info' : 'defaults',
|
|
19688
|
+
catalogVersion: GAME_CATEGORY_CATALOG_VERSION,
|
|
19689
|
+
},
|
|
18987
19690
|
iconSizeBytes: input.icon.bytes.length,
|
|
18988
19691
|
packageSizeBytes: input.code.bytes.length,
|
|
18989
19692
|
packageSha256: crypto.createHash('sha256')
|
|
@@ -19175,6 +19878,8 @@ async function executeVibeUpload(options, onEvent = () => { }, dependencies = {}
|
|
|
19175
19878
|
? 'cancelled'
|
|
19176
19879
|
: 'failed';
|
|
19177
19880
|
const messages = {
|
|
19881
|
+
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.'],
|
|
19882
|
+
VIBE_INVALID_CATEGORY: ['类型或子类型无效,或两者不匹配。使用 ttmg game categories --format json 查询有效组合。', 'Invalid category/subcategory pair. Query valid values with ttmg game categories --format json.'],
|
|
19178
19883
|
VIBE_WEB_OPTION_CONFLICT: ['--web 使用真实网页授权,不能与 --mock、--mock-scenario、--vibe-api 或 --no-open 同时使用。', '--web uses real browser authorization and cannot be combined with --mock, --mock-scenario, --vibe-api or --no-open.'],
|
|
19179
19884
|
VIBE_AUTH_DENIED: [
|
|
19180
19885
|
'已取消授权,本次未上传。',
|