@townco/debugger 0.1.74 → 0.1.76

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/package.json +3 -3
  2. package/src/server.ts +106 -50
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/debugger",
3
- "version": "0.1.74",
3
+ "version": "0.1.76",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bun": ">=1.3.0"
@@ -24,8 +24,8 @@
24
24
  "@radix-ui/react-select": "^2.2.6",
25
25
  "@radix-ui/react-slot": "^1.2.4",
26
26
  "@radix-ui/react-tabs": "^1.1.13",
27
- "@townco/otlp-server": "0.1.73",
28
- "@townco/tsconfig": "0.1.115",
27
+ "@townco/otlp-server": "0.1.75",
28
+ "@townco/tsconfig": "0.1.117",
29
29
  "@townco/ui": "^0.1.77",
30
30
  "bun-plugin-tailwind": "^0.1.2",
31
31
  "class-variance-authority": "^0.7.1",
package/src/server.ts CHANGED
@@ -18,6 +18,62 @@ import type {
18
18
  export const DEFAULT_DEBUGGER_PORT = 4000;
19
19
  export const DEFAULT_OTLP_PORT = 4318;
20
20
 
21
+ /**
22
+ * Middleware to check HTTP Basic Auth credentials
23
+ */
24
+ function requireBasicAuth<T extends Request = Request>(
25
+ handler: (req: T) => Response | Promise<Response>,
26
+ ): (req: T) => Response | Promise<Response> {
27
+ return (req: T) => {
28
+ // Skip auth check for localhost
29
+ const url = new URL(req.url);
30
+ const isLocalhost =
31
+ url.hostname === "localhost" ||
32
+ url.hostname === "127.0.0.1" ||
33
+ url.hostname === "::1";
34
+
35
+ if (isLocalhost) {
36
+ return handler(req);
37
+ }
38
+
39
+ const username = Bun.env.BASIC_AUTH_USER;
40
+ const password = Bun.env.BASIC_AUTH_PASS;
41
+
42
+ // If credentials are not configured, allow access
43
+ if (!username || !password) {
44
+ return handler(req);
45
+ }
46
+
47
+ // Check Authorization header
48
+ const authHeader = req.headers.get("Authorization");
49
+ if (!authHeader || !authHeader.startsWith("Basic ")) {
50
+ return new Response("Unauthorized", {
51
+ status: 401,
52
+ headers: {
53
+ "WWW-Authenticate": 'Basic realm="Debugger"',
54
+ },
55
+ });
56
+ }
57
+
58
+ // Decode and verify credentials
59
+ const base64Credentials = authHeader.slice(6); // Remove "Basic " prefix
60
+ const credentials = atob(base64Credentials);
61
+ const [providedUsername, providedPassword] = credentials.split(":");
62
+
63
+ if (providedUsername !== username || providedPassword !== password) {
64
+ return new Response("Unauthorized", {
65
+ status: 401,
66
+ headers: {
67
+ "WWW-Authenticate": 'Basic realm="Debugger"',
68
+ },
69
+ });
70
+ }
71
+
72
+ // Credentials valid, proceed with handler
73
+ return handler(req);
74
+ };
75
+ }
76
+
21
77
  /**
22
78
  * Creates the debugger API routes. Can be used standalone without starting a
23
79
  * server.
@@ -39,13 +95,13 @@ export function createDebuggerRoutes(options: {
39
95
 
40
96
  return defineRoutes({
41
97
  "/api/config": {
42
- GET() {
98
+ GET: requireBasicAuth(() => {
43
99
  return Response.json({ agentName });
44
- },
100
+ }),
45
101
  },
46
102
 
47
103
  "/api/reset-database": {
48
- POST() {
104
+ POST: requireBasicAuth(() => {
49
105
  try {
50
106
  resetDb();
51
107
  return new Response("Database reset successfully", { status: 200 });
@@ -56,11 +112,11 @@ export function createDebuggerRoutes(options: {
56
112
  { status: 500 },
57
113
  );
58
114
  }
59
- },
115
+ }),
60
116
  },
61
117
 
62
118
  "/api/sessions": {
63
- GET(req) {
119
+ GET: requireBasicAuth((req) => {
64
120
  const url = new URL(req.url);
65
121
  const limit = Number.parseInt(
66
122
  url.searchParams.get("limit") || "1000",
@@ -72,11 +128,11 @@ export function createDebuggerRoutes(options: {
72
128
  );
73
129
  const sessions = db.listSessions(limit, offset);
74
130
  return Response.json(sessions);
75
- },
131
+ }),
76
132
  },
77
133
 
78
134
  "/api/traces": {
79
- GET(req) {
135
+ GET: requireBasicAuth((req) => {
80
136
  const url = new URL(req.url);
81
137
  const limit = Number.parseInt(
82
138
  url.searchParams.get("limit") || "50",
@@ -89,11 +145,11 @@ export function createDebuggerRoutes(options: {
89
145
  const sessionId = url.searchParams.get("sessionId") || undefined;
90
146
  const traces = db.listTraces(limit, offset, sessionId);
91
147
  return Response.json(traces);
92
- },
148
+ }),
93
149
  },
94
150
 
95
151
  "/api/traces/:traceId": {
96
- GET(req) {
152
+ GET: requireBasicAuth((req) => {
97
153
  const traceId = req.params.traceId;
98
154
  const data = db.getTraceById(traceId);
99
155
  if (!data.trace) {
@@ -102,11 +158,11 @@ export function createDebuggerRoutes(options: {
102
158
  // Extract messages on the server side
103
159
  const messages = extractTurnMessages(data.spans, data.logs);
104
160
  return Response.json({ ...data, messages });
105
- },
161
+ }),
106
162
  },
107
163
 
108
164
  "/api/session-conversation": {
109
- GET(req) {
165
+ GET: requireBasicAuth((req) => {
110
166
  const url = new URL(req.url);
111
167
  const sessionId = url.searchParams.get("sessionId");
112
168
  if (!sessionId) {
@@ -133,13 +189,13 @@ export function createDebuggerRoutes(options: {
133
189
  });
134
190
 
135
191
  return Response.json(conversation);
136
- },
192
+ }),
137
193
  },
138
194
 
139
195
  // Town Hall API endpoints
140
196
 
141
197
  "/api/agent-config": {
142
- async GET() {
198
+ GET: requireBasicAuth(async () => {
143
199
  const config = await fetchAgentConfig();
144
200
  if (!config) {
145
201
  return Response.json(
@@ -148,11 +204,11 @@ export function createDebuggerRoutes(options: {
148
204
  );
149
205
  }
150
206
  return Response.json(config);
151
- },
207
+ }),
152
208
  },
153
209
 
154
210
  "/api/available-models": {
155
- GET() {
211
+ GET: requireBasicAuth(() => {
156
212
  // List of supported models for comparison
157
213
  const models = [
158
214
  // Anthropic models
@@ -165,11 +221,11 @@ export function createDebuggerRoutes(options: {
165
221
  "gemini-1.5-flash",
166
222
  ];
167
223
  return Response.json({ models });
168
- },
224
+ }),
169
225
  },
170
226
 
171
227
  "/api/session-first-message/:sessionId": {
172
- GET(req) {
228
+ GET: requireBasicAuth((req) => {
173
229
  const sessionId = req.params.sessionId;
174
230
 
175
231
  // Query logs directly by session attribute to avoid race conditions
@@ -184,15 +240,15 @@ export function createDebuggerRoutes(options: {
184
240
  }
185
241
 
186
242
  return Response.json({ message });
187
- },
243
+ }),
188
244
  },
189
245
 
190
246
  "/api/comparison-config": {
191
- GET() {
247
+ GET: requireBasicAuth(() => {
192
248
  const config = comparisonDb.getLatestConfig();
193
249
  return Response.json(config);
194
- },
195
- async POST(req) {
250
+ }),
251
+ POST: requireBasicAuth(async (req) => {
196
252
  try {
197
253
  const body = await req.json();
198
254
  const config: ComparisonConfig = {
@@ -214,11 +270,11 @@ export function createDebuggerRoutes(options: {
214
270
  { status: 400 },
215
271
  );
216
272
  }
217
- },
273
+ }),
218
274
  },
219
275
 
220
276
  "/api/comparison-config/:configId": {
221
- GET(req) {
277
+ GET: requireBasicAuth((req) => {
222
278
  const configId = req.params.configId;
223
279
  const config = comparisonDb.getConfig(configId);
224
280
  if (!config) {
@@ -228,18 +284,18 @@ export function createDebuggerRoutes(options: {
228
284
  );
229
285
  }
230
286
  return Response.json(config);
231
- },
287
+ }),
232
288
  },
233
289
 
234
290
  "/api/comparison-session-ids": {
235
- GET() {
291
+ GET: requireBasicAuth(() => {
236
292
  const sessionIds = comparisonDb.getComparisonSessionIds();
237
293
  return Response.json({ sessionIds });
238
- },
294
+ }),
239
295
  },
240
296
 
241
297
  "/api/comparison-runs": {
242
- GET(req) {
298
+ GET: requireBasicAuth((req) => {
243
299
  const url = new URL(req.url);
244
300
  const limit = Number.parseInt(
245
301
  url.searchParams.get("limit") || "50",
@@ -258,11 +314,11 @@ export function createDebuggerRoutes(options: {
258
314
 
259
315
  const runs = comparisonDb.listRuns(limit, offset);
260
316
  return Response.json(runs);
261
- },
317
+ }),
262
318
  },
263
319
 
264
320
  "/api/comparison-run/:runId": {
265
- GET(req) {
321
+ GET: requireBasicAuth((req) => {
266
322
  const runId = req.params.runId;
267
323
  const run = comparisonDb.getRun(runId);
268
324
  if (!run) {
@@ -318,11 +374,11 @@ export function createDebuggerRoutes(options: {
318
374
  controlMetrics,
319
375
  variantMetrics,
320
376
  });
321
- },
377
+ }),
322
378
  },
323
379
 
324
380
  "/api/run-comparison": {
325
- async POST(req) {
381
+ POST: requireBasicAuth(async (req) => {
326
382
  try {
327
383
  const body = await req.json();
328
384
  const { sessionId, configId } = body;
@@ -391,11 +447,11 @@ export function createDebuggerRoutes(options: {
391
447
  { status: 500 },
392
448
  );
393
449
  }
394
- },
450
+ }),
395
451
  },
396
452
 
397
453
  "/api/comparison-run/:runId/update": {
398
- async POST(req) {
454
+ POST: requireBasicAuth(async (req) => {
399
455
  try {
400
456
  const runId = req.params.runId;
401
457
  const body = await req.json();
@@ -425,11 +481,11 @@ export function createDebuggerRoutes(options: {
425
481
  { status: 500 },
426
482
  );
427
483
  }
428
- },
484
+ }),
429
485
  },
430
486
 
431
487
  "/api/session-metrics/:sessionId": {
432
- async GET(req) {
488
+ GET: requireBasicAuth(async (req) => {
433
489
  const sessionId = req.params.sessionId;
434
490
  const url = new URL(req.url);
435
491
  const model = url.searchParams.get("model") || "unknown";
@@ -452,11 +508,11 @@ export function createDebuggerRoutes(options: {
452
508
  // Extract metrics
453
509
  const metrics = extractSessionMetrics(traces, allSpans, model);
454
510
  return Response.json(metrics);
455
- },
511
+ }),
456
512
  },
457
513
 
458
514
  "/api/analyze-session/:sessionId": {
459
- async POST(req) {
515
+ POST: requireBasicAuth(async (req) => {
460
516
  const sessionId = req.params.sessionId;
461
517
 
462
518
  try {
@@ -542,11 +598,11 @@ export function createDebuggerRoutes(options: {
542
598
  { status: 500 },
543
599
  );
544
600
  }
545
- },
601
+ }),
546
602
  },
547
603
 
548
604
  "/api/analyze-all-sessions": {
549
- async POST(req) {
605
+ POST: requireBasicAuth(async (req) => {
550
606
  try {
551
607
  const body = await req.json();
552
608
  const { sessionIds } = body as { sessionIds: string[] };
@@ -705,11 +761,11 @@ export function createDebuggerRoutes(options: {
705
761
  { status: 500 },
706
762
  );
707
763
  }
708
- },
764
+ }),
709
765
  },
710
766
 
711
767
  "/api/session-analyses": {
712
- async GET(req) {
768
+ GET: requireBasicAuth(async (req) => {
713
769
  try {
714
770
  const url = new URL(req.url);
715
771
  const sessionId = url.searchParams.get("sessionId");
@@ -750,11 +806,11 @@ export function createDebuggerRoutes(options: {
750
806
  { status: 500 },
751
807
  );
752
808
  }
753
- },
809
+ }),
754
810
  },
755
811
 
756
812
  "/api/session-analyses/:sessionId/similar": {
757
- async GET(req) {
813
+ GET: requireBasicAuth(async (req) => {
758
814
  try {
759
815
  const sessionId = req.params.sessionId;
760
816
  const url = new URL(req.url);
@@ -792,12 +848,12 @@ export function createDebuggerRoutes(options: {
792
848
  { status: 500 },
793
849
  );
794
850
  }
795
- },
851
+ }),
796
852
  },
797
853
 
798
854
  // Comparison analysis endpoints
799
855
  "/api/analyze-comparison/:runId": {
800
- async POST(req) {
856
+ POST: requireBasicAuth(async (req) => {
801
857
  const runId = req.params.runId;
802
858
 
803
859
  try {
@@ -902,11 +958,11 @@ export function createDebuggerRoutes(options: {
902
958
  { status: 500 },
903
959
  );
904
960
  }
905
- },
961
+ }),
906
962
  },
907
963
 
908
964
  "/api/comparison-analysis/:runId": {
909
- async GET(req) {
965
+ GET: requireBasicAuth(async (req) => {
910
966
  try {
911
967
  const runId = req.params.runId;
912
968
  const analysis = comparisonDb.getComparisonAnalysis(runId);
@@ -931,11 +987,11 @@ export function createDebuggerRoutes(options: {
931
987
  { status: 500 },
932
988
  );
933
989
  }
934
- },
990
+ }),
935
991
  },
936
992
 
937
993
  "/api/comparison-analysis/:runId/exists": {
938
- async GET(req) {
994
+ GET: requireBasicAuth(async (req) => {
939
995
  try {
940
996
  const runId = req.params.runId;
941
997
  const exists = comparisonDb.hasComparisonAnalysis(runId);
@@ -943,7 +999,7 @@ export function createDebuggerRoutes(options: {
943
999
  } catch (_error) {
944
1000
  return Response.json({ exists: false });
945
1001
  }
946
- },
1002
+ }),
947
1003
  },
948
1004
  });
949
1005
  }