gamealgo-cli 0.1.0 → 0.1.2
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 +1 -1
- package/cli/README.md +27 -5
- package/dist/gamealgo.js +116 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ docs/ 接入、埋点、报表和优化文档
|
|
|
111
111
|
| 环境 | SDK / REST API | Admin / CLI |
|
|
112
112
|
| --- | --- | --- |
|
|
113
113
|
| 国内 | `https://game-algo-sdk.dictapis.cn` | `https://game-algo-admin.dictapis.cn` |
|
|
114
|
-
| 海外 | `https://
|
|
114
|
+
| 海外 | `https://dirichlet.ai/algo_sdk` | `https://dirichlet.ai/algo_admin` |
|
|
115
115
|
|
|
116
116
|
客户端 SDK 只配置 SDK / REST API 地址。Admin / CLI 地址只给开发者、AI Agent 和 CI 使用。
|
|
117
117
|
|
package/cli/README.md
CHANGED
|
@@ -18,6 +18,7 @@ CLI 使用游戏维度的 Game Admin Key 鉴权。这个 key 只绑定一个游
|
|
|
18
18
|
```bash
|
|
19
19
|
npm install -g gamealgo-cli@latest
|
|
20
20
|
gamealgo help
|
|
21
|
+
gamealgo --version
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
更新 CLI 使用同一条命令:
|
|
@@ -28,6 +29,8 @@ npm install -g gamealgo-cli@latest
|
|
|
28
29
|
|
|
29
30
|
安装和更新不需要拉取 GameAlgo Client 仓库。Node.js 版本需要为 24 或更高。
|
|
30
31
|
|
|
32
|
+
Client SDK 源码仓库:<https://github.com/sunyuhao-code/GameAlgoClient>
|
|
33
|
+
|
|
31
34
|
只有开发 CLI 本身时才需要源码仓库。源码内调试可以用 npm script,但涉及 `--json` 的命令必须加 `--silent`,避免 npm 自己的日志污染 JSON stdout:
|
|
32
35
|
|
|
33
36
|
```bash
|
|
@@ -163,7 +166,8 @@ gamealgo experiment strategy publish strategy.yaml --yes
|
|
|
163
166
|
displayName: 广告频率第一轮
|
|
164
167
|
type: manual
|
|
165
168
|
platform: ios
|
|
166
|
-
|
|
169
|
+
objectiveTemplate: ltv_proxy@1
|
|
170
|
+
baselineVariantId: control
|
|
167
171
|
variants:
|
|
168
172
|
- variantId: control
|
|
169
173
|
weight: 1
|
|
@@ -189,15 +193,22 @@ gamealgo experiment run create ad_frequency run.yaml --yes
|
|
|
189
193
|
gamealgo experiment strategies
|
|
190
194
|
gamealgo experiment strategy show ad_frequency
|
|
191
195
|
gamealgo experiment run show xrun_xxxxxxxxxxxxxxxx
|
|
196
|
+
gamealgo experiment run status xrun_xxxxxxxxxxxxxxxx
|
|
192
197
|
gamealgo experiment run evaluate xrun_xxxxxxxxxxxxxxxx --from 2026-07-01 --to 2026-07-07 --yes
|
|
193
198
|
gamealgo experiment run report xrun_xxxxxxxxxxxxxxxx
|
|
194
|
-
gamealgo experiment run promote xrun_xxxxxxxxxxxxxxxx --variant slower_ads --yes
|
|
199
|
+
gamealgo experiment run promote xrun_xxxxxxxxxxxxxxxx --variant slower_ads --message "采用低频广告组" --yes
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`baselineVariantId` 是本次评估的对照组,必须指向一个有流量的实验组。平台按小时更新“当前评估”;`report` 返回已生成的正式报告。`promote` 会把指定 variant 写回 Strategy 默认参数,并结束当前 Run。取消实验用:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
gamealgo experiment run cancel xrun_xxxxxxxxxxxxxxxx --message "实验停止" --yes
|
|
195
206
|
```
|
|
196
207
|
|
|
197
|
-
|
|
208
|
+
回滚默认参数会创建一个新的参数版本,不会改写历史版本:
|
|
198
209
|
|
|
199
210
|
```bash
|
|
200
|
-
gamealgo experiment
|
|
211
|
+
gamealgo experiment strategy rollback ad_frequency --version-id xv_xxxxxxxxxxxxxxxx --message "回滚线上参数" --yes
|
|
201
212
|
```
|
|
202
213
|
|
|
203
214
|
调试指定用户/设备强制进某个实验组:
|
|
@@ -214,7 +225,8 @@ gamealgo experiment override delete xrun_xxxxxxxxxxxxxxxx --user user-1 --yes
|
|
|
214
225
|
displayName: 广告频率托管实验
|
|
215
226
|
type: managed
|
|
216
227
|
platform: ios
|
|
217
|
-
|
|
228
|
+
objectiveTemplate: ltv_proxy@1
|
|
229
|
+
baselineVariantId: alpha
|
|
218
230
|
cycleDays: 7
|
|
219
231
|
maxVariantsPerRound: 3
|
|
220
232
|
# 可选:仅托管实验支持。最近 24 小时同时满足两个门槛后才开始首轮。
|
|
@@ -232,6 +244,16 @@ variants:
|
|
|
232
244
|
|
|
233
245
|
`minCoverage` 表示指定平台最近 24 小时内,实验接入版本达到 Strategy `requiredIntegrationVersion` 的去重用户占比;`minUsers` 表示同一窗口内的非 Debug 去重用户数。两项可单独配置,同时配置时必须同时满足。等待期间 Strategy 继续下发默认参数,满足门槛后平台才生成首轮时间窗并开始分流。手动实验不支持 `startCondition`,托管实验不配置时仍会立即启动。`experiment run show` 返回 `startConditionSnapshot`,可查看当前用户数、覆盖率和最近检查时间。
|
|
234
246
|
|
|
247
|
+
托管实验只执行候选组数量决定的计划轮次,不会因为结果不显著而自动追加轮次。每轮结束都会生成正式报告;最后一轮只有在证据足够明确时才更新默认参数。没有明确胜出组时,实验正常完成,Strategy 默认参数保持不变。
|
|
248
|
+
|
|
249
|
+
`ltv_proxy@1` 的标准口径为:
|
|
250
|
+
|
|
251
|
+
```text
|
|
252
|
+
LTV Proxy = DAU ARPU * (1 + D1 + D2 + D3 + D4 + D5)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
其中 DAU ARPU 是实验窗口内总收入除以活跃用户天数;Dx 留存只使用截至评估时已经成熟的 cohort,并按 cohort 用户数加权。平台不会等待最后一天用户的 D5 成熟后再结束实验。
|
|
256
|
+
|
|
235
257
|
所有会修改线上状态的实验命令都必须显式传 `--yes`。
|
|
236
258
|
|
|
237
259
|
## 脚本和配置
|
package/dist/gamealgo.js
CHANGED
|
@@ -19,6 +19,16 @@ async function main() {
|
|
|
19
19
|
printHelp();
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
+
if (command === "version" || command === "--version" || command === "-V") {
|
|
23
|
+
const metadata = await loadPackageMetadata();
|
|
24
|
+
if (global.json) {
|
|
25
|
+
await printResult({ ok: true, name: metadata.name, version: metadata.version }, global);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log(metadata.version);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
22
32
|
try {
|
|
23
33
|
if (command === "login") {
|
|
24
34
|
await login(args, global);
|
|
@@ -353,7 +363,19 @@ async function handleExperimentStrategy(client, args, global) {
|
|
|
353
363
|
await printResult(await client.archiveExperimentV2Strategy(strategyKey, sub === "archive"), global);
|
|
354
364
|
return;
|
|
355
365
|
}
|
|
356
|
-
|
|
366
|
+
if (sub === "rollback") {
|
|
367
|
+
const flags = parseFlags(args);
|
|
368
|
+
const strategyKey = optionalString(flags.strategy || flags.key) || optionalString(args.shift());
|
|
369
|
+
const versionId = optionalString(flags.version || flags["version-id"]);
|
|
370
|
+
const message = requiredMessage(flags, "experiment strategy rollback");
|
|
371
|
+
if (!strategyKey || !versionId) {
|
|
372
|
+
throw new Error("usage: gamealgo experiment strategy rollback <strategyKey> --version-id <versionId> --message <reason> --yes");
|
|
373
|
+
}
|
|
374
|
+
await requireYes(flags, global, "experiment strategy rollback");
|
|
375
|
+
await printResult(await client.rollbackExperimentV2Strategy(strategyKey, versionId, message), global);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
throw new Error("usage: gamealgo experiment strategy <show|publish|default|archive|restore|rollback>");
|
|
357
379
|
}
|
|
358
380
|
async function handleExperimentRun(client, args, global) {
|
|
359
381
|
const sub = args.shift();
|
|
@@ -367,6 +389,12 @@ async function handleExperimentRun(client, args, global) {
|
|
|
367
389
|
const body = await readObjectFile(filePath, "experiment run");
|
|
368
390
|
if (!optionalString(body.displayName))
|
|
369
391
|
throw new Error("experiment run requires displayName");
|
|
392
|
+
if (optionalString(body.objectiveTemplate) !== "ltv_proxy@1") {
|
|
393
|
+
throw new Error("experiment run requires objectiveTemplate: ltv_proxy@1");
|
|
394
|
+
}
|
|
395
|
+
if (!optionalString(body.baselineVariantId)) {
|
|
396
|
+
throw new Error("experiment run requires explicit baselineVariantId");
|
|
397
|
+
}
|
|
370
398
|
if ((optionalString(body.type) || "manual") !== "managed" && body.startCondition !== undefined) {
|
|
371
399
|
throw new Error("experiment run startCondition is supported only when type is managed");
|
|
372
400
|
}
|
|
@@ -387,6 +415,33 @@ async function handleExperimentRun(client, args, global) {
|
|
|
387
415
|
await printResult(response, global);
|
|
388
416
|
return;
|
|
389
417
|
}
|
|
418
|
+
if (sub === "status") {
|
|
419
|
+
const flags = parseFlags(args);
|
|
420
|
+
const runId = optionalString(flags.run || flags["run-id"] || flags.id) || optionalString(args.shift());
|
|
421
|
+
if (!runId)
|
|
422
|
+
throw new Error("usage: gamealgo experiment run status <runId>");
|
|
423
|
+
const response = await client.getExperimentV2Run(runId);
|
|
424
|
+
const run = response.run;
|
|
425
|
+
if (!run)
|
|
426
|
+
throw new Error(`experiment run not found: ${runId}`);
|
|
427
|
+
await printResult({
|
|
428
|
+
run: {
|
|
429
|
+
id: run.id,
|
|
430
|
+
strategyKey: run.strategyKey,
|
|
431
|
+
displayName: run.displayName,
|
|
432
|
+
type: run.type,
|
|
433
|
+
status: run.status,
|
|
434
|
+
objective: run.objective,
|
|
435
|
+
baselineVariantId: run.baselineVariantId,
|
|
436
|
+
requiredIntegrationVersion: run.requiredIntegrationVersion,
|
|
437
|
+
startedAt: run.startedAt,
|
|
438
|
+
endedAt: run.endedAt,
|
|
439
|
+
promotedVariantId: run.promotedVariantId,
|
|
440
|
+
currentAssessment: run.currentAssessment ?? null,
|
|
441
|
+
},
|
|
442
|
+
}, global);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
390
445
|
if (sub === "evaluate") {
|
|
391
446
|
const flags = parseFlags(args);
|
|
392
447
|
const runId = optionalString(flags.run || flags["run-id"] || flags.id) || optionalString(args.shift());
|
|
@@ -397,7 +452,6 @@ async function handleExperimentRun(client, args, global) {
|
|
|
397
452
|
from: optionalString(flags.from || flags.start || flags.startDate),
|
|
398
453
|
to: optionalString(flags.to || flags.end || flags.endDate),
|
|
399
454
|
days: optionalString(flags.days),
|
|
400
|
-
platform: optionalString(flags.platform),
|
|
401
455
|
}), global);
|
|
402
456
|
return;
|
|
403
457
|
}
|
|
@@ -405,15 +459,29 @@ async function handleExperimentRun(client, args, global) {
|
|
|
405
459
|
const flags = parseFlags(args);
|
|
406
460
|
const runId = optionalString(flags.run || flags["run-id"] || flags.id) || optionalString(args.shift());
|
|
407
461
|
if (!runId)
|
|
408
|
-
throw new Error("usage: gamealgo experiment run report <runId> [--report-id <id>]");
|
|
462
|
+
throw new Error("usage: gamealgo experiment run report <runId> [--round <number> | --report-id <id>]");
|
|
409
463
|
const response = await client.getExperimentV2Run(runId);
|
|
410
464
|
const reports = Array.isArray(response.run?.reports) ? response.run.reports : [];
|
|
411
465
|
const reportId = optionalString(flags.report || flags["report-id"] || flags.id);
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
466
|
+
const round = flags.round === undefined ? undefined : positiveInteger(flags.round, "round");
|
|
467
|
+
const latestReport = objectValue(reports[reports.length - 1]);
|
|
468
|
+
const report = reports
|
|
469
|
+
.map((item) => objectValue(item))
|
|
470
|
+
.find((item) => {
|
|
471
|
+
if (!item)
|
|
472
|
+
return false;
|
|
473
|
+
if (reportId)
|
|
474
|
+
return item.id === reportId || item.evaluationId === reportId;
|
|
475
|
+
if (round !== undefined) {
|
|
476
|
+
const managed = objectValue(objectValue(item.report)?.managed);
|
|
477
|
+
return Number(managed?.roundNumber ?? Number(managed?.roundIndex) + 1) === round;
|
|
478
|
+
}
|
|
479
|
+
return item === latestReport;
|
|
480
|
+
});
|
|
481
|
+
if (!report) {
|
|
482
|
+
const selector = reportId ? `: ${reportId}` : round !== undefined ? ` for round ${round}` : "";
|
|
483
|
+
throw new Error(`experiment run report not found${selector}`);
|
|
484
|
+
}
|
|
417
485
|
await printResult({ runId, report }, global);
|
|
418
486
|
return;
|
|
419
487
|
}
|
|
@@ -422,18 +490,20 @@ async function handleExperimentRun(client, args, global) {
|
|
|
422
490
|
const runId = optionalString(flags.run || flags["run-id"] || flags.id) || optionalString(args.shift());
|
|
423
491
|
const variantId = optionalString(flags.variant || flags["variant-id"]) || optionalString(args.shift());
|
|
424
492
|
if (!runId || !variantId)
|
|
425
|
-
throw new Error("usage: gamealgo experiment run promote <runId> --variant <variantId> --yes");
|
|
493
|
+
throw new Error("usage: gamealgo experiment run promote <runId> --variant <variantId> --message <reason> --yes");
|
|
494
|
+
const message = requiredMessage(flags, "experiment run promote");
|
|
426
495
|
await requireYes(flags, global, "experiment run promote");
|
|
427
|
-
await printResult(await client.promoteExperimentV2Run(runId, variantId,
|
|
496
|
+
await printResult(await client.promoteExperimentV2Run(runId, variantId, message), global);
|
|
428
497
|
return;
|
|
429
498
|
}
|
|
430
499
|
if (sub === "cancel") {
|
|
431
500
|
const flags = parseFlags(args);
|
|
432
501
|
const runId = optionalString(flags.run || flags["run-id"] || flags.id) || optionalString(args.shift());
|
|
433
502
|
if (!runId)
|
|
434
|
-
throw new Error("usage: gamealgo experiment run cancel <runId> --yes");
|
|
503
|
+
throw new Error("usage: gamealgo experiment run cancel <runId> --message <reason> --yes");
|
|
504
|
+
const message = requiredMessage(flags, "experiment run cancel");
|
|
435
505
|
await requireYes(flags, global, "experiment run cancel");
|
|
436
|
-
await printResult(await client.cancelExperimentV2Run(runId), global);
|
|
506
|
+
await printResult(await client.cancelExperimentV2Run(runId, message), global);
|
|
437
507
|
return;
|
|
438
508
|
}
|
|
439
509
|
if (sub === "traffic") {
|
|
@@ -441,9 +511,10 @@ async function handleExperimentRun(client, args, global) {
|
|
|
441
511
|
const runId = optionalString(flags.run || flags["run-id"] || flags.id) || optionalString(args.shift());
|
|
442
512
|
const variantId = optionalString(flags.variant || flags["variant-id"]) || optionalString(args.shift());
|
|
443
513
|
if (!runId || !variantId)
|
|
444
|
-
throw new Error("usage: gamealgo experiment run traffic <runId> <variantId> [--weight n] [--status running|paused] --yes");
|
|
514
|
+
throw new Error("usage: gamealgo experiment run traffic <runId> <variantId> [--weight n] [--status running|paused] --message <reason> --yes");
|
|
515
|
+
const message = requiredMessage(flags, "experiment run traffic");
|
|
445
516
|
await requireYes(flags, global, "experiment run traffic");
|
|
446
|
-
const body = {};
|
|
517
|
+
const body = { message };
|
|
447
518
|
if (flags.weight !== undefined)
|
|
448
519
|
body.weight = optionalNonNegativeInteger(flags.weight, "weight");
|
|
449
520
|
if (flags.status !== undefined)
|
|
@@ -451,7 +522,13 @@ async function handleExperimentRun(client, args, global) {
|
|
|
451
522
|
await printResult(await client.updateExperimentV2VariantTraffic(runId, variantId, body), global);
|
|
452
523
|
return;
|
|
453
524
|
}
|
|
454
|
-
throw new Error("usage: gamealgo experiment run <create|show|evaluate|report|promote|cancel|traffic>");
|
|
525
|
+
throw new Error("usage: gamealgo experiment run <create|show|status|evaluate|report|promote|cancel|traffic>");
|
|
526
|
+
}
|
|
527
|
+
function requiredMessage(flags, action) {
|
|
528
|
+
const message = optionalString(flags.message);
|
|
529
|
+
if (!message)
|
|
530
|
+
throw new Error(`${action} requires --message <reason>`);
|
|
531
|
+
return message;
|
|
455
532
|
}
|
|
456
533
|
async function handleExperimentOverride(client, args, global) {
|
|
457
534
|
const sub = args.shift();
|
|
@@ -957,6 +1034,12 @@ class GameAlgoAdminClient {
|
|
|
957
1034
|
async archiveExperimentV2Strategy(strategyKey, archived) {
|
|
958
1035
|
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/strategies-v2/${encodeURIComponent(strategyKey)}/archive`, { archived });
|
|
959
1036
|
}
|
|
1037
|
+
async rollbackExperimentV2Strategy(strategyKey, versionId, message) {
|
|
1038
|
+
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/strategies-v2/${encodeURIComponent(strategyKey)}/rollback`, {
|
|
1039
|
+
versionId,
|
|
1040
|
+
message,
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
960
1043
|
async createExperimentV2Run(strategyKey, body) {
|
|
961
1044
|
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/strategies-v2/${encodeURIComponent(strategyKey)}/runs`, body);
|
|
962
1045
|
}
|
|
@@ -979,11 +1062,11 @@ class GameAlgoAdminClient {
|
|
|
979
1062
|
async promoteExperimentV2Run(runId, variantId, message) {
|
|
980
1063
|
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/experiment-runs-v2/${encodeURIComponent(runId)}/promote`, {
|
|
981
1064
|
variantId,
|
|
982
|
-
|
|
1065
|
+
message,
|
|
983
1066
|
});
|
|
984
1067
|
}
|
|
985
|
-
async cancelExperimentV2Run(runId) {
|
|
986
|
-
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/experiment-runs-v2/${encodeURIComponent(runId)}/cancel`, {});
|
|
1068
|
+
async cancelExperimentV2Run(runId, message) {
|
|
1069
|
+
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/experiment-runs-v2/${encodeURIComponent(runId)}/cancel`, { message });
|
|
987
1070
|
}
|
|
988
1071
|
async listExperimentV2Overrides(runId) {
|
|
989
1072
|
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/experiment-runs-v2/${encodeURIComponent(runId)}/overrides`);
|
|
@@ -1502,11 +1585,20 @@ function contentTypeForFileName(name) {
|
|
|
1502
1585
|
return "text/javascript; charset=utf-8";
|
|
1503
1586
|
return "text/plain; charset=utf-8";
|
|
1504
1587
|
}
|
|
1588
|
+
async function loadPackageMetadata() {
|
|
1589
|
+
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
1590
|
+
const name = typeof packageJson.name === "string" ? packageJson.name : "gamealgo-cli";
|
|
1591
|
+
if (typeof packageJson.version !== "string" || !packageJson.version) {
|
|
1592
|
+
throw new Error("CLI package version is missing");
|
|
1593
|
+
}
|
|
1594
|
+
return { name, version: packageJson.version };
|
|
1595
|
+
}
|
|
1505
1596
|
function printHelp() {
|
|
1506
1597
|
console.log(`
|
|
1507
1598
|
GameAlgo CLI
|
|
1508
1599
|
|
|
1509
1600
|
Usage:
|
|
1601
|
+
gamealgo --version
|
|
1510
1602
|
gamealgo login --host <admin-url> --admin-key <game-admin-key>
|
|
1511
1603
|
gamealgo whoami
|
|
1512
1604
|
|
|
@@ -1527,13 +1619,15 @@ Usage:
|
|
|
1527
1619
|
gamealgo experiment strategy publish strategy.yaml --yes
|
|
1528
1620
|
gamealgo experiment strategy default ad_frequency strategy.yaml --yes
|
|
1529
1621
|
gamealgo experiment strategy archive ad_frequency --yes
|
|
1622
|
+
gamealgo experiment strategy rollback ad_frequency --version-id xv_xxxxxxxxxxxxxxxx --message "回滚参数" --yes
|
|
1530
1623
|
gamealgo experiment run create ad_frequency run.yaml --yes
|
|
1531
1624
|
gamealgo experiment run show xrun_xxxxxxxxxxxxxxxx
|
|
1625
|
+
gamealgo experiment run status xrun_xxxxxxxxxxxxxxxx
|
|
1532
1626
|
gamealgo experiment run evaluate xrun_xxxxxxxxxxxxxxxx --from 2026-07-01 --to 2026-07-07 --yes
|
|
1533
|
-
gamealgo experiment run report xrun_xxxxxxxxxxxxxxxx
|
|
1534
|
-
gamealgo experiment run promote xrun_xxxxxxxxxxxxxxxx --variant bravo --yes
|
|
1535
|
-
gamealgo experiment run cancel xrun_xxxxxxxxxxxxxxxx --yes
|
|
1536
|
-
gamealgo experiment run traffic xrun_xxxxxxxxxxxxxxxx bravo --weight 0 --status paused --yes
|
|
1627
|
+
gamealgo experiment run report xrun_xxxxxxxxxxxxxxxx --round 1
|
|
1628
|
+
gamealgo experiment run promote xrun_xxxxxxxxxxxxxxxx --variant bravo --message "采用胜出组" --yes
|
|
1629
|
+
gamealgo experiment run cancel xrun_xxxxxxxxxxxxxxxx --message "停止实验" --yes
|
|
1630
|
+
gamealgo experiment run traffic xrun_xxxxxxxxxxxxxxxx bravo --weight 0 --status paused --message "暂停异常组" --yes
|
|
1537
1631
|
gamealgo experiment override set xrun_xxxxxxxxxxxxxxxx --user user-1 --variant bravo --yes
|
|
1538
1632
|
gamealgo experiment override list xrun_xxxxxxxxxxxxxxxx
|
|
1539
1633
|
|