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 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 experiments.
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|rest] --message <reason> --yes");
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|rest>");
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) {
@@ -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 response = await client.createGameKey(name);
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
  }
@@ -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 out = String(flags.out || "gamealgo-report-pack-v1.json");
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
- await printResult(full ? response : summarizeReportManifest(response), global);
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|rest] [--tab name] [--group name] [--chart name] [--selector k=v] [--sample-rows 0..20] [--full] [--format json|csv] [--out file-or-directory]");
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 = await client.queryReportDashboard({
69
- startDate,
70
- endDate,
71
- tab: optionalString(flags.tab),
72
- tabId: optionalString(flags["tab-id"]),
73
- group: optionalString(flags.group),
74
- groupId: optionalString(flags["group-id"]),
75
- chart: optionalString(flags.chart),
76
- chartId: optionalString(flags["chart-id"]),
77
- platform,
78
- selectors,
79
- refresh: Boolean(flags.refresh),
80
- responseMode: full ? "full" : "summary",
81
- sampleRows,
82
- }, { timeoutMs });
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
- const startDate = String(flags.from || flags.start || flags.startDate || "");
100
- const endDate = String(flags.to || flags.end || flags.endDate || "");
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 format = reportOutputFormat(flags.format);
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("Previewing report results", timeoutMs);
136
+ const stopProgress = startProgress("Validating Report Pack v2 preview", timeoutMs);
113
137
  let response;
114
138
  try {
115
- response = await client.previewReportDashboard({
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 = summarizeReportDashboard(rawOutputValue, { command: "report.preview", sampleRows });
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
- await writeReportQueryOutput(outputValue, flags, global, elapsedMs, statusSummary.ok);
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, --platform android, or --platform rest");
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 === "rest")
441
+ if (value === "ios" || value === "android" || value === "maker" || value === "web")
305
442
  return value;
306
- throw new Error(`${field} must be ios, android, or rest`);
443
+ throw new Error(`${field} must be ios, android, maker, or web`);
307
444
  }
@@ -1,6 +1,6 @@
1
1
  import { loadPackageMetadata } from "./config.js";
2
2
  import { requestJson } from "./http.js";
3
- export const CLI_PROTOCOL_VERSION = 1;
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 === "maker")
463
- return "lua";
464
- if (platform === "lua" || platform === "ios" || platform === "android" || platform === "rest") {
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 lua, maker, ios, android, or rest");
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 === "lua" || item === "ios" || item === "android" || item === "rest");
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;
@@ -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: "report.validate",
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";