claudeye 0.2.2 → 0.3.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.
Potentially problematic release.
This version of claudeye might be problematic. Click here for more details.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/routes-manifest.json +9 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +5 -5
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +5 -5
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +6 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/icon.png/route.js +2 -1
- package/.next/standalone/.next/server/app/icon.png/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +35 -5
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +4 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f408c708._.js +21 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fde83e67._.js +3 -0
- package/.next/standalone/.next/server/chunks/ce889_server_app_api_download_[project]_[session]_route_actions_bbdd823f.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_64175717.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__070e2009._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0a745465._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__bc37261c._.js → [root-of-the-server]__14f58da3._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__164d9311._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__2822fd21._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__31b4c2fd._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__45656df2._.js → [root-of-the-server]__4e339665._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__55018089._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__6313e929._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__7e21395a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ee388ee0._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0b4924bd._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_9e089768._.js → _1404b353._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_3d21dde5._.js +8 -0
- package/.next/standalone/.next/server/chunks/ssr/_fd9b1ff7._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_7769b563._.js → node_modules_next_dist_esm_eedfc1fd._.js} +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +35 -5
- package/.next/standalone/.next/static/chunks/0e266948a26a3cdf.js +1 -0
- package/.next/standalone/.next/static/chunks/2774382cf796393c.js +4 -0
- package/.next/standalone/.next/static/chunks/6189ca16caad4352.js +3 -0
- package/.next/standalone/.next/static/chunks/8111dbe882e31821.js +1 -0
- package/.next/standalone/.next/static/chunks/{5a424275276f2bb9.js → bdeaeb8c9876394b.js} +1 -1
- package/.next/standalone/.next/static/chunks/cdbb6932218650fd.js +1 -0
- package/.next/standalone/.next/static/chunks/ea03555bb726c073.css +1 -0
- package/.next/standalone/.next/static/chunks/f091501564eb2ea3.js +4 -0
- package/.next/standalone/.next/static/chunks/{turbopack-2315171089e56fad.js → turbopack-fc1f23734a087d36.js} +1 -1
- package/.next/standalone/README.md +528 -41
- package/.next/standalone/app/actions/run-enrichments.ts +26 -5
- package/.next/standalone/app/actions/run-evals.ts +26 -5
- package/.next/standalone/app/actions/run-subagent-enrichments.ts +89 -0
- package/.next/standalone/app/actions/run-subagent-evals.ts +88 -0
- package/.next/standalone/app/api/download/[project]/[session]/route.ts +49 -0
- package/.next/standalone/app/components/copy-button.tsx +37 -0
- package/.next/standalone/app/components/enrichment-results-panel.tsx +33 -13
- package/.next/standalone/app/components/eval-results-panel.tsx +33 -13
- package/.next/standalone/app/components/log-viewer/entry-row.tsx +43 -14
- package/.next/standalone/app/components/log-viewer/queue-divider.tsx +50 -7
- package/.next/standalone/app/components/log-viewer/tool-input-output.tsx +13 -3
- package/.next/standalone/app/components/project-list.tsx +11 -11
- package/.next/standalone/app/components/raw-log-viewer.tsx +80 -11
- package/.next/standalone/app/components/refresh-button.tsx +79 -0
- package/.next/standalone/app/components/sessions-list.tsx +23 -14
- package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +23 -12
- package/.next/standalone/bin/claudeye.mjs +112 -25
- package/.next/standalone/components/navbar.tsx +2 -0
- package/.next/standalone/dist/app.js +10 -4
- package/.next/standalone/dist/condition-registry.js +20 -0
- package/.next/standalone/dist/enrich-registry.js +26 -3
- package/.next/standalone/dist/enrich-runner.js +68 -13
- package/.next/standalone/dist/registry.js +26 -3
- package/.next/standalone/dist/runner.js +78 -20
- package/.next/standalone/dist/server-spawn.js +58 -34
- package/.next/standalone/lib/cache/hash.ts +67 -0
- package/.next/standalone/lib/cache/index.ts +9 -0
- package/.next/standalone/lib/cache/local-backend.ts +81 -0
- package/.next/standalone/lib/cache/manager.ts +127 -0
- package/.next/standalone/lib/cache/types.ts +19 -0
- package/.next/standalone/lib/evals/app.ts +30 -7
- package/.next/standalone/lib/evals/condition-registry.ts +26 -0
- package/.next/standalone/lib/evals/enrich-registry.ts +29 -3
- package/.next/standalone/lib/evals/enrich-runner.ts +68 -14
- package/.next/standalone/lib/evals/enrich-types.ts +6 -1
- package/.next/standalone/lib/evals/index.ts +3 -1
- package/.next/standalone/lib/evals/registry.ts +29 -4
- package/.next/standalone/lib/evals/runner.ts +77 -20
- package/.next/standalone/lib/evals/server-spawn.ts +67 -41
- package/.next/standalone/lib/evals/types.ts +16 -0
- package/.next/standalone/lib/log-format.ts +22 -1
- package/.next/standalone/package-lock.json +244 -308
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/dev.ts +3 -1
- package/.next/standalone/scripts/parse-script-args.ts +30 -2
- package/.next/standalone/scripts/start.ts +3 -1
- package/.next/standalone/tsconfig.tsbuildinfo +1 -1
- package/README.md +528 -41
- package/bin/claudeye.mjs +112 -25
- package/dist/app.d.ts +17 -3
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +10 -4
- package/dist/app.js.map +1 -1
- package/dist/condition-registry.d.ts +9 -0
- package/dist/condition-registry.d.ts.map +1 -0
- package/dist/condition-registry.js +20 -0
- package/dist/condition-registry.js.map +1 -0
- package/dist/enrich-registry.d.ts +5 -1
- package/dist/enrich-registry.d.ts.map +1 -1
- package/dist/enrich-registry.js +26 -3
- package/dist/enrich-registry.js.map +1 -1
- package/dist/enrich-runner.d.ts +3 -3
- package/dist/enrich-runner.d.ts.map +1 -1
- package/dist/enrich-runner.js +68 -13
- package/dist/enrich-runner.js.map +1 -1
- package/dist/enrich-types.d.ts +6 -1
- package/dist/enrich-types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/registry.d.ts +5 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +26 -3
- package/dist/registry.js.map +1 -1
- package/dist/runner.d.ts +2 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +78 -20
- package/dist/runner.js.map +1 -1
- package/dist/server-spawn.d.ts +2 -1
- package/dist/server-spawn.d.ts.map +1 -1
- package/dist/server-spawn.js +58 -34
- package/dist/server-spawn.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__24a1e50a._.js +0 -21
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__32f115c9._.js +0 -6
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__476a1712._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4ddcabf2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ad593585._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__afd8e13b._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__dd7ee810._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ff3004de._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_53472598._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_863b6ca8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_f7347c74._.js +0 -5
- package/.next/standalone/.next/static/chunks/2243ff2814e7a781.js +0 -3
- package/.next/standalone/.next/static/chunks/50531467396cea91.css +0 -1
- package/.next/standalone/.next/static/chunks/8f288c01f8d7ef2d.js +0 -1
- package/.next/standalone/.next/static/chunks/abab1b00b2788443.js +0 -4
- package/.next/standalone/.next/static/chunks/d250d7f6f0a8c325.js +0 -1
- package/.next/standalone/.next/static/chunks/d7a572a8b7eb1ec8.js +0 -1
- package/.next/standalone/.next/static/chunks/fb1b0b9da3f03023.js +0 -4
- /package/.next/standalone/.next/static/{LoGIEEP4cORCqcFv-Ywg0 → 5JsV7rfAEOIwNOQPaX3UP}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{LoGIEEP4cORCqcFv-Ywg0 → 5JsV7rfAEOIwNOQPaX3UP}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{LoGIEEP4cORCqcFv-Ywg0 → 5JsV7rfAEOIwNOQPaX3UP}/_ssgManifest.js +0 -0
|
@@ -14,20 +14,34 @@ const node_net_1 = require("node:net");
|
|
|
14
14
|
const node_path_1 = require("node:path");
|
|
15
15
|
const node_fs_1 = require("node:fs");
|
|
16
16
|
const node_os_1 = require("node:os");
|
|
17
|
-
function
|
|
18
|
-
|
|
17
|
+
function getNetworkAddress() {
|
|
18
|
+
for (const addrs of Object.values((0, node_os_1.networkInterfaces)())) {
|
|
19
|
+
for (const iface of addrs) {
|
|
20
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
21
|
+
return iface.address;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return "0.0.0.0";
|
|
26
|
+
}
|
|
27
|
+
function resolveBindAddress(host) {
|
|
28
|
+
return host === "localhost" ? "127.0.0.1" : host;
|
|
29
|
+
}
|
|
30
|
+
function findAvailablePort(preferred, host = "localhost") {
|
|
31
|
+
const bindAddr = resolveBindAddress(host);
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
19
33
|
const srv = (0, node_net_1.createServer)();
|
|
20
|
-
srv.listen(preferred,
|
|
21
|
-
srv.close(() =>
|
|
34
|
+
srv.listen(preferred, bindAddr, () => {
|
|
35
|
+
srv.close(() => resolve(preferred));
|
|
22
36
|
});
|
|
23
37
|
srv.on("error", () => {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const addr =
|
|
38
|
+
const fallback = (0, node_net_1.createServer)();
|
|
39
|
+
fallback.listen(0, bindAddr, () => {
|
|
40
|
+
const addr = fallback.address();
|
|
27
41
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
28
|
-
|
|
42
|
+
fallback.close(() => resolve(port));
|
|
29
43
|
});
|
|
30
|
-
|
|
44
|
+
fallback.on("error", reject);
|
|
31
45
|
});
|
|
32
46
|
});
|
|
33
47
|
}
|
|
@@ -81,41 +95,52 @@ function resolveServerInfo() {
|
|
|
81
95
|
// Fallback to dev mode
|
|
82
96
|
return { mode: "dev" };
|
|
83
97
|
}
|
|
84
|
-
|
|
85
|
-
const port = await findAvailablePort(preferredPort);
|
|
86
|
-
if (port !== preferredPort) {
|
|
87
|
-
console.log(`Port ${preferredPort} is busy, using port ${port} instead.`);
|
|
88
|
-
}
|
|
89
|
-
const url = `http://localhost:${port}`;
|
|
90
|
-
const evalsModule = process.argv[1] ? (0, node_path_1.resolve)(process.argv[1]) : undefined;
|
|
98
|
+
function logServerInfo(host, port, localUrl, evalsModule) {
|
|
91
99
|
console.log(`Starting Claudeye dashboard...`);
|
|
92
100
|
if (evalsModule) {
|
|
93
101
|
console.log(` Evals: ${evalsModule}`);
|
|
94
102
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
env.CLAUDEYE_EVALS_MODULE = evalsModule;
|
|
103
|
+
if (host === "0.0.0.0") {
|
|
104
|
+
console.log(` Local: ${localUrl}`);
|
|
105
|
+
console.log(` Network: http://${getNetworkAddress()}:${port}`);
|
|
99
106
|
}
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
else {
|
|
108
|
+
console.log(` URL: http://${host}:${port}`);
|
|
109
|
+
}
|
|
110
|
+
console.log();
|
|
111
|
+
}
|
|
112
|
+
function spawnChildProcess(serverInfo, env, port) {
|
|
102
113
|
if (serverInfo.mode === "standalone") {
|
|
103
|
-
|
|
114
|
+
return (0, node_child_process_1.spawn)(process.execPath, [serverInfo.script], {
|
|
104
115
|
cwd: serverInfo.cwd,
|
|
105
116
|
stdio: "inherit",
|
|
106
117
|
env,
|
|
107
118
|
});
|
|
108
119
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
return (0, node_child_process_1.spawn)("npx", ["next", "dev", "--port", String(port)], {
|
|
121
|
+
stdio: "inherit",
|
|
122
|
+
shell: true,
|
|
123
|
+
env,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async function spawnServer(preferredPort, options) {
|
|
127
|
+
var _a;
|
|
128
|
+
const host = (_a = options.host) !== null && _a !== void 0 ? _a : "localhost";
|
|
129
|
+
const port = await findAvailablePort(preferredPort, host);
|
|
130
|
+
if (port !== preferredPort) {
|
|
131
|
+
console.log(`Port ${preferredPort} is busy, using port ${port} instead.`);
|
|
132
|
+
}
|
|
133
|
+
const localUrl = `http://localhost:${port}`;
|
|
134
|
+
const evalsModule = process.argv[1] ? (0, node_path_1.resolve)(process.argv[1]) : undefined;
|
|
135
|
+
logServerInfo(host, port, localUrl, evalsModule);
|
|
136
|
+
const env = Object.assign(Object.assign({}, process.env), { PORT: String(port), HOSTNAME: host });
|
|
137
|
+
if (evalsModule) {
|
|
138
|
+
env.CLAUDEYE_EVALS_MODULE = evalsModule;
|
|
115
139
|
}
|
|
140
|
+
const child = spawnChildProcess(resolveServerInfo(), env, port);
|
|
116
141
|
if (options.open) {
|
|
117
|
-
waitForServer(
|
|
118
|
-
console.warn(`\nServer did not respond within 15 s.\nOpen manually: ${
|
|
142
|
+
waitForServer(localUrl).then(() => openBrowser(localUrl)).catch(() => {
|
|
143
|
+
console.warn(`\nServer did not respond within 15 s.\nOpen manually: ${localUrl}\n`);
|
|
119
144
|
});
|
|
120
145
|
}
|
|
121
146
|
// Forward signals for clean shutdown
|
|
@@ -125,12 +150,11 @@ async function spawnServer(preferredPort, options) {
|
|
|
125
150
|
};
|
|
126
151
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
127
152
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
128
|
-
|
|
153
|
+
// Block until the child exits — server runs until killed
|
|
154
|
+
await new Promise(() => {
|
|
129
155
|
child.on("exit", (code) => {
|
|
130
156
|
process.exit(code !== null && code !== void 0 ? code : 0);
|
|
131
157
|
});
|
|
132
|
-
// Never resolves — server runs until killed
|
|
133
|
-
void res;
|
|
134
158
|
});
|
|
135
159
|
}
|
|
136
160
|
//# sourceMappingURL=server-spawn.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { stat, readFile } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { getClaudeProjectsPath } from "../paths";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hashes a session file using its mtime + size for speed.
|
|
8
|
+
* JSONL session files are append-only, so mtime+size reliably
|
|
9
|
+
* detects changes without reading the full file.
|
|
10
|
+
*/
|
|
11
|
+
export async function hashSessionFile(
|
|
12
|
+
projectName: string,
|
|
13
|
+
sessionId: string,
|
|
14
|
+
): Promise<string> {
|
|
15
|
+
const projectsPath = getClaudeProjectsPath();
|
|
16
|
+
const filePath = join(projectsPath, projectName, `${sessionId}.jsonl`);
|
|
17
|
+
const s = await stat(filePath);
|
|
18
|
+
const content = `${s.mtimeMs}:${s.size}`;
|
|
19
|
+
return createHash("sha256").update(content).digest("hex");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hashes a subagent log file using its mtime + size for speed.
|
|
24
|
+
* Tries multiple candidate paths (same logic as load-subagent-log)
|
|
25
|
+
* and hashes the first one found. Returns empty string if none found.
|
|
26
|
+
*/
|
|
27
|
+
export async function hashSubagentFile(
|
|
28
|
+
projectName: string,
|
|
29
|
+
sessionId: string,
|
|
30
|
+
agentId: string,
|
|
31
|
+
): Promise<string> {
|
|
32
|
+
const projectsPath = getClaudeProjectsPath();
|
|
33
|
+
const fileName = `agent-${agentId}.jsonl`;
|
|
34
|
+
const candidatePaths = [
|
|
35
|
+
join(projectsPath, projectName, fileName),
|
|
36
|
+
join(projectsPath, projectName, sessionId, fileName),
|
|
37
|
+
join(projectsPath, projectName, sessionId, "subagents", fileName),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const filePath of candidatePaths) {
|
|
41
|
+
try {
|
|
42
|
+
const s = await stat(filePath);
|
|
43
|
+
const content = `${s.mtimeMs}:${s.size}`;
|
|
44
|
+
return createHash("sha256").update(content).digest("hex");
|
|
45
|
+
} catch {
|
|
46
|
+
// Try next candidate
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Hashes the evals module file content. These files are small,
|
|
55
|
+
* so a full content hash is fine.
|
|
56
|
+
* Returns empty string if no evals module is configured.
|
|
57
|
+
*/
|
|
58
|
+
export async function hashEvalsModule(): Promise<string> {
|
|
59
|
+
const evalsModule = process.env.CLAUDEYE_EVALS_MODULE;
|
|
60
|
+
if (!evalsModule) return "";
|
|
61
|
+
try {
|
|
62
|
+
const content = await readFile(evalsModule, "utf-8");
|
|
63
|
+
return createHash("sha256").update(content).digest("hex");
|
|
64
|
+
} catch {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { CacheBackend, CacheMeta, CacheEntry } from "./types";
|
|
2
|
+
export { hashSessionFile, hashSubagentFile, hashEvalsModule } from "./hash";
|
|
3
|
+
export { LocalCacheBackend } from "./local-backend";
|
|
4
|
+
export {
|
|
5
|
+
initCacheBackend,
|
|
6
|
+
getCachedResult,
|
|
7
|
+
setCachedResult,
|
|
8
|
+
closeCacheBackend,
|
|
9
|
+
} from "./manager";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { readFile, writeFile, unlink, readdir, rm, mkdir } from "fs/promises";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import type { CacheBackend, CacheEntry, CacheMeta } from "./types";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CACHE_PATH = join(homedir(), ".claudeye", "cache");
|
|
7
|
+
|
|
8
|
+
export class LocalCacheBackend implements CacheBackend {
|
|
9
|
+
private basePath: string;
|
|
10
|
+
|
|
11
|
+
constructor(basePath?: string) {
|
|
12
|
+
this.basePath = basePath || DEFAULT_CACHE_PATH;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private keyToPath(key: string): string {
|
|
16
|
+
return join(this.basePath, `${key}.json`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async get<T>(key: string): Promise<CacheEntry<T> | null> {
|
|
20
|
+
try {
|
|
21
|
+
const filePath = this.keyToPath(key);
|
|
22
|
+
const content = await readFile(filePath, "utf-8");
|
|
23
|
+
return JSON.parse(content) as CacheEntry<T>;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async set<T>(key: string, value: T, meta: CacheMeta): Promise<void> {
|
|
30
|
+
try {
|
|
31
|
+
const filePath = this.keyToPath(key);
|
|
32
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
33
|
+
const entry: CacheEntry<T> = { value, meta };
|
|
34
|
+
await writeFile(filePath, JSON.stringify(entry), "utf-8");
|
|
35
|
+
} catch {
|
|
36
|
+
// Cache write failures should never break eval execution
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async invalidate(key: string): Promise<void> {
|
|
41
|
+
try {
|
|
42
|
+
await unlink(this.keyToPath(key));
|
|
43
|
+
} catch {
|
|
44
|
+
// Ignore if file doesn't exist
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async invalidateByPrefix(prefix: string): Promise<void> {
|
|
49
|
+
try {
|
|
50
|
+
// Split prefix into directory part and filename prefix.
|
|
51
|
+
// "evals/project-a/" → dir segments = ["evals","project-a"], filePrefix = ""
|
|
52
|
+
// "evals/project-a/s" → dir segments = ["evals","project-a"], filePrefix = "s"
|
|
53
|
+
const parts = prefix.split("/");
|
|
54
|
+
const filePrefix = parts.pop() || "";
|
|
55
|
+
const dir = join(this.basePath, ...parts);
|
|
56
|
+
const entries = await readdir(dir);
|
|
57
|
+
for (const name of entries) {
|
|
58
|
+
if (name.endsWith(".json") && name.startsWith(filePrefix)) {
|
|
59
|
+
await unlink(join(dir, name));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// Ignore errors during invalidation
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async close(): Promise<void> {
|
|
68
|
+
// No-op for file backend
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Remove the entire cache directory. Used by --cache-clear.
|
|
73
|
+
*/
|
|
74
|
+
async clearAll(): Promise<void> {
|
|
75
|
+
try {
|
|
76
|
+
await rm(this.basePath, { recursive: true, force: true });
|
|
77
|
+
} catch {
|
|
78
|
+
// Ignore if directory doesn't exist
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { LocalCacheBackend } from "./local-backend";
|
|
2
|
+
import { hashSessionFile, hashEvalsModule } from "./hash";
|
|
3
|
+
import type { CacheBackend, CacheEntry } from "./types";
|
|
4
|
+
|
|
5
|
+
const BACKEND_KEY = "__CLAUDEYE_CACHE_BACKEND__";
|
|
6
|
+
const DISABLED_KEY = "__CLAUDEYE_CACHE_DISABLED__";
|
|
7
|
+
|
|
8
|
+
interface GlobalWithCache {
|
|
9
|
+
[BACKEND_KEY]?: CacheBackend;
|
|
10
|
+
[DISABLED_KEY]?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize the cache backend (idempotent).
|
|
15
|
+
* Reads CLAUDEYE_CACHE and CLAUDEYE_CACHE_PATH env vars.
|
|
16
|
+
* Stores backend in globalThis to survive Next.js hot reloading.
|
|
17
|
+
*/
|
|
18
|
+
export function initCacheBackend(): CacheBackend | null {
|
|
19
|
+
const g = globalThis as GlobalWithCache;
|
|
20
|
+
|
|
21
|
+
if (g[DISABLED_KEY]) return null;
|
|
22
|
+
|
|
23
|
+
if (g[BACKEND_KEY]) return g[BACKEND_KEY];
|
|
24
|
+
|
|
25
|
+
const cacheEnv = process.env.CLAUDEYE_CACHE;
|
|
26
|
+
if (cacheEnv === "off") {
|
|
27
|
+
g[DISABLED_KEY] = true;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cachePath = process.env.CLAUDEYE_CACHE_PATH || undefined;
|
|
32
|
+
const backend = new LocalCacheBackend(cachePath);
|
|
33
|
+
g[BACKEND_KEY] = backend;
|
|
34
|
+
return backend;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cacheKey(kind: "evals" | "enrichments", projectName: string, sessionKey: string): string {
|
|
38
|
+
return `${kind}/${projectName}/${sessionKey}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Look up cached result. Returns null on any mismatch or cache miss.
|
|
43
|
+
* `sessionKey` can be a plain session ID or a composite key like `sessionId/agent-xxx`.
|
|
44
|
+
* When `overrideContentHash` is provided, it is used instead of computing via `hashSessionFile()`.
|
|
45
|
+
*/
|
|
46
|
+
export async function getCachedResult<T>(
|
|
47
|
+
kind: "evals" | "enrichments",
|
|
48
|
+
projectName: string,
|
|
49
|
+
sessionKey: string,
|
|
50
|
+
registeredNames: string[],
|
|
51
|
+
overrideContentHash?: string,
|
|
52
|
+
): Promise<(CacheEntry<T> & { cached: true }) | null> {
|
|
53
|
+
const backend = initCacheBackend();
|
|
54
|
+
if (!backend) return null;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const entry = await backend.get<T>(cacheKey(kind, projectName, sessionKey));
|
|
58
|
+
if (!entry) return null;
|
|
59
|
+
|
|
60
|
+
// Validate content hash (session/subagent file changed?)
|
|
61
|
+
const currentContentHash = overrideContentHash ?? await hashSessionFile(projectName, sessionKey);
|
|
62
|
+
if (entry.meta.contentHash !== currentContentHash) return null;
|
|
63
|
+
|
|
64
|
+
// Validate evals module hash (evals file changed?)
|
|
65
|
+
const currentEvalsHash = await hashEvalsModule();
|
|
66
|
+
if (entry.meta.evalsModuleHash !== currentEvalsHash) return null;
|
|
67
|
+
|
|
68
|
+
// Validate registered names match
|
|
69
|
+
const cachedNames = [...entry.meta.registeredNames].sort();
|
|
70
|
+
const currentNames = [...registeredNames].sort();
|
|
71
|
+
if (
|
|
72
|
+
cachedNames.length !== currentNames.length ||
|
|
73
|
+
cachedNames.some((n, i) => n !== currentNames[i])
|
|
74
|
+
) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { ...entry, cached: true };
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Store result in cache. Fire-and-forget — errors are swallowed.
|
|
86
|
+
* `sessionKey` can be a plain session ID or a composite key like `sessionId/agent-xxx`.
|
|
87
|
+
* When `overrideContentHash` is provided, it is used instead of computing via `hashSessionFile()`.
|
|
88
|
+
*/
|
|
89
|
+
export async function setCachedResult<T>(
|
|
90
|
+
kind: "evals" | "enrichments",
|
|
91
|
+
projectName: string,
|
|
92
|
+
sessionKey: string,
|
|
93
|
+
value: T,
|
|
94
|
+
registeredNames: string[],
|
|
95
|
+
overrideContentHash?: string,
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
const backend = initCacheBackend();
|
|
98
|
+
if (!backend) return;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const [contentHash, evalsModuleHash] = await Promise.all([
|
|
102
|
+
overrideContentHash ? Promise.resolve(overrideContentHash) : hashSessionFile(projectName, sessionKey),
|
|
103
|
+
hashEvalsModule(),
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
await backend.set(cacheKey(kind, projectName, sessionKey), value, {
|
|
107
|
+
cachedAt: new Date().toISOString(),
|
|
108
|
+
contentHash,
|
|
109
|
+
evalsModuleHash,
|
|
110
|
+
registeredNames,
|
|
111
|
+
});
|
|
112
|
+
} catch {
|
|
113
|
+
// Cache write failures should never break eval execution
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Close the cache backend for clean shutdown.
|
|
119
|
+
*/
|
|
120
|
+
export async function closeCacheBackend(): Promise<void> {
|
|
121
|
+
const g = globalThis as GlobalWithCache;
|
|
122
|
+
const backend = g[BACKEND_KEY];
|
|
123
|
+
if (backend) {
|
|
124
|
+
await backend.close();
|
|
125
|
+
delete g[BACKEND_KEY];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface CacheBackend {
|
|
2
|
+
get<T>(key: string): Promise<CacheEntry<T> | null>;
|
|
3
|
+
set<T>(key: string, value: T, meta: CacheMeta): Promise<void>;
|
|
4
|
+
invalidate(key: string): Promise<void>;
|
|
5
|
+
invalidateByPrefix(prefix: string): Promise<void>;
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CacheMeta {
|
|
10
|
+
cachedAt: string;
|
|
11
|
+
contentHash: string; // session file mtime+size hash
|
|
12
|
+
evalsModuleHash: string; // evals file content hash
|
|
13
|
+
registeredNames: string[]; // eval/enricher names at cache time
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CacheEntry<T> {
|
|
17
|
+
value: T;
|
|
18
|
+
meta: CacheMeta;
|
|
19
|
+
}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { registerEval } from "./registry";
|
|
11
11
|
import { registerEnricher } from "./enrich-registry";
|
|
12
|
-
import
|
|
12
|
+
import { setGlobalCondition } from "./condition-registry";
|
|
13
|
+
import type { ConditionFunction, EvalFunction, EvalScope } from "./types";
|
|
13
14
|
import type { EnrichFunction } from "./enrich-types";
|
|
14
15
|
|
|
15
16
|
const LOADING_KEY = "__CLAUDEYE_LOADING_EVALS__";
|
|
@@ -21,28 +22,49 @@ interface GlobalWithLoading {
|
|
|
21
22
|
export interface ClaudeyeAppOptions {
|
|
22
23
|
/** Port for the dashboard (default: 8020). */
|
|
23
24
|
port?: number;
|
|
25
|
+
/** Host to bind to (default: "localhost"). Use "0.0.0.0" for LAN access. */
|
|
26
|
+
host?: string;
|
|
24
27
|
/** Whether to auto-open the browser (default: true). */
|
|
25
28
|
open?: boolean;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
export interface EvalOptions {
|
|
32
|
+
condition?: ConditionFunction;
|
|
33
|
+
scope?: EvalScope;
|
|
34
|
+
subagentType?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EnrichOptions {
|
|
38
|
+
condition?: ConditionFunction;
|
|
39
|
+
scope?: EvalScope;
|
|
40
|
+
subagentType?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
export interface ClaudeyeApp {
|
|
44
|
+
/** Register a global condition that gates all evals and enrichments. Chainable. */
|
|
45
|
+
condition(fn: ConditionFunction): ClaudeyeApp;
|
|
29
46
|
/** Register an eval function. Chainable. */
|
|
30
|
-
eval(name: string, fn: EvalFunction): ClaudeyeApp;
|
|
47
|
+
eval(name: string, fn: EvalFunction, options?: EvalOptions): ClaudeyeApp;
|
|
31
48
|
/** Register an enricher function. Chainable. */
|
|
32
|
-
enrich(name: string, fn: EnrichFunction): ClaudeyeApp;
|
|
49
|
+
enrich(name: string, fn: EnrichFunction, options?: EnrichOptions): ClaudeyeApp;
|
|
33
50
|
/** Start the Claudeye dashboard server. No-op when loading evals in the Next.js process. */
|
|
34
51
|
listen(port?: number, options?: ClaudeyeAppOptions): Promise<void>;
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
export function createApp(): ClaudeyeApp {
|
|
38
55
|
const app: ClaudeyeApp = {
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
condition(fn: ConditionFunction): ClaudeyeApp {
|
|
57
|
+
setGlobalCondition(fn);
|
|
58
|
+
return app;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
eval(name: string, fn: EvalFunction, options?: EvalOptions): ClaudeyeApp {
|
|
62
|
+
registerEval(name, fn, options?.condition, options?.scope, options?.subagentType);
|
|
41
63
|
return app;
|
|
42
64
|
},
|
|
43
65
|
|
|
44
|
-
enrich(name: string, fn: EnrichFunction): ClaudeyeApp {
|
|
45
|
-
registerEnricher(name, fn);
|
|
66
|
+
enrich(name: string, fn: EnrichFunction, options?: EnrichOptions): ClaudeyeApp {
|
|
67
|
+
registerEnricher(name, fn, options?.condition, options?.scope, options?.subagentType);
|
|
46
68
|
return app;
|
|
47
69
|
},
|
|
48
70
|
|
|
@@ -59,6 +81,7 @@ export function createApp(): ClaudeyeApp {
|
|
|
59
81
|
const { spawnServer } = await import("./server-spawn");
|
|
60
82
|
await spawnServer(port ?? options?.port ?? 8020, {
|
|
61
83
|
open: options?.open ?? true,
|
|
84
|
+
host: options?.host,
|
|
62
85
|
});
|
|
63
86
|
},
|
|
64
87
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-level singleton condition registry backed by globalThis.
|
|
3
|
+
* Stores an optional global condition function that gates all evals and enrichments.
|
|
4
|
+
*/
|
|
5
|
+
import type { ConditionFunction } from "./types";
|
|
6
|
+
|
|
7
|
+
const REGISTRY_KEY = "__CLAUDEYE_CONDITION_REGISTRY__";
|
|
8
|
+
|
|
9
|
+
interface GlobalWithCondition {
|
|
10
|
+
[REGISTRY_KEY]?: ConditionFunction | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function setGlobalCondition(fn: ConditionFunction): void {
|
|
14
|
+
const g = globalThis as GlobalWithCondition;
|
|
15
|
+
g[REGISTRY_KEY] = fn;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getGlobalCondition(): ConditionFunction | null {
|
|
19
|
+
const g = globalThis as GlobalWithCondition;
|
|
20
|
+
return g[REGISTRY_KEY] ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function clearGlobalCondition(): void {
|
|
24
|
+
const g = globalThis as GlobalWithCondition;
|
|
25
|
+
g[REGISTRY_KEY] = null;
|
|
26
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Using globalThis ensures the registry survives webpack chunk splitting
|
|
4
4
|
* and remains a true singleton across dynamic imports.
|
|
5
5
|
*/
|
|
6
|
+
import type { ConditionFunction, EvalScope } from "./types";
|
|
6
7
|
import type { EnrichFunction, RegisteredEnricher } from "./enrich-types";
|
|
7
8
|
|
|
8
9
|
const REGISTRY_KEY = "__CLAUDEYE_ENRICHER_REGISTRY__";
|
|
@@ -19,14 +20,23 @@ function getRegistry(): RegisteredEnricher[] {
|
|
|
19
20
|
return g[REGISTRY_KEY];
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export function registerEnricher(
|
|
23
|
+
export function registerEnricher(
|
|
24
|
+
name: string,
|
|
25
|
+
fn: EnrichFunction,
|
|
26
|
+
condition?: ConditionFunction,
|
|
27
|
+
scope: EvalScope = 'session',
|
|
28
|
+
subagentType?: string,
|
|
29
|
+
): void {
|
|
23
30
|
const registry = getRegistry();
|
|
31
|
+
const entry: RegisteredEnricher = { name, fn, scope };
|
|
32
|
+
if (condition) entry.condition = condition;
|
|
33
|
+
if (subagentType) entry.subagentType = subagentType;
|
|
24
34
|
// Replace if an enricher with the same name already exists
|
|
25
35
|
const idx = registry.findIndex((e) => e.name === name);
|
|
26
36
|
if (idx >= 0) {
|
|
27
|
-
registry[idx] =
|
|
37
|
+
registry[idx] = entry;
|
|
28
38
|
} else {
|
|
29
|
-
registry.push(
|
|
39
|
+
registry.push(entry);
|
|
30
40
|
}
|
|
31
41
|
}
|
|
32
42
|
|
|
@@ -34,6 +44,22 @@ export function getRegisteredEnrichers(): RegisteredEnricher[] {
|
|
|
34
44
|
return getRegistry();
|
|
35
45
|
}
|
|
36
46
|
|
|
47
|
+
export function getSessionScopedEnrichers(): RegisteredEnricher[] {
|
|
48
|
+
return getRegistry().filter((e) => e.scope === 'session' || e.scope === 'both');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getSubagentScopedEnrichers(subagentType?: string): RegisteredEnricher[] {
|
|
52
|
+
return getRegistry().filter((e) => {
|
|
53
|
+
if (e.scope !== 'subagent' && e.scope !== 'both') return false;
|
|
54
|
+
if (e.subagentType && subagentType && e.subagentType !== subagentType) return false;
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function hasSubagentEnrichers(): boolean {
|
|
60
|
+
return getRegistry().some((e) => e.scope === 'subagent' || e.scope === 'both');
|
|
61
|
+
}
|
|
62
|
+
|
|
37
63
|
export function hasEnrichers(): boolean {
|
|
38
64
|
return getRegistry().length > 0;
|
|
39
65
|
}
|