gamealgo-cli 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/cli/README.md +98 -24
- package/dist/gamealgo.js +132 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,11 +23,14 @@ gamealgo docs agent-onboarding --host <admin-host>
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
gamealgo login --host <admin-host> --admin-key <ga_admin_xxx>
|
|
26
|
+
gamealgo integration get-plan --environment <domestic|overseas> --platform <ios|android|maker|rest> --out gamealgo-integration-plan.json
|
|
26
27
|
gamealgo key list --json
|
|
27
28
|
gamealgo key create --name <runtime-name> --json
|
|
28
29
|
gamealgo key reveal --name <runtime-name> --json
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
AI Agent 必须先执行 `integration get-plan`,并按返回的完整清单完成接入和人工验收。不要依赖只读某一份 README 后自行推断其他平台规则。
|
|
33
|
+
|
|
31
34
|
凭据保存在当前游戏项目的 `.gamealgo/cli.json`,不同游戏互不覆盖。需要其他主题时使用:
|
|
32
35
|
|
|
33
36
|
```bash
|
package/cli/README.md
CHANGED
|
@@ -71,6 +71,21 @@ GAMEALGO_GAME_ADMIN_KEY=ga_admin_xxx
|
|
|
71
71
|
gamealgo whoami
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
## 接入计划
|
|
75
|
+
|
|
76
|
+
登录后先让 CLI 根据开发者明确指定的环境和平台生成完整接入计划。CLI 不自动猜测环境或平台:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
gamealgo integration get-plan \
|
|
80
|
+
--environment domestic \
|
|
81
|
+
--platform maker \
|
|
82
|
+
--out gamealgo-integration-plan.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
支持的环境是 `domestic`、`overseas`;支持的平台是 `ios`、`android`、`maker`、`rest`。计划包含正确地址、强制初始化规则、核心事件、广告/归因/实验条件项、验收标准和按需参考文档。
|
|
86
|
+
|
|
87
|
+
计划同时包含接入完成后必须逐项执行的人工验收清单。不能只根据命令执行成功或生产报表有数据来判断接入已经完成。
|
|
88
|
+
|
|
74
89
|
## 最新文档
|
|
75
90
|
|
|
76
91
|
AI Agent 可以通过 CLI 从 Admin Host 获取最新文档,不需要拉取本仓库:
|
|
@@ -161,15 +176,17 @@ gamealgo experiment strategy publish strategy.yaml --yes
|
|
|
161
176
|
|
|
162
177
|
版本升级判断、Strategy 最低版本和托管实验覆盖率门槛的完整说明可通过 `gamealgo docs experiment-integration-versions` 获取。
|
|
163
178
|
|
|
164
|
-
|
|
179
|
+
默认创建托管实验 Run。除非开发者具备专业的实验设计和统计判断能力,并且会持续人工管理实验,否则不要使用手动实验:
|
|
165
180
|
|
|
166
181
|
```yaml
|
|
167
182
|
# run.yaml
|
|
168
|
-
displayName:
|
|
169
|
-
type: manual
|
|
183
|
+
displayName: 广告频率托管实验
|
|
170
184
|
platform: ios
|
|
171
185
|
objectiveTemplate: ltv_proxy@1
|
|
172
186
|
baselineVariantId: control
|
|
187
|
+
minWindowDays: 7
|
|
188
|
+
maxWindowDays: 14
|
|
189
|
+
maxVariantsPerRound: 2
|
|
173
190
|
variants:
|
|
174
191
|
- variantId: control
|
|
175
192
|
weight: 1
|
|
@@ -191,10 +208,14 @@ variants:
|
|
|
191
208
|
gamealgo experiment run create ad_frequency run.yaml --yes
|
|
192
209
|
```
|
|
193
210
|
|
|
211
|
+
CLI 在未声明 `type` 时默认提交 `managed`。托管实验由平台管理轮次、在参考周期后判断是否需要延长,并在证据明确时完成后续轮次和推全;缺少 `minWindowDays` 或 `maxWindowDays` 会直接拒绝创建,不会静默套用周期。
|
|
212
|
+
|
|
194
213
|
Run 不会继承 Strategy 的脚本。每个需要执行脚本的 variant 都必须显式配置 `script.versionId`;省略 `script` 明确表示该组是 config-only。即使多个 variant 使用同一份脚本,也要在每个 variant 上重复填写同一个不可变 `versionId`。
|
|
195
214
|
|
|
196
215
|
如果要把当前默认参数也放进实验,需要显式配置 `variantId: default`。`default` 会快照 Strategy 当前默认参数,因此不能写 `config`;如果默认组需要执行脚本,也必须在 `default` 上显式写 `script.versionId`。否则默认参数不参与流量。所有有流量的实验组数量必须至少为 2。
|
|
197
216
|
|
|
217
|
+
Run 创建后,用户不能修改 variant、权重或启停状态,否则同一用户可能换组,前后数据也无法按同一实验口径比较。托管实验只允许平台在轮次结束后按既定流程切换下一轮候选,用户不能干预流量。需要改变候选或分流时,先取消当前 Run,再创建新 Run。
|
|
218
|
+
|
|
198
219
|
查看、评估和完成实验闭环:
|
|
199
220
|
|
|
200
221
|
```bash
|
|
@@ -207,7 +228,7 @@ gamealgo experiment run report xrun_xxxxxxxxxxxxxxxx
|
|
|
207
228
|
gamealgo experiment run promote xrun_xxxxxxxxxxxxxxxx --variant slower_ads --message "采用低频广告组" --yes
|
|
208
229
|
```
|
|
209
230
|
|
|
210
|
-
`baselineVariantId` 是本次评估的对照组,必须指向一个有流量的实验组。平台按小时更新“当前评估”;`report` 返回已生成的正式报告。`promote`
|
|
231
|
+
`baselineVariantId` 是本次评估的对照组,必须指向一个有流量的实验组。平台按小时更新“当前评估”;`report` 返回已生成的正式报告。`promote` 可在手动或托管 Run 仍处于运行中时提前人工推全,不要求平台已经给出正式 winner。它会立即结束 Run,把指定 variant 的 `config` / `script` 写回 Strategy 默认参数,并淘汰其他实验组;必须通过 `--message` 记录人工推全原因。取消实验用:
|
|
211
232
|
|
|
212
233
|
`objectiveTemplate` 必须显式选择:
|
|
213
234
|
|
|
@@ -229,6 +250,22 @@ gamealgo experiment run objective xrun_xxxxxxxxxxxxxxxx \
|
|
|
229
250
|
gamealgo experiment run cancel xrun_xxxxxxxxxxxxxxxx --message "实验停止" --yes
|
|
230
251
|
```
|
|
231
252
|
|
|
253
|
+
### 高级用法:手动实验
|
|
254
|
+
|
|
255
|
+
手动实验不会自动延长、形成最终结论或推全。只有开发者能够自行决定采集周期、判断统计证据并负责完成实验闭环时才使用。必须通过以下任一方式显式开启:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
gamealgo experiment run create ad_frequency manual-run.yaml --manual --yes
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
或者在 Run 文件中明确填写:
|
|
262
|
+
|
|
263
|
+
```yaml
|
|
264
|
+
type: manual
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
手动 Run 不配置 `minWindowDays`、`maxWindowDays` 和 `startCondition`。其分流同样在整个 Run 内固定;需要调整分流时,取消当前 Run 后重新创建。
|
|
268
|
+
|
|
232
269
|
回滚默认参数会创建一个新的参数版本,不会改写历史版本:
|
|
233
270
|
|
|
234
271
|
```bash
|
|
@@ -243,31 +280,13 @@ gamealgo experiment override list xrun_xxxxxxxxxxxxxxxx
|
|
|
243
280
|
gamealgo experiment override delete xrun_xxxxxxxxxxxxxxxx --user user-1 --yes
|
|
244
281
|
```
|
|
245
282
|
|
|
246
|
-
|
|
283
|
+
托管实验可以使用 `startCondition` 等待接入版本覆盖率和用户数达到门槛:
|
|
247
284
|
|
|
248
285
|
```yaml
|
|
249
|
-
displayName: 广告频率托管实验
|
|
250
|
-
type: managed
|
|
251
|
-
platform: ios
|
|
252
|
-
objectiveTemplate: ltv_proxy@1
|
|
253
|
-
baselineVariantId: alpha
|
|
254
|
-
minWindowDays: 7
|
|
255
|
-
maxWindowDays: 14
|
|
256
|
-
maxVariantsPerRound: 3
|
|
257
286
|
# 可选:仅托管实验支持。最近 24 小时同时满足两个门槛后才开始首轮。
|
|
258
287
|
startCondition:
|
|
259
288
|
minCoverage: 0.8
|
|
260
289
|
minUsers: 100
|
|
261
|
-
variants:
|
|
262
|
-
- variantId: alpha
|
|
263
|
-
config: { firstAdLevel: 4, interval: 30 }
|
|
264
|
-
script: { versionId: sv_xxxxxxxxxxxxxxxx }
|
|
265
|
-
- variantId: bravo
|
|
266
|
-
config: { firstAdLevel: 5, interval: 30 }
|
|
267
|
-
script: { versionId: sv_xxxxxxxxxxxxxxxx }
|
|
268
|
-
- variantId: charlie
|
|
269
|
-
config: { firstAdLevel: 4, interval: 45 }
|
|
270
|
-
script: { versionId: sv_xxxxxxxxxxxxxxxx }
|
|
271
290
|
```
|
|
272
291
|
|
|
273
292
|
`minCoverage` 表示指定平台最近 24 小时内,实验接入版本达到 Strategy `requiredIntegrationVersion` 的去重用户占比;`minUsers` 表示同一窗口内的非 Debug 去重用户数。两项可单独配置,同时配置时必须同时满足。等待期间 Strategy 继续下发默认参数,满足门槛后平台才生成首轮时间窗并开始分流。手动实验不支持 `startCondition`,托管实验不配置时仍会立即启动。`experiment run show` 返回 `startConditionSnapshot`,可查看当前用户数、覆盖率和最近检查时间。
|
|
@@ -307,6 +326,8 @@ gamealgo config publish configs/gameplay.json
|
|
|
307
326
|
## Report Pack
|
|
308
327
|
|
|
309
328
|
```bash
|
|
329
|
+
gamealgo events list --days 30 --json
|
|
330
|
+
gamealgo events apply gamealgo-events.yaml --json
|
|
310
331
|
gamealgo report pull --out gamealgo-report-pack.json
|
|
311
332
|
gamealgo report validate gamealgo-report-pack.json
|
|
312
333
|
gamealgo report publish gamealgo-report-pack.json
|
|
@@ -314,7 +335,7 @@ gamealgo report publish gamealgo-report-pack.json
|
|
|
314
335
|
|
|
315
336
|
每个游戏只有一个当前 Report Pack。`publish` 会整体覆盖上一次提交的配置,不需要传版本号;配置历史由游戏项目自己的 Git 管理。
|
|
316
337
|
|
|
317
|
-
`report validate` 和
|
|
338
|
+
`report validate` 和 `report publish` 使用同一套服务端元数据校验逻辑,只检查配置结构、事件词典与生成 SQL,不查询线上数据。凡是 Report Pack 顶层 `events` 引用的标准事件或自定义事件,其字段都必须已经登记、未废弃且字段类型一致;只引用标准看板和纯 marketing dataset 时,不需要重复登记平台内部字段。
|
|
318
339
|
|
|
319
340
|
## Adjust 投放花费
|
|
320
341
|
|
|
@@ -406,6 +427,59 @@ gamealgo report preview \
|
|
|
406
427
|
|
|
407
428
|
## 事件上报调试
|
|
408
429
|
|
|
430
|
+
### 先查看并维护事件词典
|
|
431
|
+
|
|
432
|
+
开始新增埋点或设计报表前,Agent 必须先查看当前游戏已经登记和实际上报的埋点:
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
gamealgo events list --days 30
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
列表会展示埋点描述、生效版本、创建时间、状态和字段定义,并根据近 30 天非调试数据给出事件级诊断:
|
|
439
|
+
|
|
440
|
+
- 线上出现但词典未登记;
|
|
441
|
+
- 词典已登记但近期未观测;
|
|
442
|
+
- 埋点已废弃但线上仍在上报。
|
|
443
|
+
|
|
444
|
+
诊断只检查埋点是否存在,不根据线上 payload 自动推断字段或字段类型。字段必须由开发 Agent 显式登记后再用于 Report Pack。标准事件同样可以维护词典并追加游戏业务字段,例如在 `level_end.payload` 中增加关卡、章节或玩法信息。
|
|
445
|
+
|
|
446
|
+
Report Pack 的 `events.<eventType>.fields` 字段 ID 必须与词典字段名一致,`path` 只描述 payload 中的实际 JSON 路径。例如词典登记 `ad_type` 后,Report Pack 可以用 `"path": "$.adType"` 读取驼峰字段,但字段类型必须与词典一致。
|
|
447
|
+
|
|
448
|
+
用 YAML 或 JSON 增量登记埋点:
|
|
449
|
+
|
|
450
|
+
```yaml
|
|
451
|
+
events:
|
|
452
|
+
level_end:
|
|
453
|
+
desc: 玩家结束关卡
|
|
454
|
+
effectiveVersion: 1.2.0
|
|
455
|
+
category: progression
|
|
456
|
+
source: standard
|
|
457
|
+
fields:
|
|
458
|
+
level_id:
|
|
459
|
+
type: string
|
|
460
|
+
required: true
|
|
461
|
+
desc: 关卡 ID
|
|
462
|
+
effectiveVersion: 1.2.0
|
|
463
|
+
chapter_id:
|
|
464
|
+
type: string
|
|
465
|
+
required: false
|
|
466
|
+
desc: 游戏追加的章节 ID
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
gamealgo events apply gamealgo-events.yaml
|
|
471
|
+
gamealgo events get level_end
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
规则:
|
|
475
|
+
|
|
476
|
+
- 埋点必须填写 `desc`,`effectiveVersion` 可省略;创建时间由平台首次登记时生成。
|
|
477
|
+
- 字段必须填写 `type` 和 `desc`,`effectiveVersion` 可省略;字段创建时间由平台生成。
|
|
478
|
+
- 标准事件使用 `source: standard`,自定义事件使用 `source: custom`;两者都可以登记游戏追加的 payload 字段。
|
|
479
|
+
- `apply` 只新增或更新文件中出现的埋点和字段,不会删除其他已有定义。
|
|
480
|
+
- 不再使用的埋点需要显式废弃:`gamealgo events deprecate old_event --yes`。
|
|
481
|
+
- 再次 `apply` 已废弃埋点会将其恢复为启用状态。
|
|
482
|
+
|
|
409
483
|
接入 SDK 后,Agent 可以用 `events count` 检查某天事件是否已经进入原始事件表:
|
|
410
484
|
|
|
411
485
|
```bash
|
package/dist/gamealgo.js
CHANGED
|
@@ -74,6 +74,10 @@ async function main() {
|
|
|
74
74
|
await handleEvents(client, args, global);
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
|
+
if (command === "integration") {
|
|
78
|
+
await handleIntegration(client, args, global);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
77
81
|
if (command === "marketing") {
|
|
78
82
|
await handleMarketing(client, args, global);
|
|
79
83
|
return;
|
|
@@ -387,7 +391,7 @@ async function handleExperimentRun(client, args, global) {
|
|
|
387
391
|
const strategyKey = optionalString(flags.strategy || flags.key) || optionalString(args.shift());
|
|
388
392
|
const filePath = optionalString(flags.file || flags.input) || optionalString(args.shift());
|
|
389
393
|
if (!strategyKey || !filePath)
|
|
390
|
-
throw new Error("usage: gamealgo experiment run create <strategyKey> <run.yaml> --yes");
|
|
394
|
+
throw new Error("usage: gamealgo experiment run create <strategyKey> <run.yaml> [--manual] --yes");
|
|
391
395
|
await requireYes(flags, global, "experiment run create");
|
|
392
396
|
const body = await readObjectFile(filePath, "experiment run");
|
|
393
397
|
if (!optionalString(body.displayName))
|
|
@@ -400,7 +404,24 @@ async function handleExperimentRun(client, args, global) {
|
|
|
400
404
|
throw new Error("experiment run requires explicit baselineVariantId");
|
|
401
405
|
}
|
|
402
406
|
validateExperimentRunVariantScripts(body.variants);
|
|
403
|
-
const
|
|
407
|
+
const fileRunType = optionalString(body.type);
|
|
408
|
+
const flagRunType = optionalString(flags.type);
|
|
409
|
+
const manualFlag = flags.manual === true;
|
|
410
|
+
if (fileRunType && fileRunType !== "managed" && fileRunType !== "manual") {
|
|
411
|
+
throw new Error("experiment run type must be managed or manual");
|
|
412
|
+
}
|
|
413
|
+
if (flagRunType && flagRunType !== "managed" && flagRunType !== "manual") {
|
|
414
|
+
throw new Error("--type must be managed or manual");
|
|
415
|
+
}
|
|
416
|
+
if (manualFlag && flagRunType && flagRunType !== "manual") {
|
|
417
|
+
throw new Error("--manual conflicts with --type managed");
|
|
418
|
+
}
|
|
419
|
+
const requestedRunType = manualFlag ? "manual" : flagRunType;
|
|
420
|
+
if (requestedRunType && fileRunType && requestedRunType !== fileRunType) {
|
|
421
|
+
throw new Error(`experiment run type conflict: file uses ${fileRunType}, CLI requests ${requestedRunType}`);
|
|
422
|
+
}
|
|
423
|
+
const runType = requestedRunType || fileRunType || "managed";
|
|
424
|
+
body.type = runType;
|
|
404
425
|
if (runType !== "managed" && body.startCondition !== undefined) {
|
|
405
426
|
throw new Error("experiment run startCondition is supported only when type is managed");
|
|
406
427
|
}
|
|
@@ -414,6 +435,9 @@ async function handleExperimentRun(client, args, global) {
|
|
|
414
435
|
throw new Error("managed experiment run requires maxWindowDays between minWindowDays and 60");
|
|
415
436
|
}
|
|
416
437
|
}
|
|
438
|
+
else {
|
|
439
|
+
process.stderr.write("Warning: creating a manual experiment. The platform will not automatically extend, conclude, or promote it; use manual mode only when the developer will manage the experiment decision.\n");
|
|
440
|
+
}
|
|
417
441
|
const result = await client.createExperimentV2Run(strategyKey, body);
|
|
418
442
|
await printResult(result, global);
|
|
419
443
|
if (!global.json)
|
|
@@ -540,23 +564,7 @@ async function handleExperimentRun(client, args, global) {
|
|
|
540
564
|
await printResult(await client.cancelExperimentV2Run(runId, message), global);
|
|
541
565
|
return;
|
|
542
566
|
}
|
|
543
|
-
|
|
544
|
-
const flags = parseFlags(args);
|
|
545
|
-
const runId = optionalString(flags.run || flags["run-id"] || flags.id) || optionalString(args.shift());
|
|
546
|
-
const variantId = optionalString(flags.variant || flags["variant-id"]) || optionalString(args.shift());
|
|
547
|
-
if (!runId || !variantId)
|
|
548
|
-
throw new Error("usage: gamealgo experiment run traffic <runId> <variantId> [--weight n] [--status running|paused] --message <reason> --yes");
|
|
549
|
-
const message = requiredMessage(flags, "experiment run traffic");
|
|
550
|
-
await requireYes(flags, global, "experiment run traffic");
|
|
551
|
-
const body = { message };
|
|
552
|
-
if (flags.weight !== undefined)
|
|
553
|
-
body.weight = optionalNonNegativeInteger(flags.weight, "weight");
|
|
554
|
-
if (flags.status !== undefined)
|
|
555
|
-
body.status = optionalString(flags.status);
|
|
556
|
-
await printResult(await client.updateExperimentV2VariantTraffic(runId, variantId, body), global);
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
throw new Error("usage: gamealgo experiment run <create|show|status|evaluate|report|objective|promote|cancel|traffic>");
|
|
567
|
+
throw new Error("usage: gamealgo experiment run <create|show|status|evaluate|report|objective|promote|cancel>");
|
|
560
568
|
}
|
|
561
569
|
function requiredMessage(flags, action) {
|
|
562
570
|
const message = optionalString(flags.message);
|
|
@@ -828,9 +836,42 @@ async function handleReport(client, args, global) {
|
|
|
828
836
|
}
|
|
829
837
|
async function handleEvents(client, args, global) {
|
|
830
838
|
const sub = args.shift();
|
|
831
|
-
if (sub !== "count")
|
|
832
|
-
throw new Error("usage: gamealgo events count [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--event-type level_end]");
|
|
833
839
|
const flags = parseFlags(args);
|
|
840
|
+
if (sub === "list") {
|
|
841
|
+
if (args.length)
|
|
842
|
+
throw new Error("usage: gamealgo events list [--days 30] [--no-diagnostics]");
|
|
843
|
+
const days = Number(flags.days || 30);
|
|
844
|
+
if (!Number.isInteger(days) || days < 1 || days > 90)
|
|
845
|
+
throw new Error("--days must be an integer between 1 and 90");
|
|
846
|
+
await writeEventCatalogOutput(await client.listEventCatalog(days, flags["no-diagnostics"] !== true), flags, global);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (sub === "get") {
|
|
850
|
+
const eventType = args.shift();
|
|
851
|
+
if (!eventType || args.length)
|
|
852
|
+
throw new Error("usage: gamealgo events get <eventType>");
|
|
853
|
+
await writeEventCatalogOutput(await client.getEventCatalog(eventType), flags, global);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (sub === "apply") {
|
|
857
|
+
const filePath = args.shift();
|
|
858
|
+
if (!filePath || args.length)
|
|
859
|
+
throw new Error("usage: gamealgo events apply <events.yaml>");
|
|
860
|
+
const content = await readObjectFile(filePath, "event catalog");
|
|
861
|
+
await writeEventCatalogOutput(await client.applyEventCatalog(content), flags, global);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (sub === "deprecate") {
|
|
865
|
+
const eventType = args.shift();
|
|
866
|
+
if (!eventType || args.length)
|
|
867
|
+
throw new Error("usage: gamealgo events deprecate <eventType> [--yes]");
|
|
868
|
+
if (!flags.yes && !(await confirm(`Deprecate event ${eventType}?`)))
|
|
869
|
+
throw new Error("Cancelled");
|
|
870
|
+
await writeEventCatalogOutput(await client.deprecateEventCatalog(eventType), flags, global);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (sub !== "count")
|
|
874
|
+
throw new Error("usage: gamealgo events <list|get|apply|deprecate|count>");
|
|
834
875
|
const timeoutMs = reportTimeoutMs(flags);
|
|
835
876
|
const startedAt = Date.now();
|
|
836
877
|
const stopProgress = startProgress("Querying event counts", timeoutMs);
|
|
@@ -855,6 +896,40 @@ async function handleEvents(client, args, global) {
|
|
|
855
896
|
}
|
|
856
897
|
await printResult(outputValue, global);
|
|
857
898
|
}
|
|
899
|
+
async function writeEventCatalogOutput(value, flags, global) {
|
|
900
|
+
if (flags.out) {
|
|
901
|
+
const filePath = String(flags.out);
|
|
902
|
+
const content = filePath.endsWith(".json") ? JSON.stringify(value, null, 2) + "\n" : YAML.stringify(value);
|
|
903
|
+
await writeTextFile(filePath, content);
|
|
904
|
+
await printResult({ ok: true, out: filePath }, global);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
await printResult(value, global);
|
|
908
|
+
}
|
|
909
|
+
async function handleIntegration(client, args, global) {
|
|
910
|
+
const sub = args.shift();
|
|
911
|
+
const flags = parseFlags(args);
|
|
912
|
+
if (args.length)
|
|
913
|
+
throw new Error("unexpected positional arguments for integration command");
|
|
914
|
+
const environment = integrationEnvironment(flags.environment);
|
|
915
|
+
const platform = integrationPlatform(flags.platform);
|
|
916
|
+
if (sub === "get-plan" || sub === "plan") {
|
|
917
|
+
const response = await client.getIntegrationPlan(environment, platform);
|
|
918
|
+
await writeIntegrationOutput(response, flags, global);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
throw new Error("usage: gamealgo integration get-plan --environment <domestic|overseas> --platform <ios|android|maker|rest>");
|
|
922
|
+
}
|
|
923
|
+
async function writeIntegrationOutput(value, flags, global) {
|
|
924
|
+
if (flags.out) {
|
|
925
|
+
if (typeof flags.out !== "string")
|
|
926
|
+
throw new Error("--out requires a file path");
|
|
927
|
+
await writeTextFile(flags.out, JSON.stringify(value, null, 2) + "\n");
|
|
928
|
+
await printResult({ ok: true, out: flags.out }, global);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
await printResult(value, global);
|
|
932
|
+
}
|
|
858
933
|
async function handleMarketing(client, args, global) {
|
|
859
934
|
const provider = args.shift();
|
|
860
935
|
if (provider !== "adjust")
|
|
@@ -1117,9 +1192,6 @@ class GameAlgoAdminClient {
|
|
|
1117
1192
|
async deleteExperimentV2Override(runId, userId) {
|
|
1118
1193
|
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/experiment-runs-v2/${encodeURIComponent(runId)}/overrides/delete`, { userId });
|
|
1119
1194
|
}
|
|
1120
|
-
async updateExperimentV2VariantTraffic(runId, variantId, body) {
|
|
1121
|
-
return await this.patch(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/experiment-runs-v2/${encodeURIComponent(runId)}/variants/${encodeURIComponent(variantId)}/traffic`, body);
|
|
1122
|
-
}
|
|
1123
1195
|
async listConfigFiles() {
|
|
1124
1196
|
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/config-files`);
|
|
1125
1197
|
}
|
|
@@ -1167,6 +1239,23 @@ class GameAlgoAdminClient {
|
|
|
1167
1239
|
async countEvents(body, options = {}) {
|
|
1168
1240
|
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/events/count`, body, options);
|
|
1169
1241
|
}
|
|
1242
|
+
async listEventCatalog(days = 30, diagnostics = true) {
|
|
1243
|
+
const params = new URLSearchParams({ days: String(days), diagnostics: diagnostics ? "1" : "0" });
|
|
1244
|
+
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/events/catalog?${params.toString()}`);
|
|
1245
|
+
}
|
|
1246
|
+
async getEventCatalog(eventType) {
|
|
1247
|
+
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/events/catalog/${encodeURIComponent(eventType)}`);
|
|
1248
|
+
}
|
|
1249
|
+
async applyEventCatalog(content) {
|
|
1250
|
+
return await this.put(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/events/catalog`, content);
|
|
1251
|
+
}
|
|
1252
|
+
async deprecateEventCatalog(eventType) {
|
|
1253
|
+
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/events/catalog/${encodeURIComponent(eventType)}/deprecate`, {});
|
|
1254
|
+
}
|
|
1255
|
+
async getIntegrationPlan(environment, platform) {
|
|
1256
|
+
const params = new URLSearchParams({ environment, platform });
|
|
1257
|
+
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/integration/plan?${params.toString()}`);
|
|
1258
|
+
}
|
|
1170
1259
|
async getAdjustMarketing() {
|
|
1171
1260
|
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/marketing/adjust`);
|
|
1172
1261
|
}
|
|
@@ -1679,6 +1768,18 @@ async function confirm(question) {
|
|
|
1679
1768
|
function normalizeHost(value) {
|
|
1680
1769
|
return value.trim().replace(/\/+$/, "");
|
|
1681
1770
|
}
|
|
1771
|
+
function integrationEnvironment(value) {
|
|
1772
|
+
const environment = optionalString(value);
|
|
1773
|
+
if (environment === "domestic" || environment === "overseas")
|
|
1774
|
+
return environment;
|
|
1775
|
+
throw new Error("--environment must be domestic or overseas");
|
|
1776
|
+
}
|
|
1777
|
+
function integrationPlatform(value) {
|
|
1778
|
+
const platform = optionalString(value);
|
|
1779
|
+
if (platform === "ios" || platform === "android" || platform === "maker" || platform === "rest")
|
|
1780
|
+
return platform;
|
|
1781
|
+
throw new Error("--platform must be ios, android, maker, or rest");
|
|
1782
|
+
}
|
|
1682
1783
|
function optionalString(value) {
|
|
1683
1784
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1684
1785
|
}
|
|
@@ -1718,6 +1819,8 @@ Usage:
|
|
|
1718
1819
|
gamealgo docs experiment-integration-versions --open
|
|
1719
1820
|
gamealgo docs --host <admin-url>
|
|
1720
1821
|
|
|
1822
|
+
gamealgo integration get-plan --environment domestic --platform maker --out gamealgo-integration-plan.json
|
|
1823
|
+
|
|
1721
1824
|
gamealgo experiment strategies
|
|
1722
1825
|
gamealgo experiment strategies --include-archived
|
|
1723
1826
|
gamealgo experiment integration-version latest
|
|
@@ -1730,7 +1833,7 @@ Usage:
|
|
|
1730
1833
|
gamealgo experiment strategy default ad_frequency strategy.yaml --yes
|
|
1731
1834
|
gamealgo experiment strategy archive ad_frequency --yes
|
|
1732
1835
|
gamealgo experiment strategy rollback ad_frequency --version-id xv_xxxxxxxxxxxxxxxx --message "回滚参数" --yes
|
|
1733
|
-
gamealgo experiment run create ad_frequency run.yaml --yes
|
|
1836
|
+
gamealgo experiment run create ad_frequency run.yaml [--manual] --yes
|
|
1734
1837
|
gamealgo experiment run show xrun_xxxxxxxxxxxxxxxx
|
|
1735
1838
|
gamealgo experiment run status xrun_xxxxxxxxxxxxxxxx
|
|
1736
1839
|
gamealgo experiment run evaluate xrun_xxxxxxxxxxxxxxxx --from 2026-07-01 --to 2026-07-07 --yes
|
|
@@ -1738,7 +1841,6 @@ Usage:
|
|
|
1738
1841
|
gamealgo experiment run objective xrun_xxxxxxxxxxxxxxxx --objective active_days@1 --message "改为关注活跃天数" --yes
|
|
1739
1842
|
gamealgo experiment run promote xrun_xxxxxxxxxxxxxxxx --variant bravo --message "采用胜出组" --yes
|
|
1740
1843
|
gamealgo experiment run cancel xrun_xxxxxxxxxxxxxxxx --message "停止实验" --yes
|
|
1741
|
-
gamealgo experiment run traffic xrun_xxxxxxxxxxxxxxxx bravo --weight 0 --status paused --message "暂停异常组" --yes
|
|
1742
1844
|
gamealgo experiment override set xrun_xxxxxxxxxxxxxxxx --user user-1 --variant bravo --yes
|
|
1743
1845
|
gamealgo experiment override list xrun_xxxxxxxxxxxxxxxx
|
|
1744
1846
|
|
|
@@ -1767,6 +1869,10 @@ Usage:
|
|
|
1767
1869
|
|
|
1768
1870
|
gamealgo events count --from 2026-06-23 --to 2026-06-23
|
|
1769
1871
|
gamealgo events count --from 2026-06-23 --to 2026-06-23 --event-type level_end
|
|
1872
|
+
gamealgo events list --days 30
|
|
1873
|
+
gamealgo events get level_start
|
|
1874
|
+
gamealgo events apply gamealgo-events.yaml
|
|
1875
|
+
gamealgo events deprecate old_event --yes
|
|
1770
1876
|
|
|
1771
1877
|
gamealgo marketing adjust get
|
|
1772
1878
|
gamealgo marketing adjust configure --api-token <token> --app-token <app-token> --platform ios --currency USD
|