@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.
- package/dist/cli.js +398 -24
- 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
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var CONFIG_DIR = (0,
|
|
11
|
-
var CONFIG_FILE = (0,
|
|
12
|
-
var
|
|
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,
|
|
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,
|
|
23
|
-
(0,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
98
|
-
const cursorPath = (0,
|
|
99
|
-
const claudePath = (0,
|
|
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,
|
|
482
|
+
if ((0, import_fs2.existsSync)(path)) {
|
|
107
483
|
try {
|
|
108
|
-
const existing = JSON.parse((0,
|
|
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,
|
|
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(`${
|
|
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
|
|
165
|
-
|
|
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.
|
|
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'"
|