-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
executable file
·75 lines (66 loc) · 2.6 KB
/
Copy pathserver.js
File metadata and controls
executable file
·75 lines (66 loc) · 2.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#!/usr/bin/env node
'use strict';
/**
* cc-vis — a session visualizer for Claude Code transcripts.
*
* Mirrors the kimi vis architecture: read local session data directly from
* disk, expose it over a small read-only HTTP API, and serve an embedded
* single-page app to inspect it. No LLM calls, no build step, no deps.
*
* Data source: ~/.claude/projects/<project-slug>/<session-id>.jsonl
* ~/.claude/projects/<project-slug>/<session-id>/subagents/agent-*.jsonl
*
* Usage:
* cc-vis [--port N] [--host H] [--no-open]
* VIS_AUTH_TOKEN=secret cc-vis --host 0.0.0.0 # required off-loopback
*/
const path = require('node:path');
const os = require('node:os');
const { exec } = require('node:child_process');
const { createServer } = require('./src/httpd');
function parseArgs(argv) {
const opts = { port: 0, host: '127.0.0.1', open: true };
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === '--port') opts.port = Number(argv[++i]);
else if (a === '--host') opts.host = argv[++i];
else if (a === '--no-open') opts.open = false;
else if (a === '--help' || a === '-h') {
console.log('cc-vis [--port N] [--host H] [--no-open]');
process.exit(0);
}
}
return opts;
}
function isLoopback(host) {
return host === '127.0.0.1' || host === 'localhost' || host === '::1' || host === '[::1]';
}
async function main() {
const opts = parseArgs(process.argv);
const claudeHome = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
const projectsDir = path.join(claudeHome, 'projects');
const publicDir = path.join(__dirname, 'public');
const authToken = (process.env.VIS_AUTH_TOKEN || '').trim() || undefined;
if (!isLoopback(opts.host) && !authToken) {
console.error(`VIS_AUTH_TOKEN is required when binding cc-vis outside loopback (host=${opts.host})`);
process.exit(1);
}
const server = createServer({ projectsDir, publicDir, authToken });
await new Promise((resolve, reject) => {
server.once('error', reject);
server.listen(opts.port, opts.host, resolve);
});
const { port } = server.address();
const auth = authToken ? 'auth=required' : 'auth=disabled';
const url = `http://${opts.host}:${String(port)}`;
console.log(`[cc-vis] listening on ${url} (${auth}, CLAUDE_HOME=${claudeHome})`);
if (opts.open && isLoopback(opts.host)) {
const opener =
process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
exec(`${opener} ${url}`, () => {});
}
}
main().catch((err) => {
console.error(`[cc-vis] fatal: ${(err && err.stack) || err}`);
process.exit(1);
});