mindsim 0.1.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/.github/workflows/publish.yml +32 -0
- package/.github/workflows/test.yml +28 -0
- package/.github/workflows/type-checks.yml +29 -0
- package/LICENSE +21 -0
- package/README.md +748 -0
- package/assets/mindsim-logo.svg +15 -0
- package/biome.jsonc +43 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +115 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +36 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +63 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/artifacts.d.ts +8 -0
- package/dist/resources/artifacts.d.ts.map +1 -0
- package/dist/resources/artifacts.js +17 -0
- package/dist/resources/artifacts.js.map +1 -0
- package/dist/resources/minds.d.ts +39 -0
- package/dist/resources/minds.d.ts.map +1 -0
- package/dist/resources/minds.js +85 -0
- package/dist/resources/minds.js.map +1 -0
- package/dist/resources/psychometrics.d.ts +11 -0
- package/dist/resources/psychometrics.d.ts.map +1 -0
- package/dist/resources/psychometrics.js +18 -0
- package/dist/resources/psychometrics.js.map +1 -0
- package/dist/resources/simulations.d.ts +26 -0
- package/dist/resources/simulations.d.ts.map +1 -0
- package/dist/resources/simulations.js +41 -0
- package/dist/resources/simulations.js.map +1 -0
- package/dist/resources/snapshots.d.ts +27 -0
- package/dist/resources/snapshots.d.ts.map +1 -0
- package/dist/resources/snapshots.js +101 -0
- package/dist/resources/snapshots.js.map +1 -0
- package/dist/resources/tags.d.ts +23 -0
- package/dist/resources/tags.d.ts.map +1 -0
- package/dist/resources/tags.js +32 -0
- package/dist/resources/tags.js.map +1 -0
- package/dist/types.d.ts +161 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +15 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +105 -0
- package/dist/version.js.map +1 -0
- package/package.json +55 -0
- package/src/auth.ts +131 -0
- package/src/cli.ts +41 -0
- package/src/config.ts +60 -0
- package/src/index.ts +59 -0
- package/src/resources/artifacts.ts +13 -0
- package/src/resources/minds.ts +98 -0
- package/src/resources/psychometrics.ts +16 -0
- package/src/resources/simulations.ts +49 -0
- package/src/resources/snapshots.ts +126 -0
- package/src/resources/tags.ts +30 -0
- package/src/types.ts +185 -0
- package/src/version.ts +111 -0
- package/tests/auth.test.ts +41 -0
- package/tests/config.test.ts +129 -0
- package/tests/resources/minds.test.ts +119 -0
- package/tests/resources/psychometrics.test.ts +38 -0
- package/tests/resources/simulation.test.ts +94 -0
- package/tests/resources/snapshots.test.ts +135 -0
- package/tests/resources/tags.test.ts +87 -0
- package/tests/use-cases/quickstart.test.ts +84 -0
- package/tests/version.test.ts +221 -0
- package/tsconfig.json +29 -0
- package/vitest.config.ts +12 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export interface AuthConfig {
|
|
2
|
+
authServerUrl: string;
|
|
3
|
+
authServerLandingPath: string;
|
|
4
|
+
authRedirectPath: string;
|
|
5
|
+
listenPort: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface AuthResponse {
|
|
9
|
+
apiKey?: {
|
|
10
|
+
id: string;
|
|
11
|
+
key: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Tag {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
organizationId?: string | null;
|
|
19
|
+
createdAt?: string;
|
|
20
|
+
updatedAt?: string;
|
|
21
|
+
mindCount?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Mind {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
slug?: string | null;
|
|
28
|
+
email?: string | null;
|
|
29
|
+
imageUrl?: string | null;
|
|
30
|
+
socialMedia?: Record<string, any> | null;
|
|
31
|
+
scope: string;
|
|
32
|
+
isSelf: boolean;
|
|
33
|
+
organizationId?: string | null;
|
|
34
|
+
createdAt?: string;
|
|
35
|
+
updatedAt?: string;
|
|
36
|
+
tags: Tag[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CreateMindRequest {
|
|
40
|
+
name: string;
|
|
41
|
+
email?: string;
|
|
42
|
+
imageUrl?: string;
|
|
43
|
+
socialMedia?: Record<string, any>;
|
|
44
|
+
tags?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface UpdateMindRequest {
|
|
48
|
+
name?: string;
|
|
49
|
+
email?: string;
|
|
50
|
+
socialMedia?: Record<string, any>;
|
|
51
|
+
tags?: string[]; // SDK converts this array to a comma-separated string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SearchMindsParams {
|
|
55
|
+
query: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ListMindsParams {
|
|
59
|
+
tags?: string[]; // SDK converts this array to a comma-separated string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SimulationScenario {
|
|
63
|
+
message: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RunSimulationRequest {
|
|
67
|
+
mindId: string;
|
|
68
|
+
scenario: SimulationScenario;
|
|
69
|
+
runInBackground?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SimulationResponse {
|
|
73
|
+
message: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface SimulationMessage {
|
|
77
|
+
id: string;
|
|
78
|
+
role: "assistant" | "user" | "system";
|
|
79
|
+
content: { text: string } | any;
|
|
80
|
+
status: string;
|
|
81
|
+
createdAt?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface Simulation {
|
|
85
|
+
id: string;
|
|
86
|
+
userId: string;
|
|
87
|
+
title: string;
|
|
88
|
+
mindId?: string | null;
|
|
89
|
+
createdAt?: string;
|
|
90
|
+
updatedAt?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface GetSimulationResponse {
|
|
94
|
+
simulation: Simulation;
|
|
95
|
+
messages: SimulationMessage[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ListSimulationsParams {
|
|
99
|
+
offset?: number;
|
|
100
|
+
limit?: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface ListSimulationsResponse {
|
|
104
|
+
simulations: Simulation[];
|
|
105
|
+
count: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface GetSignedUrlParams {
|
|
109
|
+
fileName?: string;
|
|
110
|
+
contentType?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface GetSignedUrlResponse {
|
|
114
|
+
signedUrl: string;
|
|
115
|
+
fileName: string;
|
|
116
|
+
artifactId: string;
|
|
117
|
+
contentType: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface CreateSnapshotParams {
|
|
121
|
+
file: Buffer | any; // Buffer (Node) or Blob/File (Browser)
|
|
122
|
+
fileName: string;
|
|
123
|
+
contentType: string;
|
|
124
|
+
mindsetDate?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface CreateSnapshotFromFileParams {
|
|
128
|
+
file?: Buffer | any; // Buffer for Node, File for Browser
|
|
129
|
+
fileName?: string; // Required if passing a Buffer
|
|
130
|
+
artifactId?: string;
|
|
131
|
+
mindsetDate?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface CreateSnapshotResponse {
|
|
135
|
+
message: string;
|
|
136
|
+
mindAssessmentId: string;
|
|
137
|
+
artifactId: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface SnapshotStatus {
|
|
141
|
+
id: string;
|
|
142
|
+
mindId: string;
|
|
143
|
+
status: string;
|
|
144
|
+
startedAt?: string;
|
|
145
|
+
completedAt?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface MindAssessment {
|
|
149
|
+
id: string;
|
|
150
|
+
mindId: string;
|
|
151
|
+
artifactId?: string | null;
|
|
152
|
+
mindsetDate?: string | null;
|
|
153
|
+
status: string;
|
|
154
|
+
startedAt?: string | null;
|
|
155
|
+
completedAt?: string | null;
|
|
156
|
+
createdAt?: string | null;
|
|
157
|
+
updatedAt?: string | null;
|
|
158
|
+
psychometricCount?: number | null;
|
|
159
|
+
keyTopicCount?: number | null;
|
|
160
|
+
findingCount?: number | null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface ListSnapshotsResponse {
|
|
164
|
+
snapshots: MindAssessment[];
|
|
165
|
+
count: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// -- Psychometrics --
|
|
169
|
+
|
|
170
|
+
export interface PersonDetails {
|
|
171
|
+
id: string;
|
|
172
|
+
slug?: string | null;
|
|
173
|
+
name: string;
|
|
174
|
+
imageUrl?: string | null;
|
|
175
|
+
mindDate: string;
|
|
176
|
+
mindAssesssmentId: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface GetPsychometricsResponse {
|
|
180
|
+
personDetails: PersonDetails;
|
|
181
|
+
dimensionAnalysis?: Record<string, any> | null;
|
|
182
|
+
metaAnalysis?: Record<string, any> | null;
|
|
183
|
+
topicsSentimentSummary?: Record<string, any> | null;
|
|
184
|
+
mindReasonerStopError?: any | null;
|
|
185
|
+
}
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import axios from "axios";
|
|
6
|
+
import semver from "semver";
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
|
|
10
|
+
const PACKAGE_NAME = "@mindsim/mindsim-sdk-typescript";
|
|
11
|
+
const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* REUSABLE VERSION EXTRACTOR
|
|
15
|
+
* Reads the version directly from the SDK's package.json
|
|
16
|
+
*/
|
|
17
|
+
export const getPackageVersion = (): string => {
|
|
18
|
+
try {
|
|
19
|
+
// Determine path based on whether we are in ./src or ./dist
|
|
20
|
+
// __dirname is usually ./dist in production
|
|
21
|
+
const packagePath = path.resolve(__dirname, "../package.json");
|
|
22
|
+
|
|
23
|
+
// Fallback if structure differs slightly in dev vs prod
|
|
24
|
+
if (!fs.existsSync(packagePath)) {
|
|
25
|
+
const altPath = path.resolve(__dirname, "../../package.json");
|
|
26
|
+
if (fs.existsSync(altPath)) {
|
|
27
|
+
return JSON.parse(fs.readFileSync(altPath, "utf-8")).version;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const content = fs.readFileSync(packagePath, "utf-8");
|
|
32
|
+
const pkg = JSON.parse(content);
|
|
33
|
+
return pkg.version;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.warn("MindSim SDK: Unable to determine current package version.", error);
|
|
36
|
+
return "0.0.0";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Checks NPM registry for updates and logs a warning if outdated.
|
|
42
|
+
* @param verbose If true, logs when the version is up to date (useful for CLI 'version' command)
|
|
43
|
+
*/
|
|
44
|
+
export const checkForUpdates = async (verbose = false): Promise<void> => {
|
|
45
|
+
try {
|
|
46
|
+
const currentVersion = getPackageVersion();
|
|
47
|
+
|
|
48
|
+
// Fetch latest version data from NPM
|
|
49
|
+
// Set a short timeout so we don't block application boot significantly
|
|
50
|
+
const { data } = await axios.get(REGISTRY_URL, { timeout: 1500 });
|
|
51
|
+
const latestVersion = data["dist-tags"].latest;
|
|
52
|
+
|
|
53
|
+
if (semver.gt(latestVersion, currentVersion)) {
|
|
54
|
+
const changelogUrl = `https://www.npmjs.com/package/${PACKAGE_NAME}/v/${latestVersion}`;
|
|
55
|
+
|
|
56
|
+
console.warn("\n" + "=".repeat(60));
|
|
57
|
+
console.warn(`⚠️ UPDATE AVAILABLE: @mindsim/mindsim-sdk-typescript`);
|
|
58
|
+
console.warn("=".repeat(60));
|
|
59
|
+
console.warn(` Current Version: ${currentVersion}`);
|
|
60
|
+
console.warn(` Latest Version: ${latestVersion}`);
|
|
61
|
+
console.warn("-".repeat(60));
|
|
62
|
+
console.warn(` Changelog: ${changelogUrl}`);
|
|
63
|
+
console.warn(` Run 'mindsim update' to install the latest version.`);
|
|
64
|
+
console.warn("=".repeat(60) + "\n");
|
|
65
|
+
} else if (verbose) {
|
|
66
|
+
console.log(`MindSim SDK is up to date (v${currentVersion})`);
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// Fail silently on network errors so we don't break the user's workflow
|
|
70
|
+
if (verbose) {
|
|
71
|
+
console.error(
|
|
72
|
+
"Failed to check for updates:",
|
|
73
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Auto-updates the SDK using npm
|
|
81
|
+
*/
|
|
82
|
+
export const updateSdk = async (): Promise<void> => {
|
|
83
|
+
console.log(`Checking for updates for ${PACKAGE_NAME}...`);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const currentVersion = getPackageVersion();
|
|
87
|
+
const { data } = await axios.get(REGISTRY_URL);
|
|
88
|
+
const latestVersion = data["dist-tags"].latest;
|
|
89
|
+
|
|
90
|
+
if (!semver.gt(latestVersion, currentVersion)) {
|
|
91
|
+
console.log(`You are already on the latest version (${currentVersion}).`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(`Updating from ${currentVersion} to ${latestVersion}...`);
|
|
96
|
+
console.log(`Running: npm install ${PACKAGE_NAME}@latest --save`);
|
|
97
|
+
|
|
98
|
+
// We use npm install --save to ensure it updates package.json
|
|
99
|
+
await execAsync(`npm install ${PACKAGE_NAME}@latest --save`);
|
|
100
|
+
|
|
101
|
+
console.log("✅ Update complete! Please restart your application.");
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error("❌ Failed to update MindSim SDK.");
|
|
104
|
+
if (error instanceof Error) {
|
|
105
|
+
console.error(error.message);
|
|
106
|
+
}
|
|
107
|
+
console.error(
|
|
108
|
+
"Please try running manually: npm install @mindsim/mindsim-sdk-typescript@latest",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import * as configModule from "../src/config";
|
|
3
|
+
import { MindSim } from "../src/index";
|
|
4
|
+
|
|
5
|
+
// Mock the config loader specifically
|
|
6
|
+
vi.mock("../src/config", async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal<typeof configModule>();
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
loadApiKey: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("MindSim Authentication", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.resetModules();
|
|
17
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue(null);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
vi.unstubAllEnvs();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should instantiate successfully when API key is passed to constructor", () => {
|
|
25
|
+
const mindsim = new MindSim("test-api-key-const");
|
|
26
|
+
expect(mindsim).toBeInstanceOf(MindSim);
|
|
27
|
+
expect(mindsim.minds).toBeDefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should instantiate successfully when API key is in config/env (via loadApiKey)", () => {
|
|
31
|
+
vi.mocked(configModule.loadApiKey).mockReturnValue("test-api-key-env");
|
|
32
|
+
const mindsim = new MindSim();
|
|
33
|
+
expect(mindsim).toBeInstanceOf(MindSim);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should throw error if no API key is provided or found in env", () => {
|
|
37
|
+
expect(() => {
|
|
38
|
+
new MindSim();
|
|
39
|
+
}).toThrow("API Key not found");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
|
|
5
|
+
import { getApiBaseUrl, getAuthConfig, loadApiKey, saveApiKey } from "../src/config";
|
|
6
|
+
|
|
7
|
+
// Mock Node built-ins
|
|
8
|
+
vi.mock("node:fs");
|
|
9
|
+
vi.mock("node:os");
|
|
10
|
+
|
|
11
|
+
const mockedFs = fs as Mocked<typeof fs>;
|
|
12
|
+
const mockedOs = os as Mocked<typeof os>;
|
|
13
|
+
|
|
14
|
+
describe("Config Module", () => {
|
|
15
|
+
const MOCK_HOME_DIR = "/mock/home";
|
|
16
|
+
const EXPECTED_CONFIG_DIR = path.join(MOCK_HOME_DIR, ".mindsim");
|
|
17
|
+
const EXPECTED_CONFIG_FILE = path.join(EXPECTED_CONFIG_DIR, "config");
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.resetModules();
|
|
21
|
+
mockedOs.homedir.mockReturnValue(MOCK_HOME_DIR);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.unstubAllEnvs();
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("loadApiKey", () => {
|
|
30
|
+
it("should prioritize API key from process.env", () => {
|
|
31
|
+
vi.stubEnv("MINDSIM_API_KEY", "env-key-123");
|
|
32
|
+
|
|
33
|
+
// Even if file exists, env should win
|
|
34
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
35
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ apiKey: "file-key-456" }));
|
|
36
|
+
|
|
37
|
+
const key = loadApiKey();
|
|
38
|
+
expect(key).toBe("env-key-123");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should load from config file if env var is missing", () => {
|
|
42
|
+
vi.stubEnv("MINDSIM_API_KEY", ""); // Ensure env is empty
|
|
43
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
44
|
+
mockedFs.readFileSync.mockReturnValue(JSON.stringify({ apiKey: "file-key-456" }));
|
|
45
|
+
|
|
46
|
+
const key = loadApiKey();
|
|
47
|
+
expect(key).toBe("file-key-456");
|
|
48
|
+
expect(mockedFs.readFileSync).toHaveBeenCalledWith(EXPECTED_CONFIG_FILE, "utf-8");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should return null if neither env var nor file exists", () => {
|
|
52
|
+
vi.stubEnv("MINDSIM_API_KEY", "");
|
|
53
|
+
mockedFs.existsSync.mockReturnValue(false);
|
|
54
|
+
|
|
55
|
+
const key = loadApiKey();
|
|
56
|
+
expect(key).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should return null and log error if file read fails", () => {
|
|
60
|
+
vi.stubEnv("MINDSIM_API_KEY", "");
|
|
61
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
62
|
+
mockedFs.readFileSync.mockImplementation(() => {
|
|
63
|
+
throw new Error("File permission denied");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Spy on console.error to suppress output during test
|
|
67
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
68
|
+
|
|
69
|
+
const key = loadApiKey();
|
|
70
|
+
expect(key).toBeNull();
|
|
71
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("saveApiKey", () => {
|
|
76
|
+
it("should create directory and write config file", () => {
|
|
77
|
+
const NEW_KEY = "new-api-key-789";
|
|
78
|
+
mockedFs.existsSync.mockReturnValue(false); // Dir doesn't exist
|
|
79
|
+
|
|
80
|
+
saveApiKey(NEW_KEY);
|
|
81
|
+
|
|
82
|
+
expect(mockedFs.mkdirSync).toHaveBeenCalledWith(EXPECTED_CONFIG_DIR, { recursive: true });
|
|
83
|
+
expect(mockedFs.writeFileSync).toHaveBeenCalledWith(
|
|
84
|
+
EXPECTED_CONFIG_FILE,
|
|
85
|
+
JSON.stringify({ apiKey: NEW_KEY }, null, 2),
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should write config file without creating dir if dir exists", () => {
|
|
90
|
+
mockedFs.existsSync.mockReturnValue(true); // Dir exists
|
|
91
|
+
|
|
92
|
+
saveApiKey("key");
|
|
93
|
+
|
|
94
|
+
expect(mockedFs.mkdirSync).not.toHaveBeenCalled();
|
|
95
|
+
expect(mockedFs.writeFileSync).toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("getAuthConfig", () => {
|
|
100
|
+
it("should return default values when env vars are not set", () => {
|
|
101
|
+
const config = getAuthConfig();
|
|
102
|
+
expect(config.authServerUrl).toBe("https://app.mindsim.com/sdk/authorize");
|
|
103
|
+
expect(config.authRedirectPath).toBe("http://localhost:4242/callback");
|
|
104
|
+
expect(config.listenPort).toBe("4242");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should return env overrides when set", () => {
|
|
108
|
+
vi.stubEnv("MINDSIM_AUTH_SERVER_URL", "https://dev.mindsim.com/auth");
|
|
109
|
+
vi.stubEnv("MINDSIM_AUTH_REDIRECT_URI", "http://custom-callback");
|
|
110
|
+
vi.stubEnv("MINDSIM_AUTH_PORT", "8080");
|
|
111
|
+
|
|
112
|
+
const config = getAuthConfig();
|
|
113
|
+
expect(config.authServerUrl).toBe("https://dev.mindsim.com/auth");
|
|
114
|
+
expect(config.authRedirectPath).toBe("http://custom-callback");
|
|
115
|
+
expect(config.listenPort).toBe("8080");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("getApiBaseUrl", () => {
|
|
120
|
+
it("should return default URL", () => {
|
|
121
|
+
expect(getApiBaseUrl()).toBe("https://app.mindsim.com/api/public/v1");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should return override from environment", () => {
|
|
125
|
+
vi.stubEnv("MIND_SIM_API_BASE_URL", "https://staging.api.mindsim.com");
|
|
126
|
+
expect(getApiBaseUrl()).toBe("https://staging.api.mindsim.com");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
|
|
3
|
+
import { MindSim } from "../../src/index";
|
|
4
|
+
import type { Mind } from "../../src/types";
|
|
5
|
+
|
|
6
|
+
vi.mock("axios");
|
|
7
|
+
const mockedAxios = axios as Mocked<typeof axios>;
|
|
8
|
+
|
|
9
|
+
describe("MindSim Minds Resource", () => {
|
|
10
|
+
let mindsim: MindSim;
|
|
11
|
+
|
|
12
|
+
const MIND_ID = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d";
|
|
13
|
+
const TAG_ID_1 = "56255954-189f-4a1e-a355-08a1704af943";
|
|
14
|
+
|
|
15
|
+
const mockClient = {
|
|
16
|
+
get: vi.fn(),
|
|
17
|
+
post: vi.fn(),
|
|
18
|
+
put: vi.fn(),
|
|
19
|
+
delete: vi.fn(),
|
|
20
|
+
defaults: { headers: { common: {} } },
|
|
21
|
+
interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
// Mock axios.create to return our mockClient
|
|
26
|
+
mockedAxios.create.mockReturnValue(mockClient as any);
|
|
27
|
+
mindsim = new MindSim("fake-key");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("create() should transform tag array to comma-separated string", async () => {
|
|
31
|
+
const mockResponse: Mind = {
|
|
32
|
+
id: MIND_ID,
|
|
33
|
+
name: "Bobby",
|
|
34
|
+
scope: "private",
|
|
35
|
+
isSelf: false,
|
|
36
|
+
tags: [],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
mockClient.post.mockResolvedValue({ data: { mind: mockResponse } });
|
|
40
|
+
|
|
41
|
+
const result = await mindsim.minds.create({
|
|
42
|
+
name: "Bobby",
|
|
43
|
+
tags: ["dev", "ts"],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(mockClient.post).toHaveBeenCalledWith("/minds", {
|
|
47
|
+
name: "Bobby",
|
|
48
|
+
tags: "dev,ts", // Verify transformation
|
|
49
|
+
});
|
|
50
|
+
expect(result).toEqual(mockResponse);
|
|
51
|
+
expect(result.id).toMatch(
|
|
52
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("update() should PUT with transformed tags", async () => {
|
|
57
|
+
const mockResponse: Mind = {
|
|
58
|
+
id: MIND_ID,
|
|
59
|
+
name: "Bobby Updated",
|
|
60
|
+
scope: "private",
|
|
61
|
+
isSelf: false,
|
|
62
|
+
tags: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
mockClient.put.mockResolvedValue({ data: { mind: mockResponse } });
|
|
66
|
+
|
|
67
|
+
const result = await mindsim.minds.update(MIND_ID, {
|
|
68
|
+
name: "Bobby Updated",
|
|
69
|
+
tags: ["lead", "admin"],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(mockClient.put).toHaveBeenCalledWith(`/minds/${MIND_ID}`, {
|
|
73
|
+
name: "Bobby Updated",
|
|
74
|
+
tags: "lead,admin", // Verify transformation to string
|
|
75
|
+
});
|
|
76
|
+
expect(result.name).toBe("Bobby Updated");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("delete() should DELETE mind", async () => {
|
|
80
|
+
mockClient.delete.mockResolvedValue({ data: { success: true } });
|
|
81
|
+
|
|
82
|
+
const result = await mindsim.minds.delete(MIND_ID);
|
|
83
|
+
|
|
84
|
+
expect(mockClient.delete).toHaveBeenCalledWith(`/minds/${MIND_ID}`);
|
|
85
|
+
expect(result.success).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("list() should handle tag filtering", async () => {
|
|
89
|
+
const mockMinds = [{ id: MIND_ID, name: "A" }];
|
|
90
|
+
mockClient.get.mockResolvedValue({ data: { minds: mockMinds } });
|
|
91
|
+
|
|
92
|
+
await mindsim.minds.list({ tags: ["hero"] });
|
|
93
|
+
|
|
94
|
+
expect(mockClient.get).toHaveBeenCalledWith("/minds", {
|
|
95
|
+
params: { tagNames: "hero" },
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("get() should retrieve a mind by ID", async () => {
|
|
100
|
+
const mockMind = { id: MIND_ID, name: "Sarah" };
|
|
101
|
+
mockClient.get.mockResolvedValue({ data: mockMind });
|
|
102
|
+
|
|
103
|
+
const result = await mindsim.minds.get(MIND_ID);
|
|
104
|
+
expect(mockClient.get).toHaveBeenCalledWith(`/minds/${MIND_ID}`);
|
|
105
|
+
expect(result.id).toBe(MIND_ID);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("setTags() should PUT to correct endpoint", async () => {
|
|
109
|
+
mockClient.put.mockResolvedValue({
|
|
110
|
+
data: { mind: { id: MIND_ID, tags: [{ id: TAG_ID_1, name: "one" }] } },
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await mindsim.minds.setTags(MIND_ID, { tags: ["one", "two"] });
|
|
114
|
+
|
|
115
|
+
expect(mockClient.put).toHaveBeenCalledWith(`/minds/${MIND_ID}/tags`, {
|
|
116
|
+
tagNames: ["one", "two"],
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
|
|
3
|
+
import { MindSim } from "../../src/index";
|
|
4
|
+
|
|
5
|
+
vi.mock("axios");
|
|
6
|
+
const mockedAxios = axios as Mocked<typeof axios>;
|
|
7
|
+
|
|
8
|
+
describe("MindSim Psychometrics Resource", () => {
|
|
9
|
+
let mindsim: MindSim;
|
|
10
|
+
const SNAPSHOT_ID = "123e4567-e89b-12d3-a456-426614174003";
|
|
11
|
+
|
|
12
|
+
const mockApiClient = {
|
|
13
|
+
get: vi.fn(),
|
|
14
|
+
defaults: { headers: { common: {} } },
|
|
15
|
+
interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
mockedAxios.create.mockReturnValue(mockApiClient as any);
|
|
20
|
+
mindsim = new MindSim("fake-key");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("get() should fetch psychometrics for a specific snapshot", async () => {
|
|
24
|
+
const mockResponse = {
|
|
25
|
+
personDetails: { name: "John Doe", mindAssesssmentId: SNAPSHOT_ID },
|
|
26
|
+
dimensionAnalysis: { openness: 0.8 },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
mockApiClient.get.mockResolvedValue({ data: mockResponse });
|
|
30
|
+
|
|
31
|
+
const result = await mindsim.psychometrics.get(SNAPSHOT_ID);
|
|
32
|
+
|
|
33
|
+
// Note: Endpoint is /snapshots/{id}/psychometrics, not /minds/...
|
|
34
|
+
expect(mockApiClient.get).toHaveBeenCalledWith(`/snapshots/${SNAPSHOT_ID}/psychometrics`);
|
|
35
|
+
expect(result.personDetails.name).toBe("John Doe");
|
|
36
|
+
expect(result.dimensionAnalysis?.openness).toBe(0.8);
|
|
37
|
+
});
|
|
38
|
+
});
|