@witchpot/gmc 0.2.1 → 0.4.0
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/CHANGELOG.md +36 -1
- package/README.md +9 -3
- package/package.json +4 -1
- package/scripts/check-release-readiness.mjs +31 -0
- package/scripts/smoke-pack.mjs +3 -0
- package/skill/gmc-analysis/CHANGELOG.md +42 -0
- package/skill/gmc-analysis/SKILL.md +66 -6
- package/skill/gmc-analysis/references/charts.md +236 -0
- package/skill/gmc-analysis/references/pitfalls.md +50 -0
- package/skill/gmc-analysis/references/quota.md +14 -7
- package/skill/gmc-analysis/references/validity.md +78 -0
- package/src/args.js +1 -0
- package/src/constants.js +4 -9
- package/src/help.js +15 -0
- package/src/requests.js +14 -15
- package/src/requests.test.js +35 -0
- package/src/skill.js +1 -1
- package/src/skill.test.js +9 -0
- package/src/workflows.js +23 -1589
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Game Market Copilot CLI will be recorded here.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.4.0 - 2026-07-09
|
|
6
|
+
|
|
7
|
+
- gmc-analysis skill v0.4.0: new `references/validity.md` with the
|
|
8
|
+
statistical-validity checklist and claim-safety rules (NAK-82), and new
|
|
9
|
+
pitfall P9 documenting the single-assignment vs multi-assignment tag
|
|
10
|
+
counting bases (NAK-81). Both ship in `gmc skill install` and the
|
|
11
|
+
`gmc skill print --format agents-md` self-contained edition.
|
|
12
|
+
- Bundled chart-card generation into the gmc-analysis skill (v0.3.0,
|
|
13
|
+
NAK-89): new `references/charts.md` with attribution rules (fixed
|
|
14
|
+
`GMC database` source label, brand lockup, denominator + observation
|
|
15
|
+
window caption, no causal titles), a chart-type mapping from gmc output
|
|
16
|
+
shapes, and a self-contained HTML/Chart.js card template. The reference
|
|
17
|
+
is included in `gmc skill install` and in the
|
|
18
|
+
`gmc skill print --format agents-md` self-contained edition.
|
|
19
|
+
|
|
20
|
+
## 0.3.0 - 2026-07-05
|
|
21
|
+
|
|
22
|
+
- Added `gmc tags` for tag taxonomy discovery: `gmc tags` lists the full
|
|
23
|
+
slug/name dictionary and `gmc tags --q <term> [--limit <number>]` searches
|
|
24
|
+
it. The endpoint is anonymous and unmetered, so agents can resolve a genre
|
|
25
|
+
or keyword to valid tag slugs before spending quota on `--tags` filters.
|
|
26
|
+
- Split shared, pure composition and analytics helpers (resolve/cohort/
|
|
27
|
+
showcase composers, deadline filtering) out of the CLI workflow layer and
|
|
28
|
+
into the new `@witchpot/gmc-core` package, now a published dependency of
|
|
29
|
+
this CLI. No command-facing behavior change.
|
|
30
|
+
- This release rides on server-side additions already live in the API:
|
|
31
|
+
- Tag filters on games search/count/aggregate/benchmark, review patterns,
|
|
32
|
+
and the marketing/creators/cohort/showcases workflows now validate and
|
|
33
|
+
canonicalize `--tags` slugs, rejecting unknown ones with a 400
|
|
34
|
+
`INVALID_TAG` error carrying `details.unknown_tags` and
|
|
35
|
+
`details.suggestions`. `gmc tags` is the recommended preflight for
|
|
36
|
+
resolving valid slugs before hitting these filters.
|
|
37
|
+
- The `market_aggregate` MCP tool gained `segment_filter`, which narrows
|
|
38
|
+
the base filter (AND) and reports `segment.matched`/`segment.ratio`
|
|
39
|
+
against the base cohort at no extra credit cost (not yet combinable
|
|
40
|
+
with `group_by`).
|
|
6
41
|
|
|
7
42
|
## 0.2.1 - 2026-07-04
|
|
8
43
|
|
package/README.md
CHANGED
|
@@ -30,7 +30,9 @@ A fuller user guide lives in
|
|
|
30
30
|
|
|
31
31
|
Closed-beta access is controlled by GMC API credentials, not npm package
|
|
32
32
|
visibility. Installing the public package does not grant access to paid or beta
|
|
33
|
-
data.
|
|
33
|
+
data. Authenticated access (login, API keys) requires a paid GMC plan
|
|
34
|
+
(Starter or Pro) — see https://gamemarketcopilot.com/plans. Without
|
|
35
|
+
credentials the CLI serves redacted public previews with a small quota.
|
|
34
36
|
|
|
35
37
|
## Auth
|
|
36
38
|
|
|
@@ -64,8 +66,10 @@ a non-secret prefix.
|
|
|
64
66
|
## Agent Skill
|
|
65
67
|
|
|
66
68
|
The package bundles the `gmc-analysis` Agent Skill — analysis recipes, pivot
|
|
67
|
-
heuristics, quota etiquette,
|
|
68
|
-
|
|
69
|
+
heuristics, quota etiquette, honesty rules, and branded chart-card generation
|
|
70
|
+
(publish-ready, correctly attributed charts from gmc output) for coding
|
|
71
|
+
agents. The core analysis guidance is shared, but the registration step
|
|
72
|
+
depends on the agent.
|
|
69
73
|
|
|
70
74
|
For Claude Code, install the packaged skill directory:
|
|
71
75
|
|
|
@@ -91,6 +95,7 @@ Re-run install/print after `npm update` to pick up newer skill versions.
|
|
|
91
95
|
|
|
92
96
|
```bash
|
|
93
97
|
gmc schema --json
|
|
98
|
+
gmc tags --q roguelike --json
|
|
94
99
|
gmc update --json
|
|
95
100
|
gmc games search --source steam --release-year 2025 --reviews-min 1000 --fields title,appid,rating,reviews --json
|
|
96
101
|
gmc games aggregate --source steam --release-year 2025 --metric count,median_reviews --group-by release_year --reviews-min 1000 --json
|
|
@@ -107,6 +112,7 @@ when using output in agent answers.
|
|
|
107
112
|
- `gmc status`
|
|
108
113
|
- `gmc auth status`
|
|
109
114
|
- `gmc schema`
|
|
115
|
+
- `gmc tags`
|
|
110
116
|
- `gmc games search`
|
|
111
117
|
- `gmc games count`
|
|
112
118
|
- `gmc games aggregate`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@witchpot/gmc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Command line tools for Game Market Copilot market research.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=20.9.0"
|
|
29
29
|
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@witchpot/gmc-core": "^0.1.0"
|
|
32
|
+
},
|
|
30
33
|
"files": [
|
|
31
34
|
"bin",
|
|
32
35
|
"src",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
|
|
@@ -16,7 +17,9 @@ const requiredFiles = [
|
|
|
16
17
|
"skill/gmc-analysis/SKILL.md",
|
|
17
18
|
"skill/gmc-analysis/references/recipes.md",
|
|
18
19
|
"skill/gmc-analysis/references/pitfalls.md",
|
|
20
|
+
"skill/gmc-analysis/references/validity.md",
|
|
19
21
|
"skill/gmc-analysis/references/quota.md",
|
|
22
|
+
"skill/gmc-analysis/references/charts.md",
|
|
20
23
|
"skill/gmc-analysis/CHANGELOG.md",
|
|
21
24
|
"README.md",
|
|
22
25
|
"CHANGELOG.md"
|
|
@@ -119,6 +122,34 @@ for (const file of sourceFiles) {
|
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
// The public distribution repo (github.com/witchpot-studio/gmc-agent-skills)
|
|
126
|
+
// mirrors skill/gmc-analysis. When it is checked out as a workspace sibling,
|
|
127
|
+
// a drift between the two is a release blocker — MCP-only users install the
|
|
128
|
+
// skill from that repo and must get the same content the npm package ships.
|
|
129
|
+
const explicitAgentSkillsDir = process.env.GMC_AGENT_SKILLS_DIR;
|
|
130
|
+
const agentSkillsRepo =
|
|
131
|
+
explicitAgentSkillsDir ?? path.join(packageDir, "..", "..", "..", "gmc-agent-skills");
|
|
132
|
+
const agentSkillsSyncScript = path.join(agentSkillsRepo, "scripts", "sync-from-cli.mjs");
|
|
133
|
+
if (explicitAgentSkillsDir && !existsSync(agentSkillsSyncScript)) {
|
|
134
|
+
failures.push(
|
|
135
|
+
`GMC_AGENT_SKILLS_DIR is set but ${agentSkillsSyncScript} does not exist — fix the path or unset the variable.`
|
|
136
|
+
);
|
|
137
|
+
} else if (existsSync(agentSkillsSyncScript)) {
|
|
138
|
+
const drift = spawnSync(process.execPath, ["scripts/sync-from-cli.mjs", "--check"], {
|
|
139
|
+
cwd: agentSkillsRepo,
|
|
140
|
+
encoding: "utf8",
|
|
141
|
+
// Pin the canonical source to THIS checkout — the sync script's own
|
|
142
|
+
// default (../GMC relative to the skills repo) could silently compare
|
|
143
|
+
// against a different GMC checkout.
|
|
144
|
+
env: { ...process.env, GMC_REPO_DIR: path.join(packageDir, "..", "..") }
|
|
145
|
+
});
|
|
146
|
+
if (drift.status !== 0) {
|
|
147
|
+
failures.push(
|
|
148
|
+
`gmc-agent-skills repo has drifted from skill/gmc-analysis — run node scripts/sync-from-cli.mjs there.\n${(drift.stderr || "").trim()}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
122
153
|
if (failures.length > 0) {
|
|
123
154
|
process.stderr.write(`GMC CLI release readiness failed:\n${failures.map((failure) => `- ${failure}`).join("\n")}\n`);
|
|
124
155
|
process.exit(1);
|
package/scripts/smoke-pack.mjs
CHANGED
|
@@ -81,6 +81,9 @@ try {
|
|
|
81
81
|
if (!skillPrintResult.stdout.includes("GMC Analysis") || !skillPrintResult.stdout.includes("Pitfalls")) {
|
|
82
82
|
throw new Error("Packed gmc skill print did not emit the inlined agents-md section");
|
|
83
83
|
}
|
|
84
|
+
if (!skillPrintResult.stdout.includes("Chart cards") || !skillPrintResult.stdout.includes("gmc-lockup.svg")) {
|
|
85
|
+
throw new Error("Packed gmc skill print did not inline the chart cards reference");
|
|
86
|
+
}
|
|
84
87
|
|
|
85
88
|
// gmc is shadowed by Oh My Zsh's `git merge --continue` alias, so the
|
|
86
89
|
// package ships alias binaries; make sure npm actually installed them.
|
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# gmc-analysis skill changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 - 2026-07-09
|
|
4
|
+
|
|
5
|
+
- Added `references/validity.md` (NAK-82): statistical-validity checklist
|
|
6
|
+
(denominator + N on every share, observation window, counting basis,
|
|
7
|
+
N<30 warning, effect size over bare significance, confound
|
|
8
|
+
stratification for publisher axis / price / release year, multiple-
|
|
9
|
+
comparison disclosure) and claim-safety rules (no causal verbs for
|
|
10
|
+
observational associations, external-estimate labeling, `not_collected`
|
|
11
|
+
means not collected), with a compliant output example. Wired into the
|
|
12
|
+
SKILL.md hard rules and the AGENTS.md edition.
|
|
13
|
+
- New pitfall P9 (NAK-81): single-assignment (`primary_market_tag`) vs
|
|
14
|
+
multi-assignment (`tag` grouping / `tags` filter) tag counting bases
|
|
15
|
+
diverge sharply — pick one basis per question, name it next to the
|
|
16
|
+
denominator, never mix or compare across bases.
|
|
17
|
+
|
|
18
|
+
## 0.3.1 - 2026-07-08
|
|
19
|
+
|
|
20
|
+
- quota.md: updated the paid allowances to the resized uniform per-credit
|
|
21
|
+
pricing (Starter 2,500 / Pro 7,000 credits per month, NAK-110); reset and
|
|
22
|
+
gate-time charging semantics unchanged.
|
|
23
|
+
|
|
24
|
+
## 0.3.0 - 2026-07-07
|
|
25
|
+
|
|
26
|
+
- Added `references/charts.md`: publish-ready branded chart cards from gmc
|
|
27
|
+
CLI / MCP outputs (NAK-89). Bakes attribution into the artifact — fixed
|
|
28
|
+
`GMC database` source label, brand lockup (icon + wordmark), denominator
|
|
29
|
+
+ observation window caption, no causal titles — and maps gmc output
|
|
30
|
+
shapes (market aggregates, cohort evidence, coverage checks, single
|
|
31
|
+
KPIs) to chart types with a self-contained HTML/Chart.js template.
|
|
32
|
+
- SKILL.md: new "Chart cards" section wiring the reference into the
|
|
33
|
+
workflow and into the AGENTS.md edition.
|
|
34
|
+
- SKILL.md: new "MCP-only setups" section mapping the CLI workflow onto
|
|
35
|
+
the GMC MCP tools (market_aggregate, coverage_check, cohort_evidence,
|
|
36
|
+
game_profile, ...) so agents with only the MCP connection do not attempt
|
|
37
|
+
gmc commands (NAK-98).
|
|
38
|
+
- quota.md: corrected to the monthly credit model (Starter 5,000 / Pro
|
|
39
|
+
25,000 credits per month, reset on the 1st at 00:00 UTC, gate-time
|
|
40
|
+
charging) — the per-UTC-day request caps it described were outdated.
|
|
41
|
+
- Documented the paid-plan requirement (Starter/Pro) for authenticated
|
|
42
|
+
access, and the `PLAN_REQUIRED` vs `QUOTA_EXCEEDED` distinction for
|
|
43
|
+
MCP tool errors (upgrade vs wait-for-reset).
|
|
44
|
+
|
|
3
45
|
## 0.2.0 - 2026-06-12
|
|
4
46
|
|
|
5
47
|
- Folded in the five learnings from the first field test (2025 city
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gmc-analysis
|
|
3
3
|
description: >-
|
|
4
|
-
Use when analyzing Steam game market data with
|
|
5
|
-
|
|
6
|
-
patterns, pricing), single-title deep dives, market
|
|
7
|
-
about Steam games, reviews, creators, showcases,
|
|
8
|
-
answer
|
|
4
|
+
Use when analyzing Steam game market data with Game Market Copilot (the
|
|
5
|
+
gmc CLI or the GMC MCP tools) — cross-game cohort analysis (common
|
|
6
|
+
complaints, marketing patterns, pricing), single-title deep dives, market
|
|
7
|
+
sizing, or any question about Steam games, reviews, creators, showcases,
|
|
8
|
+
or campaigns that GMC can answer — including turning results into
|
|
9
|
+
publish-ready branded chart cards.
|
|
9
10
|
metadata:
|
|
10
|
-
version: 0.
|
|
11
|
+
version: 0.4.0
|
|
11
12
|
---
|
|
12
13
|
|
|
13
14
|
# GMC Analysis
|
|
@@ -26,12 +27,49 @@ gmc auth status --json # anonymous => redacted previews + tiny quota
|
|
|
26
27
|
gmc schema --json # the authoritative command/endpoint reference
|
|
27
28
|
```
|
|
28
29
|
|
|
30
|
+
Authenticated access (CLI login, API keys, MCP) requires a paid GMC plan
|
|
31
|
+
(Starter or Pro); Free workspaces cannot mint credentials. Anonymous CLI
|
|
32
|
+
use works but returns redacted previews with a tiny quota.
|
|
33
|
+
|
|
29
34
|
Never guess flags: `gmc schema --json` is the source of truth for commands
|
|
30
35
|
and parameters. Endpoint query params are camelCase and the matching CLI
|
|
31
36
|
flag is the kebab-case equivalent (`priceMin` -> `--price-min`,
|
|
32
37
|
`reviewsGt` -> `--reviews-gt`). Per-command `--help` currently prints one
|
|
33
38
|
generic usage list, so trust the schema. Always pass `--json`.
|
|
34
39
|
|
|
40
|
+
## MCP-only setups (no CLI installed)
|
|
41
|
+
|
|
42
|
+
If you have the GMC MCP connection instead of the CLI, do NOT attempt gmc
|
|
43
|
+
commands — use the MCP tools; every workflow and honesty rule in this skill
|
|
44
|
+
applies identically. Mechanics map as follows:
|
|
45
|
+
|
|
46
|
+
- Sizing and denominators (`gmc games count` / `games aggregate`) ->
|
|
47
|
+
`market_aggregate` (population-true; prefer it over paginating
|
|
48
|
+
`list_games`).
|
|
49
|
+
- Coverage pre-check -> `coverage_check` before fanning out `game_profile`
|
|
50
|
+
on more than 3 titles.
|
|
51
|
+
- Cohort evidence (`reviews patterns`, `cohort ...`) -> `cohort_evidence`,
|
|
52
|
+
`cohort_review_categories`, `search_review_claims`.
|
|
53
|
+
- Single-title drill-down (`games detail` / `games analysis`) ->
|
|
54
|
+
`game_profile`; name resolution -> `resolve`; showcases ->
|
|
55
|
+
`showcase_fit`.
|
|
56
|
+
- The response envelope differs from the CLI: MCP tool responses carry
|
|
57
|
+
`meta.quota` = `{ used, limit, remaining, resets_at }` (credits) plus
|
|
58
|
+
`meta.credits_charged`, `meta.basis`, `meta.denominator`,
|
|
59
|
+
`meta.coverage`, and `meta.warnings` — there is no `meta.entitlements`
|
|
60
|
+
wrapper. The read-before-you-spend and honesty rules apply unchanged.
|
|
61
|
+
- CLI-only mechanics (pagination flags, `--fields` projection, map-reduce
|
|
62
|
+
recipes) may have no MCP equivalent — stay within the tool contracts
|
|
63
|
+
instead of simulating them.
|
|
64
|
+
- A `PLAN_REQUIRED` tool error means the workspace plan has no API access
|
|
65
|
+
(e.g. Free) — retrying or waiting will not help; tell the user an
|
|
66
|
+
upgrade at gamemarketcopilot.com/plans is required. Only
|
|
67
|
+
`QUOTA_EXCEEDED` is the wait-for-monthly-reset condition.
|
|
68
|
+
|
|
69
|
+
References in this skill use CLI syntax; translate to the matching MCP
|
|
70
|
+
tool. Chart-card rules (references/charts.md) apply to both paths
|
|
71
|
+
unchanged.
|
|
72
|
+
|
|
35
73
|
## Core workflow for cohort questions
|
|
36
74
|
|
|
37
75
|
1. **Size the cohort and its research coverage first** (1-2 requests):
|
|
@@ -69,6 +107,16 @@ unless you explicitly pass `--include-total`; for count-only questions use
|
|
|
69
107
|
- **Bounded evidence, not market truth.** Carry the CLI's own caveats:
|
|
70
108
|
evidence packs are sampled/returned-page evidence. Always state
|
|
71
109
|
denominators ("87 of 107 analyzed titles") and preserve `warnings`.
|
|
110
|
+
- **Pass the validity checklist before handing off numbers.** Every
|
|
111
|
+
reported share carries its denominator, N, observation window, and tag
|
|
112
|
+
counting basis; small-N groups are labeled directional; group
|
|
113
|
+
comparisons are stratified for obvious confounds (publisher axis, price,
|
|
114
|
+
release year) or the unchecked confound is named; scanned-extreme
|
|
115
|
+
findings disclose the scan. Details in the validity reference.
|
|
116
|
+
- **Claim safety.** No causal verbs for observational associations; label
|
|
117
|
+
estimates not derived from gmc data as external with a source;
|
|
118
|
+
`not_collected` means not collected, never zero or absent. Details in
|
|
119
|
+
the validity reference.
|
|
72
120
|
- **Separate API facts from your synthesis.** Quote theme labels and player
|
|
73
121
|
counts as data; label your clustering and conclusions as your analysis.
|
|
74
122
|
- **Pace your quota.** Read `meta.entitlements.quota` (`used`/`limit`)
|
|
@@ -76,12 +124,24 @@ unless you explicitly pass `--include-total`; for count-only questions use
|
|
|
76
124
|
Details in the quota reference.
|
|
77
125
|
- Preserve `meta.requestId` values for anything you may need to report.
|
|
78
126
|
|
|
127
|
+
## Chart cards
|
|
128
|
+
|
|
129
|
+
When the user wants a shareable or publish-ready visual of gmc results,
|
|
130
|
+
follow the charts reference. Its rules are part of GMC attribution: a chart
|
|
131
|
+
under the GMC label must carry the fixed source label, the brand lockup,
|
|
132
|
+
and a denominator + observation window caption, and its title must not
|
|
133
|
+
make causal claims. Never emit a GMC-attributed chart that skips these.
|
|
134
|
+
|
|
79
135
|
## References
|
|
80
136
|
|
|
81
137
|
- `references/recipes.md` — step-by-step playbooks (cohort complaints,
|
|
82
138
|
full-cohort map-reduce, single-title deep dive, market sizing).
|
|
83
139
|
- `references/pitfalls.md` — known failure modes and the correct pivots.
|
|
140
|
+
- `references/validity.md` — statistical-validity checklist and
|
|
141
|
+
claim-safety rules for any published number.
|
|
84
142
|
- `references/quota.md` — quota model and cost estimation.
|
|
143
|
+
- `references/charts.md` — branded chart cards: attribution rules, chart
|
|
144
|
+
type mapping from gmc output shapes, self-contained HTML template.
|
|
85
145
|
|
|
86
146
|
(In the AGENTS.md edition of this skill the references are inlined below.)
|
|
87
147
|
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Chart cards
|
|
2
|
+
|
|
3
|
+
Turn gmc CLI / MCP outputs into publish-ready, correctly attributed GMC
|
|
4
|
+
chart cards (self-contained HTML -> PNG). Follow this reference whenever a
|
|
5
|
+
chart, graph, or shareable visual is requested from GMC data. A chart that
|
|
6
|
+
violates the non-negotiables below must not carry GMC attribution.
|
|
7
|
+
|
|
8
|
+
## Non-negotiables (every card)
|
|
9
|
+
|
|
10
|
+
1. **Footer, left**: the fixed source label `GMC database` plus at most one
|
|
11
|
+
short factual note (collection method, cutoff, exclusions). Japanese
|
|
12
|
+
edition: `GMCデータベース`. Never change, translate loosely, or omit the
|
|
13
|
+
label.
|
|
14
|
+
2. **Footer, right**: the brand lockup — icon + wordmark, one image:
|
|
15
|
+
`https://gamemarketcopilot.com/assets/gmc-lockup.svg`. Never the icon
|
|
16
|
+
alone, never a retyped wordmark, never someone else's logo.
|
|
17
|
+
3. **Caption (top-left of the card)**: denominator + observation window,
|
|
18
|
+
e.g. `1,204 of 20,994 titles released 2025 / aggregated as of
|
|
19
|
+
2026-07-06`. Take denominators from `gmc games count` (or the tool's own
|
|
20
|
+
`cohort.usableGames`), not from page sizes. The caption is the card's
|
|
21
|
+
denominator disclosure — exports must never crop it (or the footer) off.
|
|
22
|
+
4. **Title**: state the observed fact — a level, difference, share, or
|
|
23
|
+
rank. No causal verbs (`drove`, `boosted`, `caused`, `led to`,
|
|
24
|
+
`because`); the data supports co-occurrence, not causation. No hype
|
|
25
|
+
metaphors. Correlation phrasing is fine.
|
|
26
|
+
5. **Data honesty**: `not_collected` is never charted as zero. Locked or
|
|
27
|
+
`preview: true` payloads are never charted as full data. Small
|
|
28
|
+
denominators (n < 30) are presented as individual cases, not a trend
|
|
29
|
+
bar. `skippedGames` / `skippedReasons` go in the footnote when they
|
|
30
|
+
change the reading.
|
|
31
|
+
6. **External estimates stay outside the label**: units multipliers,
|
|
32
|
+
wishlist folklore, or any non-GMC number never appear under the
|
|
33
|
+
`GMC database` label — prefer leaving them off a GMC-branded card
|
|
34
|
+
entirely. If one must appear, add a second, visually separate footnote
|
|
35
|
+
line: `External estimate: <source> — not GMC data`.
|
|
36
|
+
7. **Opaque background** (`#fafaf7` page, `#ffffff` card). Never export
|
|
37
|
+
transparent PNGs. Recommended export: X 1200x675, LinkedIn 1200x627,
|
|
38
|
+
or the natural 720px card width at 2x.
|
|
39
|
+
|
|
40
|
+
## Picking the chart type from gmc output shapes
|
|
41
|
+
|
|
42
|
+
| Output | Shape | Chart |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `gmc games aggregate --group-by <dim>` / MCP `market_aggregate` | 6+ groups, rank matters | Horizontal bar, sorted desc |
|
|
45
|
+
| same | 2-5 groups | Vertical column |
|
|
46
|
+
| same, time buckets (release windows, months) | series over time | Line (single/few series) or column |
|
|
47
|
+
| MCP `market_aggregate` with `segment_filter` | segment vs base | Two big numbers + ratio, or grouped bar |
|
|
48
|
+
| `gmc reviews patterns` / MCP `cohort_evidence` / clustered `negativeThemes` | theme -> game counts | Horizontal bar; caption carries `x of N usable games` |
|
|
49
|
+
| MCP `coverage_check` / `gmc games count --group-by coverage` | composition, 2-4 segments | Donut with absolute counts printed, or one 100% stacked bar |
|
|
50
|
+
| single KPI (one count, one median, one share) | one number | Hero number card (no chart canvas) |
|
|
51
|
+
|
|
52
|
+
Rules of thumb: when unsure, use a horizontal bar. One message per card.
|
|
53
|
+
Never: 3D, dual axes, cropped bar baselines, pie with 5+ slices, line
|
|
54
|
+
charts for non-time data.
|
|
55
|
+
|
|
56
|
+
## Color
|
|
57
|
+
|
|
58
|
+
Neutrals (fixed): page `#fafaf7`, card `#ffffff`, ink `#1a1d20`, muted
|
|
59
|
+
`#3e454d`, faint `#818d9a`, grid `rgba(26,29,32,0.06)`.
|
|
60
|
+
|
|
61
|
+
- **Single series / ranking**: accent ladder only — Ember `#EC7336` at
|
|
62
|
+
alpha 0.85 (lead), 0.55, 0.30, rest `rgba(26,29,32,0.08)`.
|
|
63
|
+
- **2-3 categories**: Ember `#EC7336` + Seagrass `#1AA29D` (+ Inkwell
|
|
64
|
+
`#4057C7`). Never distinguish categories by alpha alone.
|
|
65
|
+
- **4-5 categories**: add Moss `#3E9C5F`, Mulberry `#9B4ABF`. Max 5 colors;
|
|
66
|
+
gray out non-focus series with `rgba(26,29,32,0.12)`. 6+ categories:
|
|
67
|
+
split the chart or bucket into "other".
|
|
68
|
+
- Direct labels beat legends: print values at bar ends / line ends.
|
|
69
|
+
|
|
70
|
+
## Card template (self-contained)
|
|
71
|
+
|
|
72
|
+
Fill the `{{...}}` slots, adjust the dataset + type per the table above,
|
|
73
|
+
open in a browser, capture the `.card` element as PNG. The template needs
|
|
74
|
+
network access for fonts, Chart.js CDN, and the brand lockup.
|
|
75
|
+
|
|
76
|
+
```html
|
|
77
|
+
<!DOCTYPE html>
|
|
78
|
+
<html lang="en">
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="utf-8">
|
|
81
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
82
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&family=Instrument+Serif&display=swap" rel="stylesheet">
|
|
83
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
|
|
84
|
+
<style>
|
|
85
|
+
:root {
|
|
86
|
+
--bg: #fafaf7; --card: #ffffff;
|
|
87
|
+
--ink: #1a1d20; --ink-muted: #3e454d; --ink-faint: #818d9a;
|
|
88
|
+
--rule: #e8e5de; --accent: #ec7336;
|
|
89
|
+
--sans: 'Inter', system-ui, sans-serif;
|
|
90
|
+
--mono: 'JetBrains Mono', ui-monospace, monospace;
|
|
91
|
+
--serif: 'Instrument Serif', Georgia, serif;
|
|
92
|
+
}
|
|
93
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
94
|
+
body { background: var(--bg); font-family: var(--sans); color: var(--ink);
|
|
95
|
+
display: flex; justify-content: center; padding: 40px 16px; }
|
|
96
|
+
.card { width: 720px; max-width: 100%; background: var(--card);
|
|
97
|
+
border: 1px solid var(--rule); display: flex; flex-direction: column; }
|
|
98
|
+
.card-head { display: flex; justify-content: space-between; gap: 12px;
|
|
99
|
+
padding: 18px 20px 12px; border-bottom: 1px solid var(--rule); }
|
|
100
|
+
.card-num { font-family: var(--mono); font-size: 12.5px; font-weight: 500;
|
|
101
|
+
color: var(--accent); max-width: 76%; }
|
|
102
|
+
.card-cat { font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em;
|
|
103
|
+
text-transform: uppercase; color: var(--ink-faint); white-space: nowrap; }
|
|
104
|
+
.card-title { padding: 26px 20px 8px; font-family: var(--serif);
|
|
105
|
+
font-size: 34px; line-height: 1.12; font-weight: 400; }
|
|
106
|
+
.chart-wrap { position: relative; margin: 18px 20px 8px;
|
|
107
|
+
min-height: 470px; height: 470px; }
|
|
108
|
+
.card-foot { display: flex; justify-content: space-between; align-items: center;
|
|
109
|
+
gap: 16px; border-top: 1px solid var(--rule);
|
|
110
|
+
padding: 14px 20px; margin-top: 12px; }
|
|
111
|
+
.foot-meta { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
112
|
+
.foot-source { font-family: var(--mono); font-size: 13px; color: var(--ink-muted); }
|
|
113
|
+
.foot-note { font-size: 11.5px; line-height: 1.45; color: var(--ink-faint); }
|
|
114
|
+
.brand-lockup { flex: 0 0 auto; height: 24px; width: auto; display: block; }
|
|
115
|
+
</style>
|
|
116
|
+
</head>
|
|
117
|
+
<body>
|
|
118
|
+
<article class="card">
|
|
119
|
+
<div class="card-head">
|
|
120
|
+
<div class="card-num">{{DENOMINATOR AND WINDOW, e.g. 1,204 of 20,994 titles released 2025 / as of 2026-07-06}}</div>
|
|
121
|
+
<div class="card-cat">{{CHART TYPE, e.g. RANKED BAR}}</div>
|
|
122
|
+
</div>
|
|
123
|
+
<h2 class="card-title">{{FACTUAL TITLE — observed level/difference/share, no causal verbs}}</h2>
|
|
124
|
+
<div class="chart-wrap"><canvas id="chart"></canvas></div>
|
|
125
|
+
<div class="card-foot">
|
|
126
|
+
<div class="foot-meta">
|
|
127
|
+
<div class="foot-source">GMC database</div>
|
|
128
|
+
<div class="foot-note">{{ONE-LINE NOTE: method, cutoff, exclusions, skipped counts}}</div>
|
|
129
|
+
</div>
|
|
130
|
+
<img class="brand-lockup" src="https://gamemarketcopilot.com/assets/gmc-lockup.svg" alt="Game Market Copilot">
|
|
131
|
+
</div>
|
|
132
|
+
</article>
|
|
133
|
+
<script>
|
|
134
|
+
const ink = (a) => `rgba(26, 29, 32, ${a})`;
|
|
135
|
+
const accent = (a) => `rgba(236, 115, 54, ${a})`;
|
|
136
|
+
Chart.defaults.font.family = "'Inter', system-ui, sans-serif";
|
|
137
|
+
Chart.defaults.font.size = 13;
|
|
138
|
+
Chart.defaults.color = '#3e454d';
|
|
139
|
+
|
|
140
|
+
// Direct value labels at bar ends (skip for line charts; use point labels there).
|
|
141
|
+
const valueLabels = {
|
|
142
|
+
id: 'valueLabels',
|
|
143
|
+
afterDatasetsDraw(chart) {
|
|
144
|
+
const { ctx } = chart;
|
|
145
|
+
const horizontal = chart.options.indexAxis === 'y';
|
|
146
|
+
chart.data.datasets.forEach((ds, di) => {
|
|
147
|
+
chart.getDatasetMeta(di).data.forEach((el, i) => {
|
|
148
|
+
const v = ds.data[i];
|
|
149
|
+
if (v == null) return;
|
|
150
|
+
ctx.save();
|
|
151
|
+
ctx.font = "500 12px 'JetBrains Mono', monospace";
|
|
152
|
+
ctx.fillStyle = '#3e454d';
|
|
153
|
+
ctx.textBaseline = 'middle';
|
|
154
|
+
if (horizontal) { ctx.textAlign = 'left'; ctx.fillText(v.toLocaleString(), el.x + 8, el.y); }
|
|
155
|
+
else { ctx.textAlign = 'center'; ctx.textBaseline = 'bottom'; ctx.fillText(v.toLocaleString(), el.x, el.y - 6); }
|
|
156
|
+
ctx.restore();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const labels = ['{{GROUP A}}', '{{GROUP B}}', '{{GROUP C}}']; // sorted desc for rankings
|
|
163
|
+
const values = [0, 0, 0]; // real aggregates only
|
|
164
|
+
new Chart(document.getElementById('chart'), {
|
|
165
|
+
type: 'bar', // ranked bar. Column: drop indexAxis. Line: type 'line'.
|
|
166
|
+
data: { labels, datasets: [{
|
|
167
|
+
data: values,
|
|
168
|
+
// Ranking ladder — lead bar strongest, tail neutral:
|
|
169
|
+
backgroundColor: values.map((_, i) => [accent(0.85), accent(0.55), accent(0.30)][i] ?? ink(0.08)),
|
|
170
|
+
borderRadius: 2, barPercentage: 0.72
|
|
171
|
+
}]},
|
|
172
|
+
options: {
|
|
173
|
+
indexAxis: 'y', responsive: true, maintainAspectRatio: false,
|
|
174
|
+
layout: { padding: { right: 64 } }, // room for end-of-bar labels
|
|
175
|
+
plugins: { legend: { display: false }, tooltip: { enabled: false } },
|
|
176
|
+
scales: {
|
|
177
|
+
x: { beginAtZero: true, grid: { color: ink(0.06) }, border: { color: ink(0.18) } },
|
|
178
|
+
y: { grid: { display: false }, border: { color: ink(0.18) } }
|
|
179
|
+
},
|
|
180
|
+
animation: { onComplete: () => { window.__animComplete = true; } }
|
|
181
|
+
},
|
|
182
|
+
plugins: [valueLabels]
|
|
183
|
+
});
|
|
184
|
+
</script>
|
|
185
|
+
</body>
|
|
186
|
+
</html>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Variants:
|
|
190
|
+
|
|
191
|
+
- **Column** (2-5 groups): remove `indexAxis`, move padding to `top`,
|
|
192
|
+
labels render above bars automatically via the plugin.
|
|
193
|
+
- **Line** (time buckets): `type: 'line'`, `borderColor: accent(0.85)`,
|
|
194
|
+
`pointRadius: 3`, `fill: false` (always false when more than one dataset
|
|
195
|
+
or when combined with bars), label only first/last/inflection points.
|
|
196
|
+
- **Donut** (coverage split, max 4 segments): `type: 'doughnut'`,
|
|
197
|
+
`cutout: '62%'`, colors Ember/Seagrass/Inkwell + gray; print
|
|
198
|
+
`label — count (share%)` per segment as direct labels or a compact list
|
|
199
|
+
under the chart; absolute counts are mandatory, share alone is not
|
|
200
|
+
enough.
|
|
201
|
+
- **Hero number** (single KPI): replace `.chart-wrap` with
|
|
202
|
+
`<div style="padding:56px 20px 40px; text-align:center;">
|
|
203
|
+
<div style="font-size:96px; font-weight:600; letter-spacing:-0.04em;
|
|
204
|
+
color:var(--accent);">{{VALUE}}</div>
|
|
205
|
+
<div style="font-size:15px; color:var(--ink-muted); margin-top:8px;">
|
|
206
|
+
{{ONE-LINE QUALIFIER}}</div></div>` and delete the chart script. The
|
|
207
|
+
caption and footer rules still apply unchanged.
|
|
208
|
+
|
|
209
|
+
Japanese edition: add `&family=Noto+Sans+JP:wght@400;500;600` to the fonts
|
|
210
|
+
URL, append `'Noto Sans JP'` to `--sans`, set `<html lang="ja">`, and use
|
|
211
|
+
`GMCデータベース` as the source label. Steam tags in JA charts:
|
|
212
|
+
`English Tag(日本語タグ名)` on first appearance.
|
|
213
|
+
|
|
214
|
+
## Capture
|
|
215
|
+
|
|
216
|
+
Any browser screenshot of the `.card` element works. Headless example:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
npx playwright screenshot --wait-for-timeout 2500 --full-page card.html card.png
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
then crop to the card, or capture the element via a driver that waits for
|
|
223
|
+
`window.__animComplete === true`. Verify before publishing: bars/labels not
|
|
224
|
+
clipped, text legible at mobile size (12px+ effective), background opaque.
|
|
225
|
+
|
|
226
|
+
## Pre-publish checklist
|
|
227
|
+
|
|
228
|
+
- [ ] Title is factual, no causal verbs, no hype
|
|
229
|
+
- [ ] Caption has denominator AND observation window
|
|
230
|
+
- [ ] `GMC database` label + brand lockup both present, unmodified
|
|
231
|
+
- [ ] Every number comes from a real gmc/MCP response in this session
|
|
232
|
+
(no remembered or estimated values); re-run the query if stale
|
|
233
|
+
- [ ] `not_collected` / locked data not charted; skipped counts disclosed
|
|
234
|
+
when they change the reading
|
|
235
|
+
- [ ] External estimates (if any) credited separately in the footnote
|
|
236
|
+
- [ ] Colors follow the tier rules; max 5 colors; opaque background
|
|
@@ -72,3 +72,53 @@ single-request server aggregates. Reserve pagination loops for when you
|
|
|
72
72
|
actually need the rows (recipe R2). `gmc games search` is optimized for rows
|
|
73
73
|
and may omit `pagination.total`; pass `--include-total` only when a row page
|
|
74
74
|
also needs an exact count.
|
|
75
|
+
|
|
76
|
+
## P8. Wide aggregations can time out (2026-07)
|
|
77
|
+
|
|
78
|
+
Aggregate calls that request many metrics and/or two `group_by` dimensions
|
|
79
|
+
over the full catalog can exceed the server-side query timeout. Over MCP this
|
|
80
|
+
surfaces as an `UPSTREAM_TIMEOUT` tool error (older builds: a generic
|
|
81
|
+
`UPSTREAM_ERROR`); via the CLI as a failed request. A timeout is a load
|
|
82
|
+
limit, not a data problem — the cohort itself is fine, and retrying the same
|
|
83
|
+
call unchanged will usually time out again.
|
|
84
|
+
|
|
85
|
+
**Rule**: split the request instead of retrying as-is — fewer metrics per
|
|
86
|
+
call, one `group_by` dimension at a time, or a narrower filter (e.g. a
|
|
87
|
+
shorter release window) — then combine the results. Splits are safe: each
|
|
88
|
+
call returns population-true numbers for its slice, so recombined totals stay
|
|
89
|
+
consistent. Note the substitution in your analysis log so reviewers know the
|
|
90
|
+
numbers came from split calls.
|
|
91
|
+
|
|
92
|
+
## P9. Two tag counting bases exist — never mix them (2026-07)
|
|
93
|
+
|
|
94
|
+
Tag shares can be computed on two different bases, and the numbers diverge
|
|
95
|
+
sharply (observed in a field comparison: the same cohort's genre shares
|
|
96
|
+
disagreed between two reports because each used a different basis):
|
|
97
|
+
|
|
98
|
+
- **Single-assignment** — `group_by primary_market_tag` (field
|
|
99
|
+
`primaryMarketTag`, derived from `tagRanks[0]`). Each game is counted at
|
|
100
|
+
most once, under its top-ranked tag. Rows are mutually exclusive and sum
|
|
101
|
+
to the TAGGED subset of the cohort — games without tags are omitted from
|
|
102
|
+
these rows, so compare the row total against the plain cohort count and
|
|
103
|
+
use "of tagged titles" as the denominator when the two differ.
|
|
104
|
+
- **Multi-assignment** — `group_by tag`, or separate per-tag filtered
|
|
105
|
+
counts compared or summed across tags (fields `tags` / `tagRanks` /
|
|
106
|
+
`facets.marketTags`). Each game is counted under every tag it carries, so
|
|
107
|
+
one game contributes to many rows and shares sum well past 100%. The
|
|
108
|
+
server marks `group_by tag` with `summary.methods.groupMembership:
|
|
109
|
+
"multi"` and a multi-membership caveat. (A single `tags`-filtered count
|
|
110
|
+
by itself still counts each matching title once — the double-counting
|
|
111
|
+
appears when per-tag numbers are set side by side.)
|
|
112
|
+
|
|
113
|
+
Neither basis is wrong — they answer different questions:
|
|
114
|
+
|
|
115
|
+
- "What is the market composed of?" / "which segment is a game in?" /
|
|
116
|
+
portfolio-style shares -> single-assignment (`primary_market_tag`).
|
|
117
|
+
- "How many games carry tag X?" / tag reach, co-occurrence, feature
|
|
118
|
+
adoption -> multi-assignment (`tag` grouping or `tags`-filtered counts).
|
|
119
|
+
|
|
120
|
+
**Rule**: pick one basis per question, name it next to the denominator
|
|
121
|
+
("single-assignment by primary_market_tag, N=1,204"), and never compare or
|
|
122
|
+
combine numbers across bases — not within a report, and not against an
|
|
123
|
+
earlier report computed on the other basis. If a prior number's basis is
|
|
124
|
+
unknown, recompute it rather than citing it.
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
# Quota etiquette
|
|
2
2
|
|
|
3
|
-
Quotas are
|
|
4
|
-
(API keys / CLI credentials). As of 2026-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Quotas are monthly, workspace-level credit allowances enforced for bearer
|
|
4
|
+
credentials (API keys / CLI credentials / MCP tokens). As of 2026-07:
|
|
5
|
+
Starter 2,500 credits/month, Pro 7,000; the counter resets at 00:00 UTC on
|
|
6
|
+
the first of each month. Anonymous public preview is far smaller. Credits
|
|
7
|
+
are charged when a request is accepted at the gate, so requests that later
|
|
8
|
+
fail validation or 404 still consume — validate appids before batch
|
|
9
|
+
fan-outs. Treat these numbers as indicative — the response metadata is the
|
|
10
|
+
source of truth.
|
|
7
11
|
|
|
8
12
|
## Rules
|
|
9
13
|
|
|
10
14
|
1. **Read before you spend.** Every bearer response carries
|
|
11
|
-
`meta.entitlements.quota` = `{ used, limit
|
|
15
|
+
`meta.entitlements.quota` = `{ used, limit, remaining, exceeded,
|
|
16
|
+
resetsAt }` in credits. Check it at the start of a
|
|
12
17
|
session and after each fan-out. Exception: CLI-composed evidence packs
|
|
13
18
|
(`cohort ...`, `reviews patterns`, `games benchmark`, ...) expose quota
|
|
14
19
|
per upstream call instead — read the last entry of
|
|
@@ -21,8 +26,10 @@ indicative — the response metadata is the source of truth.
|
|
|
21
26
|
`--coverage full,partial` and tighter filters so requests are not spent on
|
|
22
27
|
titles that would be skipped anyway.
|
|
23
28
|
4. **On HTTP 429 (`QUOTA_EXCEEDED`)**: stop fanning out, respect the
|
|
24
|
-
`Retry-After` header / `error.retryAfterSeconds` (the
|
|
25
|
-
|
|
29
|
+
`Retry-After` header / `error.retryAfterSeconds` (the allowance resets at
|
|
30
|
+
00:00 UTC on the first of the next month; a short `Retry-After` means the
|
|
31
|
+
separate burst backstop), and tell the user how much of the analysis
|
|
32
|
+
completed.
|
|
26
33
|
5. **Prefer server aggregates** (`count`, `aggregate`) over row pagination —
|
|
27
34
|
they answer sizing questions in one request.
|
|
28
35
|
6. **Keep totals explicit.** Search commands prioritize fast row pages and
|