@witchpot/gmc 0.8.0 → 0.9.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.
@@ -1,246 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { spawnSync } from "node:child_process";
4
- import { mkdtempSync, rmSync } from "node:fs";
5
- import { tmpdir } from "node:os";
6
- import path from "node:path";
7
-
8
- const packageDir = path.resolve(new URL("..", import.meta.url).pathname);
9
- const cliBin = path.join(packageDir, "bin", "gmc.mjs");
10
- const workDir = mkdtempSync(path.join(tmpdir(), "gmc-cli-beta-smoke-"));
11
- const configPath = path.join(workDir, "config.json");
12
- const allowApiErrors = process.argv.includes("--allow-api-errors");
13
-
14
- const smokeCommands = [
15
- {
16
- name: "version",
17
- args: ["--version", "--json"],
18
- validate: (payload) => requireFields(payload, ["data.name", "data.version"])
19
- },
20
- {
21
- name: "update",
22
- args: ["update", "--json"],
23
- validate: (payload) => requireFields(payload, ["data.currentVersion", "data.commands.npmGlobal"])
24
- },
25
- {
26
- name: "auth status",
27
- args: ["auth", "status", "--json"],
28
- validate: (payload) => requireSuccessEnvelope(payload, ["data.caller", "data.cli"])
29
- },
30
- {
31
- name: "schema",
32
- args: ["schema", "--json"],
33
- validate: (payload) => requireSuccessEnvelope(payload, ["data.version", "data.cliCommands"])
34
- },
35
- {
36
- name: "search recent reviewed games",
37
- args: [
38
- "games",
39
- "search",
40
- "--source",
41
- "steam",
42
- "--release-year",
43
- "2025",
44
- "--reviews-min",
45
- "1000",
46
- "--limit",
47
- "5",
48
- "--json"
49
- ],
50
- validate: (payload) => requireSuccessEnvelope(payload, ["data"])
51
- },
52
- {
53
- name: "aggregate recent reviewed games",
54
- args: [
55
- "games",
56
- "aggregate",
57
- "--source",
58
- "steam",
59
- "--release-year",
60
- "2025",
61
- "--metric",
62
- "count,median_reviews",
63
- "--group-by",
64
- "release_year",
65
- "--reviews-min",
66
- "1000",
67
- "--json"
68
- ],
69
- validate: (payload) => requireSuccessEnvelope(payload, ["data"])
70
- },
71
- {
72
- name: "benchmark city builder automation",
73
- args: [
74
- "games",
75
- "benchmark",
76
- "--source",
77
- "steam",
78
- "--tags",
79
- "city_builder,automation",
80
- "--reviews-min",
81
- "1000",
82
- "--preset",
83
- "summary",
84
- "--json"
85
- ],
86
- validate: (payload) => requireComposedEnvelope(payload, ["data.summary"])
87
- },
88
- {
89
- name: "showcase patterns by tag",
90
- args: ["showcases", "patterns", "--source", "steam", "--tags", "city_builder,automation", "--json"],
91
- validate: (payload) => requireComposedEnvelope(payload, ["data.summary"])
92
- }
93
- ];
94
-
95
- try {
96
- const results = smokeCommands.map(runSmokeCommand);
97
- const failed = results.filter((result) => result.status === "failed");
98
- const summary = {
99
- ok: failed.length === 0,
100
- apiBaseUrl: process.env.GMC_API_BASE_URL || "https://gamemarketcopilot.com",
101
- auth: {
102
- apiKeyConfigured: Boolean(process.env.GMC_API_KEY)
103
- },
104
- allowApiErrors,
105
- commands: results
106
- };
107
-
108
- process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
109
- if (failed.length > 0) process.exit(1);
110
- } finally {
111
- rmSync(workDir, { recursive: true, force: true });
112
- }
113
-
114
- function runSmokeCommand(command) {
115
- const result = spawnSync(process.execPath, [cliBin, ...command.args], {
116
- encoding: "utf8",
117
- env: {
118
- ...process.env,
119
- GMC_CONFIG_PATH: configPath
120
- }
121
- });
122
-
123
- const payload = parseJson(result.stdout);
124
- if (!payload.ok) {
125
- return {
126
- name: command.name,
127
- status: "failed",
128
- exitCode: result.status,
129
- error: `stdout was not valid JSON: ${payload.error}`
130
- };
131
- }
132
-
133
- if (payload.value?.error) {
134
- const stable = isStableError(payload.value.error);
135
- const status = allowApiErrors && stable ? "api_error_allowed" : "failed";
136
- return {
137
- name: command.name,
138
- status,
139
- exitCode: result.status,
140
- error: summarizeError(payload.value.error)
141
- };
142
- }
143
-
144
- if (result.status !== 0) {
145
- return {
146
- name: command.name,
147
- status: "failed",
148
- exitCode: result.status,
149
- error: "command exited non-zero without a structured error envelope"
150
- };
151
- }
152
-
153
- const validation = command.validate(payload.value);
154
- if (!validation.ok) {
155
- return {
156
- name: command.name,
157
- status: "failed",
158
- exitCode: result.status,
159
- error: validation.error
160
- };
161
- }
162
-
163
- return {
164
- name: command.name,
165
- status: "passed",
166
- exitCode: result.status,
167
- requestId: payload.value.meta?.requestId,
168
- upstreamRequestIds: Array.isArray(payload.value.meta?.upstreamRequests)
169
- ? payload.value.meta.upstreamRequests.map((request) => request?.requestId).filter(Boolean)
170
- : undefined,
171
- warnings: Array.isArray(payload.value.warnings) ? payload.value.warnings.length : 0,
172
- hasPagination: Boolean(payload.value.pagination),
173
- hasEntitlements: Boolean(payload.value.meta?.entitlements),
174
- hasUpstreamEntitlements: Array.isArray(payload.value.meta?.upstreamRequests)
175
- ? payload.value.meta.upstreamRequests.some((request) => request?.entitlements)
176
- : undefined
177
- };
178
- }
179
-
180
- function parseJson(stdout) {
181
- try {
182
- return {
183
- ok: true,
184
- value: JSON.parse(stdout)
185
- };
186
- } catch (error) {
187
- return {
188
- ok: false,
189
- error: error instanceof Error ? error.message : "unknown parse error"
190
- };
191
- }
192
- }
193
-
194
- function requireSuccessEnvelope(payload, fields) {
195
- const base = requireFields(payload, ["data", "meta.requestId", ...fields]);
196
- if (!base.ok) return base;
197
- return {
198
- ok: true
199
- };
200
- }
201
-
202
- function requireComposedEnvelope(payload, fields) {
203
- const base = requireFields(payload, ["data", "meta.requestId", "meta.upstreamRequests", ...fields]);
204
- if (!base.ok) return base;
205
- if (!Array.isArray(payload.meta.upstreamRequests) || payload.meta.upstreamRequests.length === 0) {
206
- return {
207
- ok: false,
208
- error: "meta.upstreamRequests must include at least one request"
209
- };
210
- }
211
- return {
212
- ok: true
213
- };
214
- }
215
-
216
- function requireFields(payload, fields) {
217
- const missing = fields.filter((field) => getPath(payload, field) == null);
218
- if (missing.length > 0) {
219
- return {
220
- ok: false,
221
- error: `missing fields: ${missing.join(", ")}`
222
- };
223
- }
224
- return {
225
- ok: true
226
- };
227
- }
228
-
229
- function getPath(value, pathName) {
230
- return pathName.split(".").reduce((current, part) => current?.[part], value);
231
- }
232
-
233
- function isStableError(error) {
234
- return Boolean(error && typeof error.code === "string" && typeof error.message === "string");
235
- }
236
-
237
- function summarizeError(error) {
238
- return {
239
- code: error.code,
240
- message: error.message,
241
- requestId: error.requestId,
242
- status: error.status,
243
- contentType: error.contentType,
244
- path: error.path
245
- };
246
- }
@@ -1,123 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { mkdtempSync, rmSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import path from "node:path";
6
- import { spawnSync } from "node:child_process";
7
-
8
- const packageDir = path.resolve(new URL("..", import.meta.url).pathname);
9
- const dryRunOnly = process.argv.includes("--dry-run");
10
- const workDir = mkdtempSync(path.join(tmpdir(), "gmc-cli-pack-"));
11
- const npmCache = path.join(workDir, "npm-cache");
12
-
13
- try {
14
- run("npm", ["pack", "--dry-run"], {
15
- cwd: packageDir,
16
- env: {
17
- npm_config_cache: npmCache
18
- }
19
- });
20
-
21
- if (dryRunOnly) process.exit(0);
22
-
23
- const packResult = run("npm", ["pack", "--json", "--pack-destination", workDir], {
24
- cwd: packageDir,
25
- env: {
26
- npm_config_cache: npmCache
27
- },
28
- capture: true
29
- });
30
- const packed = JSON.parse(packResult.stdout);
31
- const filename = packed?.[0]?.filename;
32
- if (!filename) throw new Error("npm pack did not return a tarball filename");
33
-
34
- const projectDir = path.join(workDir, "project");
35
- run("npm", ["init", "-y"], {
36
- cwd: workDir,
37
- env: {
38
- npm_config_cache: npmCache
39
- }
40
- });
41
- run("npm", ["install", path.join(workDir, filename)], {
42
- cwd: workDir,
43
- env: {
44
- npm_config_cache: npmCache
45
- }
46
- });
47
-
48
- const binName = process.platform === "win32" ? "gmc.cmd" : "gmc";
49
- const gmcBin = path.join(workDir, "node_modules", ".bin", binName);
50
- const configPath = path.join(projectDir, "config.json");
51
- const helpResult = run(gmcBin, ["--help"], {
52
- env: { GMC_CONFIG_PATH: configPath },
53
- capture: true
54
- });
55
- if (!helpResult.stdout.includes("Game Market Copilot CLI") || !helpResult.stdout.includes("gmc --version")) {
56
- throw new Error("Packed gmc --help did not print the expected help text");
57
- }
58
-
59
- const configResult = run(gmcBin, ["config", "get", "--json"], {
60
- env: { GMC_CONFIG_PATH: configPath },
61
- capture: true
62
- });
63
- const configPayload = JSON.parse(configResult.stdout);
64
- if (configPayload?.data?.configPath !== configPath || configPayload?.data?.authMode !== "anonymous") {
65
- throw new Error("Packed gmc config get --json did not print the expected config payload");
66
- }
67
-
68
- const skillStatusResult = run(gmcBin, ["skill", "status", "--json"], {
69
- env: { GMC_CONFIG_PATH: configPath },
70
- capture: true
71
- });
72
- const skillStatus = JSON.parse(skillStatusResult.stdout);
73
- if (!skillStatus?.data?.packaged?.version) {
74
- throw new Error("Packed gmc skill status did not report a packaged skill version");
75
- }
76
-
77
- const skillPrintResult = run(gmcBin, ["skill", "print", "--format", "agents-md"], {
78
- env: { GMC_CONFIG_PATH: configPath },
79
- capture: true
80
- });
81
- if (!skillPrintResult.stdout.includes("GMC Analysis") || !skillPrintResult.stdout.includes("Pitfalls")) {
82
- throw new Error("Packed gmc skill print did not emit the inlined agents-md section");
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
- }
87
-
88
- // gmc is shadowed by Oh My Zsh's `git merge --continue` alias, so the
89
- // package ships alias binaries; make sure npm actually installed them.
90
- for (const aliasName of ["gmcp", "game-market-copilot"]) {
91
- const aliasBinName = process.platform === "win32" ? `${aliasName}.cmd` : aliasName;
92
- const aliasBin = path.join(workDir, "node_modules", ".bin", aliasBinName);
93
- const aliasResult = run(aliasBin, ["--version"], {
94
- env: { GMC_CONFIG_PATH: configPath },
95
- capture: true
96
- });
97
- if (!/^gmc \d+\.\d+\.\d+/.test(aliasResult.stdout)) {
98
- throw new Error(`Packed ${aliasName} --version did not print the CLI version`);
99
- }
100
- }
101
- } finally {
102
- rmSync(workDir, { recursive: true, force: true });
103
- }
104
-
105
- function run(command, args, options = {}) {
106
- const result = spawnSync(command, args, {
107
- cwd: options.cwd,
108
- encoding: "utf8",
109
- env: {
110
- ...process.env,
111
- npm_config_dry_run: "false",
112
- ...(options.env ?? {})
113
- },
114
- stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit"
115
- });
116
-
117
- if (result.status !== 0) {
118
- const stderr = result.stderr ? `\n${result.stderr}` : "";
119
- throw new Error(`${command} ${args.join(" ")} failed with exit code ${result.status}${stderr}`);
120
- }
121
-
122
- return result;
123
- }