tanuki-telemetry 1.1.2 → 1.1.3

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 (2) hide show
  1. package/bin/tanuki.mjs +139 -125
  2. package/package.json +1 -1
package/bin/tanuki.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execSync, spawn } from "child_process";
3
+ import { execSync } from "child_process";
4
4
  import fs from "fs";
5
5
  import path from "path";
6
6
  import os from "os";
@@ -17,29 +17,75 @@ const PORT = process.env.TANUKI_PORT || "3333";
17
17
  const args = process.argv.slice(2);
18
18
  const command = args[0] || "start";
19
19
 
20
+ // --- Colors ---
21
+ const c = {
22
+ reset: "\x1b[0m",
23
+ bold: "\x1b[1m",
24
+ dim: "\x1b[2m",
25
+ green: "\x1b[32m",
26
+ yellow: "\x1b[33m",
27
+ blue: "\x1b[34m",
28
+ magenta: "\x1b[35m",
29
+ cyan: "\x1b[36m",
30
+ white: "\x1b[37m",
31
+ gray: "\x1b[90m",
32
+ red: "\x1b[31m",
33
+ bgGreen: "\x1b[42m",
34
+ bgRed: "\x1b[41m",
35
+ };
36
+
37
+ const g = c.green;
38
+ const d = c.dim;
39
+ const w = c.white;
40
+ const r = c.reset;
41
+ const b = c.bold;
42
+ const y = c.yellow;
43
+ const cy = c.cyan;
44
+
45
+ const LOGO = `
46
+ ${g} ╱▔▔╲ ╱▔▔╲${r}
47
+ ${g} ╱ ◆ ╲▁╱ ◆ ╲${r}
48
+ ${g} ▕ ▔▔▔▔▔▔▔ ▏${r}
49
+ ${g} ╲ ╱▔▔▔▔▔╲ ╱${r}
50
+ ${g} ╲╱ ▕▔▔▏ ╲╱${r}
51
+ ${g} ╲▁▁▕▁▁▏▁▁╱${r}
52
+ `;
53
+
54
+ const BANNER = `
55
+ ${d} ┌─────────────────────────────────────────┐${r}
56
+ ${d} │${r} ${g}${b}TANUKI${r} ${d}v${VERSION}${r} ${d}│${r}
57
+ ${d} │${r} ${d}workflow monitor for claude code${r} ${d}│${r}
58
+ ${d} └─────────────────────────────────────────┘${r}
59
+ `;
60
+
20
61
  const HELP = `
21
- tanuki v${VERSION} — workflow monitor for Claude Code
22
-
23
- Usage:
24
- npx tanuki-telemetry Start dashboard (default)
25
- npx tanuki-telemetry start Start dashboard on port ${PORT}
26
- npx tanuki-telemetry stop Stop dashboard
27
- npx tanuki-telemetry status Check if running
28
- npx tanuki-telemetry setup Configure Claude Code MCP
29
- npx tanuki-telemetry update Rebuild with latest code
30
- npx tanuki-telemetry version Show version
31
-
32
- Environment:
33
- TANUKI_PORT Dashboard port (default: 3333)
34
- TANUKI_DATA Data directory (default: ~/.tanuki/data)
62
+ ${g}${b} tanuki${r} ${d}v${VERSION}${r} — workflow monitor for Claude Code
63
+
64
+ ${w} Usage:${r}
65
+ ${cy}npx tanuki-telemetry${r} start dashboard
66
+ ${cy}npx tanuki-telemetry stop${r} stop dashboard
67
+ ${cy}npx tanuki-telemetry status${r} check if running
68
+ ${cy}npx tanuki-telemetry setup${r} configure claude code
69
+ ${cy}npx tanuki-telemetry update${r} rebuild with latest
70
+ ${cy}npx tanuki-telemetry version${r} show version
71
+
72
+ ${w} Environment:${r}
73
+ ${d}TANUKI_PORT dashboard port (default: 3333)${r}
74
+ ${d}TANUKI_DATA data directory (default: ~/.tanuki/data)${r}
35
75
  `;
36
76
 
77
+ function log(msg) { console.log(msg); }
78
+ function step(n, total, msg) { log(` ${g}[${n}/${total}]${r} ${msg}`); }
79
+ function ok(msg) { log(` ${d} ${g}✓${r} ${d}${msg}${r}`); }
80
+ function fail(msg) { log(` ${c.red} ✗ ${msg}${r}`); }
81
+ function info(msg) { log(` ${d} ${msg}${r}`); }
82
+
37
83
  function run(cmd, opts = {}) {
38
84
  try {
39
85
  return execSync(cmd, { encoding: "utf-8", stdio: opts.quiet ? "pipe" : "inherit", ...opts });
40
86
  } catch (e) {
41
87
  if (!opts.ignoreError) {
42
- console.error(` Error: ${e.message}`);
88
+ fail(e.message);
43
89
  process.exit(1);
44
90
  }
45
91
  return "";
@@ -47,46 +93,44 @@ function run(cmd, opts = {}) {
47
93
  }
48
94
 
49
95
  function checkRequirements() {
50
- let ok = true;
96
+ let allOk = true;
51
97
 
52
- // Docker installed?
53
98
  try {
54
99
  execSync("docker --version", { stdio: "pipe" });
55
100
  } catch {
56
- console.error(" Docker not installed — install Docker Desktop: https://docker.com/products/docker-desktop");
57
- ok = false;
101
+ fail("docker not installed — https://docker.com/products/docker-desktop");
102
+ allOk = false;
58
103
  }
59
104
 
60
- // Docker running?
61
- if (ok) {
105
+ if (allOk) {
62
106
  try {
63
107
  execSync("docker info", { stdio: "pipe" });
64
108
  } catch {
65
- console.error(" Docker is not running — start Docker Desktop first");
66
- ok = false;
109
+ fail("docker is not running — start Docker Desktop");
110
+ allOk = false;
67
111
  }
68
112
  }
69
113
 
70
- // Node version
71
114
  const nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
72
115
  if (nodeVersion < 18) {
73
- console.error(` Node ${process.versions.node} — need Node 18+ (current: ${process.versions.node})`);
74
- ok = false;
116
+ fail(`node ${process.versions.node} — need 18+`);
117
+ allOk = false;
75
118
  }
76
119
 
77
- // curl (used for health checks)
78
120
  try {
79
121
  execSync("curl --version", { stdio: "pipe" });
80
122
  } catch {
81
- console.error("curl not found — needed for health checks");
82
- ok = false;
123
+ fail("curl not found");
124
+ allOk = false;
83
125
  }
84
126
 
85
- if (!ok) {
86
- console.error("");
87
- console.error(" Fix the above and try again.");
127
+ if (!allOk) {
128
+ log("");
129
+ fail("fix the above and try again");
88
130
  process.exit(1);
89
131
  }
132
+
133
+ ok("docker, node, curl");
90
134
  }
91
135
 
92
136
  function isRunning() {
@@ -99,29 +143,19 @@ function isRunning() {
99
143
  }
100
144
 
101
145
  function copySourceToTanukiDir() {
102
- // The source code is wherever this package was installed/run from
103
146
  const srcDir = path.resolve(path.dirname(import.meta.url.replace("file://", "")), "..");
147
+ if (srcDir === TANUKI_DIR) return;
104
148
 
105
- if (srcDir === TANUKI_DIR) return; // already in place
106
-
107
- // Copy source to TANUKI_DIR for Docker builds
108
149
  fs.mkdirSync(TANUKI_DIR, { recursive: true });
109
150
 
110
- const filesToCopy = ["package.json", "tsconfig.json", "Dockerfile"];
111
- const dirsToCopy = ["src", "frontend"];
112
-
113
- for (const f of filesToCopy) {
151
+ for (const f of ["package.json", "tsconfig.json", "Dockerfile"]) {
114
152
  const src = path.join(srcDir, f);
115
- if (fs.existsSync(src)) {
116
- fs.copyFileSync(src, path.join(TANUKI_DIR, f));
117
- }
153
+ if (fs.existsSync(src)) fs.copyFileSync(src, path.join(TANUKI_DIR, f));
118
154
  }
119
155
 
120
- for (const d of dirsToCopy) {
156
+ for (const d of ["src", "frontend"]) {
121
157
  const src = path.join(srcDir, d);
122
- if (fs.existsSync(src)) {
123
- copyDirSync(src, path.join(TANUKI_DIR, d));
124
- }
158
+ if (fs.existsSync(src)) copyDirSync(src, path.join(TANUKI_DIR, d));
125
159
  }
126
160
  }
127
161
 
@@ -130,12 +164,9 @@ function copyDirSync(src, dest) {
130
164
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
131
165
  const srcPath = path.join(src, entry.name);
132
166
  const destPath = path.join(dest, entry.name);
133
- if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
134
- if (entry.isDirectory()) {
135
- copyDirSync(srcPath, destPath);
136
- } else {
137
- fs.copyFileSync(srcPath, destPath);
138
- }
167
+ if (["node_modules", "dist", ".git"].includes(entry.name)) continue;
168
+ if (entry.isDirectory()) copyDirSync(srcPath, destPath);
169
+ else fs.copyFileSync(srcPath, destPath);
139
170
  }
140
171
  }
141
172
 
@@ -161,10 +192,9 @@ function autoConfigureClaude() {
161
192
  const claudeConfig = path.join(os.homedir(), ".claude.json");
162
193
 
163
194
  if (!fs.existsSync(claudeConfig)) {
164
- // Create new config
165
195
  const config = { mcpServers: { telemetry: getMcpEntry() } };
166
196
  fs.writeFileSync(claudeConfig, JSON.stringify(config, null, 2) + "\n");
167
- console.log(" Created ~/.claude.json with MCP config");
197
+ ok("created ~/.claude.json with MCP config");
168
198
  return true;
169
199
  }
170
200
 
@@ -173,19 +203,17 @@ function autoConfigureClaude() {
173
203
  const config = JSON.parse(raw);
174
204
 
175
205
  if (config.mcpServers?.telemetry) {
176
- console.log(" MCP already configured in ~/.claude.json");
206
+ ok("MCP already configured");
177
207
  return false;
178
208
  }
179
209
 
180
- // Add telemetry to existing config
181
210
  if (!config.mcpServers) config.mcpServers = {};
182
211
  config.mcpServers.telemetry = getMcpEntry();
183
212
  fs.writeFileSync(claudeConfig, JSON.stringify(config, null, 2) + "\n");
184
- console.log(" Added MCP config to ~/.claude.json");
213
+ ok("added MCP config to ~/.claude.json");
185
214
  return true;
186
- } catch (e) {
187
- console.log(" Could not auto-configure ~/.claude.json — add manually:");
188
- console.log(JSON.stringify({ telemetry: getMcpEntry() }, null, 2));
215
+ } catch {
216
+ fail("could not auto-configure ~/.claude.json");
189
217
  return false;
190
218
  }
191
219
  }
@@ -193,58 +221,47 @@ function autoConfigureClaude() {
193
221
  // --- Commands ---
194
222
 
195
223
  if (command === "help" || command === "--help" || command === "-h") {
196
- console.log(HELP);
224
+ log(LOGO);
225
+ log(HELP);
197
226
  process.exit(0);
198
227
  }
199
228
 
200
229
  if (command === "version" || command === "--version" || command === "-v") {
201
- console.log(`tanuki v${VERSION}`);
230
+ log(`${g}tanuki${r} ${d}v${VERSION}${r}`);
202
231
  process.exit(0);
203
232
  }
204
233
 
205
- console.log("");
206
-
207
234
  if (command === "start") {
208
- console.log(" ┌─────────────────────────────────────┐");
209
- console.log(` │ TANUKI v${VERSION} │`);
210
- console.log(" │ workflow monitor for claude code │");
211
- console.log(" └─────────────────────────────────────┘");
212
- console.log("");
213
-
214
- // Requirements
215
- console.log(" [1/4] checking requirements...");
235
+ log(LOGO);
236
+ log(BANNER);
237
+
238
+ step(1, 4, "checking requirements");
216
239
  checkRequirements();
217
- console.log(" docker, node, curl — ok");
218
240
 
219
241
  fs.mkdirSync(DATA_DIR, { recursive: true });
220
242
 
221
243
  if (isRunning()) {
222
- console.log("");
223
- console.log(` already running at http://localhost:${PORT}`);
224
- console.log("");
244
+ log("");
245
+ log(` ${g}already running${r} at ${cy}http://localhost:${PORT}${r}`);
246
+ log("");
225
247
  process.exit(0);
226
248
  }
227
249
 
228
250
  copySourceToTanukiDir();
229
251
 
230
- // Build
231
252
  const hasImage = (() => {
232
- try {
233
- execSync("docker image inspect tanuki-dashboard:latest", { stdio: "pipe" });
234
- return true;
235
- } catch { return false; }
253
+ try { execSync("docker image inspect tanuki-dashboard:latest", { stdio: "pipe" }); return true; }
254
+ catch { return false; }
236
255
  })();
237
256
 
257
+ step(2, 4, hasImage ? "docker images" : "building docker images");
238
258
  if (!hasImage) {
239
- console.log(" [2/4] building docker images (first run, ~30s)...");
259
+ info("first run this takes ~30s...");
240
260
  buildImages();
241
- console.log(" images built");
242
- } else {
243
- console.log(" [2/4] docker images — cached");
244
261
  }
262
+ ok(hasImage ? "cached" : "images built");
245
263
 
246
- // Start
247
- console.log(" [3/4] starting dashboard...");
264
+ step(3, 4, "starting dashboard");
248
265
  startDashboard();
249
266
 
250
267
  let healthy = false;
@@ -255,39 +272,31 @@ if (command === "start") {
255
272
  } catch {}
256
273
  execSync("sleep 1", { stdio: "pipe" });
257
274
  }
275
+ ok(healthy ? `running on port ${PORT}` : `starting — check port ${PORT}`);
258
276
 
259
- if (healthy) {
260
- console.log(` running on port ${PORT}`);
261
- } else {
262
- console.log(` starting up — check port ${PORT}`);
263
- }
264
-
265
- // Configure
266
- console.log(" [4/4] configuring claude code...");
277
+ step(4, 4, "configuring claude code");
267
278
  autoConfigureClaude();
268
279
 
269
- // Done
270
- console.log("");
271
- console.log(" ────────────────────────────────────");
272
- console.log("");
273
- console.log(` dashboard http://localhost:${PORT}`);
274
- console.log(` data ${DATA_DIR}`);
275
- console.log("");
276
- console.log(" if claude code is already open,");
277
- console.log(" restart it to load MCP tools.");
278
- console.log("");
279
- console.log(" commands:");
280
- console.log(" npx tanuki-telemetry stop");
281
- console.log(" npx tanuki-telemetry status");
282
- console.log(" npx tanuki-telemetry update");
283
- console.log("");
280
+ log("");
281
+ log(` ${d}─────────────────────────────────────────${r}`);
282
+ log("");
283
+ log(` ${w}dashboard${r} ${cy}http://localhost:${PORT}${r}`);
284
+ log(` ${w}data${r} ${d}${DATA_DIR}${r}`);
285
+ log("");
286
+ log(` ${y}if claude code is already open,${r}`);
287
+ log(` ${y}restart it to load MCP tools.${r}`);
288
+ log("");
289
+ log(` ${d}npx tanuki-telemetry stop${r}`);
290
+ log(` ${d}npx tanuki-telemetry status${r}`);
291
+ log(` ${d}npx tanuki-telemetry update${r}`);
292
+ log("");
284
293
  }
285
294
 
286
295
  else if (command === "stop") {
287
296
  run("docker rm -f tanuki-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
288
297
  run("docker rm -f telemetry-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
289
- console.log(" stopped");
290
- console.log("");
298
+ log(` ${g}stopped${r}`);
299
+ log("");
291
300
  }
292
301
 
293
302
  else if (command === "status") {
@@ -295,40 +304,45 @@ else if (command === "status") {
295
304
  try {
296
305
  const health = execSync(`curl -s http://localhost:${PORT}/health`, { encoding: "utf-8", stdio: "pipe" });
297
306
  const parsed = JSON.parse(health);
298
- console.log(` running at http://localhost:${PORT} (v${parsed.version})`);
307
+ log(` ${g}running${r} at ${cy}http://localhost:${PORT}${r} ${d}(v${parsed.version})${r}`);
299
308
  } catch {
300
- console.log(" container running but health check failed");
309
+ log(` ${y}container running but health check failed${r}`);
301
310
  }
302
311
  } else {
303
- console.log(" not running — npx tanuki-telemetry to start");
312
+ log(` ${d}not running${r}${cy}npx tanuki-telemetry${r} to start`);
304
313
  }
305
- console.log("");
314
+ log("");
306
315
  }
307
316
 
308
317
  else if (command === "setup") {
318
+ log(BANNER);
309
319
  autoConfigureClaude();
310
- console.log("");
320
+ log("");
311
321
  }
312
322
 
313
323
  else if (command === "update") {
324
+ log(BANNER);
325
+ step(1, 2, "rebuilding images");
314
326
  checkRequirements();
315
327
  copySourceToTanukiDir();
316
328
  buildImages();
329
+ ok("images rebuilt");
317
330
 
318
331
  if (isRunning()) {
319
- console.log(" Restarting dashboard...");
332
+ step(2, 2, "restarting dashboard");
320
333
  run("docker rm -f tanuki-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
321
334
  run("docker rm -f telemetry-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
322
335
  startDashboard();
323
- console.log(` Updated and running at http://localhost:${PORT}`);
336
+ ok(`running at http://localhost:${PORT}`);
324
337
  } else {
325
- console.log(" Images rebuilt. Run 'npx tanuki-telemetry start' to launch.");
338
+ step(2, 2, "done");
339
+ info("run 'npx tanuki-telemetry' to start");
326
340
  }
327
- console.log("");
341
+ log("");
328
342
  }
329
343
 
330
344
  else {
331
- console.error(` Unknown command: ${command}`);
332
- console.log(HELP);
345
+ log(` ${c.red}unknown command: ${command}${r}`);
346
+ log(HELP);
333
347
  process.exit(1);
334
348
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tanuki-telemetry",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Workflow monitor and telemetry dashboard for Claude Code autonomous agents",
5
5
  "type": "module",
6
6
  "bin": {