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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tanuki-telemetry",
3
- "version": "1.3.8",
3
+ "version": "1.4.0",
4
4
  "description": "Workflow monitor and telemetry dashboard for Claude Code autonomous agents",
5
5
  "type": "module",
6
6
  "bin": {
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 || "/data/telemetry.db";
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 !== TANUKI_VERSION : false,
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("/data", dirName, "screenshots");
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\//, "/data/"),
492
- artifact.file_path?.replace(/^.*?\.tanuki\/data\//, "/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\//, "/data/"),
531
- screenshot.file_path?.replace(/^.*?\.tanuki\/data\//, "/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("/data", requestedPath);
624
+ filePath = path.join(DATA_DIR, requestedPath);
608
625
  }
609
626
 
610
627
  const resolved = path.resolve(filePath);
611
- if (!resolved.startsWith("/data")) {
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 DB_PATH = process.env.DB_PATH || "/data/telemetry.db";
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 = "/data/screenshots";
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 = "/data/artifacts";
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 = "/data/walkthrough-screenshots";
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("/data", "screenshots", storedName);
408
- const thumbPath = path.join("/data", "screenshots", thumbName);
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 /data mount: /Users/.../outputs/foo → /data/foo
415
- file_path.replace(/^.*?outputs\//, "/data/"),
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("/data", "artifacts", storedName);
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\//, "/data/"),
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("/data", "walkthrough-screenshots", filename);
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