godot-cli 0.16.2 → 0.17.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/commands/configure.js +24 -1
- package/dist/commands/configure.js.map +1 -1
- package/dist/commands/install-plugin.js +99 -7
- package/dist/commands/install-plugin.js.map +1 -1
- package/dist/commands/login.js +53 -15
- package/dist/commands/login.js.map +1 -1
- package/dist/lib/configure-agent.d.ts +20 -0
- package/dist/lib/configure-agent.js +115 -0
- package/dist/lib/configure-agent.js.map +1 -0
- package/dist/lib/enroll.d.ts +21 -0
- package/dist/lib/enroll.js +100 -0
- package/dist/lib/enroll.js.map +1 -0
- package/dist/lib/install-server.d.ts +25 -0
- package/dist/lib/install-server.js +234 -0
- package/dist/lib/install-server.js.map +1 -0
- package/dist/lib/types.d.ts +138 -0
- package/dist/lib.d.ts +4 -1
- package/dist/lib.js +3 -0
- package/dist/lib.js.map +1 -1
- package/dist/utils/addon-deps.js +2 -2
- package/dist/utils/addon-deps.js.map +1 -1
- package/dist/utils/cloud-login.d.ts +22 -4
- package/dist/utils/cloud-login.js +40 -17
- package/dist/utils/cloud-login.js.map +1 -1
- package/dist/utils/connection.d.ts +4 -7
- package/dist/utils/connection.js +8 -10
- package/dist/utils/connection.js.map +1 -1
- package/dist/utils/enroll.d.ts +39 -0
- package/dist/utils/enroll.js +66 -0
- package/dist/utils/enroll.js.map +1 -0
- package/dist/utils/machine-credentials.d.ts +74 -0
- package/dist/utils/machine-credentials.js +182 -0
- package/dist/utils/machine-credentials.js.map +1 -0
- package/dist/utils/project-identity.d.ts +76 -0
- package/dist/utils/project-identity.js +111 -0
- package/dist/utils/project-identity.js.map +1 -0
- package/dist/utils/project-marker.d.ts +64 -0
- package/dist/utils/project-marker.js +192 -0
- package/dist/utils/project-marker.js.map +1 -0
- package/dist/utils/server-source.d.ts +84 -0
- package/dist/utils/server-source.js +217 -0
- package/dist/utils/server-source.js.map +1 -0
- package/package.json +1 -1
package/dist/utils/connection.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import { verbose } from './ui.js';
|
|
4
4
|
import * as ui from './ui.js';
|
|
5
5
|
import { readCloudToken } from './credentials.js';
|
|
6
|
+
import { readMachineAccessToken } from './machine-credentials.js';
|
|
6
7
|
// --- Godot MCP connection constants (mirror addons/godot_mcp GodotMcpConfig). ---
|
|
7
8
|
/** Default hosted cloud base URL (GodotMcpConfig.DefaultCloudBaseUrl). */
|
|
8
9
|
export const DEFAULT_CLOUD_BASE_URL = 'https://ai-game.dev';
|
|
@@ -85,13 +86,10 @@ function isCloudMode() {
|
|
|
85
86
|
* Token priority:
|
|
86
87
|
* 1. --token flag (explicit override)
|
|
87
88
|
* 2. GODOT_MCP_TOKEN env
|
|
88
|
-
* 3. persisted cloud token
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* the Godot plugin is a server-less client whose live config lives in `user://`
|
|
93
|
-
* (outside the project tree). The one project-local surface the CLI DOES own is
|
|
94
|
-
* the `login`-written credentials file, consulted last so `--token`/env still win.
|
|
89
|
+
* 3. persisted cloud token (Cloud mode only) — the project-local
|
|
90
|
+
* `.godot-mcp/credentials.json` (a `--project` login) first, then the shared machine
|
|
91
|
+
* store `~/.ai-game-dev/credentials.json` (a default `godot-cli login`), so a
|
|
92
|
+
* sign-once-per-machine credential is picked up here too.
|
|
95
93
|
*/
|
|
96
94
|
export function resolveConnection(projectPath, options) {
|
|
97
95
|
let url;
|
|
@@ -127,10 +125,10 @@ export function resolveConnection(projectPath, options) {
|
|
|
127
125
|
verbose(`Using ${ENV_TOKEN}`);
|
|
128
126
|
}
|
|
129
127
|
else if (isCloudMode()) {
|
|
130
|
-
const persisted = readCloudToken(projectPath);
|
|
128
|
+
const persisted = readCloudToken(projectPath) ?? readMachineAccessToken();
|
|
131
129
|
if (persisted) {
|
|
132
130
|
token = persisted;
|
|
133
|
-
verbose('Using persisted cloud token
|
|
131
|
+
verbose('Using persisted cloud token (project store, then machine store)');
|
|
134
132
|
}
|
|
135
133
|
}
|
|
136
134
|
return { url, token };
|
|
@@ -155,7 +153,7 @@ export function resolveOpenAuthToken(projectPath, options) {
|
|
|
155
153
|
}
|
|
156
154
|
const selectedMode = options.mode ?? normalizeEnv(process.env[ENV_CONNECTION_MODE]);
|
|
157
155
|
if (selectedMode !== undefined && selectedMode.toLowerCase() === 'cloud') {
|
|
158
|
-
return readCloudToken(projectPath);
|
|
156
|
+
return readCloudToken(projectPath) ?? readMachineAccessToken();
|
|
159
157
|
}
|
|
160
158
|
return undefined;
|
|
161
159
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redeem a D13 **enrollment code** against the cloud AS `POST /api/auth/enroll/redeem`
|
|
3
|
+
* endpoint (design 05 §Enrollment endpoints) — the agent-first path that plants a
|
|
4
|
+
* plugin credential with NO browser hop. The code is **burned on the first redeem
|
|
5
|
+
* attempt** (server-side), and every failure returns the SAME uniform error (no
|
|
6
|
+
* oracle), so a spent/invalid/expired code is indistinguishable — the CLI surfaces
|
|
7
|
+
* one clean message.
|
|
8
|
+
*
|
|
9
|
+
* Injectable `fetch` so the round-trip is fully unit-testable with no live network.
|
|
10
|
+
* Never throws past its boundary — returns a discriminated union.
|
|
11
|
+
*/
|
|
12
|
+
/** The redeem-endpoint success body (mirrors AGS `EnrollRedeemResponse`). */
|
|
13
|
+
export interface EnrollRedeemSuccessBody {
|
|
14
|
+
access_token: string;
|
|
15
|
+
token_type: string;
|
|
16
|
+
expires_in: number;
|
|
17
|
+
refresh_token: string;
|
|
18
|
+
scope: string;
|
|
19
|
+
/** The server target the code was minted for (the plugin boots against this hub). */
|
|
20
|
+
server_url: string;
|
|
21
|
+
}
|
|
22
|
+
export type EnrollRedeemResult = {
|
|
23
|
+
success: true;
|
|
24
|
+
credential: EnrollRedeemSuccessBody;
|
|
25
|
+
} | {
|
|
26
|
+
success: false;
|
|
27
|
+
message: string;
|
|
28
|
+
};
|
|
29
|
+
export interface RedeemEnrollmentOptions {
|
|
30
|
+
/** The enrollment code to redeem. */
|
|
31
|
+
code: string;
|
|
32
|
+
/** Cloud AS base URL (default https://ai-game.dev). */
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
/** Injectable fetch for tests (defaults to global fetch). */
|
|
35
|
+
fetchImpl?: typeof fetch;
|
|
36
|
+
/** Request timeout in ms (default 30000). */
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
}
|
|
39
|
+
export declare function redeemEnrollment(options: RedeemEnrollmentOptions): Promise<EnrollRedeemResult>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { verbose } from './ui.js';
|
|
2
|
+
export async function redeemEnrollment(options) {
|
|
3
|
+
const code = (options.code ?? '').trim();
|
|
4
|
+
if (code.length === 0) {
|
|
5
|
+
return { success: false, message: 'No enrollment code supplied.' };
|
|
6
|
+
}
|
|
7
|
+
const baseUrl = (options.baseUrl ?? '').replace(/\/$/, '');
|
|
8
|
+
const redeemUrl = `${baseUrl}/api/auth/enroll/redeem`;
|
|
9
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
10
|
+
const timeoutMs = options.timeoutMs ?? 30000;
|
|
11
|
+
verbose(`POST ${redeemUrl}`);
|
|
12
|
+
let response;
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15
|
+
try {
|
|
16
|
+
response = await doFetch(redeemUrl, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'Content-Type': 'application/json' },
|
|
19
|
+
body: JSON.stringify({ enroll_code: code }),
|
|
20
|
+
signal: controller.signal,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25
|
+
if (message.includes('ECONNREFUSED') || message.includes('fetch failed')) {
|
|
26
|
+
return { success: false, message: `Cannot reach the enrollment server at ${baseUrl}.` };
|
|
27
|
+
}
|
|
28
|
+
if (message.toLowerCase().includes('abort')) {
|
|
29
|
+
return { success: false, message: `Enrollment request to ${baseUrl} timed out.` };
|
|
30
|
+
}
|
|
31
|
+
return { success: false, message: `Enrollment request failed: ${message}` };
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
}
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
// Uniform-error contract: the server returns the same {error, error_description}
|
|
38
|
+
// for any invalid/expired/used/mismatched code. Surface one clean message.
|
|
39
|
+
let description;
|
|
40
|
+
try {
|
|
41
|
+
const body = (await response.json());
|
|
42
|
+
description = body.error_description ?? body.error;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
description = undefined;
|
|
46
|
+
}
|
|
47
|
+
verbose(`Redeem failed: HTTP ${response.status} — ${description ?? ''}`);
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
message: description ??
|
|
51
|
+
`Enrollment code could not be redeemed (HTTP ${response.status}). The code may be invalid, expired, or already used.`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
let body;
|
|
55
|
+
try {
|
|
56
|
+
body = (await response.json());
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { success: false, message: 'The enrollment server returned an unreadable response.' };
|
|
60
|
+
}
|
|
61
|
+
if (typeof body.access_token !== 'string' || body.access_token.length === 0 || typeof body.server_url !== 'string') {
|
|
62
|
+
return { success: false, message: 'The enrollment server returned an incomplete credential.' };
|
|
63
|
+
}
|
|
64
|
+
return { success: true, credential: body };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=enroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enroll.js","sourceRoot":"","sources":["../../src/utils/enroll.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared **machine credential store** at `~/.ai-game-dev/` — a single per-machine home for the
|
|
3
|
+
* ai-game.dev account credential (`credentials.json`). Engines, CLIs, and the local server all read
|
|
4
|
+
* this store, so **sign-in happens once per machine** (design `06-engine-plugins.md` / D12).
|
|
5
|
+
*
|
|
6
|
+
* This is the TypeScript peer of the shared C# `MachineCredentialStore`
|
|
7
|
+
* (`MCP-Plugin-dotnet/McpPlugin/src/AgentConfig/MachineCredentialStore.cs`). The on-disk contract is
|
|
8
|
+
* matched byte-for-byte so a credential written by `godot-cli login` is read by the engine plugin
|
|
9
|
+
* (which auto-adopts it on editor boot) and vice-versa:
|
|
10
|
+
*
|
|
11
|
+
* - **POSIX** — `credentials.json` is written plaintext with `0600` permissions inside a `0700`
|
|
12
|
+
* directory.
|
|
13
|
+
* - **Windows** — the file content is **DPAPI-encrypted** with the current user's key
|
|
14
|
+
* (`CryptProtectData`, `CurrentUser` scope, no entropy) so the on-disk bytes are never plaintext.
|
|
15
|
+
* Node has no built-in DPAPI, so we interop via PowerShell's `System.Security.Cryptography.
|
|
16
|
+
* ProtectedData`, which is a managed wrapper over the very same `CryptProtectData`/
|
|
17
|
+
* `CryptUnprotectData` calls the C# store uses — the blobs are cross-readable.
|
|
18
|
+
*
|
|
19
|
+
* Credentials are NEVER written to a project file / VCS. The optional `--project` per-project override
|
|
20
|
+
* (project-local `.godot-mcp/credentials.json`, gitignored) is handled by `credentials.ts`, not here.
|
|
21
|
+
*/
|
|
22
|
+
/** Directory name under the user home that holds the store (matches C# `MachineCredentialStore.DirectoryName`). */
|
|
23
|
+
export declare const MACHINE_STORE_DIR_NAME = ".ai-game-dev";
|
|
24
|
+
/** File name of the secret credential document. */
|
|
25
|
+
export declare const MACHINE_CREDENTIALS_FILE_NAME = "credentials.json";
|
|
26
|
+
/**
|
|
27
|
+
* Optional env override for the store directory (advanced use / tests). When set, this exact
|
|
28
|
+
* directory is used verbatim; otherwise the store lives at `~/.ai-game-dev`. The DEFAULT matches the
|
|
29
|
+
* C# store so cross-tool interop holds — the override never changes the production path.
|
|
30
|
+
*/
|
|
31
|
+
export declare const MACHINE_STORE_DIR_ENV = "AI_GAME_DEV_CREDENTIALS_DIR";
|
|
32
|
+
/**
|
|
33
|
+
* The secret credential material persisted per machine (mirrors the C# `MachineCredentials` document,
|
|
34
|
+
* camelCase field names). Unknown fields are ignored on read so the schema can grow forwards.
|
|
35
|
+
*/
|
|
36
|
+
export interface MachineCredentials {
|
|
37
|
+
/** Schema version of the persisted document (currently 1). */
|
|
38
|
+
version?: number;
|
|
39
|
+
/** The current short-lived JWT access token (the cloud bearer token). */
|
|
40
|
+
accessToken?: string;
|
|
41
|
+
/** The rotating refresh token used to mint a new access token before `expiresAt`. */
|
|
42
|
+
refreshToken?: string;
|
|
43
|
+
/** Absolute expiry of `accessToken` (ISO 8601); used to schedule proactive refresh. */
|
|
44
|
+
expiresAt?: string;
|
|
45
|
+
/** The server target the credential was issued for (hosted `https://ai-game.dev` or a local URL). */
|
|
46
|
+
serverTarget?: string;
|
|
47
|
+
/** The account id (`sub`) the credential resolves to. Audit/diagnostic only. */
|
|
48
|
+
subject?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Absolute path of the store directory (honoring the env override). */
|
|
51
|
+
export declare function getMachineStoreDir(baseDirOverride?: string): string;
|
|
52
|
+
/** Absolute path of the secret credential file. */
|
|
53
|
+
export declare function getMachineCredentialsPath(baseDirOverride?: string): string;
|
|
54
|
+
/** True when a credential file exists in the store. */
|
|
55
|
+
export declare function machineCredentialsExist(baseDirOverride?: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Read and (on Windows) decrypt the stored credentials, or `null` when none are present / empty.
|
|
58
|
+
* Throws on a decryption failure or malformed JSON (a corrupt or foreign-user credential); callers on
|
|
59
|
+
* a hot path should use {@link readMachineAccessToken}, which swallows those.
|
|
60
|
+
*/
|
|
61
|
+
export declare function readMachineCredentials(baseDirOverride?: string): MachineCredentials | null;
|
|
62
|
+
/**
|
|
63
|
+
* Encrypt (Windows) / restrict (POSIX) and write `credentials` to the store, creating the store
|
|
64
|
+
* directory with owner-only permissions if needed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function writeMachineCredentials(credentials: MachineCredentials, baseDirOverride?: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* Convenience reader for the persisted access token. Swallows a missing / malformed / undecryptable
|
|
69
|
+
* file (returns undefined) so a broken store can never crash a command; `login` re-authenticates and
|
|
70
|
+
* overwrites it.
|
|
71
|
+
*/
|
|
72
|
+
export declare function readMachineAccessToken(baseDirOverride?: string): string | undefined;
|
|
73
|
+
/** Delete the stored credentials (sign-out). No-op when none exist. */
|
|
74
|
+
export declare function deleteMachineCredentials(baseDirOverride?: string): void;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
|
+
/**
|
|
6
|
+
* The shared **machine credential store** at `~/.ai-game-dev/` — a single per-machine home for the
|
|
7
|
+
* ai-game.dev account credential (`credentials.json`). Engines, CLIs, and the local server all read
|
|
8
|
+
* this store, so **sign-in happens once per machine** (design `06-engine-plugins.md` / D12).
|
|
9
|
+
*
|
|
10
|
+
* This is the TypeScript peer of the shared C# `MachineCredentialStore`
|
|
11
|
+
* (`MCP-Plugin-dotnet/McpPlugin/src/AgentConfig/MachineCredentialStore.cs`). The on-disk contract is
|
|
12
|
+
* matched byte-for-byte so a credential written by `godot-cli login` is read by the engine plugin
|
|
13
|
+
* (which auto-adopts it on editor boot) and vice-versa:
|
|
14
|
+
*
|
|
15
|
+
* - **POSIX** — `credentials.json` is written plaintext with `0600` permissions inside a `0700`
|
|
16
|
+
* directory.
|
|
17
|
+
* - **Windows** — the file content is **DPAPI-encrypted** with the current user's key
|
|
18
|
+
* (`CryptProtectData`, `CurrentUser` scope, no entropy) so the on-disk bytes are never plaintext.
|
|
19
|
+
* Node has no built-in DPAPI, so we interop via PowerShell's `System.Security.Cryptography.
|
|
20
|
+
* ProtectedData`, which is a managed wrapper over the very same `CryptProtectData`/
|
|
21
|
+
* `CryptUnprotectData` calls the C# store uses — the blobs are cross-readable.
|
|
22
|
+
*
|
|
23
|
+
* Credentials are NEVER written to a project file / VCS. The optional `--project` per-project override
|
|
24
|
+
* (project-local `.godot-mcp/credentials.json`, gitignored) is handled by `credentials.ts`, not here.
|
|
25
|
+
*/
|
|
26
|
+
/** Directory name under the user home that holds the store (matches C# `MachineCredentialStore.DirectoryName`). */
|
|
27
|
+
export const MACHINE_STORE_DIR_NAME = '.ai-game-dev';
|
|
28
|
+
/** File name of the secret credential document. */
|
|
29
|
+
export const MACHINE_CREDENTIALS_FILE_NAME = 'credentials.json';
|
|
30
|
+
/**
|
|
31
|
+
* Optional env override for the store directory (advanced use / tests). When set, this exact
|
|
32
|
+
* directory is used verbatim; otherwise the store lives at `~/.ai-game-dev`. The DEFAULT matches the
|
|
33
|
+
* C# store so cross-tool interop holds — the override never changes the production path.
|
|
34
|
+
*/
|
|
35
|
+
export const MACHINE_STORE_DIR_ENV = 'AI_GAME_DEV_CREDENTIALS_DIR';
|
|
36
|
+
// Schema version of the persisted document (matches C# `MachineCredentials.Version`).
|
|
37
|
+
const SCHEMA_VERSION = 1;
|
|
38
|
+
/** Absolute path of the store directory (honoring the env override). */
|
|
39
|
+
export function getMachineStoreDir(baseDirOverride) {
|
|
40
|
+
if (baseDirOverride)
|
|
41
|
+
return baseDirOverride;
|
|
42
|
+
const envDir = process.env[MACHINE_STORE_DIR_ENV];
|
|
43
|
+
if (envDir && envDir.trim().length > 0)
|
|
44
|
+
return envDir;
|
|
45
|
+
return path.join(os.homedir(), MACHINE_STORE_DIR_NAME);
|
|
46
|
+
}
|
|
47
|
+
/** Absolute path of the secret credential file. */
|
|
48
|
+
export function getMachineCredentialsPath(baseDirOverride) {
|
|
49
|
+
return path.join(getMachineStoreDir(baseDirOverride), MACHINE_CREDENTIALS_FILE_NAME);
|
|
50
|
+
}
|
|
51
|
+
/** True when a credential file exists in the store. */
|
|
52
|
+
export function machineCredentialsExist(baseDirOverride) {
|
|
53
|
+
return fs.existsSync(getMachineCredentialsPath(baseDirOverride));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read and (on Windows) decrypt the stored credentials, or `null` when none are present / empty.
|
|
57
|
+
* Throws on a decryption failure or malformed JSON (a corrupt or foreign-user credential); callers on
|
|
58
|
+
* a hot path should use {@link readMachineAccessToken}, which swallows those.
|
|
59
|
+
*/
|
|
60
|
+
export function readMachineCredentials(baseDirOverride) {
|
|
61
|
+
const credentialsPath = getMachineCredentialsPath(baseDirOverride);
|
|
62
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const raw = fs.readFileSync(credentialsPath);
|
|
66
|
+
if (raw.length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const plaintext = isWindows() ? unprotectDpapi(raw) : raw;
|
|
70
|
+
const json = plaintext.toString('utf8');
|
|
71
|
+
if (json.trim().length === 0) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const parsed = JSON.parse(json);
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Encrypt (Windows) / restrict (POSIX) and write `credentials` to the store, creating the store
|
|
79
|
+
* directory with owner-only permissions if needed.
|
|
80
|
+
*/
|
|
81
|
+
export function writeMachineCredentials(credentials, baseDirOverride) {
|
|
82
|
+
const dir = getMachineStoreDir(baseDirOverride);
|
|
83
|
+
const credentialsPath = path.join(dir, MACHINE_CREDENTIALS_FILE_NAME);
|
|
84
|
+
ensureStoreDirectory(dir);
|
|
85
|
+
const json = serialize(credentials);
|
|
86
|
+
const plaintext = Buffer.from(json, 'utf8');
|
|
87
|
+
const bytes = isWindows() ? protectDpapi(plaintext) : plaintext;
|
|
88
|
+
fs.writeFileSync(credentialsPath, bytes, { mode: 0o600 });
|
|
89
|
+
setPosixPermissions(credentialsPath, 0o600);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Convenience reader for the persisted access token. Swallows a missing / malformed / undecryptable
|
|
93
|
+
* file (returns undefined) so a broken store can never crash a command; `login` re-authenticates and
|
|
94
|
+
* overwrites it.
|
|
95
|
+
*/
|
|
96
|
+
export function readMachineAccessToken(baseDirOverride) {
|
|
97
|
+
let credentials;
|
|
98
|
+
try {
|
|
99
|
+
credentials = readMachineCredentials(baseDirOverride);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const token = credentials?.accessToken;
|
|
105
|
+
return typeof token === 'string' && token.trim().length > 0 ? token : undefined;
|
|
106
|
+
}
|
|
107
|
+
/** Delete the stored credentials (sign-out). No-op when none exist. */
|
|
108
|
+
export function deleteMachineCredentials(baseDirOverride) {
|
|
109
|
+
const credentialsPath = getMachineCredentialsPath(baseDirOverride);
|
|
110
|
+
if (fs.existsSync(credentialsPath)) {
|
|
111
|
+
fs.rmSync(credentialsPath, { force: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// ── Serialization ────────────────────────────────────────────────────────────────────────────────
|
|
115
|
+
function serialize(credentials) {
|
|
116
|
+
// Mirror the C# store's camelCase + WhenWritingNull: always emit `version`, omit null/undefined
|
|
117
|
+
// optional fields. Indentation is cosmetic (the reader is whitespace-insensitive).
|
|
118
|
+
const doc = { version: credentials.version ?? SCHEMA_VERSION };
|
|
119
|
+
if (credentials.accessToken != null)
|
|
120
|
+
doc.accessToken = credentials.accessToken;
|
|
121
|
+
if (credentials.refreshToken != null)
|
|
122
|
+
doc.refreshToken = credentials.refreshToken;
|
|
123
|
+
if (credentials.expiresAt != null)
|
|
124
|
+
doc.expiresAt = credentials.expiresAt;
|
|
125
|
+
if (credentials.serverTarget != null)
|
|
126
|
+
doc.serverTarget = credentials.serverTarget;
|
|
127
|
+
if (credentials.subject != null)
|
|
128
|
+
doc.subject = credentials.subject;
|
|
129
|
+
return JSON.stringify(doc, null, 2) + '\n';
|
|
130
|
+
}
|
|
131
|
+
// ── Filesystem permissions ───────────────────────────────────────────────────────────────────────
|
|
132
|
+
function ensureStoreDirectory(dir) {
|
|
133
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
134
|
+
setPosixPermissions(dir, 0o700);
|
|
135
|
+
}
|
|
136
|
+
function setPosixPermissions(target, mode) {
|
|
137
|
+
if (isWindows())
|
|
138
|
+
return; // chmod is a no-op on Windows; DPAPI provides at-rest protection there.
|
|
139
|
+
try {
|
|
140
|
+
fs.chmodSync(target, mode);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Best-effort only — never fail persistence over a chmod (e.g. exotic filesystems).
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// ── Windows DPAPI via PowerShell (System.Security.Cryptography.ProtectedData) ───────────────────────
|
|
147
|
+
// ProtectedData.Protect/Unprotect wrap CryptProtectData/CryptUnprotectData with the SAME CurrentUser
|
|
148
|
+
// scope + null entropy the C# MachineCredentialStore uses, so the on-disk blobs are cross-readable.
|
|
149
|
+
function isWindows() {
|
|
150
|
+
return process.platform === 'win32';
|
|
151
|
+
}
|
|
152
|
+
const DPAPI_PROTECT_SCRIPT = "Add-Type -AssemblyName System.Security; " +
|
|
153
|
+
"$in = [Console]::In.ReadToEnd().Trim(); " +
|
|
154
|
+
"$bytes = [Convert]::FromBase64String($in); " +
|
|
155
|
+
"$prot = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, 'CurrentUser'); " +
|
|
156
|
+
"[Console]::Out.Write([Convert]::ToBase64String($prot))";
|
|
157
|
+
const DPAPI_UNPROTECT_SCRIPT = "Add-Type -AssemblyName System.Security; " +
|
|
158
|
+
"$in = [Console]::In.ReadToEnd().Trim(); " +
|
|
159
|
+
"$bytes = [Convert]::FromBase64String($in); " +
|
|
160
|
+
"$plain = [System.Security.Cryptography.ProtectedData]::Unprotect($bytes, $null, 'CurrentUser'); " +
|
|
161
|
+
"[Console]::Out.Write([Convert]::ToBase64String($plain))";
|
|
162
|
+
function protectDpapi(plaintext) {
|
|
163
|
+
return runDpapi(DPAPI_PROTECT_SCRIPT, plaintext, 'encrypt');
|
|
164
|
+
}
|
|
165
|
+
function unprotectDpapi(cipher) {
|
|
166
|
+
return runDpapi(DPAPI_UNPROTECT_SCRIPT, cipher, 'decrypt');
|
|
167
|
+
}
|
|
168
|
+
function runDpapi(script, input, op) {
|
|
169
|
+
try {
|
|
170
|
+
const out = execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
171
|
+
input: input.toString('base64'),
|
|
172
|
+
encoding: 'utf8',
|
|
173
|
+
windowsHide: true,
|
|
174
|
+
});
|
|
175
|
+
return Buffer.from(out.trim(), 'base64');
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
179
|
+
throw new Error(`Failed to ${op} the machine credential store via DPAPI: ${message}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=machine-credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"machine-credentials.js","sourceRoot":"","sources":["../../src/utils/machine-credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single canonical derivation of a project's **routing pin** and its
|
|
3
|
+
* **deterministic local port** from the project root path — the TypeScript port of the
|
|
4
|
+
* shared 7.0 library `com.IvanMurzak.McpPlugin.AgentConfig.ProjectIdentity`
|
|
5
|
+
* (`MCP-Plugin-dotnet/McpPlugin/src/AgentConfig/ProjectIdentity.cs`), which the Godot addon
|
|
6
|
+
* consumes via `GodotProjectIdentity`. Every runtime (Unity/Godot/Unreal plugins, the .NET
|
|
7
|
+
* sidecar, and the engine CLIs) derives identical values with no shared state and no probing,
|
|
8
|
+
* so an agent session launched in a project folder routes strictly to that project's engine
|
|
9
|
+
* instance (design `06-engine-plugins.md` § D14/D15).
|
|
10
|
+
*
|
|
11
|
+
* Algorithm (kept verbatim from the shipped Unity `GeneratePortFromDirectory` / the C# reference):
|
|
12
|
+
* 1. Trim trailing directory separators (`/` and `\`) so `/a/b` and `/a/b/` are the same project.
|
|
13
|
+
* Separators are NOT converted — `C:\a` and `C:/a` hash differently (do NOT `path.normalize`).
|
|
14
|
+
* 2. Lowercase with an invariant fold (`ToLowerInvariant`-equivalent — see {@link toLowerInvariant}).
|
|
15
|
+
* 3. UTF-8 encode, then SHA-256 hash.
|
|
16
|
+
* 4. pin = the first 4 bytes of the hash as 8 lowercase hex chars.
|
|
17
|
+
* 5. port = 20000 + (littleEndianUInt32(first 4 bytes) % 10000). Range 20000-29999.
|
|
18
|
+
*
|
|
19
|
+
* Cross-language parity (C# vs TS) is pinned by the committed golden-vector file
|
|
20
|
+
* (`ProjectIdentity.GoldenVectors.json`) and reproduced byte-for-byte by
|
|
21
|
+
* `tests/project-identity.test.ts`.
|
|
22
|
+
*/
|
|
23
|
+
/** Inclusive lower bound of the deterministic local-port range. */
|
|
24
|
+
export declare const MIN_PORT = 20000;
|
|
25
|
+
/** Inclusive upper bound of the deterministic local-port range. */
|
|
26
|
+
export declare const MAX_PORT = 29999;
|
|
27
|
+
/** Number of ports in the deterministic range (10000). */
|
|
28
|
+
export declare const PORT_RANGE: number;
|
|
29
|
+
/** Number of hex characters in the routing pin (first 4 bytes of the hash). */
|
|
30
|
+
export declare const PIN_LENGTH = 8;
|
|
31
|
+
/** The resolved identity for a project root. */
|
|
32
|
+
export interface ProjectIdentity {
|
|
33
|
+
/** The routing pin: first 8 lowercase hex chars of the SHA-256 of the normalized project root. */
|
|
34
|
+
pin: string;
|
|
35
|
+
/** The resolved local port — the hash-derived port unless an explicit override was supplied. */
|
|
36
|
+
port: number;
|
|
37
|
+
/** True when {@link port} came from an explicit user override rather than the hash. */
|
|
38
|
+
portIsOverridden: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Invariant lowercasing that matches C# `string.ToLowerInvariant()`.
|
|
42
|
+
*
|
|
43
|
+
* JS `String.prototype.toLowerCase()` and C# `ToLowerInvariant()` disagree on some Unicode
|
|
44
|
+
* characters. The one that matters for real project paths — and the one the golden-vector file
|
|
45
|
+
* explicitly pins (`unicodeDivergence`) — is **U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE**:
|
|
46
|
+
* C# `ToLowerInvariant` leaves it unchanged, whereas a naive JS `toLowerCase()` folds it to
|
|
47
|
+
* `i` + U+0307 (COMBINING DOT ABOVE), producing a DIFFERENT hash. Preserve U+0130 so the TS
|
|
48
|
+
* derivation reproduces the canonical C# value byte-for-byte; every other character lowercases
|
|
49
|
+
* identically under both implementations.
|
|
50
|
+
*/
|
|
51
|
+
export declare function toLowerInvariant(value: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* The exact string that is UTF-8/SHA-256 hashed: the project root with trailing directory
|
|
54
|
+
* separators trimmed, then invariant-lowercased. Exposed so callers/tests can reproduce the
|
|
55
|
+
* pre-hash string.
|
|
56
|
+
*/
|
|
57
|
+
export declare function normalize(projectRoot: string): string;
|
|
58
|
+
/** The routing pin only (first 8 lowercase hex chars of the hash). Never affected by overrides. */
|
|
59
|
+
export declare function derivePin(projectRoot: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* The pure hash-derived port (ignores any override). Byte-for-byte equivalent of the shipped
|
|
62
|
+
* Unity `GeneratePortFromDirectory` when given the same directory string.
|
|
63
|
+
*/
|
|
64
|
+
export declare function derivePort(projectRoot: string): number;
|
|
65
|
+
/**
|
|
66
|
+
* The FULL project-path hash: the complete 64-char lowercase hex SHA-256 of the normalized
|
|
67
|
+
* project root — the `projectPathHash` an engine plugin sends in its hub instance-metadata
|
|
68
|
+
* handshake. The routing pin is a case-insensitive prefix of this value by construction.
|
|
69
|
+
*/
|
|
70
|
+
export declare function deriveProjectPathHash(projectRoot: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Derive the identity for `projectRoot`. When `portOverride` is non-null (the user's explicit
|
|
73
|
+
* override from the project marker) it always wins for {@link ProjectIdentity.port}; the
|
|
74
|
+
* {@link ProjectIdentity.pin} is always hash-derived.
|
|
75
|
+
*/
|
|
76
|
+
export declare function deriveProjectIdentity(projectRoot: string, portOverride?: number | null): ProjectIdentity;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
/**
|
|
3
|
+
* The single canonical derivation of a project's **routing pin** and its
|
|
4
|
+
* **deterministic local port** from the project root path — the TypeScript port of the
|
|
5
|
+
* shared 7.0 library `com.IvanMurzak.McpPlugin.AgentConfig.ProjectIdentity`
|
|
6
|
+
* (`MCP-Plugin-dotnet/McpPlugin/src/AgentConfig/ProjectIdentity.cs`), which the Godot addon
|
|
7
|
+
* consumes via `GodotProjectIdentity`. Every runtime (Unity/Godot/Unreal plugins, the .NET
|
|
8
|
+
* sidecar, and the engine CLIs) derives identical values with no shared state and no probing,
|
|
9
|
+
* so an agent session launched in a project folder routes strictly to that project's engine
|
|
10
|
+
* instance (design `06-engine-plugins.md` § D14/D15).
|
|
11
|
+
*
|
|
12
|
+
* Algorithm (kept verbatim from the shipped Unity `GeneratePortFromDirectory` / the C# reference):
|
|
13
|
+
* 1. Trim trailing directory separators (`/` and `\`) so `/a/b` and `/a/b/` are the same project.
|
|
14
|
+
* Separators are NOT converted — `C:\a` and `C:/a` hash differently (do NOT `path.normalize`).
|
|
15
|
+
* 2. Lowercase with an invariant fold (`ToLowerInvariant`-equivalent — see {@link toLowerInvariant}).
|
|
16
|
+
* 3. UTF-8 encode, then SHA-256 hash.
|
|
17
|
+
* 4. pin = the first 4 bytes of the hash as 8 lowercase hex chars.
|
|
18
|
+
* 5. port = 20000 + (littleEndianUInt32(first 4 bytes) % 10000). Range 20000-29999.
|
|
19
|
+
*
|
|
20
|
+
* Cross-language parity (C# vs TS) is pinned by the committed golden-vector file
|
|
21
|
+
* (`ProjectIdentity.GoldenVectors.json`) and reproduced byte-for-byte by
|
|
22
|
+
* `tests/project-identity.test.ts`.
|
|
23
|
+
*/
|
|
24
|
+
/** Inclusive lower bound of the deterministic local-port range. */
|
|
25
|
+
export const MIN_PORT = 20000;
|
|
26
|
+
/** Inclusive upper bound of the deterministic local-port range. */
|
|
27
|
+
export const MAX_PORT = 29999;
|
|
28
|
+
/** Number of ports in the deterministic range (10000). */
|
|
29
|
+
export const PORT_RANGE = MAX_PORT - MIN_PORT + 1;
|
|
30
|
+
/** Number of hex characters in the routing pin (first 4 bytes of the hash). */
|
|
31
|
+
export const PIN_LENGTH = 8;
|
|
32
|
+
const SEPARATOR_FORWARD = '/';
|
|
33
|
+
const SEPARATOR_BACK = String.fromCharCode(92); // backslash
|
|
34
|
+
/**
|
|
35
|
+
* Invariant lowercasing that matches C# `string.ToLowerInvariant()`.
|
|
36
|
+
*
|
|
37
|
+
* JS `String.prototype.toLowerCase()` and C# `ToLowerInvariant()` disagree on some Unicode
|
|
38
|
+
* characters. The one that matters for real project paths — and the one the golden-vector file
|
|
39
|
+
* explicitly pins (`unicodeDivergence`) — is **U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE**:
|
|
40
|
+
* C# `ToLowerInvariant` leaves it unchanged, whereas a naive JS `toLowerCase()` folds it to
|
|
41
|
+
* `i` + U+0307 (COMBINING DOT ABOVE), producing a DIFFERENT hash. Preserve U+0130 so the TS
|
|
42
|
+
* derivation reproduces the canonical C# value byte-for-byte; every other character lowercases
|
|
43
|
+
* identically under both implementations.
|
|
44
|
+
*/
|
|
45
|
+
export function toLowerInvariant(value) {
|
|
46
|
+
let result = '';
|
|
47
|
+
for (const ch of value) {
|
|
48
|
+
result += ch === 'İ' ? ch : ch.toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The exact string that is UTF-8/SHA-256 hashed: the project root with trailing directory
|
|
54
|
+
* separators trimmed, then invariant-lowercased. Exposed so callers/tests can reproduce the
|
|
55
|
+
* pre-hash string.
|
|
56
|
+
*/
|
|
57
|
+
export function normalize(projectRoot) {
|
|
58
|
+
return toLowerInvariant(trimTrailingSeparators(projectRoot));
|
|
59
|
+
}
|
|
60
|
+
/** The routing pin only (first 8 lowercase hex chars of the hash). Never affected by overrides. */
|
|
61
|
+
export function derivePin(projectRoot) {
|
|
62
|
+
return hashOf(projectRoot).subarray(0, PIN_LENGTH / 2).toString('hex');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The pure hash-derived port (ignores any override). Byte-for-byte equivalent of the shipped
|
|
66
|
+
* Unity `GeneratePortFromDirectory` when given the same directory string.
|
|
67
|
+
*/
|
|
68
|
+
export function derivePort(projectRoot) {
|
|
69
|
+
return portFromHash(hashOf(projectRoot));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The FULL project-path hash: the complete 64-char lowercase hex SHA-256 of the normalized
|
|
73
|
+
* project root — the `projectPathHash` an engine plugin sends in its hub instance-metadata
|
|
74
|
+
* handshake. The routing pin is a case-insensitive prefix of this value by construction.
|
|
75
|
+
*/
|
|
76
|
+
export function deriveProjectPathHash(projectRoot) {
|
|
77
|
+
return hashOf(projectRoot).toString('hex');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Derive the identity for `projectRoot`. When `portOverride` is non-null (the user's explicit
|
|
81
|
+
* override from the project marker) it always wins for {@link ProjectIdentity.port}; the
|
|
82
|
+
* {@link ProjectIdentity.pin} is always hash-derived.
|
|
83
|
+
*/
|
|
84
|
+
export function deriveProjectIdentity(projectRoot, portOverride) {
|
|
85
|
+
const hash = hashOf(projectRoot);
|
|
86
|
+
const pin = hash.subarray(0, PIN_LENGTH / 2).toString('hex');
|
|
87
|
+
if (portOverride !== undefined && portOverride !== null) {
|
|
88
|
+
return { pin, port: portOverride, portIsOverridden: true };
|
|
89
|
+
}
|
|
90
|
+
return { pin, port: portFromHash(hash), portIsOverridden: false };
|
|
91
|
+
}
|
|
92
|
+
function hashOf(projectRoot) {
|
|
93
|
+
if (projectRoot === null || projectRoot === undefined) {
|
|
94
|
+
throw new TypeError('projectRoot must be a string');
|
|
95
|
+
}
|
|
96
|
+
return createHash('sha256').update(Buffer.from(normalize(projectRoot), 'utf8')).digest();
|
|
97
|
+
}
|
|
98
|
+
function portFromHash(hash) {
|
|
99
|
+
// First 4 bytes as an explicit little-endian uint32 — matches the C# byte-shift
|
|
100
|
+
// (`hash[0] | hash[1]<<8 | hash[2]<<16 | hash[3]<<24`) and is CPU-endianness independent.
|
|
101
|
+
const value = hash.readUInt32LE(0);
|
|
102
|
+
return MIN_PORT + (value % PORT_RANGE);
|
|
103
|
+
}
|
|
104
|
+
function trimTrailingSeparators(path) {
|
|
105
|
+
let end = path.length;
|
|
106
|
+
while (end > 1 && (path[end - 1] === SEPARATOR_FORWARD || path[end - 1] === SEPARATOR_BACK)) {
|
|
107
|
+
end--;
|
|
108
|
+
}
|
|
109
|
+
return end === path.length ? path : path.slice(0, end);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=project-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-identity.js","sourceRoot":"","sources":["../../src/utils/project-identity.ts"],"names":[],"mappings":"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"}
|