gamealgo-cli 0.1.7 → 0.2.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/dist/main.js ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ import { parseGlobalFlags } from "./args.js";
3
+ import { createClient, handleAdminKey, login } from "./commands/auth.js";
4
+ import { handleEvents } from "./commands/events.js";
5
+ import { handleExperiment } from "./commands/experiment.js";
6
+ import { handleIntegration } from "./commands/integration.js";
7
+ import { handleGameKey } from "./commands/keys.js";
8
+ import { handleMarketing } from "./commands/marketing.js";
9
+ import { handleReport } from "./commands/report.js";
10
+ import { handleFileResource } from "./commands/resources.js";
11
+ import { loadPackageMetadata } from "./config.js";
12
+ import { handleDocs, handleHelp } from "./docs.js";
13
+ import { printResult } from "./output.js";
14
+ export async function main() {
15
+ const args = process.argv.slice(2);
16
+ const global = parseGlobalFlags(args);
17
+ const command = args.shift();
18
+ if (command === "version" || command === "--version" || command === "-V") {
19
+ const metadata = await loadPackageMetadata();
20
+ if (global.json) {
21
+ await printResult({ ok: true, name: metadata.name, version: metadata.version }, global);
22
+ }
23
+ else {
24
+ console.log(metadata.version);
25
+ }
26
+ return;
27
+ }
28
+ try {
29
+ if (!command || command === "help" || command === "--help" || command === "-h") {
30
+ await handleHelp(args, global);
31
+ return;
32
+ }
33
+ const helpFlagIndex = args.findIndex((arg) => arg === "--help" || arg === "-h");
34
+ if (helpFlagIndex >= 0) {
35
+ args.splice(helpFlagIndex, 1);
36
+ await handleHelp([command, ...args], global);
37
+ return;
38
+ }
39
+ if (command === "login") {
40
+ await login(args, global);
41
+ return;
42
+ }
43
+ if (command === "whoami") {
44
+ const client = await createClient(global);
45
+ await printResult(await client.session(), global);
46
+ return;
47
+ }
48
+ if (command === "docs") {
49
+ await handleDocs(args, global);
50
+ return;
51
+ }
52
+ if (command === "listapi") {
53
+ await handleDocs(["api", "list", ...args], global);
54
+ return;
55
+ }
56
+ if (command === "admin-key") {
57
+ await handleAdminKey(args, global);
58
+ return;
59
+ }
60
+ const client = await createClient(global);
61
+ if (command === "experiment") {
62
+ await handleExperiment(client, args, global);
63
+ return;
64
+ }
65
+ if (command === "script") {
66
+ await handleFileResource(client, args, global, "script");
67
+ return;
68
+ }
69
+ if (command === "config") {
70
+ await handleFileResource(client, args, global, "config");
71
+ return;
72
+ }
73
+ if (command === "report") {
74
+ await handleReport(client, args, global);
75
+ return;
76
+ }
77
+ if (command === "key") {
78
+ await handleGameKey(client, args, global);
79
+ return;
80
+ }
81
+ if (command === "events") {
82
+ await handleEvents(client, args, global);
83
+ return;
84
+ }
85
+ if (command === "integration") {
86
+ await handleIntegration(client, args, global);
87
+ return;
88
+ }
89
+ if (command === "marketing") {
90
+ await handleMarketing(client, args, global);
91
+ return;
92
+ }
93
+ throw new Error(`Unknown command: ${command}`);
94
+ }
95
+ catch (error) {
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ if (global.json) {
98
+ console.log(JSON.stringify({ ok: false, error: message }, null, 2));
99
+ }
100
+ else {
101
+ console.error(`Error: ${message}`);
102
+ }
103
+ process.exitCode = 1;
104
+ }
105
+ }
106
+ await main();
package/dist/output.js ADDED
@@ -0,0 +1,82 @@
1
+ import YAML from "yaml";
2
+ import { createInterface } from "node:readline/promises";
3
+ import { stdin as input, stdout as output } from "node:process";
4
+ export function isInteractive() {
5
+ return Boolean(input.isTTY && output.isTTY);
6
+ }
7
+ export function reportTimeoutMs(flags) {
8
+ if (flags["timeout-ms"] !== undefined)
9
+ return Math.ceil(positiveNumberFlag(flags["timeout-ms"], "--timeout-ms"));
10
+ if (flags.timeout !== undefined)
11
+ return Math.ceil(positiveNumberFlag(flags.timeout, "--timeout") * 1000);
12
+ return undefined;
13
+ }
14
+ export function positiveNumberFlag(value, flag) {
15
+ if (typeof value === "boolean")
16
+ throw new Error(`${flag} requires a value`);
17
+ const parsed = Number(value);
18
+ if (!Number.isFinite(parsed) || parsed <= 0)
19
+ throw new Error(`${flag} must be a positive number`);
20
+ return parsed;
21
+ }
22
+ export function optionalNonNegativeInteger(value, field) {
23
+ if (value === undefined || value === null || value === "")
24
+ return undefined;
25
+ if (typeof value === "boolean")
26
+ throw new Error(`${field} must be a non-negative integer`);
27
+ const parsed = Number(value);
28
+ if (!Number.isInteger(parsed) || parsed < 0)
29
+ throw new Error(`${field} must be a non-negative integer`);
30
+ return parsed;
31
+ }
32
+ export function positiveInteger(value, field) {
33
+ const parsed = optionalNonNegativeInteger(value, field);
34
+ if (!parsed)
35
+ throw new Error(`${field} must be a positive integer`);
36
+ return parsed;
37
+ }
38
+ export function startProgress(label, timeoutMs) {
39
+ const startedAt = Date.now();
40
+ process.stderr.write(`${label}${timeoutMs ? `, timeout ${formatDuration(timeoutMs)}` : ""}...\n`);
41
+ const timer = setInterval(() => {
42
+ process.stderr.write(`${label} still running, elapsed ${formatDuration(Date.now() - startedAt)}...\n`);
43
+ }, 5000);
44
+ return () => clearInterval(timer);
45
+ }
46
+ export function withCliMeta(value, meta) {
47
+ if (!value || typeof value !== "object" || Array.isArray(value))
48
+ return value;
49
+ return {
50
+ ...value,
51
+ cli: {
52
+ elapsedMs: meta.elapsedMs,
53
+ ...(meta.timeoutMs ? { timeoutMs: meta.timeoutMs } : {}),
54
+ },
55
+ };
56
+ }
57
+ export function formatDuration(ms) {
58
+ if (ms < 1000)
59
+ return `${ms}ms`;
60
+ return `${(ms / 1000).toFixed(1)}s`;
61
+ }
62
+ export async function printResult(value, global) {
63
+ if (global.json) {
64
+ console.log(JSON.stringify(value, null, 2));
65
+ return;
66
+ }
67
+ if (typeof value === "string") {
68
+ console.log(value);
69
+ return;
70
+ }
71
+ console.log(YAML.stringify(value).trimEnd());
72
+ }
73
+ export async function confirm(question) {
74
+ const rl = createInterface({ input, output });
75
+ try {
76
+ const answer = await rl.question(`${question} [y/N] `);
77
+ return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
78
+ }
79
+ finally {
80
+ rl.close();
81
+ }
82
+ }
@@ -0,0 +1,93 @@
1
+ import YAML from "yaml";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, basename } from "node:path";
4
+ import { optionalString } from "./utils.js";
5
+ export const SCRIPT_EXTENSIONS = new Set([".js", ".lua"]);
6
+ export const FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
7
+ export async function readObjectFile(filePath, label) {
8
+ const text = await readFile(filePath, "utf8");
9
+ const parsed = filePath.endsWith(".json") ? JSON.parse(text) : YAML.parse(text);
10
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
11
+ throw new Error(`${label} file must be a JSON/YAML object`);
12
+ }
13
+ return parsed;
14
+ }
15
+ export function latestScriptVersionNames(value) {
16
+ if (!Array.isArray(value))
17
+ return [];
18
+ const names = new Set();
19
+ for (const item of value) {
20
+ const body = objectValue(item);
21
+ const name = optionalString(body?.name);
22
+ if (name && !names.has(name))
23
+ names.add(sanitizeRemoteFileName(name));
24
+ }
25
+ return [...names];
26
+ }
27
+ export function objectValue(value) {
28
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
29
+ }
30
+ export function validateExperimentRunVariantScripts(value) {
31
+ if (!Array.isArray(value))
32
+ throw new Error("experiment run requires variants array");
33
+ for (let index = 0; index < value.length; index += 1) {
34
+ const variant = objectValue(value[index]);
35
+ if (!variant)
36
+ throw new Error(`variants[${index}] must be an object`);
37
+ if (!Object.prototype.hasOwnProperty.call(variant, "script"))
38
+ continue;
39
+ const script = objectValue(variant.script);
40
+ if (!script || !optionalString(script.versionId)) {
41
+ throw new Error(`variants[${index}].script requires versionId; omit script for a config-only variant`);
42
+ }
43
+ }
44
+ }
45
+ export function printExperimentRunVariantScripts(value) {
46
+ const run = objectValue(objectValue(value)?.run);
47
+ if (!run || !Array.isArray(run.variants))
48
+ return;
49
+ console.log("\nVariant execution:");
50
+ for (const item of run.variants) {
51
+ const variant = objectValue(item);
52
+ if (!variant)
53
+ continue;
54
+ const variantId = optionalString(variant.variantId) ?? "(unknown)";
55
+ const versionId = optionalString(objectValue(variant.script)?.versionId);
56
+ console.log(` ${variantId}: ${versionId ? `script ${versionId}` : "config-only"}`);
57
+ }
58
+ }
59
+ export async function readJsonFile(filePath) {
60
+ return JSON.parse(await readFile(filePath, "utf8"));
61
+ }
62
+ export async function writeTextFile(path, content) {
63
+ await mkdir(dirname(path), { recursive: true });
64
+ await writeFile(path, content, "utf8");
65
+ }
66
+ export function sanitizeRemoteFileName(name) {
67
+ const trimmed = name.trim();
68
+ if (!trimmed || trimmed !== basename(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("..")) {
69
+ throw new Error(`invalid remote file name: ${name}`);
70
+ }
71
+ if (!FILE_NAME_PATTERN.test(trimmed)) {
72
+ throw new Error(`invalid remote file name: ${name}`);
73
+ }
74
+ return trimmed;
75
+ }
76
+ export function validateJsonText(content, filePath) {
77
+ try {
78
+ JSON.parse(content);
79
+ }
80
+ catch (error) {
81
+ const message = error instanceof Error ? error.message : String(error);
82
+ throw new Error(`invalid JSON in ${filePath}: ${message}`);
83
+ }
84
+ }
85
+ export function contentTypeForFileName(name) {
86
+ if (name.endsWith(".json"))
87
+ return "application/json; charset=utf-8";
88
+ if (name.endsWith(".lua"))
89
+ return "application/lua; charset=utf-8";
90
+ if (name.endsWith(".js"))
91
+ return "text/javascript; charset=utf-8";
92
+ return "text/plain; charset=utf-8";
93
+ }
package/dist/target.js ADDED
@@ -0,0 +1,3 @@
1
+ export async function announceTargetGame(client) {
2
+ process.stderr.write(`GameAlgo target game: ${await client.gameId()}\n`);
3
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/utils.js ADDED
@@ -0,0 +1,23 @@
1
+ import { SCRIPT_EXTENSIONS } from "./resources.js";
2
+ export function normalizeHost(value) {
3
+ return value.trim().replace(/\/+$/, "");
4
+ }
5
+ export function integrationEnvironment(value) {
6
+ const environment = optionalString(value);
7
+ if (environment === "domestic" || environment === "overseas")
8
+ return environment;
9
+ throw new Error("--environment must be domestic or overseas");
10
+ }
11
+ export function integrationPlatform(value) {
12
+ const platform = optionalString(value);
13
+ if (platform === "ios" || platform === "android" || platform === "maker" || platform === "rest")
14
+ return platform;
15
+ throw new Error("--platform must be ios, android, maker, or rest");
16
+ }
17
+ export function optionalString(value) {
18
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
19
+ }
20
+ export function isScriptFileName(name) {
21
+ const dot = name.lastIndexOf(".");
22
+ return dot >= 0 && SCRIPT_EXTENSIONS.has(name.slice(dot).toLowerCase());
23
+ }
package/package.json CHANGED
@@ -1,23 +1,32 @@
1
1
  {
2
2
  "name": "gamealgo-cli",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "GameAlgo command-line client for AI agents and game developers",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/sunyuhao-code/GameAlgoCli.git"
8
+ },
9
+ "homepage": "https://github.com/sunyuhao-code/GameAlgoCli#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/sunyuhao-code/GameAlgoCli/issues"
12
+ },
5
13
  "type": "module",
6
14
  "bin": {
7
- "gamealgo": "dist/gamealgo.js"
15
+ "gamealgo": "dist/main.js"
8
16
  },
9
17
  "files": [
10
18
  "dist/",
11
- "cli/README.md"
19
+ "README.md",
20
+ "LICENSE"
12
21
  ],
13
22
  "publishConfig": {
14
23
  "access": "public"
15
24
  },
16
25
  "scripts": {
17
- "build:cli": "tsc -p tsconfig.cli.json",
18
- "check": "npm run build:cli && node --test rest-api/src/*.test.ts",
19
- "cli": "node cli/gamealgo.ts",
20
- "prepack": "npm run build:cli"
26
+ "build": "tsc -p tsconfig.json",
27
+ "check": "npm run build && node --test test/*.test.mjs",
28
+ "start": "node src/main.ts",
29
+ "prepack": "npm run build"
21
30
  },
22
31
  "engines": {
23
32
  "node": ">=24"