unity-mcp-cli 0.84.3 → 0.85.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,114 +1,11 @@
1
1
  // Copyright (c) 2024 Ivan Murzak. All rights reserved.
2
2
  // Licensed under the Apache License, Version 2.0.
3
- import * as fs from 'fs';
4
- import * as os from 'os';
5
- import * as path from 'path';
6
- import { execFileSync } from 'child_process';
7
3
  /**
8
- * TypeScript client of the shared machine credential store — the same on-disk contract the
9
- * plugin's C# `MachineCredentialStore` (MCP-Plugin-dotnet, com.IvanMurzak.McpPlugin.AgentConfig)
10
- * reads and writes. A single ai-game.dev account credential lives once per machine at
11
- * `~/.ai-game-dev/credentials.json`, so `login` writes it here and every engine plugin/CLI reads
12
- * it — sign-in happens once per machine, never per project, and the credential is NEVER written
13
- * into a project file / VCS.
14
- *
15
- * At-rest protection matches the C# store byte-for-byte so the plugin can read what the CLI wrote:
16
- * - POSIX — plaintext JSON, file mode 0600, inside a 0700 directory.
17
- * - Windows — DPAPI-encrypted (CurrentUser scope, no entropy) via
18
- * System.Security.Cryptography.ProtectedData, invoked through PowerShell. This is
19
- * interoperable with the C# store's CryptProtectData/CryptUnprotectData (the description
20
- * string and CRYPTPROTECT_UI_FORBIDDEN flag do not affect decryptability).
4
+ * The shared machine credential store now lives in `@baizor/gamedev-cli-core` (auth-fixes T7 / b2):
5
+ * ONE implementation of `~/.ai-game-dev/credentials.json` across the three engine CLIs and the C#
6
+ * plugin — atomic, corruption-safe writes (temp + fsync + rename), DPAPI on Windows / `0600`-`0700`
7
+ * on POSIX, plus the `rotate()` used by the proactive refresh loop. This module is a thin re-export
8
+ * so the rest of the CLI keeps importing from a stable local path.
21
9
  */
22
- /** Directory name under the user home (or a project root) that holds the store. */
23
- export const MACHINE_STORE_DIR_NAME = '.ai-game-dev';
24
- /** File name of the secret credential document. */
25
- export const CREDENTIALS_FILE_NAME = 'credentials.json';
26
- const isWindows = process.platform === 'win32';
27
- /**
28
- * The shared machine credential store. Defaults to `~/.ai-game-dev/`; pass an explicit
29
- * `baseDirectory` for tests or for the `--project` per-project store
30
- * (`<project>/.ai-game-dev/`).
31
- */
32
- export class MachineCredentialStore {
33
- constructor(baseDirectory) {
34
- this._baseDirectory = baseDirectory ?? path.join(os.homedir(), MACHINE_STORE_DIR_NAME);
35
- }
36
- /** Absolute path of the store directory. */
37
- get baseDirectory() {
38
- return this._baseDirectory;
39
- }
40
- /** Absolute path of the secret credential file. */
41
- get credentialsPath() {
42
- return path.join(this._baseDirectory, CREDENTIALS_FILE_NAME);
43
- }
44
- /** True when a credential file exists in the store. */
45
- get exists() {
46
- return fs.existsSync(this.credentialsPath);
47
- }
48
- /**
49
- * Encrypt (Windows) / restrict (POSIX) and write `credentials` to the store, creating the
50
- * store directory with owner-only permissions if needed. `version` is always written as 1;
51
- * undefined fields are omitted (matching the C# `WhenWritingNull` policy).
52
- */
53
- write(credentials) {
54
- this.ensureBaseDirectory();
55
- const document = { ...credentials, version: 1 };
56
- const json = JSON.stringify(document, null, 2);
57
- const plaintext = Buffer.from(json, 'utf-8');
58
- const bytes = isWindows ? dpapiTransform('Protect', plaintext) : plaintext;
59
- fs.writeFileSync(this.credentialsPath, bytes);
60
- if (!isWindows) {
61
- fs.chmodSync(this.credentialsPath, 0o600);
62
- }
63
- }
64
- /** Read and decrypt the stored credentials, or null when none are present. */
65
- read() {
66
- if (!fs.existsSync(this.credentialsPath)) {
67
- return null;
68
- }
69
- const raw = fs.readFileSync(this.credentialsPath);
70
- if (raw.length === 0) {
71
- return null;
72
- }
73
- const plaintext = isWindows ? dpapiTransform('Unprotect', raw) : raw;
74
- const json = plaintext.toString('utf-8');
75
- if (json.trim().length === 0) {
76
- return null;
77
- }
78
- return JSON.parse(json);
79
- }
80
- /** Delete the stored credentials (sign-out). No-op when none exist. */
81
- delete() {
82
- if (fs.existsSync(this.credentialsPath)) {
83
- fs.rmSync(this.credentialsPath);
84
- }
85
- }
86
- ensureBaseDirectory() {
87
- fs.mkdirSync(this._baseDirectory, { recursive: true });
88
- if (!isWindows) {
89
- fs.chmodSync(this._baseDirectory, 0o700);
90
- }
91
- }
92
- }
93
- /**
94
- * Run a Windows DPAPI Protect/Unprotect round trip through PowerShell's
95
- * System.Security.Cryptography.ProtectedData (CurrentUser scope, no entropy) — interoperable
96
- * with the C# store's CryptProtectData/CryptUnprotectData. Input and output are passed as
97
- * base64 through an environment variable so the plaintext never lands in argv or the process
98
- * table. Only ever invoked on Windows.
99
- */
100
- function dpapiTransform(action, input) {
101
- const script = "$ErrorActionPreference='Stop';" +
102
- 'Add-Type -AssemblyName System.Security;' +
103
- '$in=[Convert]::FromBase64String($env:AIGD_DPAPI_IN);' +
104
- `$out=[System.Security.Cryptography.ProtectedData]::${action}($in,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);` +
105
- '[Convert]::ToBase64String($out)';
106
- const stdout = execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
107
- encoding: 'utf-8',
108
- env: { ...process.env, AIGD_DPAPI_IN: input.toString('base64') },
109
- timeout: 20000,
110
- windowsHide: true,
111
- });
112
- return Buffer.from(stdout.trim(), 'base64');
113
- }
10
+ export { MachineCredentialStore, MACHINE_STORE_DIR_NAME, CREDENTIALS_FILE_NAME, CREDENTIALS_SCHEMA_VERSION, } from '@baizor/gamedev-cli-core';
114
11
  //# sourceMappingURL=machine-credentials.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"machine-credentials.js","sourceRoot":"","sources":["../../src/utils/machine-credentials.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"machine-credentials.js","sourceRoot":"","sources":["../../src/utils/machine-credentials.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,kDAAkD;AAElD;;;;;;GAMG;AAEH,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC"}
@@ -1,19 +1,12 @@
1
1
  /**
2
- * The exact string that is UTF-8/SHA-256 hashed: the project root with trailing directory
3
- * separators trimmed, then lowercased with the ToLowerInvariant-matching rules above.
4
- * Exposed so the golden-vector parity test can reproduce the pre-hash string.
2
+ * Project-identity derivation (routing pin + deterministic local port) now lives in
3
+ * `@baizor/gamedev-cli-core` (auth-fixes T3/T7): ONE port of the C# `ProjectIdentity`, gated by the
4
+ * SAME golden vectors as the .NET reference. This module re-exports the **v1** algorithm under the
5
+ * historical names the CLI has always used, so existing call sites (`utils/config.ts`,
6
+ * `utils/connection.ts`) and the golden-vector parity tests keep matching byte-for-byte.
7
+ *
8
+ * The **v2** algorithm (the `\`→`/` normalization that fixes B5) is what the configurators emit —
9
+ * `setup-mcp` / `enroll` derive their pins with `derivePinV2` inside cli-core; import `derivePinV2` /
10
+ * `derivePortV2` directly from `@baizor/gamedev-cli-core` when the v2 pin is needed.
5
11
  */
6
- export declare function normalizeProjectRoot(projectRoot: string): string;
7
- /**
8
- * The routing pin: the first 4 bytes of the SHA-256 of the normalized project root as 8
9
- * lowercase hex characters. Byte-for-byte the C# ProjectIdentity.DerivePin.
10
- */
11
- export declare function deriveProjectPin(dir: string): string;
12
- /**
13
- * Generate a deterministic port from a directory path.
14
- * Ports the canonical C# ProjectIdentity derivation (DerivePort), which is itself byte-for-byte
15
- * the shipped Unity UnityMcpPlugin.GeneratePortFromDirectory() logic:
16
- * SHA256 of the normalized (trailing-separator-trimmed, ToLowerInvariant) directory → first
17
- * 4 bytes as a little-endian uint32 → modulo 10000 + 20000 (range 20000-29999).
18
- */
19
- export declare function generatePortFromDirectory(dir: string): number;
12
+ export { derivePin as deriveProjectPin, derivePort as generatePortFromDirectory, normalize as normalizeProjectRoot, } from '@baizor/gamedev-cli-core';
@@ -1,74 +1,15 @@
1
- import { createHash } from 'crypto';
2
- const MIN_PORT = 20000;
3
- const MAX_PORT = 29999;
4
- const PORT_RANGE = MAX_PORT - MIN_PORT + 1;
5
- // Routing pin = first 4 bytes of the hash rendered as 8 lowercase hex chars.
6
- const PIN_BYTES = 4;
7
- // Characters where JS String.prototype.toLowerCase() diverges from .NET
8
- // string.ToLowerInvariant(). ToLowerInvariant is the canonical origin of the
9
- // ProjectIdentity derivation (see MCP-Plugin-dotnet ProjectIdentity.GoldenVectors.json),
10
- // so the TS port must reproduce it byte-for-byte. Each entry maps a code point to the
11
- // value ToLowerInvariant produces:
12
- // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE - ToLowerInvariant leaves it unchanged
13
- // (no case fold), whereas toLowerCase() lowers it to U+0069 U+0307 (i + COMBINING DOT ABOVE).
14
- const INVARIANT_LOWER_OVERRIDES = {
15
- 'İ': 'İ',
16
- };
17
- /**
18
- * Lowercase a string the way .NET string.ToLowerInvariant() does: a simple,
19
- * culture-independent, per-code-point mapping (no context-sensitive rules such as the
20
- * Greek final-sigma or the Turkish-i special cases). We lower each code point on its own
21
- * and apply INVARIANT_LOWER_OVERRIDES for the few points where JS disagrees with .NET.
22
- */
23
- function toLowerInvariant(value) {
24
- let out = '';
25
- for (const ch of value) {
26
- out += INVARIANT_LOWER_OVERRIDES[ch] ?? ch.toLowerCase();
27
- }
28
- return out;
29
- }
30
- /**
31
- * Trim trailing directory separators ('/' and '\\') so '/a/b' and '/a/b/' are the same
32
- * project. Never trims below length 1 (matches ProjectIdentity.TrimTrailingSeparators).
33
- * Separators are NOT converted — 'C:\\a' and 'C:/a' remain distinct and hash differently.
34
- */
35
- function trimTrailingSeparators(pathStr) {
36
- let end = pathStr.length;
37
- while (end > 1 && (pathStr[end - 1] === '/' || pathStr[end - 1] === '\\')) {
38
- end--;
39
- }
40
- return end === pathStr.length ? pathStr : pathStr.slice(0, end);
41
- }
42
- /**
43
- * The exact string that is UTF-8/SHA-256 hashed: the project root with trailing directory
44
- * separators trimmed, then lowercased with the ToLowerInvariant-matching rules above.
45
- * Exposed so the golden-vector parity test can reproduce the pre-hash string.
46
- */
47
- export function normalizeProjectRoot(projectRoot) {
48
- return toLowerInvariant(trimTrailingSeparators(projectRoot));
49
- }
50
- function hashOf(projectRoot) {
51
- return createHash('sha256').update(normalizeProjectRoot(projectRoot), 'utf-8').digest();
52
- }
53
- /**
54
- * The routing pin: the first 4 bytes of the SHA-256 of the normalized project root as 8
55
- * lowercase hex characters. Byte-for-byte the C# ProjectIdentity.DerivePin.
56
- */
57
- export function deriveProjectPin(dir) {
58
- return hashOf(dir).subarray(0, PIN_BYTES).toString('hex');
59
- }
60
- /**
61
- * Generate a deterministic port from a directory path.
62
- * Ports the canonical C# ProjectIdentity derivation (DerivePort), which is itself byte-for-byte
63
- * the shipped Unity UnityMcpPlugin.GeneratePortFromDirectory() logic:
64
- * SHA256 of the normalized (trailing-separator-trimmed, ToLowerInvariant) directory → first
65
- * 4 bytes as a little-endian uint32 → modulo 10000 + 20000 (range 20000-29999).
66
- */
67
- export function generatePortFromDirectory(dir) {
68
- const hash = hashOf(dir);
69
- // Read first 4 bytes as little-endian int32, then treat as unsigned.
70
- const int32 = hash.readInt32LE(0);
71
- const uint32 = int32 >>> 0;
72
- return MIN_PORT + (uint32 % PORT_RANGE);
73
- }
1
+ // Copyright (c) 2024 Ivan Murzak. All rights reserved.
2
+ // Licensed under the Apache License, Version 2.0.
3
+ /**
4
+ * Project-identity derivation (routing pin + deterministic local port) now lives in
5
+ * `@baizor/gamedev-cli-core` (auth-fixes T3/T7): ONE port of the C# `ProjectIdentity`, gated by the
6
+ * SAME golden vectors as the .NET reference. This module re-exports the **v1** algorithm under the
7
+ * historical names the CLI has always used, so existing call sites (`utils/config.ts`,
8
+ * `utils/connection.ts`) and the golden-vector parity tests keep matching byte-for-byte.
9
+ *
10
+ * The **v2** algorithm (the `\`→`/` normalization that fixes B5) is what the configurators emit —
11
+ * `setup-mcp` / `enroll` derive their pins with `derivePinV2` inside cli-core; import `derivePinV2` /
12
+ * `derivePortV2` directly from `@baizor/gamedev-cli-core` when the v2 pin is needed.
13
+ */
14
+ export { derivePin as deriveProjectPin, derivePort as generatePortFromDirectory, normalize as normalizeProjectRoot, } from '@baizor/gamedev-cli-core';
74
15
  //# sourceMappingURL=port.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/utils/port.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,MAAM,QAAQ,GAAG,KAAK,CAAC;AACvB,MAAM,QAAQ,GAAG,KAAK,CAAC;AACvB,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,CAAC,CAAC;AAC3C,6EAA6E;AAC7E,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,wEAAwE;AACxE,6EAA6E;AAC7E,yFAAyF;AACzF,sFAAsF;AACtF,mCAAmC;AACnC,wFAAwF;AACxF,gGAAgG;AAChG,MAAM,yBAAyB,GAA2B;IACxD,GAAG,EAAE,GAAG;CACT,CAAC;AAEF;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,KAAa;IACrC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACvB,GAAG,IAAI,yBAAyB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAAC,OAAe;IAC7C,IAAI,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;IACzB,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1E,GAAG,EAAE,CAAC;IACR,CAAC;IACD,OAAO,GAAG,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,WAAmB;IACtD,OAAO,gBAAgB,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,MAAM,CAAC,WAAmB;IACjC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,oBAAoB,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;AAC1F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAW;IACnD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAEzB,qEAAqE;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC;IAE3B,OAAO,QAAQ,GAAG,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;AAC1C,CAAC"}
1
+ {"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/utils/port.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,kDAAkD;AAElD;;;;;;;;;;GAUG;AAEH,OAAO,EACL,SAAS,IAAI,gBAAgB,EAC7B,UAAU,IAAI,yBAAyB,EACvC,SAAS,IAAI,oBAAoB,GAClC,MAAM,0BAA0B,CAAC"}
@@ -1,26 +1,9 @@
1
1
  /**
2
- * The tool-neutral, NON-SECRET, committable project marker at
3
- * `<project>/.ai-game-dev/project.json` (design 06/09, D15). It records the enrolled server
4
- * target (hosted vs local) and the optional user port override so ProjectIdentity resolution and
5
- * every config writer (engine UI, CLIs, `configure`) agree on one source of truth. Credentials
6
- * NEVER go here — those live only in the machine credential store (`credentials.json`).
2
+ * The tool-neutral, NON-SECRET, committable project marker (`<project>/.ai-game-dev/project.json`)
3
+ * now lives in `@baizor/gamedev-cli-core` (auth-fixes T7): ONE implementation shared by the three
4
+ * engine CLIs. It records the enrolled server target (hosted vs local) and the optional user port
5
+ * override; credentials NEVER go here (those live only in the machine credential store). This module
6
+ * is a thin re-export so the CLI keeps a stable local import path.
7
7
  */
8
- export declare const PROJECT_MARKER_FILE = "project.json";
9
- export interface ProjectMarker {
10
- /** The server the project is enrolled against (hosted `https://ai-game.dev` or a local URL). */
11
- serverTarget?: string;
12
- /** User's explicit local-port override (wins over the deterministic derived port). */
13
- portOverride?: number;
14
- /** Unknown fields are preserved on read/merge for forward-compatibility. */
15
- [key: string]: unknown;
16
- }
17
- export declare function projectMarkerDir(projectPath: string): string;
18
- export declare function projectMarkerPath(projectPath: string): string;
19
- /** Read the marker, or null when absent/unparsable. */
20
- export declare function readProjectMarker(projectPath: string): ProjectMarker | null;
21
- /**
22
- * Merge `marker` into any existing marker and write it back (creating the `.ai-game-dev/`
23
- * directory as needed). Idempotent for the same inputs; preserves pre-existing keys. Returns the
24
- * absolute marker path.
25
- */
26
- export declare function writeProjectMarker(projectPath: string, marker: ProjectMarker): string;
8
+ export { readProjectMarker, writeProjectMarker, projectMarkerPath, projectMarkerDir, PROJECT_MARKER_FILE, } from '@baizor/gamedev-cli-core';
9
+ export type { ProjectMarker } from '@baizor/gamedev-cli-core';
@@ -1,48 +1,11 @@
1
1
  // Copyright (c) 2024 Ivan Murzak. All rights reserved.
2
2
  // Licensed under the Apache License, Version 2.0.
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import { MACHINE_STORE_DIR_NAME } from './machine-credentials.js';
6
3
  /**
7
- * The tool-neutral, NON-SECRET, committable project marker at
8
- * `<project>/.ai-game-dev/project.json` (design 06/09, D15). It records the enrolled server
9
- * target (hosted vs local) and the optional user port override so ProjectIdentity resolution and
10
- * every config writer (engine UI, CLIs, `configure`) agree on one source of truth. Credentials
11
- * NEVER go here — those live only in the machine credential store (`credentials.json`).
4
+ * The tool-neutral, NON-SECRET, committable project marker (`<project>/.ai-game-dev/project.json`)
5
+ * now lives in `@baizor/gamedev-cli-core` (auth-fixes T7): ONE implementation shared by the three
6
+ * engine CLIs. It records the enrolled server target (hosted vs local) and the optional user port
7
+ * override; credentials NEVER go here (those live only in the machine credential store). This module
8
+ * is a thin re-export so the CLI keeps a stable local import path.
12
9
  */
13
- export const PROJECT_MARKER_FILE = 'project.json';
14
- export function projectMarkerDir(projectPath) {
15
- return path.join(projectPath, MACHINE_STORE_DIR_NAME);
16
- }
17
- export function projectMarkerPath(projectPath) {
18
- return path.join(projectMarkerDir(projectPath), PROJECT_MARKER_FILE);
19
- }
20
- /** Read the marker, or null when absent/unparsable. */
21
- export function readProjectMarker(projectPath) {
22
- const markerPath = projectMarkerPath(projectPath);
23
- if (!fs.existsSync(markerPath))
24
- return null;
25
- try {
26
- const parsed = JSON.parse(fs.readFileSync(markerPath, 'utf-8'));
27
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
28
- return null;
29
- return parsed;
30
- }
31
- catch {
32
- return null;
33
- }
34
- }
35
- /**
36
- * Merge `marker` into any existing marker and write it back (creating the `.ai-game-dev/`
37
- * directory as needed). Idempotent for the same inputs; preserves pre-existing keys. Returns the
38
- * absolute marker path.
39
- */
40
- export function writeProjectMarker(projectPath, marker) {
41
- const dir = projectMarkerDir(projectPath);
42
- fs.mkdirSync(dir, { recursive: true });
43
- const merged = { ...(readProjectMarker(projectPath) ?? {}), ...marker };
44
- const markerPath = projectMarkerPath(projectPath);
45
- fs.writeFileSync(markerPath, JSON.stringify(merged, null, 2) + '\n');
46
- return markerPath;
47
- }
10
+ export { readProjectMarker, writeProjectMarker, projectMarkerPath, projectMarkerDir, PROJECT_MARKER_FILE, } from '@baizor/gamedev-cli-core';
48
11
  //# sourceMappingURL=project-marker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-marker.js","sourceRoot":"","sources":["../../src/utils/project-marker.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,kDAAkD;AAElD,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAWlD,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,sBAAsB,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,OAAO,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC,CAAC;AACvE,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,MAAM,UAAU,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAClD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAY,CAAC;QAC3E,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAChF,OAAO,MAAuB,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,MAAqB;IAC3E,MAAM,GAAG,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC1C,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,MAAM,GAAkB,EAAE,GAAG,CAAC,iBAAiB,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;IACvF,MAAM,UAAU,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAClD,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACrE,OAAO,UAAU,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"project-marker.js","sourceRoot":"","sources":["../../src/utils/project-marker.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,kDAAkD;AAElD;;;;;;GAMG;AAEH,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC"}
@@ -12,7 +12,7 @@
12
12
  * package ships no plugin sources — the plugin release cadence bumps both in the same PR, and the
13
13
  * drift-guard test fails CI if a bump touches only one side.
14
14
  */
15
- export declare const DEFAULT_SERVER_VERSION = "9.1.0";
15
+ export declare const DEFAULT_SERVER_VERSION = "9.1.1";
16
16
  /** GitHub `owner/repo` that hosts the shared server's tagged releases + per-RID zips. */
17
17
  export declare const SERVER_RELEASE_REPO = "IvanMurzak/GameDev-MCP-Server";
18
18
  /** Base executable name (no extension) of the shared server binary. */
@@ -14,7 +14,7 @@
14
14
  * package ships no plugin sources — the plugin release cadence bumps both in the same PR, and the
15
15
  * drift-guard test fails CI if a bump touches only one side.
16
16
  */
17
- export const DEFAULT_SERVER_VERSION = '9.1.0';
17
+ export const DEFAULT_SERVER_VERSION = '9.1.1';
18
18
  /** GitHub `owner/repo` that hosts the shared server's tagged releases + per-RID zips. */
19
19
  export const SERVER_RELEASE_REPO = 'IvanMurzak/GameDev-MCP-Server';
20
20
  /** Base executable name (no extension) of the shared server binary. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unity-mcp-cli",
3
- "version": "0.84.3",
3
+ "version": "0.85.0",
4
4
  "description": "Cross-platform CLI tool for AI Game Developer (Skills & MCP). Full AI develop and test loop. Efficient token usage, advanced tools. Creates Unity project, installs plugins, configures tools, and manages HTTP connection with Unity Editor and a game made with Unity. Works with Claude Code, Gemini, Copilot, Cursor and any other absolutely for free.",
5
5
  "type": "module",
6
6
  "main": "dist/lib.js",
@@ -62,6 +62,7 @@
62
62
  "node": "^20.19.0 || >=22.12.0"
63
63
  },
64
64
  "dependencies": {
65
+ "@baizor/gamedev-cli-core": "^0.1.0",
65
66
  "chalk": "^5.6.2",
66
67
  "commander": "^13.1.0",
67
68
  "yocto-spinner": "^1.1.0"
@@ -1,28 +0,0 @@
1
- export interface DeviceAuthorizeResponse {
2
- device_code: string;
3
- user_code: string;
4
- verification_uri: string;
5
- verification_uri_complete: string;
6
- expires_in: number;
7
- interval: number;
8
- }
9
- export type DeviceAuthResult = {
10
- success: true;
11
- accessToken: string;
12
- } | {
13
- success: false;
14
- reason: 'expired' | 'denied' | 'error';
15
- message: string;
16
- };
17
- export interface DeviceAuthCallbacks {
18
- onUserCode: (userCode: string, verificationUrl: string) => void;
19
- onPolling?: () => void;
20
- }
21
- /**
22
- * Run the RFC 8628 Device Authorization Grant flow against the given server.
23
- *
24
- * 1. POST /api/auth/device/authorize → get device_code + user_code
25
- * 2. Invoke onUserCode so the caller can display instructions / open browser
26
- * 3. Poll /api/auth/device/token until success, denial, or expiry
27
- */
28
- export declare function deviceAuthFlow(baseUrl: string, clientLabel: string, callbacks: DeviceAuthCallbacks, minIntervalMs?: number): Promise<DeviceAuthResult>;
@@ -1,111 +0,0 @@
1
- // Copyright (c) 2024 Ivan Murzak. All rights reserved.
2
- // Licensed under the Apache License, Version 2.0.
3
- import { verbose } from './ui.js';
4
- // ─── Flow ────────────────────────────────────────────────────────────────────
5
- /**
6
- * Run the RFC 8628 Device Authorization Grant flow against the given server.
7
- *
8
- * 1. POST /api/auth/device/authorize → get device_code + user_code
9
- * 2. Invoke onUserCode so the caller can display instructions / open browser
10
- * 3. Poll /api/auth/device/token until success, denial, or expiry
11
- */
12
- export async function deviceAuthFlow(baseUrl, clientLabel, callbacks, minIntervalMs) {
13
- const authorizeUrl = `${baseUrl}/api/auth/device/authorize`;
14
- verbose(`POST ${authorizeUrl}`);
15
- const initResponse = await fetchWithTimeout(authorizeUrl, {
16
- method: 'POST',
17
- headers: { 'Content-Type': 'application/json' },
18
- body: JSON.stringify({ client_label: clientLabel }),
19
- }, 30000);
20
- if (!initResponse.ok) {
21
- const text = await initResponse.text();
22
- return {
23
- success: false,
24
- reason: 'error',
25
- message: `Failed to initiate device auth (HTTP ${initResponse.status}): ${text}`,
26
- };
27
- }
28
- const auth = (await initResponse.json());
29
- verbose(`Device code received, user code: ${auth.user_code}, expires in ${auth.expires_in}s`);
30
- callbacks.onUserCode(auth.user_code, auth.verification_uri_complete);
31
- callbacks.onPolling?.();
32
- const tokenUrl = `${baseUrl}/api/auth/device/token`;
33
- const deadline = Date.now() + auth.expires_in * 1000;
34
- const effectiveMinInterval = minIntervalMs ?? 5000;
35
- let interval = Math.max(auth.interval * 1000, effectiveMinInterval);
36
- while (Date.now() < deadline) {
37
- await sleep(interval);
38
- if (Date.now() >= deadline)
39
- break;
40
- verbose(`Polling ${tokenUrl} (interval=${interval / 1000}s)`);
41
- const pollResponse = await fetchWithTimeout(tokenUrl, {
42
- method: 'POST',
43
- headers: { 'Content-Type': 'application/json' },
44
- body: JSON.stringify({
45
- device_code: auth.device_code,
46
- grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
47
- }),
48
- }, 15000);
49
- if (pollResponse.ok) {
50
- const data = (await pollResponse.json());
51
- return { success: true, accessToken: data.access_token };
52
- }
53
- let errorData;
54
- try {
55
- errorData = (await pollResponse.json());
56
- }
57
- catch {
58
- return {
59
- success: false,
60
- reason: 'error',
61
- message: `Unexpected response from token endpoint (HTTP ${pollResponse.status})`,
62
- };
63
- }
64
- verbose(`Poll response: ${errorData.error} — ${errorData.error_description ?? ''}`);
65
- switch (errorData.error) {
66
- case 'authorization_pending':
67
- break;
68
- case 'slow_down':
69
- interval = Math.min(interval + 5000, 30000);
70
- verbose(`Slowing down, new interval: ${interval / 1000}s`);
71
- break;
72
- case 'expired_token':
73
- return {
74
- success: false,
75
- reason: 'expired',
76
- message: errorData.error_description ?? 'Device code expired. Please try again.',
77
- };
78
- case 'access_denied':
79
- return {
80
- success: false,
81
- reason: 'denied',
82
- message: errorData.error_description ?? 'Authorization was denied.',
83
- };
84
- default:
85
- return {
86
- success: false,
87
- reason: 'error',
88
- message: errorData.error_description ?? `Unexpected error: ${errorData.error}`,
89
- };
90
- }
91
- }
92
- return {
93
- success: false,
94
- reason: 'expired',
95
- message: 'Device code expired. Please try again.',
96
- };
97
- }
98
- function sleep(ms) {
99
- return new Promise((resolve) => setTimeout(resolve, ms));
100
- }
101
- async function fetchWithTimeout(url, options, timeoutMs) {
102
- const controller = new AbortController();
103
- const timer = setTimeout(() => controller.abort(), timeoutMs);
104
- try {
105
- return await fetch(url, { ...options, signal: controller.signal });
106
- }
107
- finally {
108
- clearTimeout(timer);
109
- }
110
- }
111
- //# sourceMappingURL=auth.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"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"}