@costlens/mcp-server 0.4.1 → 0.4.2

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 (2) hide show
  1. package/dist/cli.js +398 -24
  2. package/package.json +2 -5
package/dist/cli.js CHANGED
@@ -1,31 +1,407 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ function resolveApiKey() {
23
+ const envKey = process.env.COSTLENS_API_KEY || process.env.COSTLENS_MCP_KEY || process.argv.find((a) => a.startsWith("--api-key="))?.split("=")[1];
24
+ if (envKey) return envKey;
25
+ try {
26
+ const config = JSON.parse((0, import_fs.readFileSync)((0, import_path.join)((0, import_os.homedir)(), ".costlens", "config.json"), "utf-8"));
27
+ return config.apiKey;
28
+ } catch {
29
+ return void 0;
30
+ }
31
+ }
32
+ async function loadPricing() {
33
+ try {
34
+ const headers = {};
35
+ if (API_KEY) headers["Authorization"] = `Bearer ${API_KEY}`;
36
+ const res = await fetch(`${API_BASE}/v1/pricing`, { headers, signal: AbortSignal.timeout(5e3) });
37
+ if (res.ok) {
38
+ const data = await res.json();
39
+ for (const m of data.models) {
40
+ import_classifier.PRICING[m.model] = { input: m.input, output: m.output };
41
+ }
42
+ }
43
+ } catch {
44
+ }
45
+ }
46
+ function startHeartbeat(sessionId) {
47
+ stopHeartbeat();
48
+ activeSessionId = sessionId;
49
+ heartbeatInterval = setInterval(async () => {
50
+ if (!activeSessionId || !API_KEY) return;
51
+ try {
52
+ const res = await fetch(`${API_BASE}/v1/productivity/heartbeat`, {
53
+ method: "POST",
54
+ headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" },
55
+ body: JSON.stringify({ session_id: activeSessionId }),
56
+ signal: AbortSignal.timeout(5e3)
57
+ });
58
+ if (res.status === 410) {
59
+ activeSessionId = null;
60
+ stopHeartbeat();
61
+ }
62
+ } catch {
63
+ }
64
+ }, 6e4);
65
+ }
66
+ function stopHeartbeat() {
67
+ if (heartbeatInterval) {
68
+ clearInterval(heartbeatInterval);
69
+ heartbeatInterval = null;
70
+ }
71
+ }
72
+ async function syncEvent(result, params) {
73
+ try {
74
+ await fetch(`${API_BASE}/v1/classifier/events`, {
75
+ method: "POST",
76
+ headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" },
77
+ body: JSON.stringify({
78
+ complexity: result.level,
79
+ confidence: result.confidence,
80
+ suggestedModel: result.suggestedModel,
81
+ currentModel: params.currentModel,
82
+ provider: params.provider,
83
+ staticScore: result.signals.staticScore
84
+ }),
85
+ signal: AbortSignal.timeout(5e3)
86
+ });
87
+ } catch {
88
+ }
89
+ }
90
+ async function checkFeature(feature) {
91
+ if (!API_KEY) return false;
92
+ try {
93
+ const res = await fetch(`${API_BASE}/v1/plan`, {
94
+ headers: { "Authorization": `Bearer ${API_KEY}` },
95
+ signal: AbortSignal.timeout(5e3)
96
+ });
97
+ if (!res.ok) return false;
98
+ const plan = await res.json();
99
+ return plan.features?.includes(feature) ?? false;
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+ async function ensureProductivity() {
105
+ if (!API_KEY) return "Not connected to CostLens. Run: npx @costlens/mcp-server setup";
106
+ if (productivityEnabled === null) {
107
+ productivityEnabled = await checkFeature("productivity_basic");
108
+ }
109
+ if (!productivityEnabled) return "Productivity tracking requires Business plan or Productivity add-on.";
110
+ return null;
111
+ }
112
+ async function main() {
113
+ await loadPricing();
114
+ const transport = new import_stdio.StdioServerTransport();
115
+ await server.connect(transport);
116
+ const cleanup = async () => {
117
+ if (activeSessionId && API_KEY) {
118
+ try {
119
+ await fetch(`${API_BASE}/v1/productivity/sessions/${activeSessionId}`, {
120
+ method: "PATCH",
121
+ headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" },
122
+ body: JSON.stringify({ outcome: "completed" }),
123
+ signal: AbortSignal.timeout(3e3)
124
+ });
125
+ } catch {
126
+ }
127
+ }
128
+ stopHeartbeat();
129
+ process.exit(0);
130
+ };
131
+ process.on("SIGTERM", cleanup);
132
+ process.on("SIGINT", cleanup);
133
+ }
134
+ var import_mcp, import_stdio, import_zod, import_classifier, import_fs, import_path, import_os, API_KEY, API_BASE, server, activeSessionId, heartbeatInterval, productivityEnabled;
135
+ var init_index = __esm({
136
+ "src/index.ts"() {
137
+ "use strict";
138
+ import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
139
+ import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
140
+ import_zod = require("zod");
141
+ import_classifier = require("@lens360/classifier");
142
+ import_fs = require("fs");
143
+ import_path = require("path");
144
+ import_os = require("os");
145
+ API_KEY = resolveApiKey();
146
+ API_BASE = process.env.COSTLENS_API_URL || "https://api.costlens.dev";
147
+ server = new import_mcp.McpServer({
148
+ name: "@lens360/mcp-server",
149
+ version: "0.3.1"
150
+ });
151
+ activeSessionId = null;
152
+ heartbeatInterval = null;
153
+ server.tool(
154
+ "get_spend_summary",
155
+ "Get current spend summary \u2014 daily, weekly, monthly, and session totals",
156
+ {},
157
+ async () => {
158
+ if (!API_KEY) {
159
+ return { content: [{ type: "text", text: "Not connected to CostLens. Run: npx @costlens/mcp-server setup" }] };
160
+ }
161
+ try {
162
+ const res = await fetch(`${API_BASE}/v1/spend`, {
163
+ headers: { "Authorization": `Bearer ${API_KEY}` },
164
+ signal: AbortSignal.timeout(5e3)
165
+ });
166
+ if (!res.ok) {
167
+ return { content: [{ type: "text", text: `Error: ${res.status} \u2014 ${await res.text()}` }] };
168
+ }
169
+ const data = await res.json();
170
+ const lines = [
171
+ `\u{1F4B0} Spend Summary (${data.key})`,
172
+ ``,
173
+ `Today: $${data.daily.cost.toFixed(4)} (${data.daily.requests} requests)`,
174
+ `Week: $${data.weekly.cost.toFixed(4)} (${data.weekly.requests} requests)`,
175
+ `Month: $${data.monthly.cost.toFixed(4)} (${data.monthly.requests} requests)`
176
+ ];
177
+ if (data.session) {
178
+ lines.push(`Session: $${data.session.cost.toFixed(4)} (${data.session.requests} requests) [${data.session.correlationId}]`);
179
+ }
180
+ if (data.dailyBudget) {
181
+ lines.push(``, `Budget: $${data.budgetRemaining.toFixed(2)} remaining of $${data.dailyBudget.toFixed(2)}/day`);
182
+ }
183
+ return { content: [{ type: "text", text: lines.join("\n") }] };
184
+ } catch (err) {
185
+ return { content: [{ type: "text", text: `Error fetching spend: ${err.message}` }] };
186
+ }
187
+ }
188
+ );
189
+ server.tool(
190
+ "costlens_suggest_model",
191
+ "Analyze a prompt and suggest the cheapest adequate model",
192
+ {
193
+ prompt: import_zod.z.string(),
194
+ currentModel: import_zod.z.string(),
195
+ provider: import_zod.z.string(),
196
+ messageCount: import_zod.z.number().optional(),
197
+ routingMode: import_zod.z.string().optional(),
198
+ isFirstMessage: import_zod.z.boolean().optional()
199
+ },
200
+ async (params) => {
201
+ const result = (0, import_classifier.classify)({
202
+ prompt: params.prompt,
203
+ currentModel: params.currentModel,
204
+ provider: params.provider,
205
+ messageCount: params.messageCount,
206
+ routingMode: params.routingMode || void 0,
207
+ isFirstMessage: params.isFirstMessage
208
+ });
209
+ if (API_KEY && result.suggestedModel) {
210
+ syncEvent(result, params).catch(() => {
211
+ });
212
+ }
213
+ return {
214
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
215
+ };
216
+ }
217
+ );
218
+ productivityEnabled = null;
219
+ server.tool(
220
+ "start_session",
221
+ "Start a productivity tracking session",
222
+ {
223
+ task_description: import_zod.z.string().optional(),
224
+ branch: import_zod.z.string().optional(),
225
+ repo: import_zod.z.string().optional()
226
+ },
227
+ async (params) => {
228
+ const err = await ensureProductivity();
229
+ if (err) return { content: [{ type: "text", text: err }] };
230
+ try {
231
+ const res = await fetch(`${API_BASE}/v1/productivity/sessions`, {
232
+ method: "POST",
233
+ headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" },
234
+ body: JSON.stringify(params),
235
+ signal: AbortSignal.timeout(5e3)
236
+ });
237
+ if (!res.ok) return { content: [{ type: "text", text: `Error: ${res.status} \u2014 ${await res.text()}` }] };
238
+ const data = await res.json();
239
+ if (data.data?.id) startHeartbeat(data.data.id);
240
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
241
+ } catch (e) {
242
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
243
+ }
244
+ }
245
+ );
246
+ server.tool(
247
+ "end_session",
248
+ "End a productivity tracking session",
249
+ {
250
+ session_id: import_zod.z.string(),
251
+ outcome: import_zod.z.enum(["completed", "abandoned", "paused"]).optional(),
252
+ artifacts: import_zod.z.array(import_zod.z.string()).optional()
253
+ },
254
+ async (params) => {
255
+ const err = await ensureProductivity();
256
+ if (err) return { content: [{ type: "text", text: err }] };
257
+ try {
258
+ const res = await fetch(`${API_BASE}/v1/productivity/sessions/${params.session_id}`, {
259
+ method: "PATCH",
260
+ headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" },
261
+ body: JSON.stringify({ outcome: params.outcome, artifacts: params.artifacts }),
262
+ signal: AbortSignal.timeout(5e3)
263
+ });
264
+ if (!res.ok) return { content: [{ type: "text", text: `Error: ${res.status} \u2014 ${await res.text()}` }] };
265
+ const data = await res.json();
266
+ stopHeartbeat();
267
+ activeSessionId = null;
268
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
269
+ } catch (e) {
270
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
271
+ }
272
+ }
273
+ );
274
+ server.tool(
275
+ "log_event",
276
+ "Log a productivity event within a session",
277
+ {
278
+ session_id: import_zod.z.string(),
279
+ event_type: import_zod.z.enum(["prompt", "commit", "pr_created", "test_run", "deploy", "file_edit"]),
280
+ metadata: import_zod.z.record(import_zod.z.any()).optional()
281
+ },
282
+ async (params) => {
283
+ if (!API_KEY) return { content: [{ type: "text", text: "Not connected to CostLens. Run: npx @costlens/mcp-server setup" }] };
284
+ if (productivityEnabled === null) {
285
+ productivityEnabled = await checkFeature("productivity_basic");
286
+ }
287
+ const hasFull = await checkFeature("productivity_full");
288
+ if (!hasFull) return { content: [{ type: "text", text: "Event logging requires Productivity Insights add-on." }] };
289
+ try {
290
+ const res = await fetch(`${API_BASE}/v1/productivity/events`, {
291
+ method: "POST",
292
+ headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" },
293
+ body: JSON.stringify(params),
294
+ signal: AbortSignal.timeout(5e3)
295
+ });
296
+ if (!res.ok) return { content: [{ type: "text", text: `Error: ${res.status} \u2014 ${await res.text()}` }] };
297
+ return { content: [{ type: "text", text: "Event logged." }] };
298
+ } catch (e) {
299
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
300
+ }
301
+ }
302
+ );
303
+ server.tool(
304
+ "get_productivity_summary",
305
+ "Get productivity summary for a time period",
306
+ {
307
+ period: import_zod.z.enum(["today", "week", "month"]).optional()
308
+ },
309
+ async (params) => {
310
+ const err = await ensureProductivity();
311
+ if (err) return { content: [{ type: "text", text: err }] };
312
+ try {
313
+ const period = params.period || "week";
314
+ const res = await fetch(`${API_BASE}/v1/productivity/summary?period=${period}`, {
315
+ headers: { "Authorization": `Bearer ${API_KEY}` },
316
+ signal: AbortSignal.timeout(5e3)
317
+ });
318
+ if (!res.ok) return { content: [{ type: "text", text: `Error: ${res.status} \u2014 ${await res.text()}` }] };
319
+ const data = await res.json();
320
+ const lines = [
321
+ `\u{1F4CA} Productivity Summary (${period})`,
322
+ ``,
323
+ `Sessions: ${data.sessions}`,
324
+ `Time: ${data.total_time_minutes} min`,
325
+ `Tokens: ${data.tokens_used?.toLocaleString() || 0}`,
326
+ `Cost: $${data.cost?.toFixed(4) || "0.00"}`,
327
+ `Commits: ${data.commits || 0}`,
328
+ `PRs merged: ${data.prs_merged || 0}`
329
+ ];
330
+ if (data.cost_per_commit) lines.push(`Cost/commit: $${data.cost_per_commit.toFixed(4)}`);
331
+ if (data.cost_per_pr) lines.push(`Cost/PR: $${data.cost_per_pr.toFixed(4)}`);
332
+ if (data.estimated_time_saved_hours) lines.push(`Time saved: ~${data.estimated_time_saved_hours.toFixed(1)}h`);
333
+ return { content: [{ type: "text", text: lines.join("\n") }] };
334
+ } catch (e) {
335
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
336
+ }
337
+ }
338
+ );
339
+ server.tool(
340
+ "get_github_metrics",
341
+ "Get GitHub PR metrics \u2014 PRs merged, review rounds, first-pass rate, cost per PR",
342
+ {
343
+ period: import_zod.z.enum(["today", "week", "month"]).optional()
344
+ },
345
+ async (params) => {
346
+ if (!API_KEY) {
347
+ return { content: [{ type: "text", text: "Not connected to CostLens. Run: npx @costlens/mcp-server setup" }] };
348
+ }
349
+ try {
350
+ const period = params.period || "week";
351
+ const res = await fetch(`${API_BASE}/v1/productivity/github?period=${period}`, {
352
+ headers: { "Authorization": `Bearer ${API_KEY}` },
353
+ signal: AbortSignal.timeout(5e3)
354
+ });
355
+ if (res.status === 404) {
356
+ return { content: [{ type: "text", text: "GitHub not connected. Connect in Settings \u2192 GitHub at costlens.dev" }] };
357
+ }
358
+ if (!res.ok) {
359
+ return { content: [{ type: "text", text: `Error: ${res.status} \u2014 ${await res.text()}` }] };
360
+ }
361
+ const { data } = await res.json();
362
+ const lines = [
363
+ `GitHub Metrics (${period}):`,
364
+ `PRs merged: ${data.prsMerged}`,
365
+ `Avg review rounds: ${data.avgReviewRounds}`,
366
+ `First-pass rate: ${data.firstPassRate}%`,
367
+ `Avg time to merge: ${data.avgTimeToMergeHours}h`,
368
+ `Cost per PR: $${data.costPerPr}`
369
+ ];
370
+ return { content: [{ type: "text", text: lines.join("\n") }] };
371
+ } catch (e) {
372
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
373
+ }
374
+ }
375
+ );
376
+ main().catch(console.error);
377
+ }
378
+ });
3
379
 
4
380
  // src/cli.ts
5
381
  var import_http = require("http");
6
382
  var import_child_process = require("child_process");
7
- var import_fs = require("fs");
8
- var import_os = require("os");
9
- var import_path = require("path");
10
- var CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".costlens");
11
- var CONFIG_FILE = (0, import_path.join)(CONFIG_DIR, "config.json");
12
- var API_BASE = process.env.COSTLENS_API_URL || "https://api.costlens.dev";
383
+ var import_fs2 = require("fs");
384
+ var import_os2 = require("os");
385
+ var import_path2 = require("path");
386
+ var CONFIG_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".costlens");
387
+ var CONFIG_FILE = (0, import_path2.join)(CONFIG_DIR, "config.json");
388
+ var API_BASE2 = process.env.COSTLENS_API_URL || "https://api.costlens.dev";
13
389
  var APP_URL = process.env.COSTLENS_APP_URL || "https://costlens.dev";
14
390
  function readConfig() {
15
391
  try {
16
- return JSON.parse((0, import_fs.readFileSync)(CONFIG_FILE, "utf-8"));
392
+ return JSON.parse((0, import_fs2.readFileSync)(CONFIG_FILE, "utf-8"));
17
393
  } catch {
18
394
  return {};
19
395
  }
20
396
  }
21
397
  function writeConfig(config) {
22
- if (!(0, import_fs.existsSync)(CONFIG_DIR)) (0, import_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
23
- (0, import_fs.writeFileSync)(CONFIG_FILE, JSON.stringify(config, null, 2));
398
+ if (!(0, import_fs2.existsSync)(CONFIG_DIR)) (0, import_fs2.mkdirSync)(CONFIG_DIR, { recursive: true });
399
+ (0, import_fs2.writeFileSync)(CONFIG_FILE, JSON.stringify(config, null, 2));
24
400
  }
25
401
  async function login() {
26
402
  console.log("\u{1F511} CostLens \u2014 Authenticating...\n");
27
403
  const port = 9876 + Math.floor(Math.random() * 100);
28
- const server = (0, import_http.createServer)((req, res) => {
404
+ const server2 = (0, import_http.createServer)((req, res) => {
29
405
  const url = new URL(req.url, `http://localhost:${port}`);
30
406
  const key = url.searchParams.get("key");
31
407
  if (key) {
@@ -36,7 +412,7 @@ async function login() {
36
412
  console.log(` Key saved to ${CONFIG_FILE}
37
413
  `);
38
414
  setTimeout(() => {
39
- server.close();
415
+ server2.close();
40
416
  if (process.argv[2] === "setup") {
41
417
  init();
42
418
  } else {
@@ -48,7 +424,7 @@ async function login() {
48
424
  res.end("Missing key");
49
425
  }
50
426
  });
51
- server.listen(port, () => {
427
+ server2.listen(port, () => {
52
428
  const authUrl = `${APP_URL}/cli-auth?port=${port}`;
53
429
  console.log(` Opening browser: ${authUrl}
54
430
  `);
@@ -65,7 +441,7 @@ async function login() {
65
441
  });
66
442
  setTimeout(() => {
67
443
  console.log("\n\u2717 Timed out. Try again.");
68
- server.close();
444
+ server2.close();
69
445
  process.exit(1);
70
446
  }, 12e4);
71
447
  }
@@ -94,21 +470,21 @@ function init() {
94
470
  console.log(" Claude Code: ~/.claude/mcp_servers.json");
95
471
  console.log(" VS Code: .vscode/mcp.json");
96
472
  console.log("");
97
- const kiroPath = (0, import_path.join)((0, import_os.homedir)(), ".kiro", "settings", "mcp.json");
98
- const cursorPath = (0, import_path.join)((0, import_os.homedir)(), ".cursor", "mcp.json");
99
- const claudePath = (0, import_path.join)((0, import_os.homedir)(), ".claude", "mcp_servers.json");
473
+ const kiroPath = (0, import_path2.join)((0, import_os2.homedir)(), ".kiro", "settings", "mcp.json");
474
+ const cursorPath = (0, import_path2.join)((0, import_os2.homedir)(), ".cursor", "mcp.json");
475
+ const claudePath = (0, import_path2.join)((0, import_os2.homedir)(), ".claude", "mcp_servers.json");
100
476
  const paths = [
101
477
  { name: "Kiro", path: kiroPath },
102
478
  { name: "Cursor", path: cursorPath },
103
479
  { name: "Claude Code", path: claudePath }
104
480
  ];
105
481
  for (const { name, path } of paths) {
106
- if ((0, import_fs.existsSync)(path)) {
482
+ if ((0, import_fs2.existsSync)(path)) {
107
483
  try {
108
- const existing = JSON.parse((0, import_fs.readFileSync)(path, "utf-8"));
484
+ const existing = JSON.parse((0, import_fs2.readFileSync)(path, "utf-8"));
109
485
  if (!existing.mcpServers?.costlens) {
110
486
  existing.mcpServers = { ...existing.mcpServers, ...mcpConfig.mcpServers };
111
- (0, import_fs.writeFileSync)(path, JSON.stringify(existing, null, 2));
487
+ (0, import_fs2.writeFileSync)(path, JSON.stringify(existing, null, 2));
112
488
  console.log(` \u2713 Auto-configured ${name} (${path})`);
113
489
  } else {
114
490
  console.log(` \xB7 ${name} already configured`);
@@ -137,7 +513,7 @@ async function status() {
137
513
  }
138
514
  console.log("\u{1F4CA} CostLens Status\n");
139
515
  try {
140
- const res = await fetch(`${API_BASE}/v1/spend`, {
516
+ const res = await fetch(`${API_BASE2}/v1/spend`, {
141
517
  headers: { Authorization: `Bearer ${config.apiKey}` },
142
518
  signal: AbortSignal.timeout(5e3)
143
519
  });
@@ -161,10 +537,8 @@ if (command === "login") login();
161
537
  else if (command === "init") init();
162
538
  else if (command === "status") status();
163
539
  else if (command === "setup") setup();
164
- else if (command) {
165
- console.log(`Unknown command: ${command}`);
166
- console.log("Available: setup, login, init, status");
167
- process.exit(1);
540
+ else {
541
+ init_index();
168
542
  }
169
543
  async function setup() {
170
544
  console.log("\u{1F527} CostLens \u2014 One-step setup\n");
package/package.json CHANGED
@@ -1,13 +1,10 @@
1
1
  {
2
2
  "name": "@costlens/mcp-server",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "MCP server for AI cost optimization with prompt complexity classification",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
- "bin": {
8
- "costlens-mcp": "./dist/index.js",
9
- "costlens": "./dist/cli.js"
10
- },
7
+ "bin": "./dist/cli.js",
11
8
  "scripts": {
12
9
  "build": "tsup src/index.ts src/cli.ts --format cjs --clean",
13
10
  "test": "echo 'no tests yet'"