tanuki-telemetry 1.3.3 → 1.3.5
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/Dockerfile +22 -0
- package/bin/tanuki.mjs +414 -0
- package/frontend/eslint.config.js +23 -0
- package/frontend/index.html +13 -0
- package/frontend/package-lock.json +4022 -0
- package/frontend/package.json +39 -0
- package/frontend/public/favicon.svg +1 -0
- package/frontend/public/icons.svg +24 -0
- package/frontend/src/App.tsx +232 -0
- package/frontend/src/assets/hero.png +0 -0
- package/frontend/src/assets/react.svg +1 -0
- package/frontend/src/assets/vite.svg +1 -0
- package/frontend/src/components/ArtifactsPanel.tsx +429 -0
- package/frontend/src/components/ChildStreams.tsx +176 -0
- package/frontend/src/components/CoordinatorPage.tsx +390 -0
- package/frontend/src/components/Header.tsx +144 -0
- package/frontend/src/components/InsightsPanel.tsx +142 -0
- package/frontend/src/components/IterationsTable.tsx +98 -0
- package/frontend/src/components/KnowledgePage.tsx +308 -0
- package/frontend/src/components/LoginPage.tsx +55 -0
- package/frontend/src/components/PlanProgress.tsx +163 -0
- package/frontend/src/components/QualityReport.tsx +276 -0
- package/frontend/src/components/ScreenshotUpload.tsx +117 -0
- package/frontend/src/components/ScreenshotsGrid.tsx +266 -0
- package/frontend/src/components/SessionDetail.tsx +265 -0
- package/frontend/src/components/SessionList.tsx +234 -0
- package/frontend/src/components/SettingsPage.tsx +213 -0
- package/frontend/src/components/StreamComms.tsx +228 -0
- package/frontend/src/components/TanukiLogo.tsx +16 -0
- package/frontend/src/components/Timeline.tsx +416 -0
- package/frontend/src/components/WalkthroughPage.tsx +458 -0
- package/frontend/src/hooks/useApi.ts +81 -0
- package/frontend/src/hooks/useAuth.ts +54 -0
- package/frontend/src/hooks/useKnowledge.ts +33 -0
- package/frontend/src/hooks/useWebSocket.ts +95 -0
- package/frontend/src/index.css +66 -0
- package/frontend/src/lib/api.ts +15 -0
- package/frontend/src/lib/utils.ts +58 -0
- package/frontend/src/main.tsx +10 -0
- package/frontend/src/types.ts +181 -0
- package/frontend/tsconfig.app.json +32 -0
- package/frontend/tsconfig.json +7 -0
- package/frontend/tsconfig.node.json +26 -0
- package/frontend/vite.config.ts +25 -0
- package/install.sh +87 -0
- package/package.json +54 -19
- package/{templates → skills}/cmux-guide.md +5 -14
- package/{templates → skills}/compare-image.md +75 -49
- package/{templates → skills}/coordinate.md +6 -25
- package/{templates → skills}/create-path.md +5 -5
- package/{templates → skills}/debug.md +4 -4
- package/{templates → skills}/edit-path.md +6 -6
- package/skills/speak.md +49 -0
- package/{templates → skills}/start-work.md +95 -33
- package/{templates → skills}/walkthrough.md +16 -14
- package/src/api-keys.ts +97 -0
- package/src/auth.ts +165 -0
- package/src/coordinator.ts +136 -0
- package/src/dashboard-server.ts +5 -0
- package/src/dashboard.ts +921 -0
- package/src/db.ts +1023 -0
- package/src/index.ts +20 -0
- package/src/middleware.ts +76 -0
- package/src/tools.ts +864 -0
- package/src/types-shim.d.ts +18 -0
- package/src/types.ts +171 -0
- package/tsconfig.json +19 -0
- package/README.md +0 -116
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -131
- package/dist/commands.d.ts +0 -3
- package/dist/commands.js +0 -69
- package/dist/setup.d.ts +0 -2
- package/dist/setup.js +0 -303
- package/docker/docker-compose.yml +0 -15
- /package/{templates → skills}/review-code.md +0 -0
- /package/{templates → skills}/sessions.md +0 -0
package/Dockerfile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
FROM node:22-alpine
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Install backend deps
|
|
6
|
+
COPY package.json ./
|
|
7
|
+
RUN npm install
|
|
8
|
+
|
|
9
|
+
# Build backend
|
|
10
|
+
COPY tsconfig.json ./
|
|
11
|
+
COPY src/ ./src/
|
|
12
|
+
RUN npm run build
|
|
13
|
+
|
|
14
|
+
# Build frontend
|
|
15
|
+
COPY frontend/package.json ./frontend/
|
|
16
|
+
RUN cd frontend && npm install --legacy-peer-deps
|
|
17
|
+
COPY frontend/ ./frontend/
|
|
18
|
+
RUN cd frontend && npx tsc -b && npx vite build
|
|
19
|
+
|
|
20
|
+
RUN mkdir -p /data
|
|
21
|
+
|
|
22
|
+
ENTRYPOINT ["node", "dist/dashboard-server.js"]
|
package/bin/tanuki.mjs
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const pkg = require("../package.json");
|
|
11
|
+
|
|
12
|
+
const VERSION = pkg.version;
|
|
13
|
+
const TANUKI_DIR = process.env.TANUKI_DIR || path.join(os.homedir(), ".tanuki");
|
|
14
|
+
const DATA_DIR = process.env.TANUKI_DATA || path.join(os.homedir(), ".tanuki/data");
|
|
15
|
+
const PORT = process.env.TANUKI_PORT || "3333";
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const command = args[0] || "start";
|
|
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} | ${w}o${g} __ ${w}o${g} |${r}
|
|
49
|
+
${g} \\ / \\ /${r}
|
|
50
|
+
${g} \\/ \\/${r}
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const BANNER = ` ${g}${b}TANUKI${r} ${d}v${VERSION}${r}
|
|
54
|
+
${d}workflow monitor for claude code${r}
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const HELP = `
|
|
58
|
+
${g}${b} tanuki${r} ${d}v${VERSION}${r} — workflow monitor for Claude Code
|
|
59
|
+
|
|
60
|
+
${w} Usage:${r}
|
|
61
|
+
${cy}npx tanuki-telemetry${r} start dashboard
|
|
62
|
+
${cy}npx tanuki-telemetry stop${r} stop dashboard
|
|
63
|
+
${cy}npx tanuki-telemetry status${r} check if running
|
|
64
|
+
${cy}npx tanuki-telemetry setup${r} configure claude code
|
|
65
|
+
${cy}npx tanuki-telemetry update${r} rebuild with latest
|
|
66
|
+
${cy}npx tanuki-telemetry skills${r} install/update skills
|
|
67
|
+
${cy}npx tanuki-telemetry version${r} show version
|
|
68
|
+
|
|
69
|
+
${w} Environment:${r}
|
|
70
|
+
${d}TANUKI_PORT dashboard port (default: 3333)${r}
|
|
71
|
+
${d}TANUKI_DATA data directory (default: ~/.tanuki/data)${r}
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
function log(msg) { console.log(msg); }
|
|
75
|
+
function step(n, total, msg) { log(` ${g}[${n}/${total}]${r} ${msg}`); }
|
|
76
|
+
function ok(msg) { log(` ${d} ${g}✓${r} ${d}${msg}${r}`); }
|
|
77
|
+
function fail(msg) { log(` ${c.red} ✗ ${msg}${r}`); }
|
|
78
|
+
function info(msg) { log(` ${d} ${msg}${r}`); }
|
|
79
|
+
|
|
80
|
+
function run(cmd, opts = {}) {
|
|
81
|
+
try {
|
|
82
|
+
return execSync(cmd, { encoding: "utf-8", stdio: opts.quiet ? "pipe" : "inherit", ...opts });
|
|
83
|
+
} catch (e) {
|
|
84
|
+
if (!opts.ignoreError) {
|
|
85
|
+
fail(e.message);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function checkRequirements() {
|
|
93
|
+
let allOk = true;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
execSync("docker --version", { stdio: "pipe" });
|
|
97
|
+
} catch {
|
|
98
|
+
fail("docker not installed — https://docker.com/products/docker-desktop");
|
|
99
|
+
allOk = false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (allOk) {
|
|
103
|
+
try {
|
|
104
|
+
execSync("docker info", { stdio: "pipe" });
|
|
105
|
+
} catch {
|
|
106
|
+
fail("docker is not running — start Docker Desktop");
|
|
107
|
+
allOk = false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
|
|
112
|
+
if (nodeVersion < 18) {
|
|
113
|
+
fail(`node ${process.versions.node} — need 18+`);
|
|
114
|
+
allOk = false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
execSync("curl --version", { stdio: "pipe" });
|
|
119
|
+
} catch {
|
|
120
|
+
fail("curl not found");
|
|
121
|
+
allOk = false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// cmux (optional but recommended)
|
|
125
|
+
let hasCmux = false;
|
|
126
|
+
try {
|
|
127
|
+
execSync("cmux --version", { stdio: "pipe" });
|
|
128
|
+
hasCmux = true;
|
|
129
|
+
} catch {}
|
|
130
|
+
|
|
131
|
+
if (!allOk) {
|
|
132
|
+
log("");
|
|
133
|
+
fail("fix the above and try again");
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ok(`docker, node, curl${hasCmux ? ", cmux" : ""}`);
|
|
138
|
+
if (!hasCmux) {
|
|
139
|
+
info(`${y}cmux not found — needed for /coordinate and workspace skills${r}`);
|
|
140
|
+
info(`${d}install: https://github.com/anthropics/cmux${r}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isRunning() {
|
|
145
|
+
try {
|
|
146
|
+
const out = execSync("docker ps --filter name=tanuki-dashboard --format '{{.ID}}'", { encoding: "utf-8", stdio: "pipe" });
|
|
147
|
+
return out.trim().length > 0;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function copySourceToTanukiDir() {
|
|
154
|
+
const srcDir = path.resolve(path.dirname(import.meta.url.replace("file://", "")), "..");
|
|
155
|
+
if (srcDir === TANUKI_DIR) return;
|
|
156
|
+
|
|
157
|
+
fs.mkdirSync(TANUKI_DIR, { recursive: true });
|
|
158
|
+
|
|
159
|
+
for (const f of ["package.json", "tsconfig.json", "Dockerfile"]) {
|
|
160
|
+
const src = path.join(srcDir, f);
|
|
161
|
+
if (fs.existsSync(src)) fs.copyFileSync(src, path.join(TANUKI_DIR, f));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const d of ["src", "frontend"]) {
|
|
165
|
+
const src = path.join(srcDir, d);
|
|
166
|
+
if (fs.existsSync(src)) copyDirSync(src, path.join(TANUKI_DIR, d));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function copyDirSync(src, dest) {
|
|
171
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
172
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
173
|
+
const srcPath = path.join(src, entry.name);
|
|
174
|
+
const destPath = path.join(dest, entry.name);
|
|
175
|
+
if (["node_modules", "dist", ".git"].includes(entry.name)) continue;
|
|
176
|
+
if (entry.isDirectory()) copyDirSync(srcPath, destPath);
|
|
177
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function buildImages() {
|
|
182
|
+
run(`docker build -t tanuki-mcp:latest -t tanuki-dashboard:latest -t telemetry-mcp:latest -t telemetry-dashboard:latest "${TANUKI_DIR}" -q`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function startDashboard() {
|
|
186
|
+
run("docker rm -f tanuki-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
|
|
187
|
+
run("docker rm -f telemetry-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
|
|
188
|
+
run(`docker run -d --rm --name tanuki-dashboard -p ${PORT}:3333 -v "${DATA_DIR}:/data" tanuki-dashboard:latest`, { quiet: true });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getMcpEntry() {
|
|
192
|
+
return {
|
|
193
|
+
type: "stdio",
|
|
194
|
+
command: "docker",
|
|
195
|
+
args: ["run", "--rm", "-i", "-v", `${DATA_DIR}:/data`, "--entrypoint", "node", "tanuki-mcp:latest", "dist/index.js"]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function installSkills() {
|
|
200
|
+
const skillsDir = path.resolve(path.dirname(import.meta.url.replace("file://", "")), "../skills");
|
|
201
|
+
const targetDir = path.join(os.homedir(), ".claude", "commands");
|
|
202
|
+
|
|
203
|
+
if (!fs.existsSync(skillsDir)) {
|
|
204
|
+
info("skills directory not found in package");
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
209
|
+
|
|
210
|
+
const skills = fs.readdirSync(skillsDir).filter((f) => f.endsWith(".md"));
|
|
211
|
+
let installed = 0;
|
|
212
|
+
|
|
213
|
+
for (const skill of skills) {
|
|
214
|
+
const src = path.join(skillsDir, skill);
|
|
215
|
+
const dest = path.join(targetDir, skill);
|
|
216
|
+
|
|
217
|
+
if (fs.existsSync(dest)) {
|
|
218
|
+
// Check if ours is newer/different
|
|
219
|
+
const srcContent = fs.readFileSync(src, "utf-8");
|
|
220
|
+
const destContent = fs.readFileSync(dest, "utf-8");
|
|
221
|
+
if (srcContent === destContent) continue;
|
|
222
|
+
|
|
223
|
+
// Back up existing
|
|
224
|
+
fs.copyFileSync(dest, dest + ".bak");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fs.copyFileSync(src, dest);
|
|
228
|
+
installed++;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (installed > 0) {
|
|
232
|
+
ok(`${installed} skill${installed > 1 ? "s" : ""} installed to ~/.claude/commands/`);
|
|
233
|
+
} else {
|
|
234
|
+
ok(`${skills.length} skills up to date`);
|
|
235
|
+
}
|
|
236
|
+
return installed;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function autoConfigureClaude() {
|
|
240
|
+
const claudeConfig = path.join(os.homedir(), ".claude.json");
|
|
241
|
+
|
|
242
|
+
if (!fs.existsSync(claudeConfig)) {
|
|
243
|
+
const config = { mcpServers: { telemetry: getMcpEntry() } };
|
|
244
|
+
fs.writeFileSync(claudeConfig, JSON.stringify(config, null, 2) + "\n");
|
|
245
|
+
ok("created ~/.claude.json with MCP config");
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const raw = fs.readFileSync(claudeConfig, "utf-8");
|
|
251
|
+
const config = JSON.parse(raw);
|
|
252
|
+
|
|
253
|
+
if (config.mcpServers?.telemetry) {
|
|
254
|
+
ok("MCP already configured");
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
259
|
+
config.mcpServers.telemetry = getMcpEntry();
|
|
260
|
+
fs.writeFileSync(claudeConfig, JSON.stringify(config, null, 2) + "\n");
|
|
261
|
+
ok("added MCP config to ~/.claude.json");
|
|
262
|
+
return true;
|
|
263
|
+
} catch {
|
|
264
|
+
fail("could not auto-configure ~/.claude.json");
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --- Commands ---
|
|
270
|
+
|
|
271
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
272
|
+
log(LOGO);
|
|
273
|
+
log(HELP);
|
|
274
|
+
process.exit(0);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (command === "version" || command === "--version" || command === "-v") {
|
|
278
|
+
log(`${g}tanuki${r} ${d}v${VERSION}${r}`);
|
|
279
|
+
process.exit(0);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (command === "start") {
|
|
283
|
+
log(LOGO);
|
|
284
|
+
log(BANNER);
|
|
285
|
+
|
|
286
|
+
step(1, 5, "checking requirements");
|
|
287
|
+
checkRequirements();
|
|
288
|
+
|
|
289
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
290
|
+
|
|
291
|
+
if (isRunning()) {
|
|
292
|
+
log("");
|
|
293
|
+
log(` ${g}already running${r} at ${cy}http://localhost:${PORT}${r}`);
|
|
294
|
+
log("");
|
|
295
|
+
process.exit(0);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
copySourceToTanukiDir();
|
|
299
|
+
|
|
300
|
+
const hasImage = (() => {
|
|
301
|
+
try { execSync("docker image inspect tanuki-dashboard:latest", { stdio: "pipe" }); return true; }
|
|
302
|
+
catch { return false; }
|
|
303
|
+
})();
|
|
304
|
+
|
|
305
|
+
step(2, 5, hasImage ? "docker images" : "building docker images");
|
|
306
|
+
if (!hasImage) {
|
|
307
|
+
info("first run — this takes ~30s...");
|
|
308
|
+
buildImages();
|
|
309
|
+
}
|
|
310
|
+
ok(hasImage ? "cached" : "images built");
|
|
311
|
+
|
|
312
|
+
step(3, 5, "starting dashboard");
|
|
313
|
+
startDashboard();
|
|
314
|
+
|
|
315
|
+
let healthy = false;
|
|
316
|
+
for (let i = 0; i < 10; i++) {
|
|
317
|
+
try {
|
|
318
|
+
const res = execSync(`curl -s http://localhost:${PORT}/health`, { encoding: "utf-8", stdio: "pipe" });
|
|
319
|
+
if (res.includes('"ok":true')) { healthy = true; break; }
|
|
320
|
+
} catch {}
|
|
321
|
+
execSync("sleep 1", { stdio: "pipe" });
|
|
322
|
+
}
|
|
323
|
+
ok(healthy ? `running on port ${PORT}` : `starting — check port ${PORT}`);
|
|
324
|
+
|
|
325
|
+
step(4, 5, "configuring claude code");
|
|
326
|
+
autoConfigureClaude();
|
|
327
|
+
|
|
328
|
+
step(5, 5, "installing skills");
|
|
329
|
+
installSkills();
|
|
330
|
+
|
|
331
|
+
log("");
|
|
332
|
+
log(` ${d}─────────────────────────────────────────${r}`);
|
|
333
|
+
log("");
|
|
334
|
+
log(` ${g}${b}ready${r}`);
|
|
335
|
+
log("");
|
|
336
|
+
log(` ${w}dashboard${r} ${cy}http://localhost:${PORT}${r}`);
|
|
337
|
+
log(` ${w}data${r} ${d}${DATA_DIR}${r}`);
|
|
338
|
+
log("");
|
|
339
|
+
log(` ${w}skills installed:${r}`);
|
|
340
|
+
log(` ${g}/start-work${r} ${d}autonomous dev — context to PR${r}`);
|
|
341
|
+
log(` ${g}/coordinate${r} ${d}manage cmux workspaces${r}`);
|
|
342
|
+
log(` ${g}/walkthrough${r} ${d}execute UI test scenarios${r}`);
|
|
343
|
+
log(` ${g}/debug${r} ${d}systematic bug investigation${r}`);
|
|
344
|
+
log(` ${g}/review-code${r} ${d}PR code review${r}`);
|
|
345
|
+
log(` ${g}/sessions${r} ${d}browse past sessions${r}`);
|
|
346
|
+
log(` ${g}/create-path${r} ${d}generate walkthrough scenarios${r}`);
|
|
347
|
+
log(` ${g}/edit-path${r} ${d}update walkthrough scenarios${r}`);
|
|
348
|
+
log(` ${g}/cmux-guide${r} ${d}workspace navigation ref${r}`);
|
|
349
|
+
log("");
|
|
350
|
+
log(` ${y}restart claude code to load MCP tools + skills${r}`);
|
|
351
|
+
log("");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
else if (command === "stop") {
|
|
355
|
+
run("docker rm -f tanuki-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
|
|
356
|
+
run("docker rm -f telemetry-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
|
|
357
|
+
log(` ${g}stopped${r}`);
|
|
358
|
+
log("");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
else if (command === "status") {
|
|
362
|
+
if (isRunning()) {
|
|
363
|
+
try {
|
|
364
|
+
const health = execSync(`curl -s http://localhost:${PORT}/health`, { encoding: "utf-8", stdio: "pipe" });
|
|
365
|
+
const parsed = JSON.parse(health);
|
|
366
|
+
log(` ${g}running${r} at ${cy}http://localhost:${PORT}${r} ${d}(v${parsed.version})${r}`);
|
|
367
|
+
} catch {
|
|
368
|
+
log(` ${y}container running but health check failed${r}`);
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
log(` ${d}not running${r} — ${cy}npx tanuki-telemetry${r} to start`);
|
|
372
|
+
}
|
|
373
|
+
log("");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
else if (command === "setup") {
|
|
377
|
+
log(BANNER);
|
|
378
|
+
autoConfigureClaude();
|
|
379
|
+
installSkills();
|
|
380
|
+
log("");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
else if (command === "skills") {
|
|
384
|
+
log(BANNER);
|
|
385
|
+
installSkills();
|
|
386
|
+
log("");
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
else if (command === "update") {
|
|
390
|
+
log(BANNER);
|
|
391
|
+
step(1, 2, "rebuilding images");
|
|
392
|
+
checkRequirements();
|
|
393
|
+
copySourceToTanukiDir();
|
|
394
|
+
buildImages();
|
|
395
|
+
ok("images rebuilt");
|
|
396
|
+
|
|
397
|
+
if (isRunning()) {
|
|
398
|
+
step(2, 2, "restarting dashboard");
|
|
399
|
+
run("docker rm -f tanuki-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
|
|
400
|
+
run("docker rm -f telemetry-dashboard 2>/dev/null || true", { quiet: true, ignoreError: true });
|
|
401
|
+
startDashboard();
|
|
402
|
+
ok(`running at http://localhost:${PORT}`);
|
|
403
|
+
} else {
|
|
404
|
+
step(2, 2, "done");
|
|
405
|
+
info("run 'npx tanuki-telemetry' to start");
|
|
406
|
+
}
|
|
407
|
+
log("");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
else {
|
|
411
|
+
log(` ${c.red}unknown command: ${command}${r}`);
|
|
412
|
+
log(HELP);
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs.flat.recommended,
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>tanuki</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|