@revenium/claude-code-metering 0.1.3 → 0.1.5

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 (78) hide show
  1. package/.env.example +15 -0
  2. package/.eslintrc.js +24 -0
  3. package/.github/workflows/branch-bypass-alert.yml +68 -0
  4. package/CODE_OF_CONDUCT.md +57 -0
  5. package/CONTRIBUTING.md +73 -0
  6. package/README.md +57 -3
  7. package/SECURITY.md +46 -0
  8. package/dist/cli/commands/setup.js +3 -1
  9. package/dist/cli/commands/setup.js.map +1 -1
  10. package/dist/cli/index.d.ts.map +1 -1
  11. package/dist/cli/index.js +4 -1
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/core/api/client.d.ts.map +1 -1
  14. package/dist/core/api/client.js +4 -1
  15. package/dist/core/api/client.js.map +1 -1
  16. package/dist/core/tool-context.d.ts +6 -0
  17. package/dist/core/tool-context.d.ts.map +1 -0
  18. package/dist/core/tool-context.js +21 -0
  19. package/dist/core/tool-context.js.map +1 -0
  20. package/dist/core/tool-tracker.d.ts +4 -0
  21. package/dist/core/tool-tracker.d.ts.map +1 -0
  22. package/dist/core/tool-tracker.js +156 -0
  23. package/dist/core/tool-tracker.js.map +1 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/types/index.d.ts +1 -0
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/dist/types/index.js +15 -0
  31. package/dist/types/index.js.map +1 -1
  32. package/dist/types/tool-metering.d.ts +36 -0
  33. package/dist/types/tool-metering.d.ts.map +1 -0
  34. package/dist/types/tool-metering.js +3 -0
  35. package/dist/types/tool-metering.js.map +1 -0
  36. package/docs/research/settings-json-telemetry-findings.md +171 -0
  37. package/examples/README.md +114 -0
  38. package/examples/validation/validate-installation.sh +212 -0
  39. package/package.json +1 -7
  40. package/public-allowlist-node.txt +7 -0
  41. package/src/cli/commands/backfill.ts +865 -0
  42. package/src/cli/commands/setup.ts +254 -0
  43. package/src/cli/commands/status.ts +108 -0
  44. package/src/cli/commands/test.ts +91 -0
  45. package/src/cli/index.ts +103 -0
  46. package/src/core/api/client.ts +194 -0
  47. package/src/core/config/loader.ts +217 -0
  48. package/src/core/config/validator.ts +142 -0
  49. package/src/core/config/writer.ts +212 -0
  50. package/src/core/shell/detector.ts +92 -0
  51. package/src/core/shell/profile-updater.ts +131 -0
  52. package/src/core/tool-context.ts +23 -0
  53. package/src/core/tool-tracker.ts +204 -0
  54. package/src/index.ts +12 -0
  55. package/src/types/index.ts +110 -0
  56. package/src/types/tool-metering.ts +38 -0
  57. package/src/utils/constants.ts +80 -0
  58. package/src/utils/hashing.ts +35 -0
  59. package/src/utils/masking.ts +32 -0
  60. package/tests/integration/cli-commands.test.ts +158 -0
  61. package/tests/unit/backfill-command.test.ts +366 -0
  62. package/tests/unit/backfill-helpers.test.ts +397 -0
  63. package/tests/unit/backfill-parse.test.ts +276 -0
  64. package/tests/unit/backfill-stream.test.ts +147 -0
  65. package/tests/unit/backfill.test.ts +344 -0
  66. package/tests/unit/cli-index.test.ts +193 -0
  67. package/tests/unit/client.test.ts +195 -0
  68. package/tests/unit/detector.test.ts +247 -0
  69. package/tests/unit/hashing.test.ts +121 -0
  70. package/tests/unit/loader.test.ts +272 -0
  71. package/tests/unit/masking.test.ts +46 -0
  72. package/tests/unit/profile-updater.test.ts +146 -0
  73. package/tests/unit/setup.test.ts +557 -0
  74. package/tests/unit/status.test.ts +149 -0
  75. package/tests/unit/test.test.ts +165 -0
  76. package/tests/unit/validator.test.ts +211 -0
  77. package/tests/unit/writer.test.ts +176 -0
  78. package/tsconfig.json +20 -0
@@ -0,0 +1,165 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { testCommand } from "../../src/cli/commands/test.js";
3
+ import * as loader from "../../src/core/config/loader.js";
4
+ import * as client from "../../src/core/api/client.js";
5
+
6
+ vi.mock("../../src/core/config/loader.js");
7
+ vi.mock("../../src/core/api/client.js");
8
+ vi.mock("chalk", () => ({
9
+ default: {
10
+ bold: (str: string) => str,
11
+ green: {
12
+ bold: (str: string) => str,
13
+ },
14
+ red: (str: string) => str,
15
+ yellow: (str: string) => str,
16
+ dim: (str: string) => str,
17
+ },
18
+ }));
19
+ vi.mock("ora", () => ({
20
+ default: () => ({
21
+ start: () => ({
22
+ succeed: vi.fn(),
23
+ fail: vi.fn(),
24
+ }),
25
+ }),
26
+ }));
27
+
28
+ describe("testCommand", () => {
29
+ const mockExit = vi
30
+ .spyOn(process, "exit")
31
+ .mockImplementation((code?: number) => {
32
+ throw new Error(`process.exit unexpectedly called with "${code}"`);
33
+ });
34
+
35
+ beforeEach(() => {
36
+ vi.clearAllMocks();
37
+ });
38
+
39
+ afterEach(() => {
40
+ vi.restoreAllMocks();
41
+ });
42
+
43
+ it("should exit with error when config does not exist", async () => {
44
+ vi.mocked(loader.configExists).mockReturnValue(false);
45
+
46
+ await expect(testCommand()).rejects.toThrow(
47
+ 'process.exit unexpectedly called with "1"',
48
+ );
49
+ });
50
+
51
+ it("should exit with error when config cannot be loaded", async () => {
52
+ vi.mocked(loader.configExists).mockReturnValue(true);
53
+ vi.mocked(loader.loadConfig).mockResolvedValue(null);
54
+
55
+ await expect(testCommand()).rejects.toThrow(
56
+ 'process.exit unexpectedly called with "1"',
57
+ );
58
+ });
59
+
60
+ it("should send test metric successfully", async () => {
61
+ vi.mocked(loader.configExists).mockReturnValue(true);
62
+ vi.mocked(loader.loadConfig).mockResolvedValue({
63
+ apiKey: "hak_test123",
64
+ endpoint: "https://api.revenium.ai",
65
+ email: "test@example.com",
66
+ });
67
+ vi.mocked(client.generateTestSessionId).mockReturnValue("test-session-123");
68
+ vi.mocked(client.createTestPayload).mockReturnValue({
69
+ resourceLogs: [],
70
+ });
71
+ vi.mocked(client.sendOtlpLogs).mockResolvedValue({
72
+ id: "metric-123",
73
+ resourceType: "claude-code-metering",
74
+ processedEvents: 1,
75
+ created: "2024-01-01T00:00:00Z",
76
+ });
77
+
78
+ await testCommand();
79
+
80
+ expect(client.generateTestSessionId).toHaveBeenCalled();
81
+ expect(client.createTestPayload).toHaveBeenCalledWith(
82
+ "test-session-123",
83
+ expect.objectContaining({
84
+ email: "test@example.com",
85
+ }),
86
+ );
87
+ expect(client.sendOtlpLogs).toHaveBeenCalledWith(
88
+ "https://api.revenium.ai",
89
+ "hak_test123",
90
+ expect.any(Object),
91
+ );
92
+ });
93
+
94
+ it("should include organizationId and productId in payload", async () => {
95
+ vi.mocked(loader.configExists).mockReturnValue(true);
96
+ vi.mocked(loader.loadConfig).mockResolvedValue({
97
+ apiKey: "hak_test123",
98
+ endpoint: "https://api.revenium.ai",
99
+ organizationId: "org-123",
100
+ productId: "prod-456",
101
+ });
102
+ vi.mocked(client.generateTestSessionId).mockReturnValue("test-session-123");
103
+ vi.mocked(client.createTestPayload).mockReturnValue({
104
+ resourceLogs: [],
105
+ });
106
+ vi.mocked(client.sendOtlpLogs).mockResolvedValue({
107
+ id: "metric-123",
108
+ resourceType: "claude-code-metering",
109
+ processedEvents: 1,
110
+ created: "2024-01-01T00:00:00Z",
111
+ });
112
+
113
+ await testCommand();
114
+
115
+ expect(client.createTestPayload).toHaveBeenCalledWith(
116
+ "test-session-123",
117
+ expect.objectContaining({
118
+ organizationId: "org-123",
119
+ productId: "prod-456",
120
+ }),
121
+ );
122
+ });
123
+
124
+ it("should handle verbose option", async () => {
125
+ vi.mocked(loader.configExists).mockReturnValue(true);
126
+ vi.mocked(loader.loadConfig).mockResolvedValue({
127
+ apiKey: "hak_test123",
128
+ endpoint: "https://api.revenium.ai",
129
+ });
130
+ vi.mocked(client.generateTestSessionId).mockReturnValue("test-session-123");
131
+ vi.mocked(client.createTestPayload).mockReturnValue({
132
+ resourceLogs: [],
133
+ });
134
+ vi.mocked(client.sendOtlpLogs).mockResolvedValue({
135
+ id: "metric-123",
136
+ resourceType: "claude-code-metering",
137
+ processedEvents: 1,
138
+ created: "2024-01-01T00:00:00Z",
139
+ });
140
+
141
+ await testCommand({ verbose: true });
142
+
143
+ expect(client.sendOtlpLogs).toHaveBeenCalled();
144
+ });
145
+
146
+ it("should exit with error when sendOtlpLogs fails", async () => {
147
+ vi.mocked(loader.configExists).mockReturnValue(true);
148
+ vi.mocked(loader.loadConfig).mockResolvedValue({
149
+ apiKey: "hak_test123",
150
+ endpoint: "https://api.revenium.ai",
151
+ });
152
+ vi.mocked(client.generateTestSessionId).mockReturnValue("test-session-123");
153
+ vi.mocked(client.createTestPayload).mockReturnValue({
154
+ resourceLogs: [],
155
+ });
156
+ vi.mocked(client.sendOtlpLogs).mockRejectedValue(
157
+ new Error("Network error"),
158
+ );
159
+
160
+ await expect(testCommand()).rejects.toThrow(
161
+ 'process.exit unexpectedly called with "1"',
162
+ );
163
+ });
164
+ });
165
+
@@ -0,0 +1,211 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ validateApiKey,
4
+ validateEmail,
5
+ validateSubscriptionTier,
6
+ validateConfig,
7
+ validateEndpointUrl,
8
+ } from "../../src/core/config/validator.js";
9
+
10
+ describe("validateApiKey", () => {
11
+ it("should accept valid API key format", () => {
12
+ const result = validateApiKey("hak_tenant_abc123xyz");
13
+ expect(result.valid).toBe(true);
14
+ expect(result.errors).toHaveLength(0);
15
+ });
16
+
17
+ it("should reject empty API key", () => {
18
+ const result = validateApiKey("");
19
+ expect(result.valid).toBe(false);
20
+ expect(result.errors).toContain("API key is required");
21
+ });
22
+
23
+ it("should reject API key without hak_ prefix", () => {
24
+ const result = validateApiKey("invalid_key_123");
25
+ expect(result.valid).toBe(false);
26
+ expect(result.errors.some((e) => e.includes("hak_"))).toBe(true);
27
+ });
28
+
29
+ it("should reject API key with wrong format", () => {
30
+ const result = validateApiKey("hak_short");
31
+ expect(result.valid).toBe(false);
32
+ expect(result.errors.some((e) => e.includes("format"))).toBe(true);
33
+ });
34
+
35
+ it("should reject very short API keys", () => {
36
+ const result = validateApiKey("hak_x_y");
37
+ expect(result.valid).toBe(false);
38
+ expect(result.errors.some((e) => e.includes("short"))).toBe(true);
39
+ });
40
+ });
41
+
42
+ describe("validateEmail", () => {
43
+ it("should accept valid email", () => {
44
+ const result = validateEmail("user@example.com");
45
+ expect(result.valid).toBe(true);
46
+ });
47
+
48
+ it("should accept empty email (optional)", () => {
49
+ const result = validateEmail("");
50
+ expect(result.valid).toBe(true);
51
+ });
52
+
53
+ it("should reject invalid email format", () => {
54
+ const result = validateEmail("invalid-email");
55
+ expect(result.valid).toBe(false);
56
+ expect(result.errors).toContain("Invalid email format");
57
+ });
58
+
59
+ it("should reject email without domain", () => {
60
+ const result = validateEmail("user@");
61
+ expect(result.valid).toBe(false);
62
+ });
63
+ });
64
+
65
+ describe("validateSubscriptionTier", () => {
66
+ it("should accept valid tiers", () => {
67
+ const tiers = [
68
+ "pro",
69
+ "max_5x",
70
+ "max_20x",
71
+ "team_premium",
72
+ "enterprise",
73
+ "api",
74
+ ];
75
+ for (const tier of tiers) {
76
+ const result = validateSubscriptionTier(tier);
77
+ expect(result.valid).toBe(true);
78
+ }
79
+ });
80
+
81
+ it("should accept empty tier (optional)", () => {
82
+ const result = validateSubscriptionTier("");
83
+ expect(result.valid).toBe(true);
84
+ });
85
+
86
+ it("should reject invalid tier", () => {
87
+ const result = validateSubscriptionTier("invalid");
88
+ expect(result.valid).toBe(false);
89
+ expect(
90
+ result.errors.some((e) => e.includes("Invalid subscription tier"))
91
+ ).toBe(true);
92
+ });
93
+ });
94
+
95
+ describe("validateConfig", () => {
96
+ it("should accept valid complete config", () => {
97
+ const result = validateConfig({
98
+ apiKey: "hak_tenant_abc123xyz",
99
+ endpoint: "https://api.revenium.ai",
100
+ email: "user@example.com",
101
+ subscriptionTier: "pro",
102
+ });
103
+ expect(result.valid).toBe(true);
104
+ expect(result.errors).toHaveLength(0);
105
+ });
106
+
107
+ it("should accept valid minimal config", () => {
108
+ const result = validateConfig({
109
+ apiKey: "hak_tenant_abc123xyz",
110
+ endpoint: "https://api.revenium.ai",
111
+ });
112
+ expect(result.valid).toBe(true);
113
+ });
114
+
115
+ it("should reject invalid endpoint URL", () => {
116
+ const result = validateConfig({
117
+ apiKey: "hak_tenant_abc123xyz",
118
+ endpoint: "not-a-url",
119
+ });
120
+ expect(result.valid).toBe(false);
121
+ expect(result.errors.some((e) => e.includes("Invalid endpoint URL"))).toBe(
122
+ true
123
+ );
124
+ });
125
+
126
+ it("should reject missing endpoint", () => {
127
+ const result = validateConfig({
128
+ apiKey: "hak_tenant_abc123xyz",
129
+ endpoint: "",
130
+ });
131
+ expect(result.valid).toBe(false);
132
+ expect(
133
+ result.errors.some((e) => e.includes("Endpoint URL is required"))
134
+ ).toBe(true);
135
+ });
136
+
137
+ it("should reject HTTP endpoint", () => {
138
+ const result = validateConfig({
139
+ apiKey: "hak_tenant_abc123xyz",
140
+ endpoint: "http://api.example.com",
141
+ });
142
+ expect(result.valid).toBe(false);
143
+ expect(result.errors.some((e) => e.includes("HTTPS is required"))).toBe(
144
+ true
145
+ );
146
+ });
147
+ });
148
+
149
+ describe("validateEndpointUrl", () => {
150
+ it("should accept HTTPS endpoint", () => {
151
+ const result = validateEndpointUrl("https://api.revenium.ai");
152
+ expect(result.valid).toBe(true);
153
+ expect(result.errors).toHaveLength(0);
154
+ });
155
+
156
+ it("should reject HTTP endpoint", () => {
157
+ const result = validateEndpointUrl("http://api.example.com");
158
+ expect(result.valid).toBe(false);
159
+ expect(result.errors.some((e) => e.includes("HTTPS is required"))).toBe(
160
+ true
161
+ );
162
+ });
163
+
164
+ it("should reject FTP endpoint", () => {
165
+ const result = validateEndpointUrl("ftp://api.example.com");
166
+ expect(result.valid).toBe(false);
167
+ expect(result.errors.some((e) => e.includes("HTTPS is required"))).toBe(
168
+ true
169
+ );
170
+ });
171
+
172
+ it("should reject WebSocket endpoint", () => {
173
+ const result = validateEndpointUrl("ws://api.example.com");
174
+ expect(result.valid).toBe(false);
175
+ expect(result.errors.some((e) => e.includes("HTTPS is required"))).toBe(
176
+ true
177
+ );
178
+ });
179
+
180
+ it("should reject file:// endpoint", () => {
181
+ const result = validateEndpointUrl("file:///etc/passwd");
182
+ expect(result.valid).toBe(false);
183
+ expect(result.errors.some((e) => e.includes("HTTPS is required"))).toBe(
184
+ true
185
+ );
186
+ });
187
+
188
+ it("should reject empty endpoint", () => {
189
+ const result = validateEndpointUrl("");
190
+ expect(result.valid).toBe(false);
191
+ expect(result.errors).toContain("Endpoint URL is required");
192
+ });
193
+
194
+ it("should reject invalid URL format", () => {
195
+ const result = validateEndpointUrl("not-a-valid-url");
196
+ expect(result.valid).toBe(false);
197
+ expect(result.errors.some((e) => e.includes("Invalid endpoint URL"))).toBe(
198
+ true
199
+ );
200
+ });
201
+
202
+ it("should accept HTTPS with port", () => {
203
+ const result = validateEndpointUrl("https://api.example.com:8443");
204
+ expect(result.valid).toBe(true);
205
+ });
206
+
207
+ it("should accept HTTPS with path", () => {
208
+ const result = validateEndpointUrl("https://api.example.com/v1/endpoint");
209
+ expect(result.valid).toBe(true);
210
+ });
211
+ });
@@ -0,0 +1,176 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import {
3
+ writeConfig,
4
+ getConfigFilePath,
5
+ } from "../../src/core/config/writer.js";
6
+ import { writeFile, mkdir, chmod } from "node:fs/promises";
7
+ import { homedir } from "node:os";
8
+ import { join } from "node:path";
9
+
10
+ vi.mock("node:fs/promises");
11
+ vi.mock("node:os");
12
+
13
+ describe("writer", () => {
14
+ beforeEach(() => {
15
+ vi.clearAllMocks();
16
+ vi.mocked(homedir).mockReturnValue("/home/user");
17
+ });
18
+
19
+ afterEach(() => {
20
+ vi.restoreAllMocks();
21
+ });
22
+
23
+ describe("writeConfig", () => {
24
+ it("should create directory if it does not exist", async () => {
25
+ const config = {
26
+ apiKey: "hak_test123",
27
+ endpoint: "https://api.revenium.ai",
28
+ };
29
+
30
+ await writeConfig(config);
31
+
32
+ expect(mkdir).toHaveBeenCalledWith("/home/user/.claude", {
33
+ recursive: true,
34
+ });
35
+ });
36
+
37
+ it("should write config file with correct content", async () => {
38
+ const config = {
39
+ apiKey: "hak_test123",
40
+ endpoint: "https://api.revenium.ai",
41
+ };
42
+
43
+ await writeConfig(config);
44
+
45
+ expect(writeFile).toHaveBeenCalledWith(
46
+ "/home/user/.claude/revenium.env",
47
+ expect.stringContaining("export CLAUDE_CODE_ENABLE_TELEMETRY=1"),
48
+ { encoding: "utf-8" },
49
+ );
50
+ expect(writeFile).toHaveBeenCalledWith(
51
+ "/home/user/.claude/revenium.env",
52
+ expect.stringContaining("export OTEL_EXPORTER_OTLP_ENDPOINT="),
53
+ { encoding: "utf-8" },
54
+ );
55
+ expect(writeFile).toHaveBeenCalledWith(
56
+ "/home/user/.claude/revenium.env",
57
+ expect.stringContaining("x-api-key=hak_test123"),
58
+ { encoding: "utf-8" },
59
+ );
60
+ });
61
+
62
+ it("should set file permissions to 0600", async () => {
63
+ const config = {
64
+ apiKey: "hak_test123",
65
+ endpoint: "https://api.revenium.ai",
66
+ };
67
+
68
+ await writeConfig(config);
69
+
70
+ expect(chmod).toHaveBeenCalledWith(
71
+ "/home/user/.claude/revenium.env",
72
+ 0o600,
73
+ );
74
+ });
75
+
76
+ it("should include email when provided", async () => {
77
+ const config = {
78
+ apiKey: "hak_test123",
79
+ endpoint: "https://api.revenium.ai",
80
+ email: "test@example.com",
81
+ };
82
+
83
+ await writeConfig(config);
84
+
85
+ expect(writeFile).toHaveBeenCalledWith(
86
+ "/home/user/.claude/revenium.env",
87
+ expect.stringContaining("export REVENIUM_SUBSCRIBER_EMAIL="),
88
+ { encoding: "utf-8" },
89
+ );
90
+ expect(writeFile).toHaveBeenCalledWith(
91
+ "/home/user/.claude/revenium.env",
92
+ expect.stringContaining("test@example.com"),
93
+ { encoding: "utf-8" },
94
+ );
95
+ });
96
+
97
+ it("should include subscription tier and cost multiplier when provided", async () => {
98
+ const config = {
99
+ apiKey: "hak_test123",
100
+ endpoint: "https://api.revenium.ai",
101
+ subscriptionTier: "pro" as const,
102
+ };
103
+
104
+ await writeConfig(config);
105
+
106
+ expect(writeFile).toHaveBeenCalledWith(
107
+ "/home/user/.claude/revenium.env",
108
+ expect.stringContaining("export CLAUDE_CODE_SUBSCRIPTION="),
109
+ { encoding: "utf-8" },
110
+ );
111
+ expect(writeFile).toHaveBeenCalledWith(
112
+ "/home/user/.claude/revenium.env",
113
+ expect.stringContaining("cost_multiplier="),
114
+ { encoding: "utf-8" },
115
+ );
116
+ });
117
+
118
+ it("should escape special characters in API key", async () => {
119
+ const config = {
120
+ apiKey: 'hak_test"$`\\special',
121
+ endpoint: "https://api.revenium.ai",
122
+ };
123
+
124
+ await writeConfig(config);
125
+
126
+ const writeFileCall = vi.mocked(writeFile).mock.calls[0];
127
+ const content = writeFileCall[1] as string;
128
+
129
+ expect(content).toContain('\\"');
130
+ expect(content).toContain("\\$");
131
+ expect(content).toContain("\\`");
132
+ expect(content).toContain("\\\\");
133
+ });
134
+
135
+ it("should return the config file path", async () => {
136
+ const config = {
137
+ apiKey: "hak_test123",
138
+ endpoint: "https://api.revenium.ai",
139
+ };
140
+
141
+ const path = await writeConfig(config);
142
+
143
+ expect(path).toBe("/home/user/.claude/revenium.env");
144
+ });
145
+
146
+ it("should include organizationId and productId in OTEL_RESOURCE_ATTRIBUTES", async () => {
147
+ const config = {
148
+ apiKey: "hak_test123",
149
+ endpoint: "https://api.revenium.ai",
150
+ subscriptionTier: "pro" as const,
151
+ organizationId: "org-123",
152
+ productId: "prod-456",
153
+ };
154
+
155
+ await writeConfig(config);
156
+
157
+ expect(writeFile).toHaveBeenCalledWith(
158
+ "/home/user/.claude/revenium.env",
159
+ expect.stringContaining("organization.name=org-123"),
160
+ { encoding: "utf-8" },
161
+ );
162
+ expect(writeFile).toHaveBeenCalledWith(
163
+ "/home/user/.claude/revenium.env",
164
+ expect.stringContaining("product.name=prod-456"),
165
+ { encoding: "utf-8" },
166
+ );
167
+ });
168
+ });
169
+
170
+ describe("getConfigFilePath", () => {
171
+ it("should return correct config file path", () => {
172
+ const path = getConfigFilePath();
173
+ expect(path).toBe("/home/user/.claude/revenium.env");
174
+ });
175
+ });
176
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist", "tests"]
20
+ }