gamealgo-cli 0.3.8 → 0.4.1
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 +18 -1
- package/dist/client.js +23 -2
- package/dist/commands/diagnosis.js +17 -0
- package/dist/commands/experiment.js +1 -1
- package/dist/commands/integration.js +1 -1
- package/dist/commands/keys.js +7 -3
- package/dist/commands/report.js +191 -54
- package/dist/compatibility.js +1 -1
- package/dist/docs.js +6 -6
- package/dist/main.js +5 -0
- package/dist/report-summary.js +14 -2
- package/dist/report-v2/expression.js +130 -0
- package/dist/report-v2/result.js +209 -0
- package/dist/report-v2/schema.js +18 -0
- package/dist/report-v2/summary.js +110 -0
- package/dist/report-v2/view-executor.js +198 -0
- package/dist/utils.js +4 -4
- package/package.json +1 -1
- package/skills/gamealgo/SKILL.md +20 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GameAlgo CLI
|
|
2
2
|
|
|
3
|
-
`gamealgo-cli` is the independently released command-line client for GameAlgo. It is intended for AI coding agents and game developers to manage integration plans, event dictionaries, reports, scripts, configuration, keys, and
|
|
3
|
+
`gamealgo-cli` is the independently released command-line client for GameAlgo. It is intended for AI coding agents and game developers to manage integration plans, event dictionaries, reports, scripts, configuration, keys, experiments, and game diagnosis.
|
|
4
4
|
|
|
5
5
|
Source repository: <https://github.com/sunyuhao-code/GameAlgoCli>
|
|
6
6
|
|
|
@@ -35,6 +35,20 @@ gamealgo listapi
|
|
|
35
35
|
gamealgo docs api GameAlgo.Init --platform lua
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
Report Pack v2 uses materialized ordinary Datasets and inline Views. Common report commands are:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
gamealgo report pull --out gamealgo-report-pack.json
|
|
42
|
+
gamealgo report validate gamealgo-report-pack.json
|
|
43
|
+
gamealgo report preview --pack gamealgo-report-pack.json
|
|
44
|
+
gamealgo report publish gamealgo-report-pack.json
|
|
45
|
+
gamealgo report result --from 2026-09-04 --to 2026-09-17 --platform maker --tab-id overview
|
|
46
|
+
gamealgo report advanced run --query-id battle_analysis --from 2026-09-04 --to 2026-09-17 --platform maker --wait
|
|
47
|
+
gamealgo report advanced list
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The current CLI reads legacy v1 Packs during migration, but all Report Pack write commands require `schemaVersion: 2`. Advanced query runs are explicit, are never preloaded, and remain available as immutable reports for 30 days.
|
|
51
|
+
|
|
38
52
|
Help and documentation are fetched from the configured Server on every invocation. The CLI intentionally does not keep a documentation cache.
|
|
39
53
|
|
|
40
54
|
## Agent skill
|
|
@@ -51,8 +65,11 @@ The skill uses the project-bound CLI and live Server content. It can discover th
|
|
|
51
65
|
```bash
|
|
52
66
|
gamealgo optimize list --json
|
|
53
67
|
gamealgo optimize get ad-monetization --json
|
|
68
|
+
gamealgo diagnosis --json
|
|
54
69
|
```
|
|
55
70
|
|
|
71
|
+
`gamealgo diagnosis` returns the same structured competition, monetization, and retention diagnosis shown in the dashboard. It requires a Game Admin Key with `reports:read` and an account-verified Maker game binding; save the complete result with `--out diagnosis.json` when an agent needs to use it in a local workflow.
|
|
72
|
+
|
|
56
73
|
Adjust S2S event forwarding can be configured with an explicit event allowlist:
|
|
57
74
|
|
|
58
75
|
```bash
|
package/dist/client.js
CHANGED
|
@@ -168,6 +168,24 @@ export class GameAlgoAdminClient {
|
|
|
168
168
|
const query = params.size ? `?${params.toString()}` : "";
|
|
169
169
|
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/reports/manifest${query}`);
|
|
170
170
|
}
|
|
171
|
+
async readReportDataset(input) {
|
|
172
|
+
const params = new URLSearchParams({
|
|
173
|
+
platform: input.platform,
|
|
174
|
+
startDate: input.startDate,
|
|
175
|
+
endDate: input.endDate,
|
|
176
|
+
});
|
|
177
|
+
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/report-datasets/${encodeURIComponent(input.datasetFingerprint)}/results?${params.toString()}`);
|
|
178
|
+
}
|
|
179
|
+
async createAdvancedReportRun(body, options = {}) {
|
|
180
|
+
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/reports/advanced-runs`, body, options);
|
|
181
|
+
}
|
|
182
|
+
async getAdvancedReportRun(runId, includeResult = true) {
|
|
183
|
+
const params = new URLSearchParams({ includeResult: includeResult ? "true" : "false" });
|
|
184
|
+
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/reports/advanced-runs/${encodeURIComponent(runId)}?${params.toString()}`);
|
|
185
|
+
}
|
|
186
|
+
async listAdvancedReportRuns(limit = 50) {
|
|
187
|
+
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/reports/advanced-runs?limit=${encodeURIComponent(String(limit))}`);
|
|
188
|
+
}
|
|
171
189
|
async queryReportDashboard(body, options = {}) {
|
|
172
190
|
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/reports/query`, body, options);
|
|
173
191
|
}
|
|
@@ -209,6 +227,9 @@ export class GameAlgoAdminClient {
|
|
|
209
227
|
const params = new URLSearchParams({ environment, platform });
|
|
210
228
|
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/integration/plan?${params.toString()}`);
|
|
211
229
|
}
|
|
230
|
+
async getGameDiagnosis() {
|
|
231
|
+
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/diagnosis`);
|
|
232
|
+
}
|
|
212
233
|
async listOptimizationTasks() {
|
|
213
234
|
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/optimization/tasks`);
|
|
214
235
|
}
|
|
@@ -228,8 +249,8 @@ export class GameAlgoAdminClient {
|
|
|
228
249
|
async listGameKeys() {
|
|
229
250
|
return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/keys`);
|
|
230
251
|
}
|
|
231
|
-
async createGameKey(name) {
|
|
232
|
-
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/keys`, { name });
|
|
252
|
+
async createGameKey(name, platform) {
|
|
253
|
+
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/keys`, { name, ...(platform ? { platform } : {}) });
|
|
233
254
|
}
|
|
234
255
|
async revealGameKey(selector) {
|
|
235
256
|
return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/keys/reveal`, selector);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { parseFlags } from "../args.js";
|
|
2
|
+
import { printResult } from "../output.js";
|
|
3
|
+
import { writeTextFile } from "../resources.js";
|
|
4
|
+
export async function handleDiagnosis(client, args, global) {
|
|
5
|
+
const flags = parseFlags(args);
|
|
6
|
+
if (args.length)
|
|
7
|
+
throw new Error("usage: gamealgo diagnosis [--out diagnosis.json]");
|
|
8
|
+
if (flags.out === true)
|
|
9
|
+
throw new Error("--out requires a file path");
|
|
10
|
+
const diagnosis = await client.getGameDiagnosis();
|
|
11
|
+
if (typeof flags.out === "string") {
|
|
12
|
+
await writeTextFile(flags.out, `${JSON.stringify(diagnosis, null, 2)}\n`);
|
|
13
|
+
await printResult({ ok: true, out: flags.out }, global);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await printResult(diagnosis, global);
|
|
17
|
+
}
|
|
@@ -129,7 +129,7 @@ export async function handleExperimentStrategy(client, args, global) {
|
|
|
129
129
|
const platform = flags.platform === undefined ? undefined : experimentPlatform(flags.platform);
|
|
130
130
|
const message = requiredMessage(flags, "experiment strategy rollback");
|
|
131
131
|
if (!strategyKey || !versionId) {
|
|
132
|
-
throw new Error("usage: gamealgo experiment strategy rollback <strategyKey> --version-id <versionId> [--platform ios|android|
|
|
132
|
+
throw new Error("usage: gamealgo experiment strategy rollback <strategyKey> --version-id <versionId> [--platform ios|android|maker|web] --message <reason> --yes");
|
|
133
133
|
}
|
|
134
134
|
await requireYes(flags, global, "experiment strategy rollback");
|
|
135
135
|
await printResult(await client.rollbackExperimentV2Strategy(strategyKey, versionId, message, platform), global);
|
|
@@ -16,7 +16,7 @@ export async function handleIntegration(client, args, global) {
|
|
|
16
16
|
await writeIntegrationOutput(response, flags, global);
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
|
-
throw new Error("usage: gamealgo integration get-plan --environment <domestic|overseas> --platform <ios|android|maker|
|
|
19
|
+
throw new Error("usage: gamealgo integration get-plan --environment <domestic|overseas> --platform <ios|android|maker|web>");
|
|
20
20
|
}
|
|
21
21
|
export async function writeIntegrationOutput(value, flags, global) {
|
|
22
22
|
if (flags.out) {
|
package/dist/commands/keys.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseFlags } from "../args.js";
|
|
2
2
|
import { confirm, isInteractive, printResult } from "../output.js";
|
|
3
3
|
import { announceTargetGame } from "../target.js";
|
|
4
|
-
import { optionalString } from "../utils.js";
|
|
4
|
+
import { integrationPlatform, optionalString } from "../utils.js";
|
|
5
5
|
export async function handleGameKey(client, args, global) {
|
|
6
6
|
const sub = args.shift();
|
|
7
7
|
if (sub === "list") {
|
|
@@ -15,8 +15,9 @@ export async function handleGameKey(client, args, global) {
|
|
|
15
15
|
const flags = parseFlags(args);
|
|
16
16
|
const name = optionalString(flags.name) || optionalString(args.shift());
|
|
17
17
|
if (!name)
|
|
18
|
-
throw new Error("usage: gamealgo key create --name <key-name>");
|
|
19
|
-
const
|
|
18
|
+
throw new Error("usage: gamealgo key create --name <key-name> [--platform ios|android|maker|web]");
|
|
19
|
+
const platform = flags.platform === undefined ? undefined : integrationPlatform(flags.platform);
|
|
20
|
+
const response = await client.createGameKey(name, platform);
|
|
20
21
|
await printResult({
|
|
21
22
|
ok: true,
|
|
22
23
|
created: response.created !== false,
|
|
@@ -25,6 +26,7 @@ export async function handleGameKey(client, args, global) {
|
|
|
25
26
|
prefix: response.key.keyPrefix,
|
|
26
27
|
keyPrefix: response.key.keyPrefix,
|
|
27
28
|
status: response.key.status,
|
|
29
|
+
platform: response.key.platform,
|
|
28
30
|
createdAt: response.key.createdAt,
|
|
29
31
|
}, global);
|
|
30
32
|
return;
|
|
@@ -40,6 +42,7 @@ export async function handleGameKey(client, args, global) {
|
|
|
40
42
|
prefix: response.key.keyPrefix,
|
|
41
43
|
keyPrefix: response.key.keyPrefix,
|
|
42
44
|
status: response.key.status,
|
|
45
|
+
platform: response.key.platform,
|
|
43
46
|
createdAt: response.key.createdAt,
|
|
44
47
|
}, global);
|
|
45
48
|
return;
|
|
@@ -72,6 +75,7 @@ export function publicGameKeyForCli(key) {
|
|
|
72
75
|
prefix: key.keyPrefix,
|
|
73
76
|
keyPrefix: key.keyPrefix,
|
|
74
77
|
status: key.status,
|
|
78
|
+
platform: key.platform,
|
|
75
79
|
createdAt: key.createdAt,
|
|
76
80
|
};
|
|
77
81
|
}
|
package/dist/commands/report.js
CHANGED
|
@@ -4,16 +4,24 @@ import { formatDuration, markCommandFailed, printResult, reportTimeoutMs, startP
|
|
|
4
4
|
import { reportDashboardCsv, reportDashboardCsvExports } from "../report-csv.js";
|
|
5
5
|
import { normalizeReportSampleRows, summarizeReportDashboard, summarizeReportManifest, summarizeReportValidation, } from "../report-summary.js";
|
|
6
6
|
import { objectValue, readJsonFile, writeTextFile } from "../resources.js";
|
|
7
|
+
import { queryReportPackV2Result } from "../report-v2/result.js";
|
|
8
|
+
import { assertWritableReportPackV2, reportPackSchemaVersion } from "../report-v2/schema.js";
|
|
9
|
+
import { summarizeReportPackV2Manifest } from "../report-v2/summary.js";
|
|
7
10
|
import { announceTargetGame } from "../target.js";
|
|
8
11
|
import { optionalString } from "../utils.js";
|
|
9
12
|
export async function handleReport(client, args, global) {
|
|
10
13
|
const sub = args.shift();
|
|
14
|
+
if (sub === "advanced") {
|
|
15
|
+
await handleAdvancedReport(client, args, global);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
11
18
|
if (sub === "pull") {
|
|
12
19
|
const flags = parseFlags(args);
|
|
13
20
|
const pack = await client.getReportPack();
|
|
14
|
-
const
|
|
21
|
+
const schemaVersion = reportPackSchemaVersion(pack.reportPack.content);
|
|
22
|
+
const out = String(flags.out || `gamealgo-report-pack-v${schemaVersion}.json`);
|
|
15
23
|
await writeTextFile(out, JSON.stringify(pack.reportPack.content, null, 2) + "\n");
|
|
16
|
-
await printResult({ ok: true, out }, global);
|
|
24
|
+
await printResult({ ok: true, out, schemaVersion }, global);
|
|
17
25
|
return;
|
|
18
26
|
}
|
|
19
27
|
if (sub === "validate") {
|
|
@@ -23,6 +31,7 @@ export async function handleReport(client, args, global) {
|
|
|
23
31
|
const flags = parseFlags(args);
|
|
24
32
|
const full = reportFullOutput(flags);
|
|
25
33
|
const content = await readJsonFile(filePath);
|
|
34
|
+
assertWritableReportPackV2(content, "report validate");
|
|
26
35
|
const response = await client.previewReportPack(content, { responseMode: full ? "full" : "summary" });
|
|
27
36
|
const summary = summarizeReportValidation(response);
|
|
28
37
|
await printResult(full ? response : summary, global);
|
|
@@ -36,6 +45,7 @@ export async function handleReport(client, args, global) {
|
|
|
36
45
|
throw new Error("usage: gamealgo report publish <report-pack.json>");
|
|
37
46
|
parseFlags(args);
|
|
38
47
|
const content = await readJsonFile(filePath);
|
|
48
|
+
assertWritableReportPackV2(content, "report publish");
|
|
39
49
|
await announceTargetGame(client);
|
|
40
50
|
const response = await client.putReportPack(content);
|
|
41
51
|
await printResult({ ok: true, validation: response.reportPack.validation }, global);
|
|
@@ -45,7 +55,10 @@ export async function handleReport(client, args, global) {
|
|
|
45
55
|
const flags = parseFlags(args);
|
|
46
56
|
const full = reportFullOutput(flags);
|
|
47
57
|
const response = await client.reportManifest({ responseMode: full ? "full" : "summary" });
|
|
48
|
-
|
|
58
|
+
const manifest = objectValue(objectValue(response)?.manifest);
|
|
59
|
+
await printResult(full
|
|
60
|
+
? response
|
|
61
|
+
: manifest?.schemaVersion === 2 ? summarizeReportPackV2Manifest(response) : summarizeReportManifest(response), global);
|
|
49
62
|
return;
|
|
50
63
|
}
|
|
51
64
|
if (sub === "result") {
|
|
@@ -54,32 +67,50 @@ export async function handleReport(client, args, global) {
|
|
|
54
67
|
const startDate = String(flags.from || flags.start || flags.startDate || "");
|
|
55
68
|
const endDate = String(flags.to || flags.end || flags.endDate || "");
|
|
56
69
|
if (!startDate || !endDate)
|
|
57
|
-
throw new Error("usage: gamealgo report result --from YYYY-MM-DD --to YYYY-MM-DD [--platform ios|android|
|
|
70
|
+
throw new Error("usage: gamealgo report result --from YYYY-MM-DD --to YYYY-MM-DD [--platform ios|android|maker|web] [--tab name] [--group name] [--chart name] [--selector k=v] [--sample-rows 0..20] [--full] [--format json|csv] [--out file-or-directory]");
|
|
58
71
|
requireReportQueryScope(flags, "report result");
|
|
59
72
|
const timeoutMs = reportTimeoutMs(flags);
|
|
60
73
|
const format = reportOutputFormat(flags.format);
|
|
61
74
|
const full = reportFullOutput(flags) || format === "csv";
|
|
62
75
|
const sampleRows = reportSampleRows(flags, full);
|
|
63
76
|
const platform = await resolveCliReportPlatform(client, flags.platform, selectors.platform, startDate, endDate);
|
|
77
|
+
const manifestResponse = await client.reportManifest({ responseMode: "full" });
|
|
78
|
+
const manifest = objectValue(objectValue(manifestResponse)?.manifest);
|
|
79
|
+
const schemaVersion = manifest?.schemaVersion === 2 ? 2 : 1;
|
|
64
80
|
const startedAt = Date.now();
|
|
65
|
-
const stopProgress = startProgress("Querying report results", timeoutMs);
|
|
81
|
+
const stopProgress = startProgress(schemaVersion === 2 ? "Reading report results" : "Querying report results", timeoutMs);
|
|
66
82
|
let response;
|
|
67
83
|
try {
|
|
68
|
-
response =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
response = schemaVersion === 2
|
|
85
|
+
? await queryReportPackV2Result(client, manifestResponse, {
|
|
86
|
+
startDate,
|
|
87
|
+
endDate,
|
|
88
|
+
tab: optionalString(flags.tab),
|
|
89
|
+
tabId: optionalString(flags["tab-id"]),
|
|
90
|
+
group: optionalString(flags.group),
|
|
91
|
+
groupId: optionalString(flags["group-id"]),
|
|
92
|
+
chart: optionalString(flags.chart),
|
|
93
|
+
chartId: optionalString(flags["chart-id"]),
|
|
94
|
+
platform,
|
|
95
|
+
selectors,
|
|
96
|
+
refresh: Boolean(flags.refresh),
|
|
97
|
+
timeoutMs,
|
|
98
|
+
})
|
|
99
|
+
: await client.queryReportDashboard({
|
|
100
|
+
startDate,
|
|
101
|
+
endDate,
|
|
102
|
+
tab: optionalString(flags.tab),
|
|
103
|
+
tabId: optionalString(flags["tab-id"]),
|
|
104
|
+
group: optionalString(flags.group),
|
|
105
|
+
groupId: optionalString(flags["group-id"]),
|
|
106
|
+
chart: optionalString(flags.chart),
|
|
107
|
+
chartId: optionalString(flags["chart-id"]),
|
|
108
|
+
platform,
|
|
109
|
+
selectors,
|
|
110
|
+
refresh: Boolean(flags.refresh),
|
|
111
|
+
responseMode: full ? "full" : "summary",
|
|
112
|
+
sampleRows,
|
|
113
|
+
}, { timeoutMs });
|
|
83
114
|
}
|
|
84
115
|
finally {
|
|
85
116
|
stopProgress();
|
|
@@ -93,53 +124,159 @@ export async function handleReport(client, args, global) {
|
|
|
93
124
|
return;
|
|
94
125
|
}
|
|
95
126
|
if (sub === "preview") {
|
|
96
|
-
const selectors = parseSelectorFlags(args);
|
|
97
127
|
const flags = parseFlags(args);
|
|
98
128
|
const filePath = String(flags.pack || flags.file || args.shift() || "");
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (!filePath || !startDate || !endDate) {
|
|
102
|
-
throw new Error("usage: gamealgo report preview --pack report-pack.json --from YYYY-MM-DD --to YYYY-MM-DD [--platform ios|android|rest] [--tab name] [--group name] [--chart name] [--selector k=v] [--sample-rows 0..20] [--full] [--format json|csv] [--out file-or-directory]");
|
|
103
|
-
}
|
|
104
|
-
requireReportQueryScope(flags, "report preview");
|
|
129
|
+
if (!filePath)
|
|
130
|
+
throw new Error("usage: gamealgo report preview --pack report-pack.json [--full]");
|
|
105
131
|
const content = await readJsonFile(filePath);
|
|
132
|
+
assertWritableReportPackV2(content, "report preview");
|
|
106
133
|
const timeoutMs = reportTimeoutMs(flags);
|
|
107
|
-
const
|
|
108
|
-
const full = reportFullOutput(flags) || format === "csv";
|
|
109
|
-
const sampleRows = reportSampleRows(flags, full);
|
|
110
|
-
const platform = await resolveCliReportPlatform(client, flags.platform, selectors.platform, startDate, endDate);
|
|
134
|
+
const full = reportFullOutput(flags);
|
|
111
135
|
const startedAt = Date.now();
|
|
112
|
-
const stopProgress = startProgress("
|
|
136
|
+
const stopProgress = startProgress("Validating Report Pack v2 preview", timeoutMs);
|
|
113
137
|
let response;
|
|
114
138
|
try {
|
|
115
|
-
response = await client.
|
|
116
|
-
content,
|
|
117
|
-
startDate,
|
|
118
|
-
endDate,
|
|
119
|
-
tab: optionalString(flags.tab),
|
|
120
|
-
tabId: optionalString(flags["tab-id"]),
|
|
121
|
-
group: optionalString(flags.group),
|
|
122
|
-
groupId: optionalString(flags["group-id"]),
|
|
123
|
-
chart: optionalString(flags.chart),
|
|
124
|
-
chartId: optionalString(flags["chart-id"]),
|
|
125
|
-
platform,
|
|
126
|
-
selectors,
|
|
127
|
-
responseMode: full ? "full" : "summary",
|
|
128
|
-
sampleRows,
|
|
129
|
-
}, { timeoutMs });
|
|
139
|
+
response = await client.previewReportPack(content, { responseMode: full ? "full" : "summary" });
|
|
130
140
|
}
|
|
131
141
|
finally {
|
|
132
142
|
stopProgress();
|
|
133
143
|
}
|
|
134
144
|
const elapsedMs = Date.now() - startedAt;
|
|
135
145
|
const rawOutputValue = withCliMeta(response, { elapsedMs, timeoutMs });
|
|
136
|
-
const statusSummary =
|
|
146
|
+
const statusSummary = summarizeReportValidation(rawOutputValue, "report.preview");
|
|
137
147
|
const outputValue = full ? rawOutputValue : statusSummary;
|
|
138
|
-
process.stderr.write(`Report preview finished in ${formatDuration(elapsedMs)}.\n`);
|
|
139
|
-
|
|
148
|
+
process.stderr.write(`Report Pack preview finished in ${formatDuration(elapsedMs)}.\n`);
|
|
149
|
+
const out = optionalString(flags.out);
|
|
150
|
+
if (flags.out === true)
|
|
151
|
+
throw new Error("--out requires a file path");
|
|
152
|
+
if (out) {
|
|
153
|
+
await writeTextFile(out, JSON.stringify(outputValue, null, 2) + "\n");
|
|
154
|
+
await printResult({ ok: statusSummary.ok, out, elapsedMs }, global);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
await printResult(outputValue, global);
|
|
158
|
+
}
|
|
159
|
+
if (!statusSummary.ok)
|
|
160
|
+
markCommandFailed();
|
|
140
161
|
return;
|
|
141
162
|
}
|
|
142
|
-
throw new Error("usage: gamealgo report <pull|validate|publish|manifest|result|preview>");
|
|
163
|
+
throw new Error("usage: gamealgo report <pull|validate|publish|manifest|result|preview|advanced>");
|
|
164
|
+
}
|
|
165
|
+
async function handleAdvancedReport(client, args, global) {
|
|
166
|
+
const action = args.shift();
|
|
167
|
+
if (action === "run") {
|
|
168
|
+
const flags = parseFlags(args);
|
|
169
|
+
const queryId = requiredTextFlag(flags["query-id"], "--query-id");
|
|
170
|
+
const startDate = requiredTextFlag(flags.from || flags.start || flags.startDate, "--from");
|
|
171
|
+
const endDate = requiredTextFlag(flags.to || flags.end || flags.endDate, "--to");
|
|
172
|
+
const platform = optionalCliReportPlatform(flags.platform, "--platform");
|
|
173
|
+
if (!platform)
|
|
174
|
+
throw new Error("--platform is required for report advanced run");
|
|
175
|
+
if (flags.format !== undefined && flags.format !== "json")
|
|
176
|
+
throw new Error("report advanced currently supports only --format json");
|
|
177
|
+
const packPath = optionalString(flags.pack);
|
|
178
|
+
if (flags.pack === true)
|
|
179
|
+
throw new Error("--pack requires a file path");
|
|
180
|
+
const pack = packPath ? await readJsonFile(packPath) : undefined;
|
|
181
|
+
if (pack !== undefined)
|
|
182
|
+
assertWritableReportPackV2(pack, "report advanced run");
|
|
183
|
+
const wait = booleanFlag(flags.wait, "--wait");
|
|
184
|
+
const timeoutMs = reportTimeoutMs(flags) ?? 15 * 60 * 1000;
|
|
185
|
+
await announceTargetGame(client);
|
|
186
|
+
const created = await client.createAdvancedReportRun({
|
|
187
|
+
queryId,
|
|
188
|
+
startDate,
|
|
189
|
+
endDate,
|
|
190
|
+
platform,
|
|
191
|
+
...(pack !== undefined ? { pack } : {}),
|
|
192
|
+
}, { timeoutMs: Math.min(timeoutMs, 30_000) });
|
|
193
|
+
if (!wait) {
|
|
194
|
+
await writeAdvancedOutput(created, flags, global);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const runId = requiredRunId(created.run?.id);
|
|
198
|
+
const startedAt = Date.now();
|
|
199
|
+
const stopProgress = startProgress(`Waiting for advanced report ${runId}`, timeoutMs);
|
|
200
|
+
let response;
|
|
201
|
+
try {
|
|
202
|
+
response = await waitForAdvancedReport(client, runId, timeoutMs, startedAt);
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
stopProgress();
|
|
206
|
+
}
|
|
207
|
+
await writeAdvancedOutput(withCliMeta(response, { elapsedMs: Date.now() - startedAt, timeoutMs }), flags, global);
|
|
208
|
+
if (response.run.status === "failed")
|
|
209
|
+
markCommandFailed();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (action === "get") {
|
|
213
|
+
const runId = args.shift();
|
|
214
|
+
if (!runId)
|
|
215
|
+
throw new Error("usage: gamealgo report advanced get <run-id> [--metadata-only] [--out file]");
|
|
216
|
+
const flags = parseFlags(args);
|
|
217
|
+
const metadataOnly = booleanFlag(flags["metadata-only"], "--metadata-only");
|
|
218
|
+
const response = await client.getAdvancedReportRun(runId, !metadataOnly);
|
|
219
|
+
await writeAdvancedOutput(response, flags, global);
|
|
220
|
+
if (response.run.status === "failed")
|
|
221
|
+
markCommandFailed();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (action === "list") {
|
|
225
|
+
const flags = parseFlags(args);
|
|
226
|
+
const limit = positiveIntegerFlag(flags.limit, "--limit", 50);
|
|
227
|
+
await writeAdvancedOutput(await client.listAdvancedReportRuns(limit), flags, global);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
throw new Error("usage: gamealgo report advanced <run|get|list>");
|
|
231
|
+
}
|
|
232
|
+
async function waitForAdvancedReport(client, runId, timeoutMs, startedAt) {
|
|
233
|
+
while (true) {
|
|
234
|
+
const response = await client.getAdvancedReportRun(runId, true);
|
|
235
|
+
if (response.run.status === "succeeded" || response.run.status === "failed")
|
|
236
|
+
return response;
|
|
237
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
238
|
+
throw new Error(`advanced report ${runId} did not finish within ${formatDuration(timeoutMs)}; use report advanced get ${runId} later`);
|
|
239
|
+
}
|
|
240
|
+
await new Promise((resolve) => setTimeout(resolve, 2_000));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function writeAdvancedOutput(value, flags, global) {
|
|
244
|
+
if (flags.format !== undefined && flags.format !== "json")
|
|
245
|
+
throw new Error("report advanced currently supports only --format json");
|
|
246
|
+
const out = optionalString(flags.out);
|
|
247
|
+
if (flags.out === true)
|
|
248
|
+
throw new Error("--out requires a file path");
|
|
249
|
+
if (!out) {
|
|
250
|
+
await printResult(value, global);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
await writeTextFile(out, JSON.stringify(value, null, 2) + "\n");
|
|
254
|
+
await printResult({ ok: true, out }, global);
|
|
255
|
+
}
|
|
256
|
+
function requiredTextFlag(value, flag) {
|
|
257
|
+
if (typeof value !== "string" || !value.trim())
|
|
258
|
+
throw new Error(`${flag} is required`);
|
|
259
|
+
return value.trim();
|
|
260
|
+
}
|
|
261
|
+
function requiredRunId(value) {
|
|
262
|
+
if (typeof value !== "string" || !value)
|
|
263
|
+
throw new Error("advanced report create response is missing run.id");
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
function booleanFlag(value, flag) {
|
|
267
|
+
if (value === undefined)
|
|
268
|
+
return false;
|
|
269
|
+
if (value !== true)
|
|
270
|
+
throw new Error(`${flag} does not accept a value`);
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
function positiveIntegerFlag(value, flag, fallback) {
|
|
274
|
+
if (value === undefined)
|
|
275
|
+
return fallback;
|
|
276
|
+
if (typeof value !== "string" || !/^\d+$/.test(value) || Number(value) < 1) {
|
|
277
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
278
|
+
}
|
|
279
|
+
return Number(value);
|
|
143
280
|
}
|
|
144
281
|
function reportFullOutput(flags) {
|
|
145
282
|
if (flags.full === undefined)
|
|
@@ -293,7 +430,7 @@ export async function resolveCliReportPlatform(client, flagValue, selectorValue,
|
|
|
293
430
|
const detected = objectValue(await client.countEventPlatforms(startDate, endDate));
|
|
294
431
|
const detectedPlatform = optionalCliReportPlatform(detected?.recommendedPlatform, "detected report platform");
|
|
295
432
|
if (!detectedPlatform) {
|
|
296
|
-
throw new Error("report platform could not be detected for the selected date range; pass --platform ios,
|
|
433
|
+
throw new Error("report platform could not be detected for the selected date range; pass --platform ios, android, maker, or web");
|
|
297
434
|
}
|
|
298
435
|
process.stderr.write(`Report platform: ${detectedPlatform} (detected from event data)\n`);
|
|
299
436
|
return detectedPlatform;
|
|
@@ -301,7 +438,7 @@ export async function resolveCliReportPlatform(client, flagValue, selectorValue,
|
|
|
301
438
|
export function optionalCliReportPlatform(value, field) {
|
|
302
439
|
if (value === undefined || value === null || value === "")
|
|
303
440
|
return undefined;
|
|
304
|
-
if (value === "ios" || value === "android" || value === "
|
|
441
|
+
if (value === "ios" || value === "android" || value === "maker" || value === "web")
|
|
305
442
|
return value;
|
|
306
|
-
throw new Error(`${field} must be ios, android, or
|
|
443
|
+
throw new Error(`${field} must be ios, android, maker, or web`);
|
|
307
444
|
}
|
package/dist/compatibility.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadPackageMetadata } from "./config.js";
|
|
2
2
|
import { requestJson } from "./http.js";
|
|
3
|
-
export const CLI_PROTOCOL_VERSION =
|
|
3
|
+
export const CLI_PROTOCOL_VERSION = 2;
|
|
4
4
|
export async function assertServerCompatibility(host) {
|
|
5
5
|
const metadata = await requestJson(host, "/admin/v1/cli/meta", {
|
|
6
6
|
headers: { "Cache-Control": "no-cache" },
|
package/dist/docs.js
CHANGED
|
@@ -459,12 +459,12 @@ export function parseDocsApiPlatform(value) {
|
|
|
459
459
|
if (value === undefined || value === false || value === "")
|
|
460
460
|
return undefined;
|
|
461
461
|
const platform = String(value).trim().toLowerCase();
|
|
462
|
-
if (platform === "
|
|
463
|
-
return "
|
|
464
|
-
if (platform === "
|
|
462
|
+
if (platform === "lua")
|
|
463
|
+
return "maker";
|
|
464
|
+
if (platform === "maker" || platform === "ios" || platform === "android" || platform === "web") {
|
|
465
465
|
return platform;
|
|
466
466
|
}
|
|
467
|
-
throw new Error("--platform must be
|
|
467
|
+
throw new Error("--platform must be ios, android, maker, or web");
|
|
468
468
|
}
|
|
469
469
|
export async function printDocsApiResult(options) {
|
|
470
470
|
const { query, platform, manifest, topic, document, webUrl, host, global } = options;
|
|
@@ -537,8 +537,8 @@ export function parseDocsApiEntries(content) {
|
|
|
537
537
|
if (!symbols.length)
|
|
538
538
|
continue;
|
|
539
539
|
const platforms = parseDocsApiMetadataList(section, "平台")
|
|
540
|
-
.map((item) => item.toLowerCase())
|
|
541
|
-
.filter((item) => item === "
|
|
540
|
+
.map((item) => item.toLowerCase() === "lua" ? "maker" : item.toLowerCase())
|
|
541
|
+
.filter((item) => item === "maker" || item === "ios" || item === "android" || item === "web");
|
|
542
542
|
const summaryMatch = section.match(/^-\s*用途[::]\s*(.+)$/m);
|
|
543
543
|
entries.push({
|
|
544
544
|
name: stripInlineMarkdown(heading[1]),
|
package/dist/main.js
CHANGED
|
@@ -13,6 +13,7 @@ import { handleSkill } from "./commands/skill.js";
|
|
|
13
13
|
import { loadPackageMetadata } from "./config.js";
|
|
14
14
|
import { handleDocs, handleHelp } from "./docs.js";
|
|
15
15
|
import { handleDynamicConfig } from "./commands/dynamic-config.js";
|
|
16
|
+
import { handleDiagnosis } from "./commands/diagnosis.js";
|
|
16
17
|
import { cliErrorPayload } from "./http.js";
|
|
17
18
|
import { printResult } from "./output.js";
|
|
18
19
|
export async function main() {
|
|
@@ -98,6 +99,10 @@ export async function main() {
|
|
|
98
99
|
await handleIntegration(client, args, global);
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
102
|
+
if (command === "diagnosis" || command === "diagnose") {
|
|
103
|
+
await handleDiagnosis(client, args, global);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
101
106
|
if (command === "marketing") {
|
|
102
107
|
await handleMarketing(client, args, global);
|
|
103
108
|
return;
|
package/dist/report-summary.js
CHANGED
|
@@ -2,7 +2,7 @@ export const REPORT_SUMMARY_SCHEMA_VERSION = 1;
|
|
|
2
2
|
export const REPORT_SUMMARY_MAX_ITEMS = 200;
|
|
3
3
|
export const REPORT_SUMMARY_MAX_ISSUES = 100;
|
|
4
4
|
export const REPORT_SUMMARY_MAX_SAMPLE_ROWS = 20;
|
|
5
|
-
export function summarizeReportValidation(value) {
|
|
5
|
+
export function summarizeReportValidation(value, command = "report.validate") {
|
|
6
6
|
const root = objectValue(value);
|
|
7
7
|
const validation = objectValue(root?.validation);
|
|
8
8
|
const errorMessages = stringValues(validation?.errors);
|
|
@@ -24,7 +24,7 @@ export function summarizeReportValidation(value) {
|
|
|
24
24
|
const items = statementItems.slice(0, REPORT_SUMMARY_MAX_ITEMS);
|
|
25
25
|
return {
|
|
26
26
|
schemaVersion: REPORT_SUMMARY_SCHEMA_VERSION,
|
|
27
|
-
command
|
|
27
|
+
command,
|
|
28
28
|
ok: validation?.ok === true && errorMessages.length === 0,
|
|
29
29
|
summary: {
|
|
30
30
|
errorCount: errorMessages.length,
|
|
@@ -146,6 +146,13 @@ export function summarizeReportDashboard(value, options) {
|
|
|
146
146
|
if (result?.stale === true) {
|
|
147
147
|
allWarnings.push({ scope, message: "Report result is stale" });
|
|
148
148
|
}
|
|
149
|
+
if (result?.partial === true) {
|
|
150
|
+
const missingDates = stringValues(result.missingDates);
|
|
151
|
+
allWarnings.push({
|
|
152
|
+
scope,
|
|
153
|
+
message: `Report Dataset coverage is partial${missingDates.length ? `; missing dates: ${missingDates.join(", ")}` : ""}`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
149
156
|
allItems.push(compactObject({
|
|
150
157
|
kind: "chart-result",
|
|
151
158
|
status,
|
|
@@ -162,6 +169,9 @@ export function summarizeReportDashboard(value, options) {
|
|
|
162
169
|
cached: typeof result?.cached === "boolean" ? result.cached : undefined,
|
|
163
170
|
cachedAt: stringValue(result?.cachedAt),
|
|
164
171
|
stale: typeof result?.stale === "boolean" ? result.stale : undefined,
|
|
172
|
+
partial: typeof result?.partial === "boolean" ? result.partial : undefined,
|
|
173
|
+
missingDates: stringValues(result?.missingDates),
|
|
174
|
+
snapshotVersion: stringValue(result?.snapshotVersion),
|
|
165
175
|
truncated,
|
|
166
176
|
rowLimit: finiteNonNegativeNumber(result?.rowLimit),
|
|
167
177
|
error,
|
|
@@ -243,6 +253,8 @@ function reportResultStatus(result) {
|
|
|
243
253
|
return "retrying";
|
|
244
254
|
if (result?.refreshing === true)
|
|
245
255
|
return "refreshing";
|
|
256
|
+
if (result?.partial === true)
|
|
257
|
+
return "partial";
|
|
246
258
|
if (result?.stale === true)
|
|
247
259
|
return "stale";
|
|
248
260
|
return "ready";
|