@sentry/junior-dashboard 0.58.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 (89) hide show
  1. package/LICENSE +201 -0
  2. package/dist/app.d.ts +22 -0
  3. package/dist/app.js +492 -0
  4. package/dist/auth.d.ts +30 -0
  5. package/dist/client/App.d.ts +2 -0
  6. package/dist/client/api.d.ts +10 -0
  7. package/dist/client/code.d.ts +15 -0
  8. package/dist/client/components/CommandRail.d.ts +6 -0
  9. package/dist/client/components/ConversationList.d.ts +7 -0
  10. package/dist/client/components/ConversationRowStats.d.ts +6 -0
  11. package/dist/client/components/ConversationStack.d.ts +5 -0
  12. package/dist/client/components/ConversationSummary.d.ts +5 -0
  13. package/dist/client/components/EmptyTelemetry.d.ts +5 -0
  14. package/dist/client/components/FilterTabs.d.ts +6 -0
  15. package/dist/client/components/JuniorLogo.d.ts +2 -0
  16. package/dist/client/components/LoadingView.d.ts +4 -0
  17. package/dist/client/components/Section.d.ts +6 -0
  18. package/dist/client/components/SectionHeader.d.ts +6 -0
  19. package/dist/client/components/SectionTitle.d.ts +5 -0
  20. package/dist/client/components/StatusBadge.d.ts +6 -0
  21. package/dist/client/components/ToolFrame.d.ts +10 -0
  22. package/dist/client/components/Transcript.d.ts +5 -0
  23. package/dist/client/components/TranscriptHeader.d.ts +7 -0
  24. package/dist/client/components/TranscriptLoading.d.ts +2 -0
  25. package/dist/client/components/TranscriptText.d.ts +6 -0
  26. package/dist/client/components/TranscriptToolView.d.ts +9 -0
  27. package/dist/client/components/TranscriptTurn.d.ts +8 -0
  28. package/dist/client/components/TurnDurationChart.d.ts +6 -0
  29. package/dist/client/components/statusStyles.d.ts +3 -0
  30. package/dist/client/components/transcriptPreview.d.ts +4 -0
  31. package/dist/client/components/transcriptRenderModel.d.ts +30 -0
  32. package/dist/client/components/transcriptStyles.d.ts +4 -0
  33. package/dist/client/format.d.ts +64 -0
  34. package/dist/client/pages/CommandCenter.d.ts +6 -0
  35. package/dist/client/pages/ConversationPage.d.ts +5 -0
  36. package/dist/client/pages/ConversationsPage.d.ts +5 -0
  37. package/dist/client/styles.d.ts +2 -0
  38. package/dist/client/types.d.ts +153 -0
  39. package/dist/client.d.ts +7 -0
  40. package/dist/client.js +56012 -0
  41. package/dist/config.d.ts +4 -0
  42. package/dist/handler.d.ts +2 -0
  43. package/dist/handler.js +563 -0
  44. package/dist/nitro.d.ts +16 -0
  45. package/dist/nitro.js +80 -0
  46. package/dist/tailwind.css +2 -0
  47. package/package.json +60 -0
  48. package/src/app.ts +421 -0
  49. package/src/auth.ts +202 -0
  50. package/src/client/App.tsx +175 -0
  51. package/src/client/api.ts +99 -0
  52. package/src/client/code.tsx +139 -0
  53. package/src/client/components/CommandRail.tsx +74 -0
  54. package/src/client/components/ConversationList.tsx +94 -0
  55. package/src/client/components/ConversationRowStats.tsx +21 -0
  56. package/src/client/components/ConversationStack.tsx +67 -0
  57. package/src/client/components/ConversationSummary.tsx +40 -0
  58. package/src/client/components/EmptyTelemetry.tsx +11 -0
  59. package/src/client/components/FilterTabs.tsx +35 -0
  60. package/src/client/components/JuniorLogo.tsx +8 -0
  61. package/src/client/components/LoadingView.tsx +16 -0
  62. package/src/client/components/Section.tsx +17 -0
  63. package/src/client/components/SectionHeader.tsx +16 -0
  64. package/src/client/components/SectionTitle.tsx +10 -0
  65. package/src/client/components/StatusBadge.tsx +40 -0
  66. package/src/client/components/ToolFrame.tsx +52 -0
  67. package/src/client/components/Transcript.tsx +39 -0
  68. package/src/client/components/TranscriptHeader.tsx +52 -0
  69. package/src/client/components/TranscriptLoading.tsx +10 -0
  70. package/src/client/components/TranscriptText.tsx +45 -0
  71. package/src/client/components/TranscriptToolView.tsx +207 -0
  72. package/src/client/components/TranscriptTurn.tsx +487 -0
  73. package/src/client/components/TurnDurationChart.tsx +349 -0
  74. package/src/client/components/statusStyles.ts +9 -0
  75. package/src/client/components/transcriptPreview.ts +20 -0
  76. package/src/client/components/transcriptRenderModel.ts +193 -0
  77. package/src/client/components/transcriptStyles.ts +11 -0
  78. package/src/client/format.ts +628 -0
  79. package/src/client/pages/CommandCenter.tsx +37 -0
  80. package/src/client/pages/ConversationPage.tsx +145 -0
  81. package/src/client/pages/ConversationsPage.tsx +59 -0
  82. package/src/client/styles.ts +6 -0
  83. package/src/client/types.ts +153 -0
  84. package/src/client.tsx +79 -0
  85. package/src/config.ts +72 -0
  86. package/src/handler.ts +26 -0
  87. package/src/nitro.ts +110 -0
  88. package/src/tailwind.css +13 -0
  89. package/src/virtual-modules.d.ts +5 -0
package/dist/app.js ADDED
@@ -0,0 +1,492 @@
1
+ // src/app.ts
2
+ import { Hono } from "hono";
3
+ import { existsSync, readFileSync } from "fs";
4
+ import path from "path";
5
+ import { createJuniorReporting } from "@sentry/junior/reporting";
6
+ import { initSentry } from "@sentry/junior/instrumentation";
7
+
8
+ // src/auth.ts
9
+ import { betterAuth } from "better-auth";
10
+ var DEFAULT_SESSION_MAX_AGE_SECONDS = 60 * 60 * 8;
11
+ function sanitizeDashboardSession(session) {
12
+ const { email, emailVerified, hostedDomain, name } = session.user;
13
+ return {
14
+ user: {
15
+ email,
16
+ emailVerified,
17
+ hostedDomain,
18
+ name
19
+ }
20
+ };
21
+ }
22
+ function required(value, name) {
23
+ if (!value?.trim()) {
24
+ throw new Error(`${name} is required for Junior dashboard auth`);
25
+ }
26
+ return value.trim();
27
+ }
28
+ function firstHostedDomain(domains) {
29
+ return domains.length === 1 ? domains[0] : void 0;
30
+ }
31
+ function withHttps(host) {
32
+ return /^https?:\/\//.test(host) ? host : `https://${host}`;
33
+ }
34
+ function stripTrailingSlashes(value) {
35
+ let end = value.length;
36
+ while (end > 1 && value.charCodeAt(end - 1) === 47) {
37
+ end -= 1;
38
+ }
39
+ return end === value.length ? value : value.slice(0, end);
40
+ }
41
+ function resolveBaseURL(config) {
42
+ const explicit = config.baseURL ?? process.env.BETTER_AUTH_URL ?? process.env.JUNIOR_BASE_URL;
43
+ if (explicit?.trim()) {
44
+ return stripTrailingSlashes(withHttps(explicit.trim()));
45
+ }
46
+ const vercelProd = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
47
+ if (vercelProd) {
48
+ return stripTrailingSlashes(withHttps(vercelProd));
49
+ }
50
+ const vercelUrl = process.env.VERCEL_URL?.trim();
51
+ if (vercelUrl) {
52
+ return stripTrailingSlashes(withHttps(vercelUrl));
53
+ }
54
+ return "http://localhost:3000";
55
+ }
56
+ function createDashboardAuth(config) {
57
+ const secret = required(
58
+ config.secret ?? process.env.BETTER_AUTH_SECRET ?? process.env.JUNIOR_SECRET,
59
+ "JUNIOR_SECRET or BETTER_AUTH_SECRET"
60
+ );
61
+ const baseURL = resolveBaseURL(config);
62
+ const googleClientId = required(
63
+ config.googleClientId ?? process.env.GOOGLE_CLIENT_ID,
64
+ "GOOGLE_CLIENT_ID"
65
+ );
66
+ const googleClientSecret = required(
67
+ config.googleClientSecret ?? process.env.GOOGLE_CLIENT_SECRET,
68
+ "GOOGLE_CLIENT_SECRET"
69
+ );
70
+ const auth = betterAuth({
71
+ appName: "Junior Dashboard",
72
+ baseURL,
73
+ basePath: config.authPath,
74
+ secret,
75
+ trustedOrigins: config.trustedOrigins,
76
+ socialProviders: {
77
+ google: {
78
+ clientId: googleClientId,
79
+ clientSecret: googleClientSecret,
80
+ hd: config.googleHostedDomain,
81
+ prompt: "select_account",
82
+ mapProfileToUser(profile) {
83
+ return {
84
+ email: profile.email,
85
+ emailVerified: profile.email_verified,
86
+ hostedDomain: profile.hd,
87
+ image: profile.picture,
88
+ name: profile.name
89
+ };
90
+ }
91
+ }
92
+ },
93
+ user: {
94
+ additionalFields: {
95
+ hostedDomain: {
96
+ type: "string",
97
+ required: false,
98
+ input: false,
99
+ returned: true
100
+ }
101
+ }
102
+ },
103
+ account: {
104
+ storeStateStrategy: "cookie",
105
+ storeAccountCookie: false,
106
+ updateAccountOnSignIn: false
107
+ },
108
+ session: {
109
+ expiresIn: config.sessionMaxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
110
+ disableSessionRefresh: true,
111
+ cookieCache: {
112
+ enabled: true,
113
+ strategy: "jwe",
114
+ maxAge: config.sessionMaxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
115
+ refreshCache: false
116
+ }
117
+ }
118
+ });
119
+ return {
120
+ handler(request) {
121
+ return auth.handler(request);
122
+ },
123
+ async getSession(request) {
124
+ const session = await auth.api.getSession({ headers: request.headers });
125
+ if (!session) {
126
+ return null;
127
+ }
128
+ return sanitizeDashboardSession(session);
129
+ },
130
+ async signInWithGoogle(request, callbackURL) {
131
+ const result = await auth.api.signInSocial({
132
+ body: {
133
+ provider: "google",
134
+ callbackURL
135
+ },
136
+ headers: request.headers,
137
+ returnHeaders: true
138
+ });
139
+ if (!("url" in result.response) || !result.response.url) {
140
+ throw new Error("Google sign-in did not return a redirect URL");
141
+ }
142
+ result.headers.set("location", result.response.url);
143
+ return new Response(null, {
144
+ status: 302,
145
+ headers: result.headers
146
+ });
147
+ }
148
+ };
149
+ }
150
+ function resolveGoogleHostedDomainHint(domains) {
151
+ return firstHostedDomain(domains.map((domain) => domain.toLowerCase()));
152
+ }
153
+
154
+ // src/app.ts
155
+ var DEFAULT_BASE_PATH = "/";
156
+ var DEFAULT_AUTH_PATH = "/api/auth";
157
+ var DASHBOARD_CLIENT_VERSION = Date.now().toString(36);
158
+ function hasSentryConversationLinks() {
159
+ return Boolean(
160
+ process.env.SENTRY_DSN?.trim() && process.env.SENTRY_ORG_SLUG?.trim()
161
+ );
162
+ }
163
+ function normalizePath(path2, fallback) {
164
+ const value = path2.trim() || fallback;
165
+ const withSlash = value.startsWith("/") ? value : `/${value}`;
166
+ return stripTrailingSlashes2(withSlash);
167
+ }
168
+ function stripTrailingSlashes2(value) {
169
+ let end = value.length;
170
+ while (end > 1 && value.charCodeAt(end - 1) === 47) {
171
+ end -= 1;
172
+ }
173
+ return end === value.length ? value : value.slice(0, end);
174
+ }
175
+ function normalizeValues(values) {
176
+ return [
177
+ ...new Set(
178
+ (values ?? []).map((value) => value.trim().toLowerCase()).filter(Boolean)
179
+ )
180
+ ];
181
+ }
182
+ function isJsonRoute(pathname) {
183
+ return pathname.startsWith("/api/");
184
+ }
185
+ function dashboardLoginUrl(request) {
186
+ const url = new URL(request.url);
187
+ url.pathname = "/api/dashboard/login";
188
+ url.search = "";
189
+ return url.toString();
190
+ }
191
+ function callbackUrl(request, basePath) {
192
+ const url = new URL(request.url);
193
+ url.pathname = basePath;
194
+ url.search = "";
195
+ return url.toString();
196
+ }
197
+ function isAuthorized(session, allowedDomains, allowedEmails) {
198
+ const email = session.user.email?.toLowerCase();
199
+ const domain = session.user.hostedDomain?.toLowerCase();
200
+ if (session.user.emailVerified && email && allowedEmails.includes(email)) {
201
+ return true;
202
+ }
203
+ return Boolean(
204
+ session.user.emailVerified && domain && allowedDomains.includes(domain)
205
+ );
206
+ }
207
+ function unauthorized(request) {
208
+ if (isJsonRoute(new URL(request.url).pathname)) {
209
+ return Response.json({ error: "unauthenticated" }, { status: 401 });
210
+ }
211
+ return Response.redirect(dashboardLoginUrl(request), 302);
212
+ }
213
+ function forbidden(request) {
214
+ if (!isJsonRoute(new URL(request.url).pathname)) {
215
+ return new Response(
216
+ `<!DOCTYPE html>
217
+ <html lang="en">
218
+ <head>
219
+ <meta charset="utf-8" />
220
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
221
+ <title>Junior access denied</title>
222
+ <style>
223
+ ${readDashboardTailwind()}
224
+ </style>
225
+ </head>
226
+ <body class="m-0 bg-black font-sans text-white [color-scheme:dark]">
227
+ <main class="grid min-h-screen place-items-center p-8">
228
+ <section class="max-w-lg border-l-4 border-rose-400 pl-4">
229
+ <h1 class="m-0 mb-3 text-[1.75rem] font-bold leading-tight">Access denied</h1>
230
+ <p class="m-0 leading-relaxed text-[#b8b8b8]">Your Google account is authenticated, but it is not allowed to use this Junior dashboard.</p>
231
+ </section>
232
+ </main>
233
+ </body>
234
+ </html>`,
235
+ {
236
+ headers: {
237
+ "cache-control": "no-store",
238
+ "content-type": "text/html; charset=utf-8"
239
+ },
240
+ status: 403
241
+ }
242
+ );
243
+ }
244
+ return Response.json({ error: "forbidden" }, { status: 403 });
245
+ }
246
+ function dashboardSessionBypass() {
247
+ return {
248
+ user: {
249
+ email: "local-dashboard@localhost",
250
+ emailVerified: true,
251
+ hostedDomain: "localhost"
252
+ }
253
+ };
254
+ }
255
+ function readDashboardAsset(fileName) {
256
+ const localDistUrl = new URL(`./${fileName}`, import.meta.url);
257
+ if (existsSync(localDistUrl)) {
258
+ return readFileSync(localDistUrl, "utf8");
259
+ }
260
+ const sourceDistUrl = new URL(`../dist/${fileName}`, import.meta.url);
261
+ if (existsSync(sourceDistUrl)) {
262
+ return readFileSync(sourceDistUrl, "utf8");
263
+ }
264
+ const workspacePackagePath = path.join(
265
+ process.cwd(),
266
+ "node_modules",
267
+ "@sentry",
268
+ "junior-dashboard",
269
+ "dist",
270
+ fileName
271
+ );
272
+ if (existsSync(workspacePackagePath)) {
273
+ return readFileSync(workspacePackagePath, "utf8");
274
+ }
275
+ return "";
276
+ }
277
+ function readDashboardClient() {
278
+ const client = readDashboardAsset("client.js");
279
+ if (!client) {
280
+ throw new Error("Junior dashboard client bundle was not found");
281
+ }
282
+ return client;
283
+ }
284
+ function dashboardTimeZone() {
285
+ return process.env.JUNIOR_TIMEZONE || "America/Los_Angeles";
286
+ }
287
+ function readDashboardTailwind() {
288
+ return readDashboardAsset("tailwind.css");
289
+ }
290
+ function dashboardPagePaths(basePath) {
291
+ return [
292
+ basePath,
293
+ basePath === "/" ? "/conversations" : `${basePath}/conversations`,
294
+ basePath === "/" ? "/sessions" : `${basePath}/sessions`
295
+ ];
296
+ }
297
+ function renderDashboard(basePath) {
298
+ return new Response(
299
+ `<!DOCTYPE html>
300
+ <html lang="en">
301
+ <head>
302
+ <meta charset="utf-8" />
303
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
304
+ <title>Junior</title>
305
+ <style>
306
+ ${readDashboardTailwind()}
307
+ </style>
308
+ </head>
309
+ <body class="m-0 bg-black text-white [color-scheme:dark]">
310
+ <div id="dashboard-root"></div>
311
+ <script>
312
+ window.__JUNIOR_DASHBOARD_BASE_PATH__ = ${JSON.stringify(basePath)};
313
+ (function () {
314
+ function escapeHtml(value) {
315
+ return String(value)
316
+ .replace(/&/g, "&amp;")
317
+ .replace(/</g, "&lt;")
318
+ .replace(/>/g, "&gt;")
319
+ .replace(/"/g, "&quot;")
320
+ .replace(/'/g, "&#039;");
321
+ }
322
+ function errorText(error) {
323
+ if (!error) return "Unknown dashboard error";
324
+ if (typeof error === "string") return error;
325
+ if (error.stack) return error.stack;
326
+ if (error.message) return error.message;
327
+ try {
328
+ return JSON.stringify(error, null, 2);
329
+ } catch (_error) {
330
+ return String(error);
331
+ }
332
+ }
333
+ window.__JUNIOR_DASHBOARD_SHOW_ERROR__ = function (error) {
334
+ var root = document.getElementById("dashboard-root");
335
+ if (!root) return;
336
+ root.innerHTML =
337
+ '<main class="grid min-h-screen place-items-center bg-black p-8 text-white">' +
338
+ '<section class="w-full max-w-5xl border border-rose-400/50 bg-[#0b0b0b] p-5 font-sans">' +
339
+ '<div class="font-mono text-xs uppercase leading-none text-[#888]">Dashboard Error</div>' +
340
+ '<h1 class="mt-2 text-3xl font-bold leading-tight tracking-normal">Junior failed to render</h1>' +
341
+ '<p class="my-4 max-w-3xl text-[#b8b8b8]">The dashboard hit a client-side exception. The stack trace is shown here so the page does not fail blank.</p>' +
342
+ '<pre class="max-h-[60vh] overflow-auto whitespace-pre-wrap break-words border border-white/10 bg-black p-4 font-mono text-sm leading-relaxed text-white">' +
343
+ escapeHtml(errorText(error)) +
344
+ "</pre></section></main>";
345
+ };
346
+ window.addEventListener("error", function (event) {
347
+ window.__JUNIOR_DASHBOARD_SHOW_ERROR__(event.error || event.message);
348
+ });
349
+ window.addEventListener("unhandledrejection", function (event) {
350
+ window.__JUNIOR_DASHBOARD_SHOW_ERROR__(event.reason);
351
+ });
352
+ })();
353
+ </script>
354
+ <script type="module" src="/api/dashboard/client.js?v=${DASHBOARD_CLIENT_VERSION}"></script>
355
+ </body>
356
+ </html>`,
357
+ {
358
+ headers: {
359
+ "cache-control": "no-store",
360
+ "content-type": "text/html; charset=utf-8"
361
+ }
362
+ }
363
+ );
364
+ }
365
+ function renderFavicon() {
366
+ return new Response(
367
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="#000000"/><text x="16" y="20.5" fill="#ffffff" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="11" font-weight="900" text-anchor="middle">Jr</text></svg>`,
368
+ { headers: { "content-type": "image/svg+xml" } }
369
+ );
370
+ }
371
+ function createDashboardApp(options) {
372
+ if (process.env.SENTRY_DSN?.trim()) {
373
+ initSentry();
374
+ }
375
+ const basePath = normalizePath(
376
+ options.basePath ?? DEFAULT_BASE_PATH,
377
+ DEFAULT_BASE_PATH
378
+ );
379
+ const authPath = normalizePath(
380
+ options.authPath ?? DEFAULT_AUTH_PATH,
381
+ DEFAULT_AUTH_PATH
382
+ );
383
+ const allowedDomains = normalizeValues(options.allowedGoogleDomains);
384
+ const allowedEmails = normalizeValues(options.allowedEmails);
385
+ const authRequired = options.authRequired !== false;
386
+ if (authRequired && allowedDomains.length === 0 && allowedEmails.length === 0) {
387
+ throw new Error(
388
+ "Junior dashboard auth requires allowedGoogleDomains or allowedEmails"
389
+ );
390
+ }
391
+ const auth = authRequired ? options.auth ?? createDashboardAuth({
392
+ authPath,
393
+ trustedOrigins: options.trustedOrigins ?? [],
394
+ googleHostedDomain: resolveGoogleHostedDomainHint(allowedDomains),
395
+ sessionMaxAgeSeconds: options.sessionMaxAgeSeconds
396
+ }) : void 0;
397
+ const reporting = options.reporting ?? createJuniorReporting();
398
+ const app = new Hono();
399
+ if (auth) {
400
+ app.on(["GET", "POST"], `${authPath}/*`, (c) => auth.handler(c.req.raw));
401
+ }
402
+ app.get("/favicon.ico", () => renderFavicon());
403
+ app.get("/api/dashboard/login", async (c) => {
404
+ if (!auth) {
405
+ return Response.redirect(callbackUrl(c.req.raw, basePath), 302);
406
+ }
407
+ return auth.signInWithGoogle(c.req.raw, callbackUrl(c.req.raw, basePath));
408
+ });
409
+ const requireDashboardSession = async (c, next) => {
410
+ if (!authRequired) {
411
+ c.set("dashboardSession", dashboardSessionBypass());
412
+ await next();
413
+ return;
414
+ }
415
+ if (!auth) {
416
+ return unauthorized(c.req.raw);
417
+ }
418
+ const session = await auth.getSession(c.req.raw);
419
+ if (!session) {
420
+ return unauthorized(c.req.raw);
421
+ }
422
+ if (!isAuthorized(session, allowedDomains, allowedEmails)) {
423
+ return forbidden(c.req.raw);
424
+ }
425
+ c.set("dashboardSession", sanitizeDashboardSession(session));
426
+ await next();
427
+ };
428
+ if (basePath === "/") {
429
+ app.use("/*", requireDashboardSession);
430
+ } else {
431
+ app.use(basePath, requireDashboardSession);
432
+ app.use(`${basePath}/*`, requireDashboardSession);
433
+ }
434
+ app.use("/api/dashboard/*", requireDashboardSession);
435
+ for (const path2 of dashboardPagePaths(basePath)) {
436
+ app.get(path2, () => renderDashboard(basePath));
437
+ if (path2 !== "/") {
438
+ app.get(`${path2}/*`, () => renderDashboard(basePath));
439
+ }
440
+ }
441
+ app.get("/api/dashboard/health", async () => {
442
+ return Response.json(await reporting.getHealth());
443
+ });
444
+ app.get("/api/dashboard/runtime", async () => {
445
+ return Response.json(await reporting.getRuntimeInfo());
446
+ });
447
+ app.get("/api/dashboard/plugins", async () => {
448
+ return Response.json(await reporting.getPlugins());
449
+ });
450
+ app.get("/api/dashboard/skills", async () => {
451
+ return Response.json(await reporting.getSkills());
452
+ });
453
+ app.get("/api/dashboard/sessions", async () => {
454
+ return Response.json(await reporting.getSessions());
455
+ });
456
+ app.get("/api/dashboard/conversations/:conversationId", async (c) => {
457
+ return Response.json(
458
+ await reporting.getConversation(
459
+ decodeURIComponent(c.req.param("conversationId"))
460
+ )
461
+ );
462
+ });
463
+ app.get("/api/dashboard/config", () => {
464
+ return Response.json({
465
+ allowedEmailCount: allowedEmails.length,
466
+ allowedGoogleDomainCount: allowedDomains.length,
467
+ authRequired,
468
+ authPath,
469
+ basePath,
470
+ sentryConversationLinks: hasSentryConversationLinks(),
471
+ timeZone: dashboardTimeZone()
472
+ });
473
+ });
474
+ app.get("/api/dashboard/me", (c) => {
475
+ return Response.json(c.get("dashboardSession"));
476
+ });
477
+ app.get("/api/dashboard/info", async () => {
478
+ return Response.json(await reporting.getRuntimeInfo());
479
+ });
480
+ app.get("/api/dashboard/client.js", () => {
481
+ return new Response(readDashboardClient(), {
482
+ headers: {
483
+ "cache-control": "no-store",
484
+ "content-type": "application/javascript; charset=utf-8"
485
+ }
486
+ });
487
+ });
488
+ return app;
489
+ }
490
+ export {
491
+ createDashboardApp
492
+ };
package/dist/auth.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ export interface DashboardUser {
2
+ email?: string | null;
3
+ emailVerified?: boolean;
4
+ hostedDomain?: string | null;
5
+ name?: string | null;
6
+ }
7
+ export interface DashboardSession {
8
+ user: DashboardUser;
9
+ }
10
+ export interface DashboardAuthConfig {
11
+ baseURL?: string;
12
+ authPath: string;
13
+ trustedOrigins: string[];
14
+ secret?: string;
15
+ googleClientId?: string;
16
+ googleClientSecret?: string;
17
+ googleHostedDomain?: string;
18
+ sessionMaxAgeSeconds?: number;
19
+ }
20
+ export interface DashboardAuth {
21
+ handler(request: Request): Promise<Response>;
22
+ getSession(request: Request): Promise<DashboardSession | null>;
23
+ signInWithGoogle(request: Request, callbackURL: string): Promise<Response>;
24
+ }
25
+ /** Keep dashboard identity responses limited to user display fields. */
26
+ export declare function sanitizeDashboardSession(session: DashboardSession): DashboardSession;
27
+ /** Create the Better Auth bridge used by dashboard browser routes. */
28
+ export declare function createDashboardAuth(config: DashboardAuthConfig): DashboardAuth;
29
+ /** Resolve a Google hosted-domain login hint when it is unambiguous. */
30
+ export declare function resolveGoogleHostedDomainHint(domains: string[]): string | undefined;
@@ -0,0 +1,2 @@
1
+ /** Render the dashboard SPA shell and route-level loading states. */
2
+ export declare function DashboardShell(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { QueryClient } from "@tanstack/react-query";
2
+ import type { ConversationDetailFeed, DashboardData } from "./types";
3
+ /** Share dashboard query cache between route data and tooltip detail lookups. */
4
+ export declare const client: QueryClient;
5
+ /** Poll the dashboard summary feed used by command center and conversation lists. */
6
+ export declare function useDashboardData(): import("@tanstack/react-query").UseQueryResult<NoInfer<DashboardData>, Error>;
7
+ /** Poll one conversation transcript while preserving route-level disabled state. */
8
+ export declare function useConversationData(conversationId: string | undefined): import("@tanstack/react-query").UseQueryResult<NoInfer<ConversationDetailFeed>, Error>;
9
+ /** Read one conversation transcript payload for dashboard-local detail views. */
10
+ export declare function readConversationData(conversationId: string): Promise<ConversationDetailFeed>;
@@ -0,0 +1,15 @@
1
+ import { type BundledLanguage } from "shiki/bundle/web";
2
+ import type { CodeBlock } from "./types";
3
+ /** Count rendered children so transcripts can decide which markup node expands. */
4
+ export declare function countStructuredBlockChildren(block: CodeBlock): number;
5
+ /** Render structured markup blocks as collapsible nodes instead of flat code. */
6
+ export declare function StructuredMarkup(props: {
7
+ block: CodeBlock;
8
+ firstChildIndex: number;
9
+ lastChildIndex: number;
10
+ }): import("react/jsx-runtime").JSX.Element;
11
+ /** Render highlighted code while keeping Shiki output responsive in transcripts. */
12
+ export declare function HighlightedCode(props: {
13
+ code: string;
14
+ language: BundledLanguage;
15
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { DashboardData } from "../types";
2
+ /** Render the command-center summary rail from the dashboard health payload. */
3
+ export declare function CommandRail(props: {
4
+ data?: DashboardData;
5
+ error: Error | null;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { Conversation } from "../types";
2
+ /** Render the full conversation table used by the conversations page. */
3
+ export declare function ConversationList(props: {
4
+ conversations: Conversation[];
5
+ selectedId?: string;
6
+ search?: string;
7
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { Conversation } from "../types";
2
+ /** Render compact conversation metadata aligned to row context. */
3
+ export declare function ConversationRowStats(props: {
4
+ conversation: Conversation;
5
+ timeLabel: string;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import type { Conversation } from "../types";
2
+ /** Render the compact latest-conversation stack on the command center. */
3
+ export declare function ConversationStack(props: {
4
+ conversations: Conversation[];
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import type { Conversation } from "../types";
2
+ /** Render the shared conversation title, identity, status, and Sentry link. */
3
+ export declare function ConversationSummary(props: {
4
+ conversation: Conversation;
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from "react";
2
+ /** Render the dashboard empty-state block with a warning accent. */
3
+ export declare function EmptyTelemetry(props: {
4
+ children: ReactNode;
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { SessionFilter } from "../types";
2
+ /** Render conversation filters while keeping URL state owned by the page. */
3
+ export declare function FilterTabs(props: {
4
+ current: SessionFilter;
5
+ onChange(filter: SessionFilter): void;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ /** Render the compact Junior wordmark used by the dashboard shell. */
2
+ export declare function JuniorLogo(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ /** Render the full-page loading treatment before the first dashboard payload lands. */
2
+ export declare function LoadingView(props: {
3
+ label: string;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from "react";
2
+ /** Frame a dashboard content region without leaking CSS class contracts. */
3
+ export declare function Section(props: {
4
+ children: ReactNode;
5
+ className?: string;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from "react";
2
+ /** Render a dashboard section heading row with optional controls. */
3
+ export declare function SectionHeader(props: {
4
+ actions?: ReactNode;
5
+ children: ReactNode;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from "react";
2
+ /** Render compact section titles that fit inside operational panels. */
3
+ export declare function SectionTitle(props: {
4
+ children: ReactNode;
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { VisualStatus } from "../types";
2
+ /** Render readable status text while keeping severity color restrained. */
3
+ export declare function StatusBadge(props: {
4
+ label?: string;
5
+ status: VisualStatus | undefined;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from "react";
2
+ /** Render the shared expandable/non-expandable frame for transcript tools. */
3
+ export declare function ToolFrame(props: {
4
+ children?: ReactNode;
5
+ meta: string[];
6
+ raw?: boolean;
7
+ signature: ReactNode;
8
+ }): import("react/jsx-runtime").JSX.Element;
9
+ /** Provide the shared transcript tool-frame shell for nonstandard part views. */
10
+ export declare function toolFrameClass(): string;
@@ -0,0 +1,5 @@
1
+ import type { ConversationTurn } from "../types";
2
+ /** Render ordered conversation turns as message, thinking, and tool-call events. */
3
+ export declare function Transcript(props: {
4
+ turns: ConversationTurn[];
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { TranscriptViewMode } from "./transcriptRenderModel";
2
+ /** Render transcript controls without coupling them to turn rendering. */
3
+ export declare function TranscriptHeader(props: {
4
+ onChange(value: TranscriptViewMode): void;
5
+ redacted: boolean;
6
+ value: TranscriptViewMode;
7
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ /** Render a transcript-shaped loading state for route transitions. */
2
+ export declare function TranscriptLoading(): import("react/jsx-runtime").JSX.Element;