tanuki-telemetry 1.3.8 → 1.4.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/package.json +1 -1
- package/src/dashboard.ts +26 -9
- package/src/db.ts +12 -11
package/package.json
CHANGED
package/src/dashboard.ts
CHANGED
|
@@ -19,6 +19,8 @@ import type { Session, Event, Iteration, Screenshot, Artifact, Insight, PlanStep
|
|
|
19
19
|
import { listCoordinatorSessions, getCoordinatorState, getCoordinatorHistory } from "./coordinator.js";
|
|
20
20
|
import { fileURLToPath } from "url";
|
|
21
21
|
|
|
22
|
+
const DATA_DIR = process.env.DATA_DIR || "/data";
|
|
23
|
+
|
|
22
24
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
25
|
|
|
24
26
|
const app = express();
|
|
@@ -39,7 +41,7 @@ const SqliteStore = BetterSqlite3SessionStore(session);
|
|
|
39
41
|
// (the store hardcodes table name "sessions" which conflicts with our telemetry sessions table)
|
|
40
42
|
let sessionStoreInstance: InstanceType<typeof SqliteStore> | undefined;
|
|
41
43
|
if (AUTH_ENABLED) {
|
|
42
|
-
const dbPath = process.env.DB_PATH || "
|
|
44
|
+
const dbPath = process.env.DB_PATH || path.join(DATA_DIR, "telemetry.db");
|
|
43
45
|
const sessionDbPath = dbPath.replace(/\.db$/, "-sessions.db");
|
|
44
46
|
const sessionDb = new Database(sessionDbPath);
|
|
45
47
|
sessionDb.pragma("journal_mode = WAL");
|
|
@@ -97,13 +99,26 @@ app.get("/health", (_req, res) => {
|
|
|
97
99
|
res.json({ ok: true, version: TANUKI_VERSION });
|
|
98
100
|
});
|
|
99
101
|
|
|
102
|
+
/** True when `a` is strictly newer than `b` using major.minor.patch comparison. */
|
|
103
|
+
function isNewerVersion(a: string, b: string): boolean {
|
|
104
|
+
const pa = a.split(".").map(Number);
|
|
105
|
+
const pb = b.split(".").map(Number);
|
|
106
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
107
|
+
const va = pa[i] ?? 0;
|
|
108
|
+
const vb = pb[i] ?? 0;
|
|
109
|
+
if (va > vb) return true;
|
|
110
|
+
if (va < vb) return false;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
100
115
|
// Version endpoint
|
|
101
116
|
app.get("/api/version", async (_req, res) => {
|
|
102
117
|
const latest = await getLatestNpmVersion();
|
|
103
118
|
res.json({
|
|
104
119
|
current: TANUKI_VERSION,
|
|
105
120
|
latest: latest ?? TANUKI_VERSION,
|
|
106
|
-
updateAvailable: latest ? latest
|
|
121
|
+
updateAvailable: latest ? isNewerVersion(latest, TANUKI_VERSION) : false,
|
|
107
122
|
});
|
|
108
123
|
});
|
|
109
124
|
|
|
@@ -122,7 +137,7 @@ const upload = multer({
|
|
|
122
137
|
const d = getDb();
|
|
123
138
|
const session = d.prepare("SELECT worktree_name FROM sessions WHERE id = ?").get(sessionId) as { worktree_name: string } | undefined;
|
|
124
139
|
const dirName = session?.worktree_name || sessionId;
|
|
125
|
-
const dir = path.join(
|
|
140
|
+
const dir = path.join(DATA_DIR, dirName, "screenshots");
|
|
126
141
|
fs.mkdirSync(dir, { recursive: true });
|
|
127
142
|
cb(null, dir);
|
|
128
143
|
},
|
|
@@ -488,8 +503,10 @@ app.get("/api/artifacts/by-id/:id", (req, res) => {
|
|
|
488
503
|
const candidates = [
|
|
489
504
|
artifact.stored_path,
|
|
490
505
|
artifact.file_path,
|
|
491
|
-
artifact.file_path?.replace(/^.*?outputs\//, "/
|
|
492
|
-
artifact.file_path?.replace(/^.*?\.tanuki\/data\//, "/
|
|
506
|
+
artifact.file_path?.replace(/^.*?outputs\//, DATA_DIR + "/"),
|
|
507
|
+
artifact.file_path?.replace(/^.*?\.tanuki\/data\//, DATA_DIR + "/"),
|
|
508
|
+
artifact.file_path?.replace(/^.*?outputs\//, "/outputs/"),
|
|
509
|
+
artifact.file_path?.replace(/^.*?outputs/, "/outputs"),
|
|
493
510
|
].filter(Boolean) as string[];
|
|
494
511
|
|
|
495
512
|
for (const candidate of candidates) {
|
|
@@ -527,8 +544,8 @@ app.get("/api/screenshots/by-id/:id", (req, res) => {
|
|
|
527
544
|
const candidates = [
|
|
528
545
|
screenshot.stored_path,
|
|
529
546
|
screenshot.file_path,
|
|
530
|
-
screenshot.file_path?.replace(/^.*?outputs\//, "/
|
|
531
|
-
screenshot.file_path?.replace(/^.*?\.tanuki\/data\//, "/
|
|
547
|
+
screenshot.file_path?.replace(/^.*?outputs\//, DATA_DIR + "/"),
|
|
548
|
+
screenshot.file_path?.replace(/^.*?\.tanuki\/data\//, DATA_DIR + "/"),
|
|
532
549
|
].filter(Boolean) as string[];
|
|
533
550
|
|
|
534
551
|
for (const candidate of candidates) {
|
|
@@ -604,11 +621,11 @@ app.get("/api/screenshots/*", (req, res) => {
|
|
|
604
621
|
if (requestedPath.startsWith("/")) {
|
|
605
622
|
filePath = requestedPath;
|
|
606
623
|
} else {
|
|
607
|
-
filePath = path.join(
|
|
624
|
+
filePath = path.join(DATA_DIR, requestedPath);
|
|
608
625
|
}
|
|
609
626
|
|
|
610
627
|
const resolved = path.resolve(filePath);
|
|
611
|
-
if (!resolved.startsWith(
|
|
628
|
+
if (!resolved.startsWith(DATA_DIR)) {
|
|
612
629
|
res.status(403).json({ error: "Access denied" });
|
|
613
630
|
return;
|
|
614
631
|
}
|
package/src/db.ts
CHANGED
|
@@ -14,7 +14,8 @@ import type {
|
|
|
14
14
|
WalkthroughScreenshot,
|
|
15
15
|
} from "./types.js";
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const DATA_DIR = process.env.DATA_DIR || "/data";
|
|
18
|
+
const DB_PATH = process.env.DB_PATH || path.join(DATA_DIR, "telemetry.db");
|
|
18
19
|
|
|
19
20
|
let db: Database.Database;
|
|
20
21
|
|
|
@@ -256,15 +257,15 @@ function initTables(): void {
|
|
|
256
257
|
`);
|
|
257
258
|
|
|
258
259
|
// Ensure storage directories exist
|
|
259
|
-
const screenshotsDir = "
|
|
260
|
+
const screenshotsDir = path.join(DATA_DIR, "screenshots");
|
|
260
261
|
if (!fs.existsSync(screenshotsDir)) {
|
|
261
262
|
fs.mkdirSync(screenshotsDir, { recursive: true });
|
|
262
263
|
}
|
|
263
|
-
const artifactsDir = "
|
|
264
|
+
const artifactsDir = path.join(DATA_DIR, "artifacts");
|
|
264
265
|
if (!fs.existsSync(artifactsDir)) {
|
|
265
266
|
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
266
267
|
}
|
|
267
|
-
const walkthroughScreenshotsDir = "
|
|
268
|
+
const walkthroughScreenshotsDir = path.join(DATA_DIR, "walkthrough-screenshots");
|
|
268
269
|
if (!fs.existsSync(walkthroughScreenshotsDir)) {
|
|
269
270
|
fs.mkdirSync(walkthroughScreenshotsDir, { recursive: true });
|
|
270
271
|
}
|
|
@@ -404,15 +405,15 @@ export function insertScreenshot(
|
|
|
404
405
|
const ext = path.extname(file_path) || ".png";
|
|
405
406
|
const storedName = `${session_id}_${screenshotId}${ext}`;
|
|
406
407
|
const thumbName = `${session_id}_${screenshotId}_thumb${ext}`;
|
|
407
|
-
const storedPath = path.join(
|
|
408
|
-
const thumbPath = path.join(
|
|
408
|
+
const storedPath = path.join(DATA_DIR, "screenshots", storedName);
|
|
409
|
+
const thumbPath = path.join(DATA_DIR, "screenshots", thumbName);
|
|
409
410
|
|
|
410
411
|
try {
|
|
411
412
|
// The file_path might be a host path — try multiple locations
|
|
412
413
|
const candidates = [
|
|
413
414
|
file_path,
|
|
414
|
-
// Map host path into
|
|
415
|
-
file_path.replace(/^.*?outputs\//, "/
|
|
415
|
+
// Map host path into data dir: /Users/.../outputs/foo → <DATA_DIR>/foo
|
|
416
|
+
file_path.replace(/^.*?outputs\//, DATA_DIR + "/"),
|
|
416
417
|
];
|
|
417
418
|
|
|
418
419
|
for (const candidate of candidates) {
|
|
@@ -491,12 +492,12 @@ export function insertArtifact(
|
|
|
491
492
|
|
|
492
493
|
// Try to copy the file into /data/artifacts/ for self-contained serving
|
|
493
494
|
const storedName = `${session_id}_${artifactId}${ext}`;
|
|
494
|
-
const storedPath = path.join(
|
|
495
|
+
const storedPath = path.join(DATA_DIR, "artifacts", storedName);
|
|
495
496
|
|
|
496
497
|
try {
|
|
497
498
|
const candidates = [
|
|
498
499
|
file_path,
|
|
499
|
-
file_path.replace(/^.*?outputs\//, "/
|
|
500
|
+
file_path.replace(/^.*?outputs\//, DATA_DIR + "/"),
|
|
500
501
|
];
|
|
501
502
|
|
|
502
503
|
for (const candidate of candidates) {
|
|
@@ -959,7 +960,7 @@ export function insertWalkthroughScreenshot(
|
|
|
959
960
|
|
|
960
961
|
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
961
962
|
const filename = `wt_${walkthrough_id}_${Date.now()}_${safeName}.png`;
|
|
962
|
-
const storedPath = path.join(
|
|
963
|
+
const storedPath = path.join(DATA_DIR, "walkthrough-screenshots", filename);
|
|
963
964
|
|
|
964
965
|
if (file_path_input && fs.existsSync(file_path_input)) {
|
|
965
966
|
// Copy from file path
|