tanuki-telemetry 1.1.7 → 1.2.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.
Files changed (72) hide show
  1. package/README.md +116 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +32 -0
  4. package/dist/commands.d.ts +3 -0
  5. package/dist/commands.js +69 -0
  6. package/dist/setup.d.ts +1 -0
  7. package/dist/setup.js +303 -0
  8. package/docker/docker-compose.yml +15 -0
  9. package/package.json +19 -51
  10. package/{skills → templates}/cmux-guide.md +14 -5
  11. package/templates/compare-image.md +532 -0
  12. package/{skills → templates}/coordinate.md +25 -6
  13. package/{skills → templates}/create-path.md +5 -5
  14. package/{skills → templates}/debug.md +4 -4
  15. package/{skills → templates}/edit-path.md +6 -6
  16. package/{skills → templates}/start-work.md +32 -32
  17. package/{skills → templates}/walkthrough.md +14 -16
  18. package/Dockerfile +0 -22
  19. package/bin/tanuki.mjs +0 -414
  20. package/frontend/eslint.config.js +0 -23
  21. package/frontend/index.html +0 -13
  22. package/frontend/package.json +0 -39
  23. package/frontend/src/App.tsx +0 -232
  24. package/frontend/src/assets/hero.png +0 -0
  25. package/frontend/src/assets/react.svg +0 -1
  26. package/frontend/src/assets/vite.svg +0 -1
  27. package/frontend/src/components/ArtifactsPanel.tsx +0 -429
  28. package/frontend/src/components/ChildStreams.tsx +0 -176
  29. package/frontend/src/components/CoordinatorPage.tsx +0 -317
  30. package/frontend/src/components/Header.tsx +0 -108
  31. package/frontend/src/components/InsightsPanel.tsx +0 -142
  32. package/frontend/src/components/IterationsTable.tsx +0 -98
  33. package/frontend/src/components/KnowledgePage.tsx +0 -308
  34. package/frontend/src/components/LoginPage.tsx +0 -55
  35. package/frontend/src/components/PlanProgress.tsx +0 -163
  36. package/frontend/src/components/QualityReport.tsx +0 -276
  37. package/frontend/src/components/ScreenshotUpload.tsx +0 -117
  38. package/frontend/src/components/ScreenshotsGrid.tsx +0 -266
  39. package/frontend/src/components/SessionDetail.tsx +0 -265
  40. package/frontend/src/components/SessionList.tsx +0 -234
  41. package/frontend/src/components/SettingsPage.tsx +0 -213
  42. package/frontend/src/components/StreamComms.tsx +0 -228
  43. package/frontend/src/components/TanukiLogo.tsx +0 -16
  44. package/frontend/src/components/Timeline.tsx +0 -416
  45. package/frontend/src/components/WalkthroughPage.tsx +0 -458
  46. package/frontend/src/hooks/useApi.ts +0 -81
  47. package/frontend/src/hooks/useAuth.ts +0 -54
  48. package/frontend/src/hooks/useKnowledge.ts +0 -33
  49. package/frontend/src/hooks/useWebSocket.ts +0 -95
  50. package/frontend/src/index.css +0 -66
  51. package/frontend/src/lib/api.ts +0 -15
  52. package/frontend/src/lib/utils.ts +0 -58
  53. package/frontend/src/main.tsx +0 -10
  54. package/frontend/src/types.ts +0 -181
  55. package/frontend/tsconfig.app.json +0 -32
  56. package/frontend/tsconfig.json +0 -7
  57. package/frontend/vite.config.ts +0 -25
  58. package/install.sh +0 -87
  59. package/src/api-keys.ts +0 -97
  60. package/src/auth.ts +0 -165
  61. package/src/coordinator.ts +0 -136
  62. package/src/dashboard-server.ts +0 -5
  63. package/src/dashboard.ts +0 -826
  64. package/src/db.ts +0 -1009
  65. package/src/index.ts +0 -20
  66. package/src/middleware.ts +0 -76
  67. package/src/tools.ts +0 -864
  68. package/src/types-shim.d.ts +0 -18
  69. package/src/types.ts +0 -171
  70. package/tsconfig.json +0 -19
  71. /package/{skills → templates}/review-code.md +0 -0
  72. /package/{skills → templates}/sessions.md +0 -0
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # tanuki-telemetry
2
+
3
+ Telemetry MCP server + 11 autonomous workflow skills for Claude Code. Track sessions, coordinate workspaces, run visual walkthroughs, and debug systematically.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx tanuki-telemetry setup
9
+ ```
10
+
11
+ This interactive wizard will:
12
+ 1. **Configure Telemetry MCP server** — local Docker with dashboard at `localhost:3333`
13
+ 2. **Install 11 skills** — autonomous workflows, debugging, visual QA, coordination
14
+ 3. **Set up hooks** — workspace completion notifications, coordinator inbox
15
+ 4. **Create output directories** — for screenshots, artifacts, reports
16
+
17
+ ## Skills
18
+
19
+ ### Core Workflows
20
+ | Skill | Description |
21
+ |-------|-------------|
22
+ | `/start-work` | Fully autonomous development — scope, implement, test, PR |
23
+ | `/debug` | Systematic bug investigation — replicate 3x, analyze, fix |
24
+ | `/review-code` | Thorough PR code review against project standards |
25
+
26
+ ### Visual QA
27
+ | Skill | Description |
28
+ |-------|-------------|
29
+ | `/walkthrough` | Execute walkthrough scenarios with screenshots |
30
+ | `/create-path` | Generate walkthrough scenarios from codebase routes |
31
+ | `/edit-path` | Add/update scenarios for specific features |
32
+ | `/compare-image` | Visual diff with qualitative annotations |
33
+
34
+ ### Coordination
35
+ | Skill | Description |
36
+ |-------|-------------|
37
+ | `/coordinate` | Central hub for managing multiple cmux workspaces |
38
+ | `/cmux-guide` | Reference for workspace navigation (surfaces, sending, reading) |
39
+
40
+ ### Telemetry
41
+ | Skill | Description |
42
+ |-------|-------------|
43
+ | `/sessions` | Browse past session history, stats, screenshots |
44
+
45
+ ## Dashboard
46
+
47
+ The telemetry dashboard runs at `http://localhost:3333` with:
48
+
49
+ - **Sessions** — live event timeline, screenshots, artifacts, plan progress
50
+ - **Coordinator** — workspace status, live feed from all workspaces
51
+ - **Walkthroughs** — QA run history with screenshots and action timeline
52
+ - **Knowledge** — accumulated insights from past sessions
53
+
54
+ ### Inline Artifact Viewer
55
+ - Markdown rendered as HTML
56
+ - JSON with collapsible syntax highlighting
57
+ - Images with click-to-enlarge lightbox
58
+ - Text files with line limiting
59
+
60
+ ### Live Updates
61
+ - Stats update via WebSocket (no reload needed)
62
+ - Coordinator live feed aggregates events from all workspace sessions
63
+
64
+ ## Hooks
65
+
66
+ The installer configures PostToolUse hooks for workspace communication:
67
+
68
+ - **Session end** → writes to `~/.claude/coordinator-inbox.jsonl` + macOS notification
69
+ - **Session start** → logs to coordinator inbox (async)
70
+ - **Telemetry events** → streams event messages to inbox (async)
71
+
72
+ The coordinator checks the inbox instead of polling workspace screens.
73
+
74
+ ## Architecture
75
+
76
+ ```
77
+ ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
78
+ │ Claude Code │────▶│ Telemetry MCP │────▶│ Dashboard │
79
+ │ (workspaces) │ │ (SQLite + API) │ │ (React SPA) │
80
+ └─────────────┘ └──────────────────┘ └──────────────┘
81
+ │ │
82
+ ▼ ▼
83
+ ~/.claude/ localhost:3333
84
+ coordinator- Sessions, Coordinator,
85
+ inbox.jsonl Walkthroughs, Knowledge
86
+ ```
87
+
88
+ ## Configuration
89
+
90
+ | Setting | Description | Default |
91
+ |---------|------------|---------|
92
+ | Docker image | Telemetry MCP container | `telemetry-mcp:latest` |
93
+ | Data volume | Session data + screenshots | `~/telemetry-data` |
94
+ | Repo directory | Your project root | `cwd` |
95
+ | Worktrees directory | Git worktrees location | `../worktrees` |
96
+ | Outputs directory | Screenshots, artifacts | `../outputs` |
97
+ | Base branch | Branch for worktrees | `main` |
98
+
99
+ ## Requirements
100
+
101
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI
102
+ - Docker (for telemetry server + dashboard)
103
+ - Git
104
+ - [cmux](https://cmux.dev) (for multi-workspace coordination)
105
+
106
+ ## Development
107
+
108
+ ```bash
109
+ npm install
110
+ npm run build
111
+ node dist/cli.js setup
112
+ ```
113
+
114
+ ## License
115
+
116
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { setup } from "./setup.js";
3
+ import { start, stop, status } from "./commands.js";
4
+ const command = process.argv[2];
5
+ const commands = {
6
+ setup: () => setup(),
7
+ start: () => start(),
8
+ stop: () => stop(),
9
+ status: () => status(),
10
+ };
11
+ const handler = commands[command];
12
+ if (handler) {
13
+ handler().catch((err) => {
14
+ console.error(`\nFailed: ${err.message}`);
15
+ process.exit(1);
16
+ });
17
+ }
18
+ else {
19
+ console.log(`
20
+ @junior/telemetry-mcp — Local Telemetry MCP for Claude Code
21
+
22
+ Usage:
23
+ npx @junior/telemetry-mcp setup Interactive setup wizard (local Docker)
24
+ npx @junior/telemetry-mcp start Start the telemetry dashboard
25
+ npx @junior/telemetry-mcp stop Stop the telemetry dashboard
26
+ npx @junior/telemetry-mcp status Check container health
27
+ `);
28
+ if (command && command !== "--help" && command !== "-h") {
29
+ console.error(`Unknown command: ${command}`);
30
+ process.exit(1);
31
+ }
32
+ }
@@ -0,0 +1,3 @@
1
+ export declare function start(): Promise<void>;
2
+ export declare function stop(): Promise<void>;
3
+ export declare function status(): Promise<void>;
@@ -0,0 +1,69 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const COMPOSE_FILE = join(__dirname, "..", "docker", "docker-compose.yml");
7
+ function getEnvArgs() {
8
+ const parts = [];
9
+ if (process.env.TELEMETRY_DATA_DIR) {
10
+ parts.push(`TELEMETRY_DATA_DIR=${process.env.TELEMETRY_DATA_DIR}`);
11
+ }
12
+ if (process.env.TELEMETRY_IMAGE) {
13
+ parts.push(`TELEMETRY_IMAGE=${process.env.TELEMETRY_IMAGE}`);
14
+ }
15
+ return parts.join(" ");
16
+ }
17
+ function composeCmd(args) {
18
+ if (!existsSync(COMPOSE_FILE)) {
19
+ throw new Error(`docker-compose.yml not found at ${COMPOSE_FILE}`);
20
+ }
21
+ return `${getEnvArgs()} docker compose -f ${COMPOSE_FILE} ${args}`;
22
+ }
23
+ export async function start() {
24
+ console.log("Starting telemetry dashboard...");
25
+ try {
26
+ execSync(composeCmd("up -d dashboard"), { stdio: "inherit" });
27
+ console.log("\nDashboard running at http://localhost:3333");
28
+ }
29
+ catch {
30
+ console.error("Failed to start dashboard");
31
+ process.exit(1);
32
+ }
33
+ }
34
+ export async function stop() {
35
+ console.log("Stopping telemetry services...");
36
+ try {
37
+ execSync(composeCmd("down"), { stdio: "inherit" });
38
+ console.log("Stopped.");
39
+ }
40
+ catch {
41
+ console.error("Failed to stop services");
42
+ process.exit(1);
43
+ }
44
+ }
45
+ export async function status() {
46
+ console.log("Telemetry service status:\n");
47
+ // Check containers
48
+ try {
49
+ execSync(composeCmd("ps"), { stdio: "inherit" });
50
+ }
51
+ catch {
52
+ console.log(" No compose services found (may not be running)");
53
+ }
54
+ // Health check
55
+ console.log("");
56
+ try {
57
+ const response = await fetch("http://localhost:3333/health");
58
+ if (response.ok) {
59
+ const data = await response.json();
60
+ console.log(`Dashboard health: OK (${JSON.stringify(data)})`);
61
+ }
62
+ else {
63
+ console.log(`Dashboard health: HTTP ${response.status}`);
64
+ }
65
+ }
66
+ catch {
67
+ console.log("Dashboard health: not reachable");
68
+ }
69
+ }
@@ -0,0 +1 @@
1
+ export declare function setup(): Promise<void>;
package/dist/setup.js ADDED
@@ -0,0 +1,303 @@
1
+ import prompts from "prompts";
2
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join, dirname } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { execSync } from "node:child_process";
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const CLAUDE_DIR = join(homedir(), ".claude");
10
+ const CLAUDE_JSON = join(homedir(), ".claude.json");
11
+ const COMMANDS_DIR = join(CLAUDE_DIR, "commands");
12
+ const SETTINGS_JSON = join(CLAUDE_DIR, "settings.json");
13
+ export async function setup() {
14
+ console.log("\n@junior/telemetry-mcp — Setup\n");
15
+ console.log("This will configure your local environment with:");
16
+ console.log(" 1. Telemetry MCP server (Docker, stdio)");
17
+ console.log(" 2. Dashboard at http://localhost:3333");
18
+ console.log(" 3. /start-work autonomous workflow command");
19
+ console.log(" 4. /compare-image visual diff command\n");
20
+ // Check Docker is available
21
+ try {
22
+ execSync("docker version", { stdio: "pipe" });
23
+ }
24
+ catch {
25
+ console.error("Docker is required but not found. Please install Docker first.");
26
+ process.exit(1);
27
+ }
28
+ const options = await promptOptions();
29
+ // 1. Build/pull Docker image
30
+ await ensureDockerImage(options.dockerImage);
31
+ // 2. Configure ~/.claude.json (stdio docker)
32
+ await writeMcpConfig(options);
33
+ // 3. Start dashboard via docker compose
34
+ await startDashboard(options);
35
+ // 4. Create directories
36
+ if (!existsSync(options.outputsDir)) {
37
+ await mkdir(options.outputsDir, { recursive: true });
38
+ console.log(` Created outputs directory: ${options.outputsDir}`);
39
+ }
40
+ // 5. Add health-check hook
41
+ await writeHealthCheckHook(options);
42
+ // 6. Install commands
43
+ await installStartWorkCommand(options);
44
+ await installCompareImageCommand();
45
+ await installAllTemplateCommands();
46
+ // 7. Health check
47
+ await healthCheck();
48
+ console.log("\nSetup complete!\n");
49
+ console.log("Next steps:");
50
+ console.log(" 1. Restart Claude Code (or start a new session)");
51
+ console.log(" 2. Run /start-work <task-name> to begin an autonomous workflow");
52
+ console.log(" 3. Dashboard: http://localhost:3333");
53
+ console.log(" 4. Run /coordinate to manage workspaces");
54
+ console.log(" 5. Run /walkthrough <url> to test pages visually");
55
+ console.log(" 6. Run /create-path <app> to generate walkthrough scenarios");
56
+ console.log("");
57
+ }
58
+ async function promptOptions() {
59
+ const answers = await prompts([
60
+ {
61
+ type: "text",
62
+ name: "dockerImage",
63
+ message: "Docker image name:",
64
+ initial: "telemetry-mcp:latest",
65
+ },
66
+ {
67
+ type: "text",
68
+ name: "dataVolume",
69
+ message: "Host path for data volume:",
70
+ initial: join(homedir(), "telemetry-data"),
71
+ hint: "Where session data / screenshots / artifacts are stored",
72
+ },
73
+ {
74
+ type: "text",
75
+ name: "repoDir",
76
+ message: "Default repo directory:",
77
+ initial: process.cwd(),
78
+ },
79
+ {
80
+ type: "text",
81
+ name: "worktreesDir",
82
+ message: "Worktrees directory:",
83
+ initial: join(process.cwd(), "../worktrees"),
84
+ },
85
+ {
86
+ type: "text",
87
+ name: "outputsDir",
88
+ message: "Outputs directory:",
89
+ initial: join(process.cwd(), "../outputs"),
90
+ },
91
+ {
92
+ type: "text",
93
+ name: "baseBranch",
94
+ message: "Base branch name:",
95
+ initial: "main",
96
+ },
97
+ {
98
+ type: "confirm",
99
+ name: "enableLinear",
100
+ message: "Enable Linear integration?",
101
+ initial: false,
102
+ },
103
+ {
104
+ type: "confirm",
105
+ name: "enablePlaywright",
106
+ message: "Enable Playwright screenshots?",
107
+ initial: false,
108
+ },
109
+ ]);
110
+ return {
111
+ dockerImage: answers.dockerImage || "telemetry-mcp:latest",
112
+ dataVolume: answers.dataVolume || join(homedir(), "telemetry-data"),
113
+ repoDir: answers.repoDir || process.cwd(),
114
+ worktreesDir: answers.worktreesDir || join(process.cwd(), "../worktrees"),
115
+ outputsDir: answers.outputsDir || join(process.cwd(), "../outputs"),
116
+ baseBranch: answers.baseBranch || "main",
117
+ enableLinear: answers.enableLinear ?? false,
118
+ enablePlaywright: answers.enablePlaywright ?? false,
119
+ };
120
+ }
121
+ async function ensureDockerImage(image) {
122
+ try {
123
+ execSync(`docker image inspect ${image}`, { stdio: "pipe" });
124
+ console.log(` Docker image ${image} found`);
125
+ }
126
+ catch {
127
+ console.log(` Docker image ${image} not found locally`);
128
+ console.log(` Build it with: docker build -t ${image} .`);
129
+ console.log(` (from the telemetry MCP server source directory)`);
130
+ }
131
+ }
132
+ async function writeMcpConfig(options) {
133
+ let claudeConfig = {};
134
+ if (existsSync(CLAUDE_JSON)) {
135
+ const raw = await readFile(CLAUDE_JSON, "utf-8");
136
+ claudeConfig = JSON.parse(raw);
137
+ }
138
+ if (!claudeConfig.mcpServers) {
139
+ claudeConfig.mcpServers = {};
140
+ }
141
+ claudeConfig.mcpServers["telemetry"] = {
142
+ type: "stdio",
143
+ command: "docker",
144
+ args: [
145
+ "run",
146
+ "--rm",
147
+ "-i",
148
+ "-v",
149
+ `${options.dataVolume}:/data`,
150
+ options.dockerImage,
151
+ ],
152
+ };
153
+ await writeFile(CLAUDE_JSON, JSON.stringify(claudeConfig, null, 2) + "\n");
154
+ console.log(" Telemetry MCP server added to ~/.claude.json");
155
+ }
156
+ async function startDashboard(options) {
157
+ const composePath = join(__dirname, "..", "docker", "docker-compose.yml");
158
+ if (!existsSync(composePath)) {
159
+ console.log(" docker-compose.yml not found — skipping dashboard auto-start");
160
+ return;
161
+ }
162
+ try {
163
+ execSync(`TELEMETRY_DATA_DIR=${options.dataVolume} TELEMETRY_IMAGE=${options.dockerImage} docker compose -f ${composePath} up -d dashboard`, { stdio: "pipe" });
164
+ console.log(" Dashboard started at http://localhost:3333");
165
+ }
166
+ catch (err) {
167
+ console.log(" Could not auto-start dashboard. Run: npx @junior/telemetry-mcp start");
168
+ }
169
+ }
170
+ async function writeHealthCheckHook(options) {
171
+ let settings = {};
172
+ if (existsSync(SETTINGS_JSON)) {
173
+ const raw = await readFile(SETTINGS_JSON, "utf-8");
174
+ settings = JSON.parse(raw);
175
+ }
176
+ if (!settings.hooks)
177
+ settings.hooks = {};
178
+ if (!settings.hooks.SessionStart)
179
+ settings.hooks.SessionStart = [];
180
+ const hasHook = settings.hooks.SessionStart.some((entry) => entry.hooks?.some((h) => h.command?.includes("telemetry")));
181
+ if (!hasHook) {
182
+ const startCmd = `docker start telemetry-mcp 2>/dev/null || docker run --rm -d --name telemetry-mcp -i -v ${options.dataVolume}:/data ${options.dockerImage} 2>/dev/null; echo 'OK'`;
183
+ settings.hooks.SessionStart.push({
184
+ matcher: "",
185
+ hooks: [{ type: "command", command: startCmd }],
186
+ });
187
+ await writeFile(SETTINGS_JSON, JSON.stringify(settings, null, 2) + "\n");
188
+ console.log(" Health-check hook added to ~/.claude/settings.json");
189
+ }
190
+ else {
191
+ console.log(" Telemetry hook already exists in settings.json");
192
+ }
193
+ }
194
+ async function installStartWorkCommand(options) {
195
+ await mkdir(COMMANDS_DIR, { recursive: true });
196
+ const templatePath = join(__dirname, "..", "templates", "start-work.md");
197
+ let template;
198
+ if (existsSync(templatePath)) {
199
+ template = await readFile(templatePath, "utf-8");
200
+ }
201
+ else {
202
+ template = "<!-- See templates/start-work.md -->";
203
+ }
204
+ template = template
205
+ .replaceAll("{{REPO_DIR}}", options.repoDir)
206
+ .replaceAll("{{WORKTREES_DIR}}", options.worktreesDir)
207
+ .replaceAll("{{OUTPUTS_DIR}}", options.outputsDir)
208
+ .replaceAll("{{BASE_BRANCH}}", options.baseBranch);
209
+ if (!options.enableLinear) {
210
+ template = removeSection(template, "LINEAR_START", "LINEAR_END");
211
+ }
212
+ else {
213
+ template = template
214
+ .replaceAll("<!-- LINEAR_START -->", "")
215
+ .replaceAll("<!-- LINEAR_END -->", "");
216
+ }
217
+ if (!options.enablePlaywright) {
218
+ template = removeSection(template, "PLAYWRIGHT_START", "PLAYWRIGHT_END");
219
+ }
220
+ else {
221
+ template = template
222
+ .replaceAll("<!-- PLAYWRIGHT_START -->", "")
223
+ .replaceAll("<!-- PLAYWRIGHT_END -->", "");
224
+ }
225
+ const tools = [
226
+ "Bash", "Read", "Glob", "Grep", "Edit", "Write", "Agent", "AskUserQuestion",
227
+ "mcp__telemetry__*",
228
+ ];
229
+ if (options.enableLinear)
230
+ tools.push("mcp__linear__*");
231
+ if (options.enablePlaywright)
232
+ tools.push("mcp__playwright__*");
233
+ template = template.replaceAll("{{ALLOWED_TOOLS}}", tools.join(", "));
234
+ const destPath = join(COMMANDS_DIR, "start-work.md");
235
+ if (existsSync(destPath)) {
236
+ const backupPath = join(COMMANDS_DIR, `start-work.backup.${Date.now()}.md`);
237
+ const existing = await readFile(destPath, "utf-8");
238
+ await writeFile(backupPath, existing);
239
+ console.log(` Backed up existing /start-work`);
240
+ }
241
+ await writeFile(destPath, template);
242
+ console.log(" /start-work command installed");
243
+ }
244
+ async function installCompareImageCommand() {
245
+ await mkdir(COMMANDS_DIR, { recursive: true });
246
+ const templatePath = join(__dirname, "..", "templates", "compare-image.md");
247
+ if (!existsSync(templatePath)) {
248
+ console.log(" compare-image.md template not found — skipping");
249
+ return;
250
+ }
251
+ const template = await readFile(templatePath, "utf-8");
252
+ const destPath = join(COMMANDS_DIR, "compare-image.md");
253
+ if (existsSync(destPath)) {
254
+ const backupPath = join(COMMANDS_DIR, `compare-image.backup.${Date.now()}.md`);
255
+ const existing = await readFile(destPath, "utf-8");
256
+ await writeFile(backupPath, existing);
257
+ console.log(` Backed up existing /compare-image`);
258
+ }
259
+ await writeFile(destPath, template);
260
+ console.log(" /compare-image command installed");
261
+ }
262
+ async function installAllTemplateCommands() {
263
+ await mkdir(COMMANDS_DIR, { recursive: true });
264
+ const templatesDir = join(__dirname, "..", "templates");
265
+ // Commands already handled by dedicated installers
266
+ const skip = new Set(["start-work.md", "compare-image.md"]);
267
+ const { readdirSync } = await import("node:fs");
268
+ const files = readdirSync(templatesDir).filter((f) => f.endsWith(".md") && !skip.has(f));
269
+ for (const file of files) {
270
+ const srcPath = join(templatesDir, file);
271
+ const destPath = join(COMMANDS_DIR, file);
272
+ const template = await readFile(srcPath, "utf-8");
273
+ if (existsSync(destPath)) {
274
+ const existing = await readFile(destPath, "utf-8");
275
+ if (existing === template) {
276
+ continue;
277
+ } // Already up to date
278
+ const name = file.replace(".md", "");
279
+ const backupPath = join(COMMANDS_DIR, `${name}.backup.${Date.now()}.md`);
280
+ await writeFile(backupPath, existing);
281
+ }
282
+ await writeFile(destPath, template);
283
+ console.log(` /${file.replace(".md", "")} command installed`);
284
+ }
285
+ }
286
+ async function healthCheck() {
287
+ try {
288
+ const response = await fetch("http://localhost:3333/health");
289
+ if (response.ok) {
290
+ console.log(" Dashboard health check: OK");
291
+ }
292
+ else {
293
+ console.log(" Dashboard health check: responded but not OK");
294
+ }
295
+ }
296
+ catch {
297
+ console.log(" Dashboard health check: not reachable (may still be starting)");
298
+ }
299
+ }
300
+ function removeSection(text, startMarker, endMarker) {
301
+ const regex = new RegExp(`<!-- ${startMarker} -->[\\s\\S]*?<!-- ${endMarker} -->`, "g");
302
+ return text.replace(regex, "");
303
+ }
@@ -0,0 +1,15 @@
1
+ services:
2
+ telemetry-mcp:
3
+ image: ${TELEMETRY_IMAGE:-telemetry-mcp:latest}
4
+ volumes:
5
+ - ${TELEMETRY_DATA_DIR:-~/telemetry-data}:/data
6
+ stdin_open: true
7
+
8
+ dashboard:
9
+ image: ${TELEMETRY_IMAGE:-telemetry-mcp:latest}
10
+ entrypoint: ["node", "dist/dashboard-server.js"]
11
+ volumes:
12
+ - ${TELEMETRY_DATA_DIR:-~/telemetry-data}:/data
13
+ ports:
14
+ - "3333:3333"
15
+ restart: unless-stopped
package/package.json CHANGED
@@ -1,72 +1,40 @@
1
1
  {
2
2
  "name": "tanuki-telemetry",
3
- "version": "1.1.7",
4
- "description": "Workflow monitor and telemetry dashboard for Claude Code autonomous agents",
3
+ "version": "1.2.0",
4
+ "description": "Local installer for Telemetry MCP server — setup, start, stop, and status for Claude Code autonomous workflows",
5
5
  "type": "module",
6
6
  "bin": {
7
- "tanuki": "./bin/tanuki.mjs",
8
- "tanuki-telemetry": "./bin/tanuki.mjs"
7
+ "telemetry-mcp": "./dist/cli.js"
9
8
  },
10
- "files": [
11
- "bin/",
12
- "skills/",
13
- "src/",
14
- "frontend/src/",
15
- "frontend/index.html",
16
- "frontend/package.json",
17
- "frontend/tsconfig.json",
18
- "frontend/tsconfig.app.json",
19
- "frontend/eslint.config.js",
20
- "frontend/vite.config.ts",
21
- "tsconfig.json",
22
- "Dockerfile",
23
- "install.sh"
24
- ],
25
9
  "scripts": {
26
10
  "build": "tsc",
27
- "build:frontend": "cd frontend && npx vite build",
28
- "build:all": "tsc && cd frontend && npx vite build",
29
- "start": "node dist/index.js",
30
- "dev": "tsx src/index.ts"
11
+ "dev": "tsc --watch",
12
+ "prepublishOnly": "npm run build"
31
13
  },
14
+ "files": [
15
+ "dist",
16
+ "templates",
17
+ "docker"
18
+ ],
32
19
  "keywords": [
33
20
  "claude",
34
- "mcp",
21
+ "claude-code",
35
22
  "telemetry",
36
- "dashboard",
23
+ "mcp",
37
24
  "autonomous",
38
- "agents",
39
- "workflow"
25
+ "ai-agent"
40
26
  ],
41
- "license": "MIT",
42
27
  "repository": {
43
28
  "type": "git",
44
- "url": "https://github.com/ykim-24/tanuki-telemetry.git"
29
+ "url": "https://github.com/ykim-24/tanuki-telemetry"
45
30
  },
31
+ "license": "MIT",
46
32
  "dependencies": {
47
- "@modelcontextprotocol/sdk": "^1.12.1",
48
- "better-sqlite3": "^11.8.1",
49
- "better-sqlite3-session-store": "^0.1.0",
50
- "express": "^4.21.0",
51
- "express-session": "^1.19.0",
52
- "multer": "^2.1.1",
53
- "passport": "^0.7.0",
54
- "passport-google-oauth20": "^2.0.0",
55
- "sharp": "^0.34.5",
56
- "uuid": "^11.1.0",
57
- "ws": "^8.19.0"
33
+ "prompts": "^2.4.2"
58
34
  },
59
35
  "devDependencies": {
60
- "@types/better-sqlite3": "^7.6.12",
61
- "@types/express": "^5.0.0",
62
- "@types/express-session": "^1.18.2",
63
- "@types/multer": "^2.1.0",
64
- "@types/node": "^22.13.10",
65
- "@types/passport": "^1.0.17",
66
- "@types/passport-google-oauth20": "^2.0.17",
67
- "@types/sharp": "^0.31.1",
68
- "@types/uuid": "^10.0.0",
69
- "@types/ws": "^8.18.1",
70
- "typescript": "^5.7.3"
36
+ "@types/node": "^22.0.0",
37
+ "@types/prompts": "^2.4.9",
38
+ "typescript": "^5.7.0"
71
39
  }
72
40
  }
@@ -110,10 +110,10 @@ cmux list-pane-surfaces --workspace "workspace:N"
110
110
 
111
111
  ### Choose the right --cwd
112
112
  The `--cwd` path determines where Claude starts. **Pick the most relevant directory for the task:**
113
- - Working on the main app? → `--cwd ~/project` (NOT the repo root)
114
- - Working on a worktree? → `--cwd ~/worktrees/<worktree-name>`
115
- - Working on a package? → `--cwd ~/project/packages/<package-name>`
116
- - General/cross-cutting? → `--cwd ~/project`
113
+ - Working on the main app? → `--cwd ~/junior-main/junior` (NOT the repo root)
114
+ - Working on a worktree? → `--cwd ~/junior-main/junior-worktrees/<worktree-name>`
115
+ - Working on a package? → `--cwd ~/junior-main/junior/packages/<package-name>`
116
+ - General/cross-cutting? → `--cwd ~/junior-main`
117
117
 
118
118
  **Wrong cwd = confused Claude.** If Claude is in the repo root, it may not find the right files or understand the project structure. Match the cwd to where the relevant code lives.
119
119
 
@@ -133,7 +133,16 @@ Keep names short but descriptive (e.g., "Benchmark Deck", "PPTX Import", "Auth M
133
133
 
134
134
  ## Monitoring Workspaces
135
135
 
136
- ### Quick status check (low context cost):
136
+ ### Inbox-first (preferred no screen reads needed):
137
+ Workspaces using `/start-work` automatically notify the coordinator via a PostToolUse hook when their session ends. Messages go to `~/.claude/coordinator-inbox.jsonl`.
138
+ ```bash
139
+ # Check for completion notifications
140
+ cat ~/.claude/coordinator-inbox.jsonl 2>/dev/null | tail -5
141
+ # Clear after processing
142
+ > ~/.claude/coordinator-inbox.jsonl
143
+ ```
144
+
145
+ ### Quick status check (fallback — when no inbox message):
137
146
  ```bash
138
147
  cmux read-screen --workspace <id> --surface <surface_id> --lines 5
139
148
  ```