claude-view 0.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.
- package/README.md +46 -0
- package/index.js +223 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# claude-view
|
|
2
|
+
|
|
3
|
+
Browse and search your Claude Code sessions in a beautiful local web UI.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx claude-view
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This downloads the pre-built binary for your platform, caches it locally, and starts a local web server. Open the printed URL in your browser to explore your Claude Code sessions.
|
|
12
|
+
|
|
13
|
+
## What It Does
|
|
14
|
+
|
|
15
|
+
- Reads your local `~/.claude/` session files (JSONL)
|
|
16
|
+
- Indexes them into a local SQLite database for fast search
|
|
17
|
+
- Serves a React UI at `http://localhost:47892`
|
|
18
|
+
- Everything stays on your machine -- no data is sent anywhere
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
| Env Variable | Default | Description |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `CLAUDE_VIEW_PORT` | `47892` | Port for the local server |
|
|
25
|
+
| `PORT` | `47892` | Alternative port override |
|
|
26
|
+
|
|
27
|
+
## Supported Platforms
|
|
28
|
+
|
|
29
|
+
| OS | Architecture |
|
|
30
|
+
|---|---|
|
|
31
|
+
| macOS | Apple Silicon (arm64), Intel (x64) |
|
|
32
|
+
| Linux | x64 |
|
|
33
|
+
| Windows | x64 |
|
|
34
|
+
|
|
35
|
+
## How It Works
|
|
36
|
+
|
|
37
|
+
On first run, `npx claude-view` downloads a platform-specific tarball from GitHub Releases containing a compiled Rust binary and bundled frontend assets. The binary is cached at `~/.cache/claude-view/` so subsequent runs start instantly.
|
|
38
|
+
|
|
39
|
+
## Links
|
|
40
|
+
|
|
41
|
+
- [GitHub Repository](https://github.com/tombelieber/claude-view)
|
|
42
|
+
- [Report an Issue](https://github.com/tombelieber/claude-view/issues)
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execFileSync, spawn } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const https = require("https");
|
|
8
|
+
const zlib = require("zlib");
|
|
9
|
+
|
|
10
|
+
const VERSION = require("./package.json").version;
|
|
11
|
+
const REPO = "tombelieber/claude-view";
|
|
12
|
+
const BINARY_NAME = process.platform === "win32" ? "vibe-recall.exe" : "vibe-recall";
|
|
13
|
+
|
|
14
|
+
// --- Platform detection ---
|
|
15
|
+
|
|
16
|
+
const PLATFORM_MAP = {
|
|
17
|
+
"darwin-arm64": { artifact: "claude-view-darwin-arm64.tar.gz", ext: "tar.gz" },
|
|
18
|
+
"darwin-x64": { artifact: "claude-view-darwin-x64.tar.gz", ext: "tar.gz" },
|
|
19
|
+
"linux-x64": { artifact: "claude-view-linux-x64.tar.gz", ext: "tar.gz" },
|
|
20
|
+
"win32-x64": { artifact: "claude-view-win32-x64.zip", ext: "zip" },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const platformKey = `${process.platform}-${process.arch}`;
|
|
24
|
+
const platformInfo = PLATFORM_MAP[platformKey];
|
|
25
|
+
|
|
26
|
+
if (!platformInfo) {
|
|
27
|
+
console.error(
|
|
28
|
+
`Error: Unsupported platform "${process.platform}" with architecture "${process.arch}".\n` +
|
|
29
|
+
`Supported: macOS (arm64, x64), Linux (x64), Windows (x64).`
|
|
30
|
+
);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Cache paths ---
|
|
35
|
+
|
|
36
|
+
const cacheDir = path.join(os.homedir(), ".cache", "claude-view");
|
|
37
|
+
const binDir = path.join(cacheDir, "bin");
|
|
38
|
+
const versionFile = path.join(cacheDir, "version");
|
|
39
|
+
const binaryPath = path.join(binDir, BINARY_NAME);
|
|
40
|
+
const distDir = path.join(binDir, "dist");
|
|
41
|
+
|
|
42
|
+
// --- Helpers ---
|
|
43
|
+
|
|
44
|
+
function download(url) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const request = https.get(url, (res) => {
|
|
47
|
+
// Follow redirects (GitHub releases redirect to S3/CDN)
|
|
48
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
49
|
+
return download(res.headers.location).then(resolve, reject);
|
|
50
|
+
}
|
|
51
|
+
if (res.statusCode !== 200) {
|
|
52
|
+
reject(new Error(`Download failed: HTTP ${res.statusCode} from ${url}`));
|
|
53
|
+
res.resume();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const chunks = [];
|
|
57
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
58
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
59
|
+
res.on("error", reject);
|
|
60
|
+
});
|
|
61
|
+
request.on("error", reject);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function extractTarGz(buffer, destDir) {
|
|
66
|
+
// Use system tar — available on macOS, Linux, and modern Windows (tar ships with Win10+)
|
|
67
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
68
|
+
const tmpFile = path.join(os.tmpdir(), `claude-view-${Date.now()}.tar.gz`);
|
|
69
|
+
fs.writeFileSync(tmpFile, buffer);
|
|
70
|
+
try {
|
|
71
|
+
execFileSync("tar", ["xzf", tmpFile, "-C", destDir], { stdio: "pipe" });
|
|
72
|
+
} finally {
|
|
73
|
+
fs.unlinkSync(tmpFile);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function extractZip(buffer, destDir) {
|
|
78
|
+
// Use system tar on Windows 10+ (supports zip) or PowerShell as fallback
|
|
79
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
80
|
+
const tmpFile = path.join(os.tmpdir(), `claude-view-${Date.now()}.zip`);
|
|
81
|
+
fs.writeFileSync(tmpFile, buffer);
|
|
82
|
+
try {
|
|
83
|
+
if (process.platform === "win32") {
|
|
84
|
+
execFileSync(
|
|
85
|
+
"powershell",
|
|
86
|
+
["-Command", `Expand-Archive -Force -Path '${tmpFile}' -DestinationPath '${destDir}'`],
|
|
87
|
+
{ stdio: "pipe" }
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
execFileSync("unzip", ["-o", tmpFile, "-d", destDir], { stdio: "pipe" });
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
fs.unlinkSync(tmpFile);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function downloadChecksums(version) {
|
|
98
|
+
const url = `https://github.com/${REPO}/releases/download/v${version}/checksums.txt`;
|
|
99
|
+
return download(url)
|
|
100
|
+
.then((buf) => {
|
|
101
|
+
const map = {};
|
|
102
|
+
const lines = buf.toString("utf-8").split("\n");
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
// Format: "<64-hex-chars> <filename>" (two spaces between hash and filename)
|
|
105
|
+
const match = line.match(/^([0-9a-f]{64}) (.+)$/);
|
|
106
|
+
if (match) {
|
|
107
|
+
map[match[2]] = match[1];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
})
|
|
112
|
+
.catch(() => null); // Graceful fallback for older releases without checksums
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function verifyChecksum(buffer, expectedHash) {
|
|
116
|
+
const crypto = require("crypto");
|
|
117
|
+
const actualHash = crypto.createHash("sha256").update(buffer).digest("hex");
|
|
118
|
+
if (actualHash !== expectedHash) {
|
|
119
|
+
console.error(`Checksum verification failed.`);
|
|
120
|
+
console.error(` Expected: ${expectedHash}`);
|
|
121
|
+
console.error(` Actual: ${actualHash}`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --- Main ---
|
|
127
|
+
|
|
128
|
+
async function main() {
|
|
129
|
+
// Check if cached version matches
|
|
130
|
+
let needsDownload = true;
|
|
131
|
+
if (fs.existsSync(versionFile) && fs.existsSync(binaryPath)) {
|
|
132
|
+
const cached = fs.readFileSync(versionFile, "utf-8").trim();
|
|
133
|
+
if (cached === VERSION) {
|
|
134
|
+
needsDownload = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (needsDownload) {
|
|
139
|
+
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${platformInfo.artifact}`;
|
|
140
|
+
console.log(`Downloading claude-view v${VERSION} for ${platformKey}...`);
|
|
141
|
+
|
|
142
|
+
let buffer;
|
|
143
|
+
try {
|
|
144
|
+
buffer = await download(url);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.error(`\nFailed to download claude-view:\n ${err.message}`);
|
|
147
|
+
console.error(`\nURL: ${url}`);
|
|
148
|
+
console.error(`\nCheck that release v${VERSION} exists at https://github.com/${REPO}/releases`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Verify checksum if available
|
|
153
|
+
const checksums = await downloadChecksums(VERSION);
|
|
154
|
+
if (checksums && checksums[platformInfo.artifact]) {
|
|
155
|
+
verifyChecksum(buffer, checksums[platformInfo.artifact]);
|
|
156
|
+
console.log("Checksum verified.");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Clean previous install
|
|
160
|
+
fs.rmSync(binDir, { recursive: true, force: true });
|
|
161
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
162
|
+
|
|
163
|
+
// Extract
|
|
164
|
+
try {
|
|
165
|
+
if (platformInfo.ext === "zip") {
|
|
166
|
+
extractZip(buffer, binDir);
|
|
167
|
+
} else {
|
|
168
|
+
extractTarGz(buffer, binDir);
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`\nFailed to extract archive:\n ${err.message}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Make binary executable (no-op on Windows)
|
|
176
|
+
if (process.platform !== "win32") {
|
|
177
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// macOS Gatekeeper: remove quarantine flag from downloaded binary
|
|
181
|
+
if (process.platform === "darwin") {
|
|
182
|
+
try {
|
|
183
|
+
execFileSync("xattr", ["-dr", "com.apple.quarantine", binDir], { stdio: "pipe" });
|
|
184
|
+
} catch {
|
|
185
|
+
// Ignore — xattr may not be available or quarantine flag may not be set
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Write version marker
|
|
190
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
191
|
+
fs.writeFileSync(versionFile, VERSION);
|
|
192
|
+
|
|
193
|
+
console.log(`Installed to ${binDir}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Verify binary exists
|
|
197
|
+
if (!fs.existsSync(binaryPath)) {
|
|
198
|
+
console.error(`Error: Binary not found at ${binaryPath}`);
|
|
199
|
+
console.error("Try deleting ~/.cache/claude-view/ and running again.");
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Set STATIC_DIR so the server finds the frontend assets
|
|
204
|
+
const env = { ...process.env, STATIC_DIR: distDir };
|
|
205
|
+
|
|
206
|
+
// Run the server, forwarding signals and exit code
|
|
207
|
+
const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit", env });
|
|
208
|
+
|
|
209
|
+
// Forward signals to child process
|
|
210
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
211
|
+
process.on(sig, () => child.kill(sig));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
child.on("exit", (code, signal) => {
|
|
215
|
+
if (signal) {
|
|
216
|
+
process.kill(process.pid, signal);
|
|
217
|
+
} else {
|
|
218
|
+
process.exit(code ?? 1);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-view",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Browse and search your Claude Code sessions in a beautiful local web UI",
|
|
5
|
+
"bin": {
|
|
6
|
+
"claude-view": "./index.js"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/tombelieber/claude-view"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"claude",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"sessions",
|
|
17
|
+
"viewer",
|
|
18
|
+
"browser"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=16"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"index.js",
|
|
25
|
+
"README.md"
|
|
26
|
+
]
|
|
27
|
+
}
|