mindsim 0.1.1 → 0.1.3

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.
Files changed (43) hide show
  1. package/README.md +92 -2
  2. package/dist/auth.d.ts +0 -3
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +184 -93
  5. package/dist/auth.js.map +1 -1
  6. package/dist/cli.js +92 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts +2 -2
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +12 -12
  11. package/dist/config.js.map +1 -1
  12. package/dist/index.d.ts +4 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +6 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/resources/mind-topics.d.ts +11 -0
  17. package/dist/resources/mind-topics.d.ts.map +1 -0
  18. package/dist/resources/mind-topics.js +18 -0
  19. package/dist/resources/mind-topics.js.map +1 -0
  20. package/dist/resources/snapshots.d.ts +29 -1
  21. package/dist/resources/snapshots.d.ts.map +1 -1
  22. package/dist/resources/snapshots.js +30 -0
  23. package/dist/resources/snapshots.js.map +1 -1
  24. package/dist/resources/users.d.ts +14 -0
  25. package/dist/resources/users.d.ts.map +1 -0
  26. package/dist/resources/users.js +20 -0
  27. package/dist/resources/users.js.map +1 -0
  28. package/dist/types.d.ts +78 -8
  29. package/dist/types.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/auth.ts +261 -105
  32. package/src/cli.ts +93 -2
  33. package/src/config.ts +11 -11
  34. package/src/index.ts +6 -0
  35. package/src/resources/mind-topics.ts +16 -0
  36. package/src/resources/snapshots.ts +60 -0
  37. package/src/resources/users.ts +16 -0
  38. package/src/types.ts +88 -7
  39. package/tests/config.test.ts +20 -15
  40. package/tests/resources/mind-topics.test.ts +69 -0
  41. package/tests/resources/snapshots.test.ts +46 -0
  42. package/tests/resources/users.test.ts +54 -0
  43. package/tests/version.test.ts +1 -1
package/src/auth.ts CHANGED
@@ -1,131 +1,287 @@
1
- import http from "node:http";
2
- import url from "node:url";
1
+ import readline from "node:readline";
2
+ import axios from "axios";
3
3
  import open from "open";
4
- import { getAuthConfig, saveApiKey } from "./config";
5
- import type { AuthResponse } from "./types";
4
+ import { getDeviceAuthConfig, saveApiKey } from "./config";
5
+ import type {
6
+ DeviceAuthResponse,
7
+ SdkKeyDetailResponse,
8
+ SdkKeyInfo,
9
+ SdkKeysResponse,
10
+ TokenResponse,
11
+ } from "./types";
6
12
 
7
- // CONFIGURATION
8
- const authConfig = getAuthConfig();
13
+ const HTTP_TIMEOUT = 30000;
9
14
 
10
- /**
11
- * Starts the login flow
12
- */
13
15
  export async function login(): Promise<void> {
14
- console.log("Initiating authentication...");
16
+ const authConfig = getDeviceAuthConfig();
15
17
 
16
- // 1. Create a Promise that resolves when the local server receives the code
17
- const apiKey = await listenForCallback();
18
+ console.log("Initiating authentication...\n");
18
19
 
19
- console.log("Authorization succeeded.");
20
+ const deviceAuth = await requestDeviceCode(authConfig);
20
21
 
21
- // 2. Exchange authorization code for access token
22
+ console.log("To authenticate, visit the following URL:");
23
+ console.log(`\n ${deviceAuth.verification_uri_complete}\n`);
24
+ console.log(`Your code: ${deviceAuth.user_code}\n`);
25
+
26
+ await open(deviceAuth.verification_uri_complete);
27
+
28
+ console.log("Waiting for authentication...");
29
+
30
+ const tokenResponse = await pollForToken(
31
+ authConfig,
32
+ deviceAuth.device_code,
33
+ deviceAuth.interval,
34
+ deviceAuth.expires_in,
35
+ );
36
+
37
+ console.log("\nAuthentication successful!");
38
+
39
+ console.log("Fetching your API keys...\n");
40
+ const keysResponse = await fetchUserKeys(authConfig, tokenResponse.access_token);
41
+
42
+ if (keysResponse.keys.length === 0) {
43
+ throw new Error(
44
+ "No API keys found. Please visit https://app.workflows.com and go to the Developer Portal to create an app and API key.",
45
+ );
46
+ }
47
+
48
+ let selectedKeyId: string;
49
+ if (keysResponse.keys.length === 1) {
50
+ const onlyKey = keysResponse.keys[0] as SdkKeyInfo;
51
+ selectedKeyId = onlyKey.id;
52
+ console.log(`Auto-selecting your only key: ${onlyKey.name}`);
53
+ } else {
54
+ selectedKeyId = await selectKey(keysResponse.keys);
55
+ }
56
+
57
+ const keyDetail = await fetchKeySecret(authConfig, tokenResponse.access_token, selectedKeyId);
58
+
59
+ saveApiKey(keyDetail.key.key);
60
+
61
+ console.log("\n✅ Successfully logged in! Credentials saved.");
62
+ }
63
+
64
+ interface AuthConfig {
65
+ deviceAuthUrl: string;
66
+ tokenUrl: string;
67
+ clientId: string;
68
+ sdkKeysApiUrl: string;
69
+ }
70
+
71
+ async function requestDeviceCode(authConfig: AuthConfig): Promise<DeviceAuthResponse> {
22
72
  try {
23
- // 3. Save credentials to disk
24
- saveApiKey(apiKey);
73
+ const response = await axios.post<DeviceAuthResponse>(
74
+ authConfig.deviceAuthUrl,
75
+ new URLSearchParams({
76
+ client_id: authConfig.clientId,
77
+ }).toString(),
78
+ {
79
+ headers: {
80
+ "Content-Type": "application/x-www-form-urlencoded",
81
+ },
82
+ timeout: HTTP_TIMEOUT,
83
+ },
84
+ );
25
85
 
26
- console.log("✅ Successfully logged in! Credentials saved.");
86
+ return response.data;
27
87
  } catch (error) {
28
- console.error("❌ Authentication failed:", error);
29
- process.exit(1);
88
+ if (axios.isAxiosError(error)) {
89
+ if (error.code === "ECONNABORTED") {
90
+ throw new Error("Device authorization request timed out. Please try again.");
91
+ }
92
+ if (error.response) {
93
+ const data = error.response.data as { error?: string; error_description?: string };
94
+ throw new Error(
95
+ `Device authorization failed: ${data.error_description || data.error || "Unknown error"}`,
96
+ );
97
+ }
98
+ }
99
+ throw error;
30
100
  }
31
101
  }
32
102
 
33
- /**
34
- * Spins up a temporary local server to catch the redirect
35
- */
36
- function listenForCallback(): Promise<string> {
37
- const callbackPath = new URL(authConfig.authRedirectPath).pathname;
38
-
39
- return new Promise((resolve, reject) => {
40
- const server = http.createServer(async (req, res) => {
41
- if (!req.url) return;
42
-
43
- const parsedUrl = url.parse(req.url, true);
44
-
45
- const headers = {
46
- "Access-Control-Allow-Origin": new URL(authConfig.authServerUrl).origin,
47
- "Access-Control-Allow-Methods": "OPTIONS, POST, GET",
48
- "Access-Control-Max-Age": 2592000, // 30 days
49
- "Access-Control-Allow-Headers": "Content-Type,Authorization",
50
- "Access-Control-Allow-Credentials": "true",
51
- };
52
-
53
- // Check if the request is for the callback path
54
- if (parsedUrl.pathname === callbackPath) {
55
- if (req.method === "OPTIONS") {
56
- res.writeHead(204, headers);
57
- res.end();
58
- return;
103
+ async function pollForToken(
104
+ authConfig: AuthConfig,
105
+ deviceCode: string,
106
+ interval: number,
107
+ expiresIn: number,
108
+ ): Promise<TokenResponse> {
109
+ const startTime = Date.now();
110
+ const expiresAtMs = startTime + expiresIn * 1000;
111
+ let currentInterval = interval;
112
+
113
+ while (Date.now() < expiresAtMs) {
114
+ await sleep(currentInterval * 1000);
115
+
116
+ try {
117
+ const response = await axios.post<TokenResponse>(
118
+ authConfig.tokenUrl,
119
+ new URLSearchParams({
120
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
121
+ device_code: deviceCode,
122
+ client_id: authConfig.clientId,
123
+ }).toString(),
124
+ {
125
+ headers: {
126
+ "Content-Type": "application/x-www-form-urlencoded",
127
+ },
128
+ timeout: HTTP_TIMEOUT,
129
+ },
130
+ );
131
+
132
+ return response.data;
133
+ } catch (error) {
134
+ if (axios.isAxiosError(error) && error.response) {
135
+ const errorData = error.response.data as { error?: string };
136
+ const errorCode = errorData?.error;
137
+
138
+ if (errorCode === "authorization_pending") {
139
+ continue;
59
140
  }
60
141
 
61
- if (req.method === "POST") {
62
- let body = "";
63
-
64
- req.on("data", (chunk) => {
65
- body += chunk.toString(); // Collect data chunks
66
- });
67
-
68
- req.on("end", () => {
69
- let authResponseData: AuthResponse | null = null;
70
-
71
- try {
72
- authResponseData = JSON.parse(body);
73
- } catch (err) {
74
- console.error(`Got an invalid response: ${body}. Reason: ${err}`);
75
- reject(err);
76
- }
77
-
78
- if (!authResponseData) {
79
- res.end("No api key found");
80
- return;
81
- }
82
-
83
- const apiKey = authResponseData?.apiKey?.key;
84
-
85
- if (!apiKey) {
86
- res.writeHead(400, { "Content-Type": "text/html" });
87
- res.end(`<h1>Authentication Error</h1><p>API Key Not Found</p>`);
88
- server.close();
89
- reject(new Error("API Key Not Found"));
90
- return;
91
- }
92
-
93
- if (apiKey) {
94
- // Send a nice "Close this window" message to the user
95
- res.writeHead(200, { "Content-Type": "text/html", ...headers });
96
- res.end(`
97
- <h1>Authentication Successful</h1>
98
- <p>You can close this window and return to your terminal.</p>
99
- <script>window.close()</script>
100
- `);
101
-
102
- server.close();
103
- resolve(apiKey);
104
- } else {
105
- res.end("No code found");
106
- }
107
- });
142
+ if (errorCode === "slow_down") {
143
+ currentInterval += 1;
144
+ continue;
108
145
  }
109
- }
110
- });
111
146
 
112
- server.listen(authConfig.listenPort, async () => {
113
- const state = JSON.stringify({
114
- redirect: `${authConfig.authServerLandingPath}?initCallbackUrl=${encodeURIComponent(authConfig.authRedirectPath)}`,
115
- });
147
+ if (errorCode === "expired_token") {
148
+ throw new Error("Authentication timed out. Please try again.");
149
+ }
116
150
 
117
- const encodedState = btoa(state);
151
+ if (errorCode === "access_denied") {
152
+ throw new Error("Authentication was denied. Please try again.");
153
+ }
154
+ }
118
155
 
119
- const loginUrl = `${authConfig.authServerUrl}?state=${encodedState}`;
156
+ throw error;
157
+ }
158
+ }
120
159
 
121
- console.log(`Listening on port ${authConfig.listenPort}...`);
122
- console.log(`Opening browser to: ${loginUrl}`);
160
+ throw new Error("Authentication timed out. Please try again.");
161
+ }
123
162
 
124
- await open(loginUrl);
163
+ async function fetchUserKeys(
164
+ authConfig: AuthConfig,
165
+ accessToken: string,
166
+ ): Promise<SdkKeysResponse> {
167
+ try {
168
+ const response = await axios.get<SdkKeysResponse>(authConfig.sdkKeysApiUrl, {
169
+ headers: {
170
+ Authorization: `Bearer ${accessToken}`,
171
+ },
172
+ timeout: HTTP_TIMEOUT,
125
173
  });
126
174
 
127
- server.on("error", (err) => {
128
- reject(err);
175
+ return response.data;
176
+ } catch (error) {
177
+ if (axios.isAxiosError(error)) {
178
+ if (error.code === "ECONNABORTED") {
179
+ throw new Error("Request to fetch API keys timed out. Please try again.");
180
+ }
181
+ if (error.response) {
182
+ const status = error.response.status;
183
+ const data = error.response.data as { error?: string; code?: string };
184
+
185
+ if (status === 401) {
186
+ throw new Error(`Authentication failed: ${data.error || "Invalid or expired token"}`);
187
+ }
188
+ if (status === 404) {
189
+ throw new Error(
190
+ `User not found: ${data.error || "Your account may not be set up correctly"}`,
191
+ );
192
+ }
193
+
194
+ throw new Error(`Failed to fetch API keys: ${data.error || "Unknown error"}`);
195
+ }
196
+ }
197
+ throw error;
198
+ }
199
+ }
200
+
201
+ async function fetchKeySecret(
202
+ authConfig: AuthConfig,
203
+ accessToken: string,
204
+ keyId: string,
205
+ ): Promise<SdkKeyDetailResponse> {
206
+ try {
207
+ const response = await axios.get<SdkKeyDetailResponse>(`${authConfig.sdkKeysApiUrl}/${keyId}`, {
208
+ headers: {
209
+ Authorization: `Bearer ${accessToken}`,
210
+ },
211
+ timeout: HTTP_TIMEOUT,
129
212
  });
213
+
214
+ return response.data;
215
+ } catch (error) {
216
+ if (axios.isAxiosError(error)) {
217
+ if (error.code === "ECONNABORTED") {
218
+ throw new Error("Request to fetch API key details timed out. Please try again.");
219
+ }
220
+ if (error.response) {
221
+ const status = error.response.status;
222
+ const data = error.response.data as { error?: string; code?: string };
223
+
224
+ if (status === 401) {
225
+ throw new Error(`Authentication failed: ${data.error || "Invalid or expired token"}`);
226
+ }
227
+ if (status === 403) {
228
+ throw new Error(`Access denied: ${data.error || "You do not have access to this key"}`);
229
+ }
230
+ if (status === 404) {
231
+ throw new Error(
232
+ `Key not found: ${data.error || "The selected key may have been deleted"}`,
233
+ );
234
+ }
235
+
236
+ throw new Error(`Failed to fetch API key details: ${data.error || "Unknown error"}`);
237
+ }
238
+ }
239
+ throw error;
240
+ }
241
+ }
242
+
243
+ async function selectKey(keys: SdkKeyInfo[]): Promise<string> {
244
+ if (!process.stdin.isTTY) {
245
+ throw new Error(
246
+ "Multiple API keys found but running in non-interactive mode. Please set MINDSIM_API_KEY environment variable directly.",
247
+ );
248
+ }
249
+
250
+ console.log("Select an API key:\n");
251
+
252
+ for (const [index, key] of keys.entries()) {
253
+ const appName = key.app?.name || "No app";
254
+ console.log(` ${index + 1}. ${key.name}`);
255
+ console.log(` Tier: ${key.tier} | App: ${appName}`);
256
+ console.log();
257
+ }
258
+
259
+ const rl = readline.createInterface({
260
+ input: process.stdin,
261
+ output: process.stdout,
130
262
  });
263
+
264
+ return new Promise((resolve) => {
265
+ const askQuestion = () => {
266
+ rl.question(`Enter selection (1-${keys.length}): `, (answer) => {
267
+ const selection = Number.parseInt(answer, 10);
268
+
269
+ if (Number.isNaN(selection) || selection < 1 || selection > keys.length) {
270
+ console.log(`Please enter a number between 1 and ${keys.length}`);
271
+ askQuestion();
272
+ return;
273
+ }
274
+
275
+ const selectedKey = keys[selection - 1] as SdkKeyInfo;
276
+ rl.close();
277
+ resolve(selectedKey.id);
278
+ });
279
+ };
280
+
281
+ askQuestion();
282
+ });
283
+ }
284
+
285
+ function sleep(ms: number): Promise<void> {
286
+ return new Promise((resolve) => setTimeout(resolve, ms));
131
287
  }
package/src/cli.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
- import { Command, Option } from "commander";
5
+ import { Command } from "commander";
6
6
  import { login } from "./auth";
7
7
  import { MindSim } from "./index";
8
8
  import { checkForUpdates, getPackageVersion, updateSdk } from "./version";
@@ -15,7 +15,7 @@ const program = new Command();
15
15
  const getSDK = () => {
16
16
  try {
17
17
  return new MindSim();
18
- } catch (error) {
18
+ } catch {
19
19
  console.error("❌ Authentication required.");
20
20
  console.error("Please run 'mindsim auth' or set MINDSIM_API_KEY environment variable.");
21
21
  process.exit(1);
@@ -213,6 +213,26 @@ const main = async () => {
213
213
  }
214
214
  });
215
215
 
216
+ snapshots
217
+ .command("get")
218
+ .description("Get snapshot detail")
219
+ .argument("<mindId>", "ID of the mind")
220
+ .argument("<snapshotId>", "ID of the snapshot")
221
+ .option("--transcript", "Include transcript")
222
+ .option("--psychometrics", "Include psychometrics")
223
+ .action(async (mindId, snapshotId, options) => {
224
+ try {
225
+ const client = getSDK();
226
+ const result = await client.snapshots.getDetail(mindId, snapshotId, {
227
+ includeTranscript: options.transcript,
228
+ includePsychometrics: options.psychometrics,
229
+ });
230
+ printOutput(result);
231
+ } catch (err) {
232
+ handleError(err);
233
+ }
234
+ });
235
+
216
236
  snapshots
217
237
  .command("create")
218
238
  .description("Upload a file to create a snapshot")
@@ -260,6 +280,51 @@ const main = async () => {
260
280
  }
261
281
  });
262
282
 
283
+ snapshots
284
+ .command("delete")
285
+ .description("Delete a snapshot")
286
+ .argument("<mindId>", "ID of the mind")
287
+ .argument("<snapshotId>", "ID of the snapshot")
288
+ .action(async (mindId, snapshotId) => {
289
+ try {
290
+ const client = getSDK();
291
+ const result = await client.snapshots.delete(mindId, snapshotId);
292
+ printOutput(result);
293
+ } catch (err) {
294
+ handleError(err);
295
+ }
296
+ });
297
+
298
+ snapshots
299
+ .command("link")
300
+ .description("Link snapshot to digital twin")
301
+ .argument("<mindId>", "ID of the mind")
302
+ .argument("<snapshotId>", "ID of the snapshot")
303
+ .action(async (mindId, snapshotId) => {
304
+ try {
305
+ const client = getSDK();
306
+ const result = await client.snapshots.link(mindId, snapshotId);
307
+ printOutput(result);
308
+ } catch (err) {
309
+ handleError(err);
310
+ }
311
+ });
312
+
313
+ snapshots
314
+ .command("unlink")
315
+ .description("Unlink snapshot from digital twin")
316
+ .argument("<mindId>", "ID of the mind")
317
+ .argument("<snapshotId>", "ID of the snapshot")
318
+ .action(async (mindId, snapshotId) => {
319
+ try {
320
+ const client = getSDK();
321
+ const result = await client.snapshots.unlink(mindId, snapshotId);
322
+ printOutput(result);
323
+ } catch (err) {
324
+ handleError(err);
325
+ }
326
+ });
327
+
263
328
  // ==========================================
264
329
  // SIMULATIONS RESOURCES
265
330
  // ==========================================
@@ -399,6 +464,32 @@ const main = async () => {
399
464
  }
400
465
  });
401
466
 
467
+ psychometrics
468
+ .command("topics")
469
+ .description("Get topic analysis")
470
+ .argument("<snapshotId>")
471
+ .action(async (sid) => {
472
+ try {
473
+ const client = getSDK();
474
+ printOutput(await client.mindTopics.get(sid));
475
+ } catch (e) {
476
+ handleError(e);
477
+ }
478
+ });
479
+
480
+ // USERS
481
+ program
482
+ .command("users")
483
+ .description("List organization users")
484
+ .action(async () => {
485
+ try {
486
+ const client = getSDK();
487
+ printOutput(await client.users.list());
488
+ } catch (e) {
489
+ handleError(e);
490
+ }
491
+ });
492
+
402
493
  program.parse(process.argv);
403
494
  };
404
495
 
package/src/config.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
- import type { AuthConfig } from "./types";
4
+ import type { DeviceAuthConfig } from "./types";
5
5
 
6
- const API_BASE_URL = "https://app.mindsim.com/api/public/v1";
7
- const AUTH_SERVER_URL = "https://app.mindsim.com/sdk/authorize";
8
- const AUTH_SERVER_LANDING_PATH = "/sdk-success";
9
- const AUTH_REDIRECT_URI = "http://localhost:4242/callback";
10
- const AUTH_PORT = "4242";
6
+ const API_BASE_URL = "https://api.reasoner.com/api/mindsim";
7
+ const WORKOS_DEVICE_AUTH_URL = "https://auth.reasoner.com/user_management/authorize/device";
8
+ const WORKOS_TOKEN_URL = "https://auth.reasoner.com/user_management/authenticate";
9
+ const WORKOS_CLIENT_ID = "client_01GPECHM1J9DMY7WQNKTJ195P6";
10
+ const SDK_KEYS_API_URL = "https://api.reasoner.com/api/sdk/keys";
11
11
 
12
12
  export function getConfigDir() {
13
13
  return path.join(os.homedir(), ".mindsim");
@@ -46,12 +46,12 @@ export const saveApiKey = (apiKey: string): void => {
46
46
  fs.writeFileSync(configFile, JSON.stringify({ apiKey }, null, 2));
47
47
  };
48
48
 
49
- export const getAuthConfig = (): AuthConfig => {
49
+ export const getDeviceAuthConfig = (): DeviceAuthConfig => {
50
50
  return {
51
- authServerUrl: process.env.MINDSIM_AUTH_SERVER_URL || AUTH_SERVER_URL,
52
- authServerLandingPath: AUTH_SERVER_LANDING_PATH,
53
- authRedirectPath: process.env.MINDSIM_AUTH_REDIRECT_URI || AUTH_REDIRECT_URI,
54
- listenPort: process.env.MINDSIM_AUTH_PORT || AUTH_PORT,
51
+ deviceAuthUrl: process.env.MINDSIM_WORKOS_DEVICE_AUTH_URL || WORKOS_DEVICE_AUTH_URL,
52
+ tokenUrl: process.env.MINDSIM_WORKOS_TOKEN_URL || WORKOS_TOKEN_URL,
53
+ clientId: process.env.MINDSIM_WORKOS_CLIENT_ID || WORKOS_CLIENT_ID,
54
+ sdkKeysApiUrl: process.env.MINDSIM_SDK_KEYS_API_URL || SDK_KEYS_API_URL,
55
55
  };
56
56
  };
57
57
 
package/src/index.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import axios, { type AxiosInstance } from "axios";
2
2
  import { getApiBaseUrl, loadApiKey } from "./config";
3
3
  import { ArtifactsResource } from "./resources/artifacts";
4
+ import { MindTopicsResource } from "./resources/mind-topics";
4
5
  import { MindsResource } from "./resources/minds";
5
6
  import { PsychometricsResource } from "./resources/psychometrics";
6
7
  import { SimulationsResource } from "./resources/simulations";
7
8
  import { SnapshotsResource } from "./resources/snapshots";
8
9
  import { TagsResource } from "./resources/tags";
10
+ import { UsersResource } from "./resources/users";
9
11
  import { checkForUpdates, getPackageVersion } from "./version";
10
12
 
11
13
  export class MindSim {
@@ -13,10 +15,12 @@ export class MindSim {
13
15
 
14
16
  public artifacts: ArtifactsResource;
15
17
  public minds: MindsResource;
18
+ public mindTopics: MindTopicsResource;
16
19
  public psychometrics: PsychometricsResource;
17
20
  public snapshots: SnapshotsResource;
18
21
  public simulations: SimulationsResource;
19
22
  public tags: TagsResource;
23
+ public users: UsersResource;
20
24
 
21
25
  constructor(apiKey?: string, options?: { apiBaseUrl?: string }) {
22
26
  // 1. Trigger the auto-update check (Fire and forget, do not await)
@@ -48,10 +52,12 @@ export class MindSim {
48
52
 
49
53
  this.artifacts = new ArtifactsResource(this.client);
50
54
  this.minds = new MindsResource(this.client);
55
+ this.mindTopics = new MindTopicsResource(this.client);
51
56
  this.psychometrics = new PsychometricsResource(this.client);
52
57
  this.snapshots = new SnapshotsResource(this.client);
53
58
  this.simulations = new SimulationsResource(this.client);
54
59
  this.tags = new TagsResource(this.client);
60
+ this.users = new UsersResource(this.client);
55
61
  }
56
62
  }
57
63
 
@@ -0,0 +1,16 @@
1
+ import type { AxiosInstance } from "axios";
2
+ import type { GetMindTopicsResponse } from "../types";
3
+
4
+ export class MindTopicsResource {
5
+ constructor(private client: AxiosInstance) {}
6
+
7
+ /**
8
+ * Get topic analysis for a specific snapshot
9
+ */
10
+ async get(snapshotId: string): Promise<GetMindTopicsResponse> {
11
+ const response = await this.client.get<GetMindTopicsResponse>(
12
+ `/snapshots/${snapshotId}/mind-topics`,
13
+ );
14
+ return response.data;
15
+ }
16
+ }