locker-cli 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.
Files changed (59) hide show
  1. package/dist/auth/client.d.ts +18 -0
  2. package/dist/auth/client.js +66 -0
  3. package/dist/auth/client.js.map +1 -0
  4. package/dist/auth/config.d.ts +23 -0
  5. package/dist/auth/config.js +69 -0
  6. package/dist/auth/config.js.map +1 -0
  7. package/dist/auth/config.test.d.ts +1 -0
  8. package/dist/auth/config.test.js +73 -0
  9. package/dist/auth/config.test.js.map +1 -0
  10. package/dist/commands/api.test.d.ts +1 -0
  11. package/dist/commands/api.test.js +116 -0
  12. package/dist/commands/api.test.js.map +1 -0
  13. package/dist/commands/commands.test.d.ts +1 -0
  14. package/dist/commands/commands.test.js +104 -0
  15. package/dist/commands/commands.test.js.map +1 -0
  16. package/dist/commands/get.d.ts +3 -0
  17. package/dist/commands/get.js +25 -0
  18. package/dist/commands/get.js.map +1 -0
  19. package/dist/commands/list.d.ts +1 -0
  20. package/dist/commands/list.js +27 -0
  21. package/dist/commands/list.js.map +1 -0
  22. package/dist/commands/login.d.ts +4 -0
  23. package/dist/commands/login.js +87 -0
  24. package/dist/commands/login.js.map +1 -0
  25. package/dist/commands/logout.d.ts +1 -0
  26. package/dist/commands/logout.js +15 -0
  27. package/dist/commands/logout.js.map +1 -0
  28. package/dist/commands/mcp.d.ts +2 -0
  29. package/dist/commands/mcp.js +83 -0
  30. package/dist/commands/mcp.js.map +1 -0
  31. package/dist/commands/revoke.d.ts +1 -0
  32. package/dist/commands/revoke.js +19 -0
  33. package/dist/commands/revoke.js.map +1 -0
  34. package/dist/commands/set.d.ts +1 -0
  35. package/dist/commands/set.js +15 -0
  36. package/dist/commands/set.js.map +1 -0
  37. package/dist/commands/whoami.d.ts +1 -0
  38. package/dist/commands/whoami.js +9 -0
  39. package/dist/commands/whoami.js.map +1 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +61 -0
  42. package/dist/index.js.map +1 -0
  43. package/package.json +26 -0
  44. package/src/auth/client.ts +86 -0
  45. package/src/auth/config.test.ts +78 -0
  46. package/src/auth/config.ts +66 -0
  47. package/src/commands/api.test.ts +143 -0
  48. package/src/commands/commands.test.ts +87 -0
  49. package/src/commands/get.ts +32 -0
  50. package/src/commands/list.ts +38 -0
  51. package/src/commands/login.ts +93 -0
  52. package/src/commands/logout.ts +12 -0
  53. package/src/commands/mcp.ts +87 -0
  54. package/src/commands/revoke.ts +23 -0
  55. package/src/commands/set.ts +20 -0
  56. package/src/commands/whoami.ts +6 -0
  57. package/src/index.ts +72 -0
  58. package/tsconfig.json +18 -0
  59. package/vitest.config.ts +8 -0
@@ -0,0 +1,143 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { apiRequest, authRequest, getDefaultApiUrl } from "../auth/client";
3
+
4
+ // Mock global fetch
5
+ const mockFetch = vi.fn();
6
+ vi.stubGlobal("fetch", mockFetch);
7
+
8
+ const testConfig = {
9
+ token: "jwt-test-token",
10
+ email: "test@example.com",
11
+ apiUrl: "http://localhost:3001",
12
+ };
13
+
14
+ beforeEach(() => {
15
+ vi.restoreAllMocks();
16
+ vi.stubGlobal("fetch", mockFetch);
17
+ });
18
+
19
+ describe("apiRequest", () => {
20
+ it("sends authenticated request with Bearer token", async () => {
21
+ mockFetch.mockResolvedValueOnce({
22
+ ok: true,
23
+ status: 200,
24
+ json: async () => ({ services: [] }),
25
+ });
26
+
27
+ const res = await apiRequest("GET", "/keys", testConfig);
28
+
29
+ expect(mockFetch).toHaveBeenCalledWith(
30
+ "http://localhost:3001/keys",
31
+ expect.objectContaining({
32
+ method: "GET",
33
+ headers: expect.objectContaining({
34
+ Authorization: "Bearer jwt-test-token",
35
+ }),
36
+ })
37
+ );
38
+ expect(res.ok).toBe(true);
39
+ });
40
+
41
+ it("sends custom headers (e.g. X-Agent-Identifier)", async () => {
42
+ mockFetch.mockResolvedValueOnce({
43
+ ok: true,
44
+ status: 200,
45
+ json: async () => ({ key: "sk-123" }),
46
+ });
47
+
48
+ await apiRequest("GET", "/keys/resend", testConfig, undefined, {
49
+ "X-Agent-Identifier": "claude-code",
50
+ });
51
+
52
+ expect(mockFetch).toHaveBeenCalledWith(
53
+ "http://localhost:3001/keys/resend",
54
+ expect.objectContaining({
55
+ headers: expect.objectContaining({
56
+ "X-Agent-Identifier": "claude-code",
57
+ Authorization: "Bearer jwt-test-token",
58
+ }),
59
+ })
60
+ );
61
+ });
62
+
63
+ it("sends JSON body for POST requests", async () => {
64
+ mockFetch.mockResolvedValueOnce({
65
+ ok: true,
66
+ status: 201,
67
+ json: async () => ({ service: "resend", message: "Key stored" }),
68
+ });
69
+
70
+ await apiRequest("POST", "/keys", testConfig, {
71
+ service: "resend",
72
+ key: "sk-resend-123",
73
+ });
74
+
75
+ expect(mockFetch).toHaveBeenCalledWith(
76
+ "http://localhost:3001/keys",
77
+ expect.objectContaining({
78
+ method: "POST",
79
+ body: JSON.stringify({ service: "resend", key: "sk-resend-123" }),
80
+ })
81
+ );
82
+ });
83
+
84
+ it("returns error data on non-ok response", async () => {
85
+ mockFetch.mockResolvedValueOnce({
86
+ ok: false,
87
+ status: 404,
88
+ json: async () => ({ error: "No key found for service: unknown" }),
89
+ });
90
+
91
+ const res = await apiRequest("GET", "/keys/unknown", testConfig);
92
+ expect(res.ok).toBe(false);
93
+ expect(res.status).toBe(404);
94
+ expect(res.data.error).toContain("No key found");
95
+ });
96
+ });
97
+
98
+ describe("authRequest", () => {
99
+ it("sends unauthenticated request", async () => {
100
+ mockFetch.mockResolvedValueOnce({
101
+ ok: true,
102
+ status: 200,
103
+ json: async () => ({
104
+ token: "new-jwt",
105
+ user: { id: "1", email: "test@example.com" },
106
+ }),
107
+ });
108
+
109
+ const res = await authRequest("POST", "/auth/login", "http://localhost:3001", {
110
+ email: "test@example.com",
111
+ password: "password123",
112
+ });
113
+
114
+ expect(res.ok).toBe(true);
115
+ expect(res.data.token).toBe("new-jwt");
116
+
117
+ // Should NOT have Authorization header
118
+ const callArgs = mockFetch.mock.calls[0];
119
+ expect(callArgs[1].headers).not.toHaveProperty("Authorization");
120
+ });
121
+ });
122
+
123
+ describe("getDefaultApiUrl", () => {
124
+ const originalEnv = process.env.LOCKER_API_URL;
125
+
126
+ afterEach(() => {
127
+ if (originalEnv) {
128
+ process.env.LOCKER_API_URL = originalEnv;
129
+ } else {
130
+ delete process.env.LOCKER_API_URL;
131
+ }
132
+ });
133
+
134
+ it("returns default URL when env var is not set", () => {
135
+ delete process.env.LOCKER_API_URL;
136
+ expect(getDefaultApiUrl()).toBe("http://localhost:3001");
137
+ });
138
+
139
+ it("returns env var when set", () => {
140
+ process.env.LOCKER_API_URL = "https://api.locker.dev";
141
+ expect(getDefaultApiUrl()).toBe("https://api.locker.dev");
142
+ });
143
+ });
@@ -0,0 +1,87 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { logoutCommand } from "./logout";
3
+ import { whoamiCommand } from "./whoami";
4
+
5
+ // Mock the config module
6
+ vi.mock("../auth/config", () => {
7
+ let mockConfig: any = null;
8
+
9
+ return {
10
+ readConfig: vi.fn(() => mockConfig),
11
+ writeConfig: vi.fn(),
12
+ clearConfig: vi.fn(),
13
+ requireAuth: vi.fn(() => {
14
+ if (!mockConfig) {
15
+ // Simulate process.exit by throwing
16
+ throw new Error("NOT_LOGGED_IN");
17
+ }
18
+ return mockConfig;
19
+ }),
20
+ __setMockConfig: (config: any) => {
21
+ mockConfig = config;
22
+ },
23
+ };
24
+ });
25
+
26
+ // Get the mock helpers
27
+ import * as configModule from "../auth/config";
28
+
29
+ const setMockConfig = (configModule as any).__setMockConfig;
30
+ const clearConfigMock = configModule.clearConfig as ReturnType<typeof vi.fn>;
31
+
32
+ describe("logout", () => {
33
+ beforeEach(() => {
34
+ vi.restoreAllMocks();
35
+ });
36
+
37
+ it("clears config and confirms logout", () => {
38
+ setMockConfig({
39
+ token: "jwt-123",
40
+ email: "test@example.com",
41
+ apiUrl: "http://localhost:3001",
42
+ });
43
+
44
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
45
+ logoutCommand();
46
+
47
+ expect(clearConfigMock).toHaveBeenCalled();
48
+ expect(consoleSpy).toHaveBeenCalledWith(
49
+ expect.stringContaining("test@example.com")
50
+ );
51
+ });
52
+
53
+ it("handles already logged out", () => {
54
+ setMockConfig(null);
55
+
56
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
57
+ logoutCommand();
58
+
59
+ expect(clearConfigMock).toHaveBeenCalled();
60
+ expect(consoleSpy).toHaveBeenCalledWith("Already logged out.");
61
+ });
62
+ });
63
+
64
+ describe("whoami", () => {
65
+ beforeEach(() => {
66
+ vi.restoreAllMocks();
67
+ });
68
+
69
+ it("prints the logged-in email", () => {
70
+ setMockConfig({
71
+ token: "jwt-123",
72
+ email: "user@locker.dev",
73
+ apiUrl: "http://localhost:3001",
74
+ });
75
+
76
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
77
+ whoamiCommand();
78
+
79
+ expect(consoleSpy).toHaveBeenCalledWith("user@locker.dev");
80
+ });
81
+
82
+ it("throws when not logged in", () => {
83
+ setMockConfig(null);
84
+
85
+ expect(() => whoamiCommand()).toThrow("NOT_LOGGED_IN");
86
+ });
87
+ });
@@ -0,0 +1,32 @@
1
+ import { requireAuth } from "../auth/config";
2
+ import { apiRequest } from "../auth/client";
3
+
4
+ export async function getCommand(service: string, options: { agent?: string }) {
5
+ const config = requireAuth();
6
+
7
+ const headers: Record<string, string> = {};
8
+ if (options.agent) {
9
+ headers["X-Agent-Identifier"] = options.agent;
10
+ }
11
+
12
+ const res = await apiRequest<{ service: string; key: string }>(
13
+ "GET",
14
+ `/keys/${encodeURIComponent(service)}`,
15
+ config,
16
+ undefined,
17
+ headers
18
+ );
19
+
20
+ if (!res.ok) {
21
+ if (res.status === 404) {
22
+ console.error(`No key found for service: ${service}`);
23
+ console.error(`Store one with: locker set ${service} <key>`);
24
+ process.exit(1);
25
+ }
26
+ console.error(res.data.error || "Failed to retrieve key.");
27
+ process.exit(1);
28
+ }
29
+
30
+ // Print key to stdout only — no extra formatting, no logging to disk
31
+ process.stdout.write(res.data.key);
32
+ }
@@ -0,0 +1,38 @@
1
+ import { requireAuth } from "../auth/config";
2
+ import { apiRequest } from "../auth/client";
3
+
4
+ interface Service {
5
+ service: string;
6
+ createdAt: string;
7
+ lastUsed: string | null;
8
+ }
9
+
10
+ export async function listCommand() {
11
+ const config = requireAuth();
12
+
13
+ const res = await apiRequest<{ services: Service[] }>(
14
+ "GET",
15
+ "/keys",
16
+ config
17
+ );
18
+
19
+ if (!res.ok) {
20
+ console.error(res.data.error || "Failed to list keys.");
21
+ process.exit(1);
22
+ }
23
+
24
+ const services = res.data.services;
25
+ if (services.length === 0) {
26
+ console.log("No keys stored yet.");
27
+ console.log("Store one with: locker set <service> <key>");
28
+ return;
29
+ }
30
+
31
+ console.log(`${services.length} key${services.length === 1 ? "" : "s"} stored:\n`);
32
+ for (const svc of services) {
33
+ const lastUsed = svc.lastUsed
34
+ ? `last used ${new Date(svc.lastUsed).toLocaleDateString()}`
35
+ : "never used";
36
+ console.log(` ${svc.service} (${lastUsed})`);
37
+ }
38
+ }
@@ -0,0 +1,93 @@
1
+ import readline from "node:readline";
2
+ import { writeConfig } from "../auth/config";
3
+ import { authRequest, getDefaultApiUrl } from "../auth/client";
4
+
5
+ function prompt(question: string, hidden = false): Promise<string> {
6
+ const rl = readline.createInterface({
7
+ input: process.stdin,
8
+ output: process.stdout,
9
+ });
10
+
11
+ return new Promise((resolve) => {
12
+ if (hidden && process.stdin.isTTY) {
13
+ // Hide password input
14
+ process.stdout.write(question);
15
+ const stdin = process.stdin;
16
+ stdin.setRawMode(true);
17
+ stdin.resume();
18
+ stdin.setEncoding("utf8");
19
+
20
+ let input = "";
21
+ const onData = (ch: string) => {
22
+ if (ch === "\n" || ch === "\r" || ch === "\u0004") {
23
+ stdin.setRawMode(false);
24
+ stdin.removeListener("data", onData);
25
+ stdin.pause();
26
+ rl.close();
27
+ process.stdout.write("\n");
28
+ resolve(input);
29
+ } else if (ch === "\u0003") {
30
+ // Ctrl-C
31
+ process.exit(0);
32
+ } else if (ch === "\u007F" || ch === "\b") {
33
+ // Backspace
34
+ if (input.length > 0) {
35
+ input = input.slice(0, -1);
36
+ }
37
+ } else {
38
+ input += ch;
39
+ }
40
+ };
41
+ stdin.on("data", onData);
42
+ } else {
43
+ rl.question(question, (answer) => {
44
+ rl.close();
45
+ resolve(answer);
46
+ });
47
+ }
48
+ });
49
+ }
50
+
51
+ export async function loginCommand(options: { register?: boolean; api?: string }) {
52
+ const apiUrl = options.api || getDefaultApiUrl();
53
+ const isRegister = options.register || false;
54
+
55
+ console.log(isRegister ? "Create a new Locker account" : "Log in to Locker");
56
+ console.log();
57
+
58
+ const email = await prompt("Email: ");
59
+ const password = await prompt("Password: ", true);
60
+
61
+ if (!email || !password) {
62
+ console.error("Email and password are required.");
63
+ process.exit(1);
64
+ }
65
+
66
+ if (isRegister && password.length < 8) {
67
+ console.error("Password must be at least 8 characters.");
68
+ process.exit(1);
69
+ }
70
+
71
+ const endpoint = isRegister ? "/auth/register" : "/auth/login";
72
+ const res = await authRequest<{ token: string; user: { id: string; email: string } }>(
73
+ "POST",
74
+ endpoint,
75
+ apiUrl,
76
+ { email, password }
77
+ );
78
+
79
+ if (!res.ok) {
80
+ console.error(res.data.error || "Authentication failed.");
81
+ process.exit(1);
82
+ }
83
+
84
+ writeConfig({
85
+ token: res.data.token,
86
+ email: res.data.user.email,
87
+ apiUrl,
88
+ });
89
+
90
+ console.log();
91
+ console.log(`Logged in as ${res.data.user.email}`);
92
+ console.log("Token stored in ~/.locker/config");
93
+ }
@@ -0,0 +1,12 @@
1
+ import { clearConfig, readConfig } from "../auth/config";
2
+
3
+ export function logoutCommand() {
4
+ const config = readConfig();
5
+ clearConfig();
6
+
7
+ if (config) {
8
+ console.log(`Logged out (${config.email}). Token cleared.`);
9
+ } else {
10
+ console.log("Already logged out.");
11
+ }
12
+ }
@@ -0,0 +1,87 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+
5
+ interface McpConfig {
6
+ mcpServers?: Record<string, { command: string; args: string[] }>;
7
+ [key: string]: unknown;
8
+ }
9
+
10
+ const TARGETS: Record<string, string> = {
11
+ claude: path.join(os.homedir(), ".claude", "claude_desktop_config.json"),
12
+ cursor: path.join(os.homedir(), ".cursor", "mcp.json"),
13
+ };
14
+
15
+ function getMcpEntry() {
16
+ // Use the published npm package — npx resolves it globally
17
+ return { command: "npx", args: ["locker-mcp-server"] };
18
+ }
19
+
20
+ function installToTarget(name: string, configPath: string): boolean {
21
+ const dir = path.dirname(configPath);
22
+ if (!fs.existsSync(dir)) {
23
+ fs.mkdirSync(dir, { recursive: true });
24
+ }
25
+
26
+ let config: McpConfig = {};
27
+ if (fs.existsSync(configPath)) {
28
+ try {
29
+ config = JSON.parse(fs.readFileSync(configPath, "utf8"));
30
+ } catch {
31
+ console.error(` Could not parse ${configPath}`);
32
+ return false;
33
+ }
34
+ }
35
+
36
+ if (!config.mcpServers) {
37
+ config.mcpServers = {};
38
+ }
39
+
40
+ const entry = getMcpEntry();
41
+ config.mcpServers.locker = entry;
42
+
43
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
44
+ console.log(` ✓ ${name} — ${configPath}`);
45
+ return true;
46
+ }
47
+
48
+ export function mcpInstallCommand() {
49
+ console.log("Installing Locker MCP server...\n");
50
+
51
+ let installed = 0;
52
+ for (const [name, configPath] of Object.entries(TARGETS)) {
53
+ const dir = path.dirname(configPath);
54
+ // Only install if the tool's config directory exists (tool is installed)
55
+ if (fs.existsSync(dir) || name === "claude") {
56
+ if (installToTarget(name, configPath)) installed++;
57
+ }
58
+ }
59
+
60
+ if (installed === 0) {
61
+ console.log("No supported AI tools detected.");
62
+ console.log("Manually add to your tool's MCP config:");
63
+ const entry = getMcpEntry();
64
+ console.log(JSON.stringify({ locker: entry }, null, 2));
65
+ } else {
66
+ console.log(`\nDone. Restart your AI tool to activate.`);
67
+ }
68
+ }
69
+
70
+ export function mcpUninstallCommand() {
71
+ console.log("Removing Locker MCP server...\n");
72
+
73
+ for (const [name, configPath] of Object.entries(TARGETS)) {
74
+ if (!fs.existsSync(configPath)) continue;
75
+ try {
76
+ const config: McpConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
77
+ if (config.mcpServers?.locker) {
78
+ delete config.mcpServers.locker;
79
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
80
+ console.log(` ✓ Removed from ${name}`);
81
+ }
82
+ } catch {
83
+ // Skip
84
+ }
85
+ }
86
+ console.log("\nDone.");
87
+ }
@@ -0,0 +1,23 @@
1
+ import { requireAuth } from "../auth/config";
2
+ import { apiRequest } from "../auth/client";
3
+
4
+ export async function revokeCommand(service: string) {
5
+ const config = requireAuth();
6
+
7
+ const res = await apiRequest(
8
+ "DELETE",
9
+ `/keys/${encodeURIComponent(service)}`,
10
+ config
11
+ );
12
+
13
+ if (!res.ok) {
14
+ if (res.status === 404) {
15
+ console.error(`No key found for service: ${service}`);
16
+ process.exit(1);
17
+ }
18
+ console.error(res.data.error || "Failed to revoke key.");
19
+ process.exit(1);
20
+ }
21
+
22
+ console.log(`Key revoked for ${service}`);
23
+ }
@@ -0,0 +1,20 @@
1
+ import { requireAuth } from "../auth/config";
2
+ import { apiRequest } from "../auth/client";
3
+
4
+ export async function setCommand(service: string, key: string) {
5
+ const config = requireAuth();
6
+
7
+ const res = await apiRequest(
8
+ "POST",
9
+ "/keys",
10
+ config,
11
+ { service, key }
12
+ );
13
+
14
+ if (!res.ok) {
15
+ console.error(res.data.error || "Failed to store key.");
16
+ process.exit(1);
17
+ }
18
+
19
+ console.log(`Key stored for ${service}`);
20
+ }
@@ -0,0 +1,6 @@
1
+ import { requireAuth } from "../auth/config";
2
+
3
+ export function whoamiCommand() {
4
+ const config = requireAuth();
5
+ console.log(config.email);
6
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { loginCommand } from "./commands/login";
5
+ import { logoutCommand } from "./commands/logout";
6
+ import { whoamiCommand } from "./commands/whoami";
7
+ import { getCommand } from "./commands/get";
8
+ import { setCommand } from "./commands/set";
9
+ import { listCommand } from "./commands/list";
10
+ import { revokeCommand } from "./commands/revoke";
11
+ import { mcpInstallCommand, mcpUninstallCommand } from "./commands/mcp";
12
+
13
+ const program = new Command();
14
+
15
+ program
16
+ .name("locker")
17
+ .description("Secure API credential manager for AI agents")
18
+ .version("0.1.0");
19
+
20
+ program
21
+ .command("login")
22
+ .description("Log in to Locker")
23
+ .option("--register", "Create a new account")
24
+ .option("--api <url>", "API server URL")
25
+ .action(loginCommand);
26
+
27
+ program
28
+ .command("logout")
29
+ .description("Log out and clear stored token")
30
+ .action(logoutCommand);
31
+
32
+ program
33
+ .command("whoami")
34
+ .description("Show the currently logged-in user")
35
+ .action(whoamiCommand);
36
+
37
+ program
38
+ .command("get <service>")
39
+ .description("Retrieve an API key (prints to stdout)")
40
+ .option("--agent <name>", "Agent identifier for audit log")
41
+ .action(getCommand);
42
+
43
+ program
44
+ .command("set <service> <key>")
45
+ .description("Store an API key")
46
+ .action(setCommand);
47
+
48
+ program
49
+ .command("list")
50
+ .description("List all stored services")
51
+ .action(listCommand);
52
+
53
+ program
54
+ .command("revoke <service>")
55
+ .description("Delete a stored API key")
56
+ .action(revokeCommand);
57
+
58
+ const mcp = program
59
+ .command("mcp")
60
+ .description("Manage the Locker MCP server");
61
+
62
+ mcp
63
+ .command("install")
64
+ .description("Install Locker MCP server into Claude Code, Cursor, etc.")
65
+ .action(mcpInstallCommand);
66
+
67
+ mcp
68
+ .command("uninstall")
69
+ .description("Remove Locker MCP server from AI tools")
70
+ .action(mcpUninstallCommand);
71
+
72
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "lib": ["ES2022"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "sourceMap": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node",
7
+ },
8
+ });