mindsim 0.1.5 → 0.1.7
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/README.md +110 -2
- package/dist/cli.js +96 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +58 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +357 -13
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +60 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +131 -4
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +63 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +98 -0
- package/dist/logger.js.map +1 -0
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +37 -6
- package/dist/version.js.map +1 -1
- package/package.json +2 -1
- package/src/cli.ts +106 -0
- package/src/config.ts +409 -17
- package/src/index.ts +177 -5
- package/src/logger.ts +111 -0
- package/src/types.ts +59 -0
- package/src/version.ts +45 -6
- package/tests/config.test.ts +465 -1
- package/tests/service-json.test.ts +567 -0
- package/tests/version.test.ts +63 -4
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { login } from "./auth";
|
|
7
|
+
import { loadServiceJsonFromFile, validateServiceJson } from "./config";
|
|
7
8
|
import { MindSim } from "./index";
|
|
8
9
|
import { checkForUpdates, getPackageVersion, updateSdk } from "./version";
|
|
9
10
|
|
|
@@ -88,6 +89,111 @@ const main = async () => {
|
|
|
88
89
|
await updateSdk();
|
|
89
90
|
});
|
|
90
91
|
|
|
92
|
+
// ==========================================
|
|
93
|
+
// SERVICE JSON VALIDATION
|
|
94
|
+
// ==========================================
|
|
95
|
+
|
|
96
|
+
program
|
|
97
|
+
.command("validate-service-json")
|
|
98
|
+
.description("Validate a service JSON credentials file")
|
|
99
|
+
.argument("<path>", "Path to the service JSON file")
|
|
100
|
+
.option("-v, --verbose", "Show detailed validation results", false)
|
|
101
|
+
.action(async (filePath: string, options: { verbose: boolean }) => {
|
|
102
|
+
try {
|
|
103
|
+
const resolvedPath = path.resolve(filePath);
|
|
104
|
+
|
|
105
|
+
// Check if file exists
|
|
106
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
107
|
+
console.error(`❌ File not found: ${resolvedPath}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Read and parse the file
|
|
112
|
+
let content: string;
|
|
113
|
+
try {
|
|
114
|
+
content = fs.readFileSync(resolvedPath, "utf-8");
|
|
115
|
+
} catch (readError) {
|
|
116
|
+
console.error(
|
|
117
|
+
`❌ Failed to read file: ${readError instanceof Error ? readError.message : readError}`,
|
|
118
|
+
);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Parse JSON
|
|
123
|
+
let json: unknown;
|
|
124
|
+
try {
|
|
125
|
+
json = JSON.parse(content);
|
|
126
|
+
} catch (parseError) {
|
|
127
|
+
console.error("❌ Invalid JSON syntax");
|
|
128
|
+
if (options.verbose) {
|
|
129
|
+
console.error(` ${parseError instanceof Error ? parseError.message : parseError}`);
|
|
130
|
+
}
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Validate service JSON structure
|
|
135
|
+
const validation = validateServiceJson(json);
|
|
136
|
+
|
|
137
|
+
if (validation.valid) {
|
|
138
|
+
console.log("✅ Service JSON is valid");
|
|
139
|
+
|
|
140
|
+
if (options.verbose && validation.serviceJson) {
|
|
141
|
+
console.log("\n📋 Credentials Summary:");
|
|
142
|
+
console.log(` Project ID: ${validation.serviceJson.project_id}`);
|
|
143
|
+
console.log(` Project Name: ${validation.serviceJson.project_name}`);
|
|
144
|
+
console.log(` Environment: ${validation.serviceJson.environment}`);
|
|
145
|
+
console.log(` Client Email: ${validation.serviceJson.client_email}`);
|
|
146
|
+
console.log(` API Base URL: ${validation.serviceJson.api_base_url}`);
|
|
147
|
+
console.log(` Scopes: ${validation.serviceJson.scopes.join(", ")}`);
|
|
148
|
+
console.log(` Created At: ${validation.serviceJson.created_at}`);
|
|
149
|
+
console.log("\n🔒 API key is present (not displayed for security)");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Test that it can actually be loaded
|
|
153
|
+
const loaded = loadServiceJsonFromFile(resolvedPath);
|
|
154
|
+
if (loaded) {
|
|
155
|
+
console.log("✅ Service JSON can be loaded successfully");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
process.exit(0);
|
|
159
|
+
} else {
|
|
160
|
+
console.error("❌ Service JSON validation failed");
|
|
161
|
+
console.error("\nErrors:");
|
|
162
|
+
for (const error of validation.errors) {
|
|
163
|
+
console.error(` • ${error}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (options.verbose) {
|
|
167
|
+
console.error("\n💡 Tip: Service JSON should have this structure:");
|
|
168
|
+
console.error(` {
|
|
169
|
+
"type": "mindsim_service_account",
|
|
170
|
+
"version": "1",
|
|
171
|
+
"project_id": "your-app-slug",
|
|
172
|
+
"project_name": "Your App Name",
|
|
173
|
+
"environment": "production",
|
|
174
|
+
"client_email": "service@your-app.mindsim.io",
|
|
175
|
+
"client_id": "uuid",
|
|
176
|
+
"api_key": "dev_xxxxx",
|
|
177
|
+
"api_base_url": "https://api.reasoner.com/api/mindsim",
|
|
178
|
+
"scopes": ["minds:read", "simulate:run"],
|
|
179
|
+
"app_metadata": {
|
|
180
|
+
"app_id": "uuid",
|
|
181
|
+
"workspace_id": "uuid",
|
|
182
|
+
"mindsim_org_id": "uuid",
|
|
183
|
+
"use_case": "customer-facing"
|
|
184
|
+
},
|
|
185
|
+
"created_at": "2025-01-01T00:00:00Z"
|
|
186
|
+
}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error("❌ Unexpected error:", error instanceof Error ? error.message : error);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
91
197
|
// ==========================================
|
|
92
198
|
// MINDS RESOURCES
|
|
93
199
|
// ==========================================
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import
|
|
4
|
+
import { getLogger } from "./logger";
|
|
5
|
+
import type { DeveloperUseCase, DeviceAuthConfig, MindsimScope, MindsimServiceJson } from "./types";
|
|
6
|
+
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Gap 7 Fix: Browser Environment Detection
|
|
9
|
+
// =============================================================================
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if we're running in a Node.js environment.
|
|
13
|
+
* Returns false in browsers to prevent process.env access errors.
|
|
14
|
+
*/
|
|
15
|
+
function isNodeEnvironment(): boolean {
|
|
16
|
+
return (
|
|
17
|
+
typeof process !== "undefined" && process.versions != null && process.versions.node != null
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Safely get an environment variable.
|
|
23
|
+
* Returns undefined in browser environments.
|
|
24
|
+
*/
|
|
25
|
+
function getEnvVar(name: string): string | undefined {
|
|
26
|
+
if (!isNodeEnvironment()) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return process.env[name];
|
|
30
|
+
}
|
|
5
31
|
|
|
6
32
|
const API_BASE_URL = "https://api.reasoner.com/api/mindsim";
|
|
7
33
|
const WORKOS_DEVICE_AUTH_URL = "https://auth.reasoner.com/user_management/authorize/device";
|
|
@@ -9,34 +35,390 @@ const WORKOS_TOKEN_URL = "https://auth.reasoner.com/user_management/authenticate
|
|
|
9
35
|
const WORKOS_CLIENT_ID = "client_01GPECHM1J9DMY7WQNKTJ195P6";
|
|
10
36
|
const SDK_KEYS_API_URL = "https://api.reasoner.com/api/sdk/keys";
|
|
11
37
|
|
|
12
|
-
|
|
38
|
+
// Valid scopes for validation (must match rainmaker's ALL_SCOPES)
|
|
39
|
+
const VALID_SCOPES: MindsimScope[] = [
|
|
40
|
+
"minds:read",
|
|
41
|
+
"minds:write",
|
|
42
|
+
"simulate:run",
|
|
43
|
+
"simulate:read",
|
|
44
|
+
"users:read",
|
|
45
|
+
"users:write",
|
|
46
|
+
"org:admin",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Valid use cases for validation
|
|
50
|
+
const VALID_USE_CASES: DeveloperUseCase[] = ["internal-workflow", "customer-facing", "agentic-ai"];
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Gap 12-14 Fix: Validation Helpers
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* UUID v4 validation regex
|
|
58
|
+
*/
|
|
59
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Validate a UUID string
|
|
63
|
+
*/
|
|
64
|
+
function isValidUUID(value: unknown): boolean {
|
|
65
|
+
return typeof value === "string" && UUID_REGEX.test(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validate a URL string (must be HTTPS for production)
|
|
70
|
+
*/
|
|
71
|
+
function isValidHttpsUrl(value: unknown): boolean {
|
|
72
|
+
if (typeof value !== "string") return false;
|
|
73
|
+
try {
|
|
74
|
+
const url = new URL(value);
|
|
75
|
+
return url.protocol === "https:";
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Validate an ISO 8601 timestamp string
|
|
83
|
+
*/
|
|
84
|
+
function isValidISOTimestamp(value: unknown): boolean {
|
|
85
|
+
if (typeof value !== "string") return false;
|
|
86
|
+
const date = new Date(value);
|
|
87
|
+
return !Number.isNaN(date.getTime()) && value.includes("T");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Gap 8 Fix: Validate an email address format
|
|
92
|
+
*/
|
|
93
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
94
|
+
function isValidEmail(value: unknown): boolean {
|
|
95
|
+
return typeof value === "string" && EMAIL_REGEX.test(value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the MindSim config directory path.
|
|
100
|
+
* @returns Path to ~/.mindsim directory
|
|
101
|
+
*/
|
|
102
|
+
export function getConfigDir(): string {
|
|
13
103
|
return path.join(os.homedir(), ".mindsim");
|
|
14
104
|
}
|
|
15
105
|
|
|
16
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Get the MindSim config file path.
|
|
108
|
+
* @returns Path to ~/.mindsim/config file
|
|
109
|
+
*/
|
|
110
|
+
export function getConfigFile(): string {
|
|
17
111
|
return path.join(getConfigDir(), "config");
|
|
18
112
|
}
|
|
19
113
|
|
|
114
|
+
// =============================================================================
|
|
115
|
+
// Service JSON Validation (Gap 6 Fix)
|
|
116
|
+
// =============================================================================
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Validation result for service JSON.
|
|
120
|
+
*/
|
|
121
|
+
export interface ServiceJsonValidationResult {
|
|
122
|
+
valid: boolean;
|
|
123
|
+
errors: string[];
|
|
124
|
+
serviceJson?: MindsimServiceJson;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Validate a service JSON object has all required fields with correct types.
|
|
129
|
+
* This provides detailed error messages for debugging.
|
|
130
|
+
*
|
|
131
|
+
* @param json - The parsed JSON object to validate
|
|
132
|
+
* @returns Validation result with errors if invalid
|
|
133
|
+
*/
|
|
134
|
+
export function validateServiceJson(json: unknown): ServiceJsonValidationResult {
|
|
135
|
+
const errors: string[] = [];
|
|
136
|
+
|
|
137
|
+
if (!json || typeof json !== "object") {
|
|
138
|
+
return { valid: false, errors: ["Service JSON must be a valid object"] };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const obj = json as Record<string, unknown>;
|
|
142
|
+
|
|
143
|
+
// Required string fields
|
|
144
|
+
const requiredStrings: Array<{ key: string; name: string }> = [
|
|
145
|
+
{ key: "type", name: "type" },
|
|
146
|
+
{ key: "version", name: "version" },
|
|
147
|
+
{ key: "project_id", name: "project_id" },
|
|
148
|
+
{ key: "project_name", name: "project_name" },
|
|
149
|
+
{ key: "environment", name: "environment" },
|
|
150
|
+
{ key: "client_email", name: "client_email" },
|
|
151
|
+
{ key: "client_id", name: "client_id" },
|
|
152
|
+
{ key: "api_key", name: "api_key" },
|
|
153
|
+
{ key: "api_base_url", name: "api_base_url" },
|
|
154
|
+
{ key: "created_at", name: "created_at" },
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
for (const { key, name } of requiredStrings) {
|
|
158
|
+
if (typeof obj[key] !== "string" || obj[key] === "") {
|
|
159
|
+
errors.push(`Missing or invalid required field: ${name}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Validate type field value
|
|
164
|
+
if (obj.type !== "mindsim_service_account") {
|
|
165
|
+
errors.push('type field must be "mindsim_service_account"');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Validate environment field value
|
|
169
|
+
if (obj.environment !== "production") {
|
|
170
|
+
errors.push('environment field must be "production"');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Validate scopes array
|
|
174
|
+
if (!Array.isArray(obj.scopes)) {
|
|
175
|
+
errors.push("scopes must be an array");
|
|
176
|
+
} else if (obj.scopes.length === 0) {
|
|
177
|
+
errors.push("scopes array cannot be empty");
|
|
178
|
+
} else {
|
|
179
|
+
for (const scope of obj.scopes) {
|
|
180
|
+
if (!VALID_SCOPES.includes(scope as MindsimScope)) {
|
|
181
|
+
errors.push(`Invalid scope: ${scope}. Valid scopes: ${VALID_SCOPES.join(", ")}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Validate app_metadata object
|
|
187
|
+
if (!obj.app_metadata || typeof obj.app_metadata !== "object") {
|
|
188
|
+
errors.push("app_metadata must be an object");
|
|
189
|
+
} else {
|
|
190
|
+
const metadata = obj.app_metadata as Record<string, unknown>;
|
|
191
|
+
const metadataFields = ["app_id", "workspace_id", "mindsim_org_id"];
|
|
192
|
+
|
|
193
|
+
for (const field of metadataFields) {
|
|
194
|
+
if (typeof metadata[field] !== "string" || metadata[field] === "") {
|
|
195
|
+
errors.push(`Missing or invalid app_metadata.${field}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Validate use_case
|
|
200
|
+
if (!VALID_USE_CASES.includes(metadata.use_case as DeveloperUseCase)) {
|
|
201
|
+
errors.push(
|
|
202
|
+
`Invalid app_metadata.use_case: ${metadata.use_case}. Valid values: ${VALID_USE_CASES.join(", ")}`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Gap 12 Fix: Validate UUID format for ID fields
|
|
207
|
+
const uuidFields = ["app_id", "workspace_id", "mindsim_org_id"];
|
|
208
|
+
for (const field of uuidFields) {
|
|
209
|
+
if (metadata[field] && !isValidUUID(metadata[field])) {
|
|
210
|
+
errors.push(`Invalid UUID format for app_metadata.${field}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Gap 12 Fix: Validate client_id as UUID
|
|
216
|
+
if (obj.client_id && !isValidUUID(obj.client_id)) {
|
|
217
|
+
errors.push("client_id must be a valid UUID");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Gap 8 Fix: Validate client_email as valid email format
|
|
221
|
+
if (obj.client_email && !isValidEmail(obj.client_email)) {
|
|
222
|
+
errors.push("client_email must be a valid email address");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Gap 13 Fix: Validate api_base_url as HTTPS URL
|
|
226
|
+
if (obj.api_base_url && !isValidHttpsUrl(obj.api_base_url)) {
|
|
227
|
+
errors.push("api_base_url must be a valid HTTPS URL");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Gap 14 Fix: Validate timestamps as ISO 8601
|
|
231
|
+
if (obj.created_at && !isValidISOTimestamp(obj.created_at)) {
|
|
232
|
+
errors.push("created_at must be a valid ISO 8601 timestamp");
|
|
233
|
+
}
|
|
234
|
+
if (obj.expires_at && !isValidISOTimestamp(obj.expires_at)) {
|
|
235
|
+
errors.push("expires_at must be a valid ISO 8601 timestamp");
|
|
236
|
+
}
|
|
237
|
+
if (obj.issued_at && !isValidISOTimestamp(obj.issued_at)) {
|
|
238
|
+
errors.push("issued_at must be a valid ISO 8601 timestamp");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (errors.length > 0) {
|
|
242
|
+
return { valid: false, errors };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { valid: true, errors: [], serviceJson: obj as unknown as MindsimServiceJson };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// =============================================================================
|
|
249
|
+
// Service JSON Loading Functions
|
|
250
|
+
// =============================================================================
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Load service JSON from a file path.
|
|
254
|
+
*
|
|
255
|
+
* @param filePath - Path to the service JSON file
|
|
256
|
+
* @returns MindsimServiceJson object or null if invalid/not found
|
|
257
|
+
*/
|
|
258
|
+
export function loadServiceJsonFromFile(filePath: string): MindsimServiceJson | null {
|
|
259
|
+
try {
|
|
260
|
+
if (!fs.existsSync(filePath)) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
264
|
+
|
|
265
|
+
let json: unknown;
|
|
266
|
+
try {
|
|
267
|
+
json = JSON.parse(content);
|
|
268
|
+
} catch (parseError) {
|
|
269
|
+
getLogger().error("Failed to parse service JSON file (invalid JSON syntax):", parseError);
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Validate all required fields
|
|
274
|
+
const validation = validateServiceJson(json);
|
|
275
|
+
if (!validation.valid) {
|
|
276
|
+
getLogger().error("Invalid service JSON:", validation.errors.join("; "));
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return validation.serviceJson as MindsimServiceJson;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
// Distinguish between different error types
|
|
283
|
+
if (error instanceof Error && "code" in error) {
|
|
284
|
+
const nodeError = error as NodeJS.ErrnoException;
|
|
285
|
+
if (nodeError.code === "EACCES") {
|
|
286
|
+
getLogger().error("Permission denied reading service JSON file:", filePath);
|
|
287
|
+
} else if (nodeError.code === "ENOENT") {
|
|
288
|
+
// File doesn't exist - this is handled above, but just in case
|
|
289
|
+
return null;
|
|
290
|
+
} else {
|
|
291
|
+
getLogger().error(`Failed to read service JSON file (${nodeError.code}):`, error);
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
getLogger().error("Failed to load service JSON:", error);
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Load service JSON from a base64-encoded string.
|
|
302
|
+
* Useful for environment variables in containerized environments.
|
|
303
|
+
*
|
|
304
|
+
* @param base64 - Base64-encoded service JSON string
|
|
305
|
+
* @returns MindsimServiceJson object or null if invalid
|
|
306
|
+
*/
|
|
307
|
+
export function loadServiceJsonFromBase64(base64: string): MindsimServiceJson | null {
|
|
308
|
+
try {
|
|
309
|
+
if (!base64 || base64.trim() === "") {
|
|
310
|
+
getLogger().error("Empty base64 string provided");
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const decoded = Buffer.from(base64, "base64").toString("utf-8");
|
|
315
|
+
|
|
316
|
+
if (!decoded || decoded.trim() === "") {
|
|
317
|
+
getLogger().error("Base64 decoded to empty string");
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let json: unknown;
|
|
322
|
+
try {
|
|
323
|
+
json = JSON.parse(decoded);
|
|
324
|
+
} catch (parseError) {
|
|
325
|
+
getLogger().error("Failed to parse decoded service JSON (invalid JSON syntax):", parseError);
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Validate all required fields
|
|
330
|
+
const validation = validateServiceJson(json);
|
|
331
|
+
if (!validation.valid) {
|
|
332
|
+
getLogger().error("Invalid service JSON:", validation.errors.join("; "));
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return validation.serviceJson as MindsimServiceJson;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
getLogger().error("Failed to decode service JSON from base64:", error);
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Load service JSON from environment variables.
|
|
345
|
+
* Priority: MINDSIM_SERVICE_JSON (file path) > MINDSIM_SERVICE_JSON_BASE64 (inline)
|
|
346
|
+
*
|
|
347
|
+
* @returns MindsimServiceJson object or null if not configured
|
|
348
|
+
*/
|
|
349
|
+
export function loadServiceJson(): MindsimServiceJson | null {
|
|
350
|
+
// Gap 7 Fix: Check Node environment before accessing env vars
|
|
351
|
+
if (!isNodeEnvironment()) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Try file path first
|
|
356
|
+
const filePath = getEnvVar("MINDSIM_SERVICE_JSON");
|
|
357
|
+
if (filePath) {
|
|
358
|
+
const serviceJson = loadServiceJsonFromFile(filePath);
|
|
359
|
+
if (serviceJson) {
|
|
360
|
+
return serviceJson;
|
|
361
|
+
}
|
|
362
|
+
getLogger().warn(`MINDSIM_SERVICE_JSON set but file not found or invalid: ${filePath}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Try base64 encoded
|
|
366
|
+
const base64 = getEnvVar("MINDSIM_SERVICE_JSON_BASE64");
|
|
367
|
+
if (base64) {
|
|
368
|
+
const serviceJson = loadServiceJsonFromBase64(base64);
|
|
369
|
+
if (serviceJson) {
|
|
370
|
+
return serviceJson;
|
|
371
|
+
}
|
|
372
|
+
getLogger().warn("MINDSIM_SERVICE_JSON_BASE64 set but failed to decode");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// =============================================================================
|
|
379
|
+
// API Key Loading
|
|
380
|
+
// =============================================================================
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Load API key with priority:
|
|
384
|
+
* 1. Service JSON (env vars)
|
|
385
|
+
* 2. MINDSIM_API_KEY env var
|
|
386
|
+
* 3. Config file (~/.mindsim/config)
|
|
387
|
+
*/
|
|
20
388
|
export const loadApiKey = (): string | null => {
|
|
21
389
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
390
|
+
// Priority 1: Service JSON
|
|
391
|
+
const serviceJson = loadServiceJson();
|
|
392
|
+
if (serviceJson?.api_key) {
|
|
393
|
+
return serviceJson.api_key;
|
|
24
394
|
}
|
|
25
395
|
|
|
26
|
-
|
|
396
|
+
// Priority 2: Direct API key env var
|
|
397
|
+
const envApiKey = getEnvVar("MINDSIM_API_KEY");
|
|
398
|
+
if (envApiKey) {
|
|
399
|
+
return envApiKey;
|
|
400
|
+
}
|
|
27
401
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
402
|
+
// Priority 3: Config file (only in Node environment)
|
|
403
|
+
if (isNodeEnvironment()) {
|
|
404
|
+
const configFile = getConfigFile();
|
|
405
|
+
if (fs.existsSync(configFile)) {
|
|
406
|
+
const config = JSON.parse(fs.readFileSync(configFile, "utf-8"));
|
|
407
|
+
return config.apiKey || null;
|
|
408
|
+
}
|
|
31
409
|
}
|
|
32
410
|
} catch (error) {
|
|
33
|
-
|
|
34
|
-
console.error(error);
|
|
411
|
+
getLogger().error("Failed to load API key:", error);
|
|
35
412
|
}
|
|
36
413
|
return null;
|
|
37
414
|
};
|
|
38
415
|
|
|
39
416
|
export const saveApiKey = (apiKey: string): void => {
|
|
417
|
+
// Gap 9 Fix: Check Node environment before accessing fs
|
|
418
|
+
if (!isNodeEnvironment()) {
|
|
419
|
+
throw new Error("saveApiKey is only available in Node.js environments");
|
|
420
|
+
}
|
|
421
|
+
|
|
40
422
|
const configDir = getConfigDir();
|
|
41
423
|
const configFile = getConfigFile();
|
|
42
424
|
|
|
@@ -48,13 +430,23 @@ export const saveApiKey = (apiKey: string): void => {
|
|
|
48
430
|
|
|
49
431
|
export const getDeviceAuthConfig = (): DeviceAuthConfig => {
|
|
50
432
|
return {
|
|
51
|
-
deviceAuthUrl:
|
|
52
|
-
tokenUrl:
|
|
53
|
-
clientId:
|
|
54
|
-
sdkKeysApiUrl:
|
|
433
|
+
deviceAuthUrl: getEnvVar("MINDSIM_WORKOS_DEVICE_AUTH_URL") || WORKOS_DEVICE_AUTH_URL,
|
|
434
|
+
tokenUrl: getEnvVar("MINDSIM_WORKOS_TOKEN_URL") || WORKOS_TOKEN_URL,
|
|
435
|
+
clientId: getEnvVar("MINDSIM_WORKOS_CLIENT_ID") || WORKOS_CLIENT_ID,
|
|
436
|
+
sdkKeysApiUrl: getEnvVar("MINDSIM_SDK_KEYS_API_URL") || SDK_KEYS_API_URL,
|
|
55
437
|
};
|
|
56
438
|
};
|
|
57
439
|
|
|
58
|
-
|
|
59
|
-
|
|
440
|
+
/**
|
|
441
|
+
* Get the API base URL.
|
|
442
|
+
* Priority: MINDSIM_API_BASE_URL > MIND_SIM_API_BASE_URL (deprecated) > default
|
|
443
|
+
* @returns API base URL
|
|
444
|
+
*/
|
|
445
|
+
export const getApiBaseUrl = (): string => {
|
|
446
|
+
// Gap 12 Fix: Use consistent naming (MINDSIM_*) with backwards compatibility
|
|
447
|
+
return (
|
|
448
|
+
getEnvVar("MINDSIM_API_BASE_URL") ||
|
|
449
|
+
getEnvVar("MIND_SIM_API_BASE_URL") || // Deprecated, for backwards compatibility
|
|
450
|
+
API_BASE_URL
|
|
451
|
+
);
|
|
60
452
|
};
|