@urateam/dashboard 0.1.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.
- package/dist/__tests__/layout.test.d.ts +2 -0
- package/dist/__tests__/layout.test.d.ts.map +1 -0
- package/dist/__tests__/layout.test.js +138 -0
- package/dist/__tests__/layout.test.js.map +1 -0
- package/dist/__tests__/security.repro.test.d.ts +16 -0
- package/dist/__tests__/security.repro.test.d.ts.map +1 -0
- package/dist/__tests__/security.repro.test.js +249 -0
- package/dist/__tests__/security.repro.test.js.map +1 -0
- package/dist/__tests__/server.test.d.ts +2 -0
- package/dist/__tests__/server.test.d.ts.map +1 -0
- package/dist/__tests__/server.test.js +268 -0
- package/dist/__tests__/server.test.js.map +1 -0
- package/dist/__tests__/ui-refresh.test.d.ts +2 -0
- package/dist/__tests__/ui-refresh.test.d.ts.map +1 -0
- package/dist/__tests__/ui-refresh.test.js +860 -0
- package/dist/__tests__/ui-refresh.test.js.map +1 -0
- package/dist/routes/config.d.ts +4 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +12 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/coordination.d.ts +4 -0
- package/dist/routes/coordination.d.ts.map +1 -0
- package/dist/routes/coordination.js +22 -0
- package/dist/routes/coordination.js.map +1 -0
- package/dist/routes/errors.d.ts +4 -0
- package/dist/routes/errors.d.ts.map +1 -0
- package/dist/routes/errors.js +36 -0
- package/dist/routes/errors.js.map +1 -0
- package/dist/routes/runs.d.ts +4 -0
- package/dist/routes/runs.d.ts.map +1 -0
- package/dist/routes/runs.js +72 -0
- package/dist/routes/runs.js.map +1 -0
- package/dist/routes/tokens.d.ts +4 -0
- package/dist/routes/tokens.d.ts.map +1 -0
- package/dist/routes/tokens.js +47 -0
- package/dist/routes/tokens.js.map +1 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +107 -0
- package/dist/server.js.map +1 -0
- package/dist/views/config.d.ts +3 -0
- package/dist/views/config.d.ts.map +1 -0
- package/dist/views/config.js +47 -0
- package/dist/views/config.js.map +1 -0
- package/dist/views/coordination.d.ts +3 -0
- package/dist/views/coordination.d.ts.map +1 -0
- package/dist/views/coordination.js +63 -0
- package/dist/views/coordination.js.map +1 -0
- package/dist/views/errors.d.ts +14 -0
- package/dist/views/errors.d.ts.map +1 -0
- package/dist/views/errors.js +52 -0
- package/dist/views/errors.js.map +1 -0
- package/dist/views/layout.d.ts +21 -0
- package/dist/views/layout.d.ts.map +1 -0
- package/dist/views/layout.js +82 -0
- package/dist/views/layout.js.map +1 -0
- package/dist/views/run-detail.d.ts +35 -0
- package/dist/views/run-detail.d.ts.map +1 -0
- package/dist/views/run-detail.js +129 -0
- package/dist/views/run-detail.js.map +1 -0
- package/dist/views/run-feed.d.ts +14 -0
- package/dist/views/run-feed.d.ts.map +1 -0
- package/dist/views/run-feed.js +80 -0
- package/dist/views/run-feed.js.map +1 -0
- package/dist/views/tokens.d.ts +13 -0
- package/dist/views/tokens.d.ts.map +1 -0
- package/dist/views/tokens.js +66 -0
- package/dist/views/tokens.js.map +1 -0
- package/package.json +28 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface StageFailure {
|
|
2
|
+
stage: string;
|
|
3
|
+
totalRuns: number;
|
|
4
|
+
failedRuns: number;
|
|
5
|
+
failureRate: number;
|
|
6
|
+
}
|
|
7
|
+
interface ErrorPattern {
|
|
8
|
+
stage: string;
|
|
9
|
+
errorMessage: string;
|
|
10
|
+
count: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function errorsView(stageFailures: StageFailure[], errorPatterns: ErrorPattern[]): string;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/views/errors.ts"],"names":[],"mappings":"AAEA,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,UAAU,CACxB,aAAa,EAAE,YAAY,EAAE,EAC7B,aAAa,EAAE,YAAY,EAAE,GAC5B,MAAM,CAyDR"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { escapeHtml } from "./layout.js";
|
|
2
|
+
export function errorsView(stageFailures, errorPatterns) {
|
|
3
|
+
const failureTable = stageFailures.length > 0
|
|
4
|
+
? `<table>
|
|
5
|
+
<thead>
|
|
6
|
+
<tr><th>Stage</th><th>Total Runs</th><th>Failed</th><th>Failure Rate</th></tr>
|
|
7
|
+
</thead>
|
|
8
|
+
<tbody>
|
|
9
|
+
${stageFailures
|
|
10
|
+
.map((f) => `<tr>
|
|
11
|
+
<td>${escapeHtml(f.stage)}</td>
|
|
12
|
+
<td>${f.totalRuns}</td>
|
|
13
|
+
<td>${f.failedRuns}</td>
|
|
14
|
+
<td>
|
|
15
|
+
<div class="bar-track" style="width:6rem;display:inline-block;vertical-align:middle;">
|
|
16
|
+
<div class="bar-fill" style="width:${Number(f.failureRate).toFixed(1)}%;background:var(--color-red);"></div>
|
|
17
|
+
</div>
|
|
18
|
+
${Number(f.failureRate).toFixed(1)}%
|
|
19
|
+
</td>
|
|
20
|
+
</tr>`)
|
|
21
|
+
.join("\n")}
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>`
|
|
24
|
+
: '<p style="color:var(--color-text-muted)">No stage data</p>';
|
|
25
|
+
const patternTable = errorPatterns.length > 0
|
|
26
|
+
? `<table>
|
|
27
|
+
<thead>
|
|
28
|
+
<tr><th>Stage</th><th>Error Message</th><th>Count</th></tr>
|
|
29
|
+
</thead>
|
|
30
|
+
<tbody>
|
|
31
|
+
${errorPatterns
|
|
32
|
+
.map((e) => `<tr>
|
|
33
|
+
<td>${escapeHtml(e.stage)}</td>
|
|
34
|
+
<td><code>${escapeHtml(e.errorMessage)}</code></td>
|
|
35
|
+
<td>${e.count}</td>
|
|
36
|
+
</tr>`)
|
|
37
|
+
.join("\n")}
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>`
|
|
40
|
+
: '<p style="color:var(--color-text-muted)">No errors recorded</p>';
|
|
41
|
+
return `
|
|
42
|
+
<div class="card">
|
|
43
|
+
<h2>Failure Rates by Stage</h2>
|
|
44
|
+
${failureTable}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="card">
|
|
48
|
+
<h2>Common Error Patterns</h2>
|
|
49
|
+
${patternTable}
|
|
50
|
+
</div>`;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/views/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezC,MAAM,UAAU,UAAU,CACxB,aAA6B,EAC7B,aAA6B;IAE7B,MAAM,YAAY,GAChB,aAAa,CAAC,MAAM,GAAG,CAAC;QACtB,CAAC,CAAC;;;;;UAKE,aAAa;aACZ,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC;kBACD,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;kBACnB,CAAC,CAAC,SAAS;kBACX,CAAC,CAAC,UAAU;;;qDAGuB,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;;gBAErE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;;gBAEhC,CACL;aACA,IAAI,CAAC,IAAI,CAAC;;aAER;QACP,CAAC,CAAC,4DAA4D,CAAC;IAEnE,MAAM,YAAY,GAChB,aAAa,CAAC,MAAM,GAAG,CAAC;QACtB,CAAC,CAAC;;;;;UAKE,aAAa;aACZ,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CAAC;kBACD,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;wBACb,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC;kBAChC,CAAC,CAAC,KAAK;gBACT,CACL;aACA,IAAI,CAAC,IAAI,CAAC;;aAER;QACP,CAAC,CAAC,iEAAiE,CAAC;IAExE,OAAO;;;MAGH,YAAY;;;;;MAKZ,YAAY;SACT,CAAC;AACV,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare function getBasePath(): string;
|
|
2
|
+
/**
|
|
3
|
+
* Render the full HTML shell (head + nav + main) around the given content.
|
|
4
|
+
*
|
|
5
|
+
* @param title - Page title shown in <h1> and <title>.
|
|
6
|
+
* @param content - Inner HTML for the <main> element.
|
|
7
|
+
* @param basePath - Root path prefix for all navigation links and static
|
|
8
|
+
* asset references. Must be provided without a trailing
|
|
9
|
+
* slash (e.g. `"/ateam"` or `""`). Every relative URL
|
|
10
|
+
* attribute (`href`, `src`, etc.) inside this template
|
|
11
|
+
* must use `${bp}/…` so links work correctly when the
|
|
12
|
+
* dashboard is mounted under a reverse-proxy path prefix.
|
|
13
|
+
* When omitted, falls back to the `DASHBOARD_BASE_PATH`
|
|
14
|
+
* environment variable (via `getBasePath()`), then to
|
|
15
|
+
* `""` (serve at root `/`). Pass an explicit value
|
|
16
|
+
* (sourced from `DashboardConfig.basePath`) to decouple
|
|
17
|
+
* link rendering from the process environment.
|
|
18
|
+
*/
|
|
19
|
+
export declare function layout(title: string, content: string, basePath?: string): string;
|
|
20
|
+
export declare function escapeHtml(str: string): string;
|
|
21
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/views/layout.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAiBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAoChF;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Base path for all dashboard links and asset references.
|
|
2
|
+
// Set DASHBOARD_BASE_PATH=/ateam (no trailing slash) when the dashboard is
|
|
3
|
+
// served under a path prefix (e.g. via a Caddy strip_prefix proxy).
|
|
4
|
+
export function getBasePath() {
|
|
5
|
+
return (process.env.DASHBOARD_BASE_PATH ?? "").replace(/\/+$/, "");
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Normalize a basePath value so it has no trailing slash and starts with
|
|
9
|
+
* a leading slash (or is empty string for root).
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* "" → ""
|
|
13
|
+
* "/" → ""
|
|
14
|
+
* "/ateam" → "/ateam"
|
|
15
|
+
* "/ateam/" → "/ateam"
|
|
16
|
+
*/
|
|
17
|
+
function normalizeBasePath(basePath) {
|
|
18
|
+
// Strip all trailing slashes; collapse bare "/" to ""
|
|
19
|
+
return basePath.replace(/\/+$/, "");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Render the full HTML shell (head + nav + main) around the given content.
|
|
23
|
+
*
|
|
24
|
+
* @param title - Page title shown in <h1> and <title>.
|
|
25
|
+
* @param content - Inner HTML for the <main> element.
|
|
26
|
+
* @param basePath - Root path prefix for all navigation links and static
|
|
27
|
+
* asset references. Must be provided without a trailing
|
|
28
|
+
* slash (e.g. `"/ateam"` or `""`). Every relative URL
|
|
29
|
+
* attribute (`href`, `src`, etc.) inside this template
|
|
30
|
+
* must use `${bp}/…` so links work correctly when the
|
|
31
|
+
* dashboard is mounted under a reverse-proxy path prefix.
|
|
32
|
+
* When omitted, falls back to the `DASHBOARD_BASE_PATH`
|
|
33
|
+
* environment variable (via `getBasePath()`), then to
|
|
34
|
+
* `""` (serve at root `/`). Pass an explicit value
|
|
35
|
+
* (sourced from `DashboardConfig.basePath`) to decouple
|
|
36
|
+
* link rendering from the process environment.
|
|
37
|
+
*/
|
|
38
|
+
export function layout(title, content, basePath) {
|
|
39
|
+
// Explicit parameter takes priority; fall back to env var for backward compat.
|
|
40
|
+
const bp = normalizeBasePath(basePath ?? getBasePath());
|
|
41
|
+
const cspContent = "default-src 'self'; script-src 'self' https://unpkg.com; style-src 'self'";
|
|
42
|
+
return `<!DOCTYPE html>
|
|
43
|
+
<html lang="en">
|
|
44
|
+
<head>
|
|
45
|
+
<meta charset="utf-8">
|
|
46
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
47
|
+
<meta http-equiv="Content-Security-Policy" content="${cspContent}">
|
|
48
|
+
<title>${escapeHtml(title)} - urateam</title>
|
|
49
|
+
<!-- Favicon -->
|
|
50
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
|
51
|
+
<!-- Inter font (falls back to system-ui in CSS) -->
|
|
52
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
53
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
54
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
|
|
55
|
+
<link rel="stylesheet" href="${bp}/static/style.css">
|
|
56
|
+
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<nav>
|
|
60
|
+
<a class="brand" href="${bp}/">⚡ urateam</a>
|
|
61
|
+
<a href="${bp}/">Runs</a>
|
|
62
|
+
<a href="${bp}/tokens">Tokens</a>
|
|
63
|
+
<a href="${bp}/errors">Errors</a>
|
|
64
|
+
<a href="${bp}/config">Config</a>
|
|
65
|
+
<a href="${bp}/coordination">Coordination</a>
|
|
66
|
+
</nav>
|
|
67
|
+
<main>
|
|
68
|
+
<h1>${escapeHtml(title)}</h1>
|
|
69
|
+
${content}
|
|
70
|
+
</main>
|
|
71
|
+
</body>
|
|
72
|
+
</html>`;
|
|
73
|
+
}
|
|
74
|
+
export function escapeHtml(str) {
|
|
75
|
+
return str
|
|
76
|
+
.replace(/&/g, "&")
|
|
77
|
+
.replace(/</g, "<")
|
|
78
|
+
.replace(/>/g, ">")
|
|
79
|
+
.replace(/"/g, """)
|
|
80
|
+
.replace(/'/g, "'");
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/views/layout.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,2EAA2E;AAC3E,oEAAoE;AACpE,MAAM,UAAU,WAAW;IACzB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,iBAAiB,CAAC,QAAgB;IACzC,sDAAsD;IACtD,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,OAAe,EAAE,QAAiB;IACtE,+EAA+E;IAC/E,MAAM,EAAE,GAAG,iBAAiB,CAAC,QAAQ,IAAI,WAAW,EAAE,CAAC,CAAC;IACxD,MAAM,UAAU,GACd,2EAA2E,CAAC;IAC9E,OAAO;;;;;wDAK+C,UAAU;WACvD,UAAU,CAAC,KAAK,CAAC;;;;;;;iCAOK,EAAE;;;;;6BAKN,EAAE;eAChB,EAAE;eACF,EAAE;eACF,EAAE;eACF,EAAE;eACF,EAAE;;;UAGP,UAAU,CAAC,KAAK,CAAC;MACrB,OAAO;;;QAGL,CAAC;AACT,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface RunInfo {
|
|
2
|
+
id: string;
|
|
3
|
+
issueId: string;
|
|
4
|
+
issueTitle: string;
|
|
5
|
+
pipelineKey: string;
|
|
6
|
+
repoUrl: string;
|
|
7
|
+
branch: string | null;
|
|
8
|
+
status: string;
|
|
9
|
+
startedAt: Date;
|
|
10
|
+
completedAt: Date | null;
|
|
11
|
+
prUrl: string | null;
|
|
12
|
+
totalInputTokens: number;
|
|
13
|
+
totalOutputTokens: number;
|
|
14
|
+
errorMessage: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface StageInfo {
|
|
17
|
+
id: string;
|
|
18
|
+
stage: string;
|
|
19
|
+
status: string;
|
|
20
|
+
startedAt: Date;
|
|
21
|
+
completedAt: Date | null;
|
|
22
|
+
inputTokens: number;
|
|
23
|
+
outputTokens: number;
|
|
24
|
+
turns: number;
|
|
25
|
+
handoffArtifact: string | null;
|
|
26
|
+
errorMessage: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface LogEntry {
|
|
29
|
+
id: string;
|
|
30
|
+
timestamp: Date;
|
|
31
|
+
type: string;
|
|
32
|
+
content: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function runDetailView(run: RunInfo, stages: StageInfo[], logs: LogEntry[], page: number, totalLogs: number): string;
|
|
35
|
+
//# sourceMappingURL=run-detail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-detail.d.ts","sourceRoot":"","sources":["../../src/views/run-detail.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AA8CD,wBAAgB,aAAa,CAC3B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,SAAS,EAAE,EACnB,IAAI,EAAE,QAAQ,EAAE,EAChB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,MAAM,CA4FR"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { escapeHtml, getBasePath } from "./layout.js";
|
|
2
|
+
function statusBadge(status) {
|
|
3
|
+
return `<span class="badge badge-${status}">${escapeHtml(status)}</span>`;
|
|
4
|
+
}
|
|
5
|
+
function formatTime(d) {
|
|
6
|
+
if (!d)
|
|
7
|
+
return "-";
|
|
8
|
+
return d.toISOString().replace("T", " ").slice(0, 19) + " UTC";
|
|
9
|
+
}
|
|
10
|
+
function toRelativeTime(d) {
|
|
11
|
+
if (!d)
|
|
12
|
+
return "-";
|
|
13
|
+
const ms = Date.now() - new Date(d).getTime();
|
|
14
|
+
if (ms < 0)
|
|
15
|
+
return "just now";
|
|
16
|
+
const secs = Math.floor(ms / 1000);
|
|
17
|
+
if (secs < 60)
|
|
18
|
+
return `${secs}s ago`;
|
|
19
|
+
const mins = Math.floor(secs / 60);
|
|
20
|
+
if (mins < 60)
|
|
21
|
+
return `${mins}m ago`;
|
|
22
|
+
const hours = Math.floor(mins / 60);
|
|
23
|
+
if (hours < 24)
|
|
24
|
+
return `${hours}h ago`;
|
|
25
|
+
const days = Math.floor(hours / 24);
|
|
26
|
+
return `${days}d ago`;
|
|
27
|
+
}
|
|
28
|
+
function formatDuration(start, end) {
|
|
29
|
+
const endMs = end ? end.getTime() : Date.now();
|
|
30
|
+
const diffSec = Math.floor((endMs - start.getTime()) / 1000);
|
|
31
|
+
if (diffSec < 60)
|
|
32
|
+
return `${diffSec}s`;
|
|
33
|
+
const min = Math.floor(diffSec / 60);
|
|
34
|
+
const sec = diffSec % 60;
|
|
35
|
+
if (min < 60)
|
|
36
|
+
return `${min}m ${sec}s`;
|
|
37
|
+
const hr = Math.floor(min / 60);
|
|
38
|
+
return `${hr}h ${min % 60}m`;
|
|
39
|
+
}
|
|
40
|
+
function formatJson(raw) {
|
|
41
|
+
if (!raw)
|
|
42
|
+
return "";
|
|
43
|
+
try {
|
|
44
|
+
const obj = JSON.parse(raw);
|
|
45
|
+
return escapeHtml(JSON.stringify(obj, null, 2));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return escapeHtml(raw);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function runDetailView(run, stages, logs, page, totalLogs) {
|
|
52
|
+
const basePath = getBasePath();
|
|
53
|
+
const logsPerPage = 50;
|
|
54
|
+
const totalPages = Math.max(1, Math.ceil(totalLogs / logsPerPage));
|
|
55
|
+
const duration = formatDuration(run.startedAt, run.completedAt);
|
|
56
|
+
const isInFlight = run.status === "running" || run.status === "queued";
|
|
57
|
+
const metaHtml = `<div class="card">
|
|
58
|
+
<h2>Run Details</h2>
|
|
59
|
+
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:1.25rem;flex-wrap:wrap;">
|
|
60
|
+
${statusBadge(run.status)}
|
|
61
|
+
<span class="duration-badge" title="Total run duration">⏱ ${duration}${isInFlight ? " (running)" : ""}</span>
|
|
62
|
+
${run.prUrl ? `<a href="${escapeHtml(run.prUrl)}" target="_blank" rel="noopener" style="font-size:0.875rem;">↗ Pull Request</a>` : ""}
|
|
63
|
+
</div>
|
|
64
|
+
<dl class="meta">
|
|
65
|
+
<div><dt>Issue</dt><dd>${escapeHtml(run.issueId)}: ${escapeHtml(run.issueTitle)}</dd></div>
|
|
66
|
+
<div><dt>Pipeline</dt><dd>${escapeHtml(run.pipelineKey)}</dd></div>
|
|
67
|
+
<div><dt>Repo</dt><dd>${escapeHtml(run.repoUrl)}</dd></div>
|
|
68
|
+
<div><dt>Branch</dt><dd>${escapeHtml(run.branch || "-")}</dd></div>
|
|
69
|
+
<div><dt>Started</dt><dd title="${escapeHtml(formatTime(run.startedAt))}">${toRelativeTime(run.startedAt)}</dd></div>
|
|
70
|
+
<div><dt>Completed</dt><dd title="${run.completedAt ? escapeHtml(formatTime(run.completedAt)) : ""}">${toRelativeTime(run.completedAt)}</dd></div>
|
|
71
|
+
<div><dt>Tokens (total)</dt><dd>${(run.totalInputTokens + run.totalOutputTokens).toLocaleString()}</dd></div>
|
|
72
|
+
<div><dt>Tokens in / out</dt><dd>${run.totalInputTokens.toLocaleString()} / ${run.totalOutputTokens.toLocaleString()}</dd></div>
|
|
73
|
+
${run.errorMessage ? `<div style="grid-column:1/-1"><dt>Error</dt><dd style="color:var(--color-red)">${escapeHtml(run.errorMessage)}</dd></div>` : ""}
|
|
74
|
+
</dl>
|
|
75
|
+
</div>`;
|
|
76
|
+
const stageHtml = `<div class="card">
|
|
77
|
+
<h2>Stage Timeline</h2>
|
|
78
|
+
<div class="timeline">
|
|
79
|
+
${stages
|
|
80
|
+
.map((s) => `<div class="timeline-item ${s.status}">
|
|
81
|
+
<div style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;">
|
|
82
|
+
<strong>${escapeHtml(s.stage)}</strong>
|
|
83
|
+
${statusBadge(s.status)}
|
|
84
|
+
</div>
|
|
85
|
+
<div class="timeline-meta">
|
|
86
|
+
<span>⏱ ${formatDuration(s.startedAt, s.completedAt)}</span>
|
|
87
|
+
<span>🔤 ${(s.inputTokens + s.outputTokens).toLocaleString()} tokens</span>
|
|
88
|
+
<span>↩ ${s.turns} turn${s.turns !== 1 ? "s" : ""}</span>
|
|
89
|
+
</div>
|
|
90
|
+
${s.errorMessage ? `<div style="color:var(--color-red);font-size:0.8125rem;margin-top:0.5rem;padding:0.375rem 0.5rem;background:color-mix(in srgb,var(--color-red) 8%,transparent);border-radius:4px;">${escapeHtml(s.errorMessage)}</div>` : ""}
|
|
91
|
+
${s.handoffArtifact
|
|
92
|
+
? `<details style="margin-top:0.5rem;">
|
|
93
|
+
<summary>Handoff Artifact</summary>
|
|
94
|
+
<pre><code>${formatJson(s.handoffArtifact)}</code></pre>
|
|
95
|
+
</details>`
|
|
96
|
+
: ""}
|
|
97
|
+
</div>`)
|
|
98
|
+
.join("\n")}
|
|
99
|
+
</div>
|
|
100
|
+
</div>`;
|
|
101
|
+
const logEntries = logs.length > 0
|
|
102
|
+
? logs
|
|
103
|
+
.map((l) => `<div class="log-entry"><span class="log-time" title="${escapeHtml(formatTime(l.timestamp))}">${toRelativeTime(l.timestamp)}</span><span class="log-type log-type-${l.type}">[${escapeHtml(l.type)}]</span>${escapeHtml(l.content)}</div>`)
|
|
104
|
+
.join("\n")
|
|
105
|
+
: '<p style="color:var(--color-text-muted);padding:1rem;">No logs recorded</p>';
|
|
106
|
+
const pagination = totalPages > 1
|
|
107
|
+
? `<div class="pagination">
|
|
108
|
+
${page > 1 ? `<a href="${basePath}/runs/${encodeURIComponent(run.id)}?page=${page - 1}">← Prev</a>` : ""}
|
|
109
|
+
${Array.from({ length: totalPages }, (_, i) => i + 1)
|
|
110
|
+
.slice(Math.max(0, page - 3), page + 2)
|
|
111
|
+
.map((p) => p === page
|
|
112
|
+
? `<span>${p}</span>`
|
|
113
|
+
: `<a href="${basePath}/runs/${encodeURIComponent(run.id)}?page=${p}">${p}</a>`)
|
|
114
|
+
.join("")}
|
|
115
|
+
${page < totalPages ? `<a href="${basePath}/runs/${encodeURIComponent(run.id)}?page=${page + 1}">Next →</a>` : ""}
|
|
116
|
+
</div>`
|
|
117
|
+
: "";
|
|
118
|
+
const logHtml = `<div class="card">
|
|
119
|
+
<details class="log-section" open>
|
|
120
|
+
<summary>Agent Logs <span style="font-weight:400;font-size:0.8125rem;color:var(--color-text-muted);">(${totalLogs} total)</span></summary>
|
|
121
|
+
<div class="log-entries">
|
|
122
|
+
${logEntries}
|
|
123
|
+
</div>
|
|
124
|
+
${pagination}
|
|
125
|
+
</details>
|
|
126
|
+
</div>`;
|
|
127
|
+
return `<p style="margin-bottom:1rem;"><a href="${basePath}/">← Back to runs</a></p>${metaHtml}${stageHtml}${logHtml}`;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=run-detail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-detail.js","sourceRoot":"","sources":["../../src/views/run-detail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface RunRow {
|
|
2
|
+
id: string;
|
|
3
|
+
issueId: string;
|
|
4
|
+
issueTitle: string;
|
|
5
|
+
pipelineKey: string;
|
|
6
|
+
repoUrl: string;
|
|
7
|
+
status: string;
|
|
8
|
+
startedAt: Date;
|
|
9
|
+
completedAt: Date | null;
|
|
10
|
+
totalInputTokens: number;
|
|
11
|
+
totalOutputTokens: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function runFeedView(runs: RunRow[]): string;
|
|
14
|
+
//# sourceMappingURL=run-feed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-feed.d.ts","sourceRoot":"","sources":["../../src/views/run-feed.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AA2CD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAoClD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { escapeHtml, getBasePath } from "./layout.js";
|
|
2
|
+
function statusBadge(status) {
|
|
3
|
+
const cls = `badge badge-${status}`;
|
|
4
|
+
return `<span class="${cls}">${escapeHtml(status)}</span>`;
|
|
5
|
+
}
|
|
6
|
+
function formatDuration(start, end) {
|
|
7
|
+
const endMs = end ? end.getTime() : Date.now();
|
|
8
|
+
const diffSec = Math.floor((endMs - start.getTime()) / 1000);
|
|
9
|
+
if (diffSec < 60)
|
|
10
|
+
return `${diffSec}s`;
|
|
11
|
+
const min = Math.floor(diffSec / 60);
|
|
12
|
+
const sec = diffSec % 60;
|
|
13
|
+
if (min < 60)
|
|
14
|
+
return `${min}m ${sec}s`;
|
|
15
|
+
const hr = Math.floor(min / 60);
|
|
16
|
+
return `${hr}h ${min % 60}m`;
|
|
17
|
+
}
|
|
18
|
+
function formatTokens(input, output) {
|
|
19
|
+
const total = input + output;
|
|
20
|
+
if (total === 0)
|
|
21
|
+
return "-";
|
|
22
|
+
if (total < 1000)
|
|
23
|
+
return String(total);
|
|
24
|
+
return `${(total / 1000).toFixed(1)}k`;
|
|
25
|
+
}
|
|
26
|
+
function repoName(url) {
|
|
27
|
+
const parts = url.split("/");
|
|
28
|
+
return parts[parts.length - 1] || url;
|
|
29
|
+
}
|
|
30
|
+
function toRelativeTime(date) {
|
|
31
|
+
const ms = Date.now() - date.getTime();
|
|
32
|
+
if (ms < 0)
|
|
33
|
+
return "just now";
|
|
34
|
+
const secs = Math.floor(ms / 1000);
|
|
35
|
+
if (secs < 60)
|
|
36
|
+
return `${secs}s ago`;
|
|
37
|
+
const mins = Math.floor(secs / 60);
|
|
38
|
+
if (mins < 60)
|
|
39
|
+
return `${mins}m ago`;
|
|
40
|
+
const hours = Math.floor(mins / 60);
|
|
41
|
+
if (hours < 24)
|
|
42
|
+
return `${hours}h ago`;
|
|
43
|
+
const days = Math.floor(hours / 24);
|
|
44
|
+
return `${days}d ago`;
|
|
45
|
+
}
|
|
46
|
+
export function runFeedView(runs) {
|
|
47
|
+
const basePath = getBasePath();
|
|
48
|
+
const rows = runs
|
|
49
|
+
.map((r) => `<tr>
|
|
50
|
+
<td>${statusBadge(r.status)}</td>
|
|
51
|
+
<td><a href="${basePath}/runs/${encodeURIComponent(r.id)}">${escapeHtml(r.issueId)}: ${escapeHtml(r.issueTitle)}</a></td>
|
|
52
|
+
<td>${escapeHtml(r.pipelineKey)}</td>
|
|
53
|
+
<td>${escapeHtml(repoName(r.repoUrl))}</td>
|
|
54
|
+
<td>${formatDuration(r.startedAt, r.completedAt)}</td>
|
|
55
|
+
<td title="${escapeHtml(r.startedAt.toISOString())}">${toRelativeTime(r.startedAt)}</td>
|
|
56
|
+
<td>${formatTokens(r.totalInputTokens, r.totalOutputTokens)}</td>
|
|
57
|
+
</tr>`)
|
|
58
|
+
.join("\n");
|
|
59
|
+
return `<div id="run-feed" hx-get="${basePath}/" hx-trigger="every 5s" hx-swap="innerHTML" hx-target="#run-feed">
|
|
60
|
+
<div class="table-wrapper">
|
|
61
|
+
<table>
|
|
62
|
+
<thead>
|
|
63
|
+
<tr>
|
|
64
|
+
<th>Status</th>
|
|
65
|
+
<th>Issue</th>
|
|
66
|
+
<th>Pipeline</th>
|
|
67
|
+
<th>Repo</th>
|
|
68
|
+
<th>Duration</th>
|
|
69
|
+
<th>Started</th>
|
|
70
|
+
<th>Tokens</th>
|
|
71
|
+
</tr>
|
|
72
|
+
</thead>
|
|
73
|
+
<tbody>
|
|
74
|
+
${rows.length > 0 ? rows : '<tr><td colspan="7" style="text-align:center;color:#999;padding:2rem;">No runs yet</td></tr>'}
|
|
75
|
+
</tbody>
|
|
76
|
+
</table>
|
|
77
|
+
</div>
|
|
78
|
+
</div>`;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=run-feed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-feed.js","sourceRoot":"","sources":["../../src/views/run-feed.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface DailyUsage {
|
|
2
|
+
date: string;
|
|
3
|
+
inputTokens: number;
|
|
4
|
+
outputTokens: number;
|
|
5
|
+
}
|
|
6
|
+
interface GroupedUsage {
|
|
7
|
+
key: string;
|
|
8
|
+
inputTokens: number;
|
|
9
|
+
outputTokens: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function tokensView(daily: DailyUsage[], byPipeline: GroupedUsage[], byStage: GroupedUsage[]): string;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/views/tokens.ts"],"names":[],"mappings":"AAEA,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAgCD,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EAAE,EACnB,UAAU,EAAE,YAAY,EAAE,EAC1B,OAAO,EAAE,YAAY,EAAE,GACtB,MAAM,CAmDR"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { escapeHtml } from "./layout.js";
|
|
2
|
+
function formatTokenCount(n) {
|
|
3
|
+
if (n < 1000)
|
|
4
|
+
return String(n);
|
|
5
|
+
if (n < 1_000_000)
|
|
6
|
+
return `${(n / 1000).toFixed(1)}k`;
|
|
7
|
+
return `${(n / 1_000_000).toFixed(2)}M`;
|
|
8
|
+
}
|
|
9
|
+
function barChart(items, maxVal) {
|
|
10
|
+
if (maxVal === 0)
|
|
11
|
+
maxVal = 1;
|
|
12
|
+
return `<div class="bar-chart">
|
|
13
|
+
${items
|
|
14
|
+
.map((item) => `<div class="bar-row">
|
|
15
|
+
<span class="bar-label">${escapeHtml(item.label)}</span>
|
|
16
|
+
<div class="bar-track">
|
|
17
|
+
<div class="bar-fill bar-fill-input" style="width:${((item.input / maxVal) * 100).toFixed(1)}%"></div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="bar-track">
|
|
20
|
+
<div class="bar-fill bar-fill-output" style="width:${((item.output / maxVal) * 100).toFixed(1)}%"></div>
|
|
21
|
+
</div>
|
|
22
|
+
<span class="bar-value">in: ${formatTokenCount(item.input)} / out: ${formatTokenCount(item.output)}</span>
|
|
23
|
+
</div>`)
|
|
24
|
+
.join("\n")}
|
|
25
|
+
</div>
|
|
26
|
+
<div style="font-size:0.75rem;color:var(--color-text-muted);margin-bottom:1rem;">
|
|
27
|
+
<span style="display:inline-block;width:0.75rem;height:0.75rem;background:var(--color-blue);border-radius:2px;vertical-align:middle;margin-right:0.25rem;"></span> Input
|
|
28
|
+
<span style="display:inline-block;width:0.75rem;height:0.75rem;background:#8b5cf6;border-radius:2px;vertical-align:middle;margin-left:0.75rem;margin-right:0.25rem;"></span> Output
|
|
29
|
+
</div>`;
|
|
30
|
+
}
|
|
31
|
+
export function tokensView(daily, byPipeline, byStage) {
|
|
32
|
+
const totalInput = daily.reduce((s, d) => s + d.inputTokens, 0);
|
|
33
|
+
const totalOutput = daily.reduce((s, d) => s + d.outputTokens, 0);
|
|
34
|
+
const dailyMax = Math.max(...daily.map((d) => Math.max(d.inputTokens, d.outputTokens)), 1);
|
|
35
|
+
const pipelineMax = Math.max(...byPipeline.map((p) => Math.max(p.inputTokens, p.outputTokens)), 1);
|
|
36
|
+
const stageMax = Math.max(...byStage.map((s) => Math.max(s.inputTokens, s.outputTokens)), 1);
|
|
37
|
+
return `
|
|
38
|
+
<div class="card">
|
|
39
|
+
<h2>Summary</h2>
|
|
40
|
+
<p>Total input tokens: <strong>${formatTokenCount(totalInput)}</strong> ·
|
|
41
|
+
Total output tokens: <strong>${formatTokenCount(totalOutput)}</strong> ·
|
|
42
|
+
Combined: <strong>${formatTokenCount(totalInput + totalOutput)}</strong></p>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="card">
|
|
46
|
+
<h2>Daily Usage (last 30 days)</h2>
|
|
47
|
+
${daily.length > 0
|
|
48
|
+
? barChart(daily.map((d) => ({ label: d.date, input: d.inputTokens, output: d.outputTokens })), dailyMax)
|
|
49
|
+
: '<p style="color:var(--color-text-muted)">No data</p>'}
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="card">
|
|
53
|
+
<h2>By Pipeline</h2>
|
|
54
|
+
${byPipeline.length > 0
|
|
55
|
+
? barChart(byPipeline.map((p) => ({ label: p.key, input: p.inputTokens, output: p.outputTokens })), pipelineMax)
|
|
56
|
+
: '<p style="color:var(--color-text-muted)">No data</p>'}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="card">
|
|
60
|
+
<h2>By Stage</h2>
|
|
61
|
+
${byStage.length > 0
|
|
62
|
+
? barChart(byStage.map((s) => ({ label: s.key, input: s.inputTokens, output: s.outputTokens })), stageMax)
|
|
63
|
+
: '<p style="color:var(--color-text-muted)">No data</p>'}
|
|
64
|
+
</div>`;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/views/tokens.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@urateam/dashboard",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/server.js",
|
|
6
|
+
"types": "dist/server.d.ts",
|
|
7
|
+
"files": ["dist"],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/JonB32/urateam.git",
|
|
11
|
+
"directory": "packages/dashboard"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"dev": "tsx watch src/server.ts"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@hono/node-server": "^1.14.0",
|
|
20
|
+
"@urateam/core": "workspace:*",
|
|
21
|
+
"drizzle-orm": "^0.38.0",
|
|
22
|
+
"hono": "^4.7.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"vitest": "^3.0.0",
|
|
26
|
+
"typescript": "^5.7.0"
|
|
27
|
+
}
|
|
28
|
+
}
|