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/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import axios, { type AxiosInstance } from "axios";
2
- import { getApiBaseUrl, loadApiKey } from "./config";
2
+ import { getApiBaseUrl, loadApiKey, loadServiceJson, validateServiceJson } from "./config";
3
3
  import { ArtifactsResource } from "./resources/artifacts";
4
4
  import { MindTopicsResource } from "./resources/mind-topics";
5
5
  import { MindsResource } from "./resources/minds";
@@ -7,10 +7,60 @@ import { PsychometricsResource } from "./resources/psychometrics";
7
7
  import { SimulationsResource } from "./resources/simulations";
8
8
  import { SnapshotsResource } from "./resources/snapshots";
9
9
  import { TagsResource } from "./resources/tags";
10
+ import type { MindsimServiceJson } from "./types";
10
11
  import { checkForUpdates, getPackageVersion } from "./version";
11
12
 
13
+ // =============================================================================
14
+ // Gap 15 Fix: Browser Compatibility Check
15
+ // =============================================================================
16
+
17
+ /**
18
+ * Check if the SDK is running in a Node.js environment.
19
+ * Some features (like file-based service JSON loading) only work in Node.js.
20
+ */
21
+ export function isNodeEnvironment(): boolean {
22
+ return (
23
+ typeof process !== "undefined" && process.versions != null && process.versions.node != null
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Check if the SDK is running in a browser environment.
29
+ */
30
+ export function isBrowserEnvironment(): boolean {
31
+ // Use typeof checks to avoid ReferenceError in Node.js
32
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
33
+ return typeof globalThis !== "undefined" && "window" in globalThis && "document" in globalThis;
34
+ }
35
+
36
+ // =============================================================================
37
+ // Gap 14 Fix: Auth Method Detection
38
+ // =============================================================================
39
+
40
+ /**
41
+ * Authentication method used by the MindSim client.
42
+ */
43
+ export type AuthMethod =
44
+ | "service_json" // Service JSON credentials (file or base64)
45
+ | "api_key_env" // MINDSIM_API_KEY environment variable
46
+ | "api_key_constructor" // API key passed to constructor
47
+ | "config_file"; // ~/.mindsim/config file
48
+
49
+ /**
50
+ * Options for initializing the MindSim client.
51
+ */
52
+ export interface MindSimOptions {
53
+ /** Custom API base URL (overrides default and service JSON) */
54
+ apiBaseUrl?: string;
55
+ /** Service JSON credentials (alternative to API key) */
56
+ serviceJson?: MindsimServiceJson;
57
+ }
58
+
12
59
  export class MindSim {
13
60
  private client: AxiosInstance;
61
+ private serviceJson: MindsimServiceJson | null = null;
62
+ // Gap 14 Fix: Track which auth method was used
63
+ private authMethod: AuthMethod = "config_file";
14
64
 
15
65
  public artifacts: ArtifactsResource;
16
66
  public minds: MindsResource;
@@ -20,20 +70,72 @@ export class MindSim {
20
70
  public simulations: SimulationsResource;
21
71
  public tags: TagsResource;
22
72
 
23
- constructor(apiKey?: string, options?: { apiBaseUrl?: string }) {
73
+ constructor(apiKey?: string, options?: MindSimOptions) {
24
74
  // 1. Trigger the auto-update check (Fire and forget, do not await)
25
75
  checkForUpdates().catch(() => {
26
76
  // Catching here ensures no unhandled promise rejections in the user's console
27
77
  // if the check fails completely.
28
78
  });
29
79
 
30
- const token = apiKey || loadApiKey();
31
80
  const sdkVersion = getPackageVersion();
32
- const apiBaseUrl = options?.apiBaseUrl || getApiBaseUrl();
81
+
82
+ // Priority for credentials:
83
+ // 1. Constructor apiKey parameter
84
+ // 2. Constructor serviceJson option
85
+ // 3. Environment service JSON (MINDSIM_SERVICE_JSON or MINDSIM_SERVICE_JSON_BASE64)
86
+ // 4. Environment API key (MINDSIM_API_KEY)
87
+ // 5. Config file (~/.mindsim/config)
88
+
89
+ let token: string | null = apiKey || null;
90
+ let apiBaseUrl = options?.apiBaseUrl || getApiBaseUrl();
91
+
92
+ // Track auth method for Gap 14 fix
93
+ if (apiKey) {
94
+ this.authMethod = "api_key_constructor";
95
+ }
96
+
97
+ // Check for service JSON if no direct API key provided
98
+ if (!token) {
99
+ let serviceJson: MindsimServiceJson | null = null;
100
+
101
+ // Gap 3 Fix: Validate serviceJson when passed via constructor options
102
+ if (options?.serviceJson) {
103
+ const validation = validateServiceJson(options.serviceJson);
104
+ if (!validation.valid) {
105
+ throw new Error(`Invalid serviceJson: ${validation.errors.join(", ")}`);
106
+ }
107
+ serviceJson = validation.serviceJson as MindsimServiceJson;
108
+ } else {
109
+ // loadServiceJson() already validates internally
110
+ serviceJson = loadServiceJson();
111
+ }
112
+
113
+ if (serviceJson) {
114
+ this.serviceJson = serviceJson;
115
+ this.authMethod = "service_json";
116
+ token = serviceJson.api_key;
117
+ // Use API base URL from service JSON if not explicitly overridden
118
+ if (!options?.apiBaseUrl && serviceJson.api_base_url) {
119
+ apiBaseUrl = serviceJson.api_base_url;
120
+ }
121
+ }
122
+ }
123
+
124
+ // Fallback to traditional loading
125
+ if (!token) {
126
+ // Gap 7 Fix: Check if MINDSIM_API_KEY is set (only in Node environment)
127
+ const hasEnvApiKey = isNodeEnvironment() && process.env.MINDSIM_API_KEY;
128
+ if (hasEnvApiKey) {
129
+ this.authMethod = "api_key_env";
130
+ } else {
131
+ this.authMethod = "config_file";
132
+ }
133
+ token = loadApiKey();
134
+ }
33
135
 
34
136
  if (!token) {
35
137
  throw new Error(
36
- "API Key not found. Please run `mindsim auth` or pass the key to the constructor.",
138
+ "API Key not found. Please run `mindsim auth`, set MINDSIM_SERVICE_JSON/MINDSIM_SERVICE_JSON_BASE64, or pass credentials to the constructor.",
37
139
  );
38
140
  }
39
141
 
@@ -45,6 +147,10 @@ export class MindSim {
45
147
  "x-reasoner-source": "sdk",
46
148
  "x-reasoner-sdk": "mindsim/mindsim-sdk-typescript",
47
149
  "x-reasoner-sdk-version": sdkVersion,
150
+ // Include project ID from service JSON for better request tracing
151
+ ...(this.serviceJson && {
152
+ "x-mindsim-project-id": this.serviceJson.project_id,
153
+ }),
48
154
  },
49
155
  });
50
156
 
@@ -56,7 +162,73 @@ export class MindSim {
56
162
  this.simulations = new SimulationsResource(this.client);
57
163
  this.tags = new TagsResource(this.client);
58
164
  }
165
+
166
+ /**
167
+ * Get the loaded service JSON (if any).
168
+ * Useful for debugging or accessing project metadata.
169
+ *
170
+ * @returns The service JSON object or null if not using service JSON auth
171
+ */
172
+ public getServiceJson(): MindsimServiceJson | null {
173
+ return this.serviceJson;
174
+ }
175
+
176
+ // ==========================================================================
177
+ // Gap 14 Fix: Auth Method Detection Helpers
178
+ // ==========================================================================
179
+
180
+ /**
181
+ * Check if the client is using service JSON authentication.
182
+ *
183
+ * @returns true if using service JSON, false otherwise
184
+ */
185
+ public isUsingServiceJsonAuth(): boolean {
186
+ return this.serviceJson !== null;
187
+ }
188
+
189
+ /**
190
+ * Get the authentication method used by this client.
191
+ *
192
+ * @returns The auth method: 'service_json', 'api_key_env', 'api_key_constructor', or 'config_file'
193
+ */
194
+ public getAuthMethod(): AuthMethod {
195
+ return this.authMethod;
196
+ }
197
+
198
+ /**
199
+ * Get authentication info for debugging.
200
+ * Does NOT expose the actual API key.
201
+ *
202
+ * @returns Object with auth details (method, project info if service JSON)
203
+ */
204
+ public getAuthInfo(): {
205
+ method: AuthMethod;
206
+ isServiceJson: boolean;
207
+ projectId: string | null;
208
+ projectName: string | null;
209
+ environment: string | null;
210
+ } {
211
+ return {
212
+ method: this.authMethod,
213
+ isServiceJson: this.serviceJson !== null,
214
+ projectId: this.serviceJson?.project_id ?? null,
215
+ projectName: this.serviceJson?.project_name ?? null,
216
+ environment: this.serviceJson?.environment ?? null,
217
+ };
218
+ }
59
219
  }
60
220
 
221
+ // Export validation utilities from config
222
+ export { type ServiceJsonValidationResult, validateServiceJson } from "./config";
223
+
224
+ // Export logger interface (Gap 8 Fix)
225
+ export {
226
+ createSilentLogger,
227
+ createVerboseLogger,
228
+ defaultLogger,
229
+ getLogger,
230
+ type MindSimLogger,
231
+ setLogger,
232
+ } from "./logger";
61
233
  // Export types for consumer use
62
234
  export * from "./types";
package/src/logger.ts ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * MindSim SDK Logger Interface
3
+ *
4
+ * Provides a configurable logging interface so users can control
5
+ * how SDK messages are logged (or suppressed).
6
+ *
7
+ * Gap 8 Fix: Replaces hardcoded console.error with configurable interface.
8
+ *
9
+ * Usage:
10
+ * import { setLogger, createSilentLogger } from 'mindsim';
11
+ *
12
+ * // Use silent logger to suppress all SDK logs
13
+ * setLogger(createSilentLogger());
14
+ *
15
+ * // Use custom logger
16
+ * setLogger({
17
+ * debug: (msg) => myLogger.debug(`[MindSim] ${msg}`),
18
+ * info: (msg) => myLogger.info(`[MindSim] ${msg}`),
19
+ * warn: (msg) => myLogger.warn(`[MindSim] ${msg}`),
20
+ * error: (msg) => myLogger.error(`[MindSim] ${msg}`),
21
+ * });
22
+ */
23
+
24
+ /**
25
+ * Logger interface for the MindSim SDK.
26
+ * Implement this interface to customize SDK logging behavior.
27
+ */
28
+ export interface MindSimLogger {
29
+ /** Debug-level messages (not shown by default) */
30
+ debug: (message: string, ...args: unknown[]) => void;
31
+ /** Informational messages */
32
+ info: (message: string, ...args: unknown[]) => void;
33
+ /** Warning messages */
34
+ warn: (message: string, ...args: unknown[]) => void;
35
+ /** Error messages */
36
+ error: (message: string, ...args: unknown[]) => void;
37
+ }
38
+
39
+ /**
40
+ * Default logger that uses console methods.
41
+ * Debug is disabled by default.
42
+ */
43
+ export const defaultLogger: MindSimLogger = {
44
+ debug: () => {
45
+ // Debug disabled by default
46
+ },
47
+ info: (message: string, ...args: unknown[]) => {
48
+ console.info(`[MindSim] ${message}`, ...args);
49
+ },
50
+ warn: (message: string, ...args: unknown[]) => {
51
+ console.warn(`[MindSim] ${message}`, ...args);
52
+ },
53
+ error: (message: string, ...args: unknown[]) => {
54
+ console.error(`[MindSim] ${message}`, ...args);
55
+ },
56
+ };
57
+
58
+ /**
59
+ * Create a silent logger that suppresses all SDK logs.
60
+ * Useful for production environments where you want no SDK output.
61
+ */
62
+ export function createSilentLogger(): MindSimLogger {
63
+ const noop = () => {};
64
+ return {
65
+ debug: noop,
66
+ info: noop,
67
+ warn: noop,
68
+ error: noop,
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Create a logger with all levels enabled (including debug).
74
+ * Useful for development and debugging.
75
+ */
76
+ export function createVerboseLogger(): MindSimLogger {
77
+ return {
78
+ debug: (message: string, ...args: unknown[]) => {
79
+ console.debug(`[MindSim:DEBUG] ${message}`, ...args);
80
+ },
81
+ info: (message: string, ...args: unknown[]) => {
82
+ console.info(`[MindSim] ${message}`, ...args);
83
+ },
84
+ warn: (message: string, ...args: unknown[]) => {
85
+ console.warn(`[MindSim] ${message}`, ...args);
86
+ },
87
+ error: (message: string, ...args: unknown[]) => {
88
+ console.error(`[MindSim] ${message}`, ...args);
89
+ },
90
+ };
91
+ }
92
+
93
+ // Global logger instance
94
+ let currentLogger: MindSimLogger = defaultLogger;
95
+
96
+ /**
97
+ * Set the global logger for the SDK.
98
+ *
99
+ * @param logger - The logger implementation to use
100
+ */
101
+ export function setLogger(logger: MindSimLogger): void {
102
+ currentLogger = logger;
103
+ }
104
+
105
+ /**
106
+ * Get the current logger.
107
+ * Used internally by the SDK.
108
+ */
109
+ export function getLogger(): MindSimLogger {
110
+ return currentLogger;
111
+ }
package/src/types.ts CHANGED
@@ -55,6 +55,65 @@ export interface SdkKeyDetailResponse {
55
55
  };
56
56
  }
57
57
 
58
+ /**
59
+ * Valid MindSim scopes for API access.
60
+ * Matches rainmaker's MindsimScope type (lib/types/developer-portal.ts).
61
+ */
62
+ export type MindsimScope =
63
+ | "minds:read" // List/read digital twins
64
+ | "minds:write" // Create/update digital twins
65
+ | "simulate:run" // Run simulations
66
+ | "simulate:read" // Read simulation history
67
+ | "users:read" // Read org users
68
+ | "users:write" // Manage org users
69
+ | "org:admin"; // Full org administration
70
+
71
+ /**
72
+ * Valid use cases for developer apps.
73
+ * Matches rainmaker's DeveloperUseCase type.
74
+ */
75
+ export type DeveloperUseCase = "internal-workflow" | "customer-facing" | "agentic-ai";
76
+
77
+ /**
78
+ * MindSim Service JSON credential format.
79
+ * Google Cloud-style service account JSON for production deployments.
80
+ */
81
+ export interface MindsimServiceJson {
82
+ /** Must be "mindsim_service_account" to identify this as a service JSON */
83
+ type: "mindsim_service_account";
84
+ /** Service JSON schema version */
85
+ version: string;
86
+ /** App slug (human-readable identifier) */
87
+ project_id: string;
88
+ /** App display name */
89
+ project_name: string;
90
+ /** Environment (always "production" for service JSON) */
91
+ environment: "production";
92
+ /** Service account email (service@{slug}.mindsim.io) */
93
+ client_email: string;
94
+ /** API key UUID */
95
+ client_id: string;
96
+ /** The actual API key secret */
97
+ api_key: string;
98
+ /** API endpoint base URL */
99
+ api_base_url: string;
100
+ /** Granted API scopes */
101
+ scopes: MindsimScope[];
102
+ /** Application metadata */
103
+ app_metadata: {
104
+ app_id: string;
105
+ workspace_id: string;
106
+ mindsim_org_id: string;
107
+ use_case: DeveloperUseCase;
108
+ };
109
+ /** ISO 8601 timestamp when credentials were created */
110
+ created_at: string;
111
+ /** Optional: ISO 8601 timestamp when credentials expire */
112
+ expires_at?: string;
113
+ /** Optional: ISO 8601 timestamp when credentials were issued (same as created_at for new keys) */
114
+ issued_at?: string;
115
+ }
116
+
58
117
  export interface Tag {
59
118
  id: string;
60
119
  name: string;
package/src/version.ts CHANGED
@@ -4,9 +4,43 @@ import path from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import axios from "axios";
6
6
  import semver from "semver";
7
+ import { getLogger } from "./logger";
7
8
 
8
9
  const execAsync = promisify(exec);
9
10
 
11
+ /**
12
+ * Detects the package manager used in the current project
13
+ */
14
+ const detectPackageManager = (): "pnpm" | "yarn" | "npm" => {
15
+ const cwd = process.cwd();
16
+
17
+ // Check for lockfiles in order of preference
18
+ if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
19
+ return "pnpm";
20
+ }
21
+ if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
22
+ return "yarn";
23
+ }
24
+ return "npm";
25
+ };
26
+
27
+ /**
28
+ * Gets the install command for a specific package manager
29
+ */
30
+ const getInstallCommand = (
31
+ packageManager: "pnpm" | "yarn" | "npm",
32
+ packageName: string,
33
+ ): string => {
34
+ switch (packageManager) {
35
+ case "pnpm":
36
+ return `pnpm add ${packageName}@latest`;
37
+ case "yarn":
38
+ return `yarn add ${packageName}@latest`;
39
+ case "npm":
40
+ return `npm install ${packageName}@latest --save`;
41
+ }
42
+ };
43
+
10
44
  const PACKAGE_NAME = "mindsim";
11
45
  const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
12
46
 
@@ -32,7 +66,7 @@ export const getPackageVersion = (): string => {
32
66
  const pkg = JSON.parse(content);
33
67
  return pkg.version;
34
68
  } catch (error) {
35
- console.warn("MindSim SDK: Unable to determine current package version.", error);
69
+ getLogger().warn("Unable to determine current package version.", error);
36
70
  return "0.0.0";
37
71
  }
38
72
  };
@@ -77,11 +111,13 @@ export const checkForUpdates = async (verbose = false): Promise<void> => {
77
111
  };
78
112
 
79
113
  /**
80
- * Auto-updates the SDK using npm
114
+ * Auto-updates the SDK using the detected package manager
81
115
  */
82
116
  export const updateSdk = async (): Promise<void> => {
83
117
  console.log(`Checking for updates for ${PACKAGE_NAME}...`);
84
118
 
119
+ const packageManager = detectPackageManager();
120
+
85
121
  try {
86
122
  const currentVersion = getPackageVersion();
87
123
  const { data } = await axios.get(REGISTRY_URL);
@@ -92,18 +128,21 @@ export const updateSdk = async (): Promise<void> => {
92
128
  return;
93
129
  }
94
130
 
131
+ const installCommand = getInstallCommand(packageManager, PACKAGE_NAME);
132
+
95
133
  console.log(`Updating from ${currentVersion} to ${latestVersion}...`);
96
- console.log(`Running: npm install ${PACKAGE_NAME}@latest --save`);
134
+ console.log(`Detected package manager: ${packageManager}`);
135
+ console.log(`Running: ${installCommand}`);
97
136
 
98
- // We use npm install --save to ensure it updates package.json
99
- await execAsync(`npm install ${PACKAGE_NAME}@latest --save`);
137
+ await execAsync(installCommand);
100
138
 
101
139
  console.log("✅ Update complete! Please restart your application.");
102
140
  } catch (error) {
141
+ const manualCommand = getInstallCommand(packageManager, PACKAGE_NAME);
103
142
  console.error("❌ Failed to update MindSim SDK.");
104
143
  if (error instanceof Error) {
105
144
  console.error(error.message);
106
145
  }
107
- console.error("Please try running manually: npm install mindsim@latest");
146
+ console.error(`Please try running manually: ${manualCommand}`);
108
147
  }
109
148
  };