claude-view 0.8.0 → 0.11.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 (3) hide show
  1. package/README.md +66 -16
  2. package/index.js +120 -106
  3. package/package.json +44 -9
package/README.md CHANGED
@@ -1,46 +1,96 @@
1
+ <div align="center">
2
+
1
3
  # claude-view
2
4
 
3
- Browse and search your Claude Code sessions in a beautiful local web UI.
5
+ **You have 10 Claude sessions running right now. What are they doing?**
6
+
7
+ <p>
8
+ <a href="https://www.npmjs.com/package/claude-view"><img src="https://img.shields.io/npm/v/claude-view.svg" alt="npm version"></a>
9
+ <a href="https://claudeview.ai"><img src="https://img.shields.io/badge/Website-claudeview.ai-orange" alt="Website"></a>
10
+ <a href="https://github.com/tombelieber/claude-view/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
11
+ <img src="https://img.shields.io/badge/Platform-macOS-lightgrey.svg" alt="macOS">
12
+ <a href="https://github.com/tombelieber/claude-view/stargazers"><img src="https://img.shields.io/github/stars/tombelieber/claude-view?style=social" alt="GitHub stars"></a>
13
+ </p>
14
+
15
+ </div>
16
+
17
+ Behind every "thinking..." spinner, Claude is spawning sub-agents, calling MCP servers, running skills, firing hooks — and you can't see any of it.
18
+
19
+ **You're paying $200/mo for Claude Code. You deserve a dashboard.**
4
20
 
5
- ## Quick Start
21
+ <div align="center">
6
22
 
7
23
  ```bash
8
- npx claude-view
24
+ curl -fsSL https://raw.githubusercontent.com/tombelieber/claude-view/main/install.sh | sh
9
25
  ```
10
26
 
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.
27
+ **One command. Every session visible. Real-time.**
28
+
29
+ </div>
30
+
31
+ ---
32
+
33
+ ## Install
34
+
35
+ | Method | Command |
36
+ |--------|---------|
37
+ | **Shell** (recommended) | `curl -fsSL https://raw.githubusercontent.com/tombelieber/claude-view/main/install.sh \| sh` |
38
+ | **npx** | `npx claude-view` |
39
+
40
+ ---
12
41
 
13
- ## What It Does
42
+ ## What You Get
14
43
 
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
44
+ - **Live session cards** see what every session is working on, right now
45
+ - **Notification sounds** get pinged when a session finishes or needs input
46
+ - **Context gauge** real-time context window usage per session
47
+ - **Cache warm countdown** time your messages to save tokens
48
+ - **Cost tracking** — per-session and aggregate spend with cache savings
49
+ - **Sub-agent visualization** — see the full agent tree, tool calls, MCP invocations
50
+ - **Full-text search** — search across all sessions, messages, tool calls, file paths
51
+ - **Analytics** — activity heatmap, cost ROI, model comparison, AI Fluency Score
52
+ - **Rich chat history** — every conversation rendered with markdown, code blocks, tool calls
53
+
54
+ ---
55
+
56
+ ## How It Works
57
+
58
+ On first run, `npx claude-view` downloads a platform-specific Rust binary (~10 MB) from GitHub Releases. The binary is cached at `~/.cache/claude-view/` so subsequent runs start instantly.
59
+
60
+ Everything stays on your machine. Zero telemetry, zero cloud, zero network requests.
61
+
62
+ ---
19
63
 
20
64
  ## Configuration
21
65
 
22
66
  | Env Variable | Default | Description |
23
- |---|---|---|
67
+ | --- | --- | --- |
24
68
  | `CLAUDE_VIEW_PORT` | `47892` | Port for the local server |
25
69
  | `PORT` | `47892` | Alternative port override |
26
70
 
27
71
  ## Supported Platforms
28
72
 
29
73
  | OS | Architecture |
30
- |---|---|
74
+ | --- | --- |
31
75
  | macOS | Apple Silicon (arm64), Intel (x64) |
32
76
  | Linux | x64 |
33
77
  | Windows | x64 |
34
78
 
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.
79
+ ---
38
80
 
39
81
  ## Links
40
82
 
41
- - [GitHub Repository](https://github.com/tombelieber/claude-view)
83
+ - [Website](https://claudeview.ai) — docs, changelog, blog
84
+ - [GitHub Repository](https://github.com/tombelieber/claude-view) — full feature list, comparison table, architecture details
85
+ - [@claude-view/plugin](https://www.npmjs.com/package/@claude-view/plugin) — Claude Code plugin with 8 MCP tools and 3 skills
86
+ - [claude-backup](https://github.com/tombelieber/claude-backup) — Claude Code deletes your sessions after 30 days. This saves them.
42
87
  - [Report an Issue](https://github.com/tombelieber/claude-view/issues)
88
+ - [Discord](https://discord.gg/G7wdZTpRfu)
43
89
 
44
- ## License
90
+ ---
91
+
92
+ <div align="center">
45
93
 
46
94
  MIT
95
+
96
+ </div>
package/index.js CHANGED
@@ -1,43 +1,43 @@
1
1
  #!/usr/bin/env node
2
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");
3
+ const { execFileSync, execSync, 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
9
 
10
- const VERSION = require("./package.json").version;
11
- const REPO = "tombelieber/claude-view";
12
- const BINARY_NAME = process.platform === "win32" ? "claude-view.exe" : "claude-view";
10
+ const VERSION = require('./package.json').version
11
+ const REPO = 'tombelieber/claude-view'
12
+ const BINARY_NAME = process.platform === 'win32' ? 'claude-view.exe' : 'claude-view'
13
13
 
14
14
  // --- Platform detection ---
15
15
 
16
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
- };
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
22
 
23
- const platformKey = `${process.platform}-${process.arch}`;
24
- const platformInfo = PLATFORM_MAP[platformKey];
23
+ const platformKey = `${process.platform}-${process.arch}`
24
+ const platformInfo = PLATFORM_MAP[platformKey]
25
25
 
26
26
  if (!platformInfo) {
27
27
  console.error(
28
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);
29
+ `Supported: macOS (arm64, x64), Linux (x64), Windows (x64).`,
30
+ )
31
+ process.exit(1)
32
32
  }
33
33
 
34
34
  // --- Cache paths ---
35
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");
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
41
 
42
42
  // --- Helpers ---
43
43
 
@@ -46,80 +46,80 @@ function download(url) {
46
46
  const request = https.get(url, (res) => {
47
47
  // Follow redirects (GitHub releases redirect to S3/CDN)
48
48
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
49
- return download(res.headers.location).then(resolve, reject);
49
+ return download(res.headers.location).then(resolve, reject)
50
50
  }
51
51
  if (res.statusCode !== 200) {
52
- reject(new Error(`Download failed: HTTP ${res.statusCode} from ${url}`));
53
- res.resume();
54
- return;
52
+ reject(new Error(`Download failed: HTTP ${res.statusCode} from ${url}`))
53
+ res.resume()
54
+ return
55
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
- });
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
63
  }
64
64
 
65
65
  function extractTarGz(buffer, destDir) {
66
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);
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
70
  try {
71
- execFileSync("tar", ["xzf", tmpFile, "-C", destDir], { stdio: "pipe" });
71
+ execFileSync('tar', ['xzf', tmpFile, '-C', destDir], { stdio: 'pipe' })
72
72
  } finally {
73
- fs.unlinkSync(tmpFile);
73
+ fs.unlinkSync(tmpFile)
74
74
  }
75
75
  }
76
76
 
77
77
  function extractZip(buffer, destDir) {
78
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);
79
+ fs.mkdirSync(destDir, { recursive: true })
80
+ const tmpFile = path.join(os.tmpdir(), `claude-view-${Date.now()}.zip`)
81
+ fs.writeFileSync(tmpFile, buffer)
82
82
  try {
83
- if (process.platform === "win32") {
83
+ if (process.platform === 'win32') {
84
84
  execFileSync(
85
- "powershell",
86
- ["-Command", `Expand-Archive -Force -Path '${tmpFile}' -DestinationPath '${destDir}'`],
87
- { stdio: "pipe" }
88
- );
85
+ 'powershell',
86
+ ['-Command', `Expand-Archive -Force -Path '${tmpFile}' -DestinationPath '${destDir}'`],
87
+ { stdio: 'pipe' },
88
+ )
89
89
  } else {
90
- execFileSync("unzip", ["-o", tmpFile, "-d", destDir], { stdio: "pipe" });
90
+ execFileSync('unzip', ['-o', tmpFile, '-d', destDir], { stdio: 'pipe' })
91
91
  }
92
92
  } finally {
93
- fs.unlinkSync(tmpFile);
93
+ fs.unlinkSync(tmpFile)
94
94
  }
95
95
  }
96
96
 
97
97
  function downloadChecksums(version) {
98
- const url = `https://github.com/${REPO}/releases/download/v${version}/checksums.txt`;
98
+ const url = `https://github.com/${REPO}/releases/download/v${version}/checksums.txt`
99
99
  return download(url)
100
100
  .then((buf) => {
101
- const map = {};
102
- const lines = buf.toString("utf-8").split("\n");
101
+ const map = {}
102
+ const lines = buf.toString('utf-8').split('\n')
103
103
  for (const line of lines) {
104
104
  // Format: "<64-hex-chars> <filename>" (two spaces between hash and filename)
105
- const match = line.match(/^([0-9a-f]{64}) (.+)$/);
105
+ const match = line.match(/^([0-9a-f]{64}) {2}(.+)$/)
106
106
  if (match) {
107
- map[match[2]] = match[1];
107
+ map[match[2]] = match[1]
108
108
  }
109
109
  }
110
- return map;
110
+ return map
111
111
  })
112
- .catch(() => null); // Graceful fallback for older releases without checksums
112
+ .catch(() => null) // Graceful fallback for older releases without checksums
113
113
  }
114
114
 
115
115
  function verifyChecksum(buffer, expectedHash) {
116
- const crypto = require("crypto");
117
- const actualHash = crypto.createHash("sha256").update(buffer).digest("hex");
116
+ const crypto = require('crypto')
117
+ const actualHash = crypto.createHash('sha256').update(buffer).digest('hex')
118
118
  if (actualHash !== expectedHash) {
119
- console.error(`Checksum verification failed.`);
120
- console.error(` Expected: ${expectedHash}`);
121
- console.error(` Actual: ${actualHash}`);
122
- process.exit(1);
119
+ console.error(`Checksum verification failed.`)
120
+ console.error(` Expected: ${expectedHash}`)
121
+ console.error(` Actual: ${actualHash}`)
122
+ process.exit(1)
123
123
  }
124
124
  }
125
125
 
@@ -127,81 +127,95 @@ function verifyChecksum(buffer, expectedHash) {
127
127
 
128
128
  async function main() {
129
129
  // Check if cached version matches
130
- let needsDownload = true;
130
+ let needsDownload = true
131
131
  if (fs.existsSync(versionFile) && fs.existsSync(binaryPath)) {
132
- const cached = fs.readFileSync(versionFile, "utf-8").trim();
132
+ const cached = fs.readFileSync(versionFile, 'utf-8').trim()
133
133
  if (cached === VERSION) {
134
- needsDownload = false;
134
+ needsDownload = false
135
135
  }
136
136
  }
137
137
 
138
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}...`);
139
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${platformInfo.artifact}`
140
+ console.log(`Downloading claude-view v${VERSION} for ${platformKey}...`)
141
141
 
142
- let buffer;
142
+ let buffer
143
143
  try {
144
- buffer = await download(url);
144
+ buffer = await download(url)
145
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);
146
+ console.error(`\nFailed to download claude-view:\n ${err.message}`)
147
+ console.error(`\nURL: ${url}`)
148
+ console.error(
149
+ `\nCheck that release v${VERSION} exists at https://github.com/${REPO}/releases`,
150
+ )
151
+ process.exit(1)
150
152
  }
151
153
 
152
154
  // Verify checksum if available
153
- const checksums = await downloadChecksums(VERSION);
155
+ const checksums = await downloadChecksums(VERSION)
154
156
  if (checksums && checksums[platformInfo.artifact]) {
155
- verifyChecksum(buffer, checksums[platformInfo.artifact]);
156
- console.log("Checksum verified.");
157
+ verifyChecksum(buffer, checksums[platformInfo.artifact])
158
+ console.log('Checksum verified.')
157
159
  }
158
160
 
159
161
  // Clean previous install
160
- fs.rmSync(binDir, { recursive: true, force: true });
161
- fs.mkdirSync(binDir, { recursive: true });
162
+ fs.rmSync(binDir, { recursive: true, force: true })
163
+ fs.mkdirSync(binDir, { recursive: true })
162
164
 
163
165
  // Extract
164
166
  try {
165
- if (platformInfo.ext === "zip") {
166
- extractZip(buffer, binDir);
167
+ if (platformInfo.ext === 'zip') {
168
+ extractZip(buffer, binDir)
167
169
  } else {
168
- extractTarGz(buffer, binDir);
170
+ extractTarGz(buffer, binDir)
169
171
  }
170
172
  } catch (err) {
171
- console.error(`\nFailed to extract archive:\n ${err.message}`);
172
- process.exit(1);
173
+ console.error(`\nFailed to extract archive:\n ${err.message}`)
174
+ process.exit(1)
173
175
  }
174
176
 
175
177
  // Make binary executable (no-op on Windows)
176
- if (process.platform !== "win32") {
177
- fs.chmodSync(binaryPath, 0o755);
178
+ if (process.platform !== 'win32') {
179
+ fs.chmodSync(binaryPath, 0o755)
178
180
  }
179
181
 
180
182
  // macOS Gatekeeper: remove quarantine flag from downloaded binary
181
- if (process.platform === "darwin") {
183
+ if (process.platform === 'darwin') {
182
184
  try {
183
- execFileSync("xattr", ["-dr", "com.apple.quarantine", binDir], { stdio: "pipe" });
185
+ execFileSync('xattr', ['-dr', 'com.apple.quarantine', binDir], { stdio: 'pipe' })
184
186
  } catch {
185
187
  // Ignore — xattr may not be available or quarantine flag may not be set
186
188
  }
187
189
  }
188
190
 
189
191
  // Write version marker
190
- fs.mkdirSync(cacheDir, { recursive: true });
191
- fs.writeFileSync(versionFile, VERSION);
192
+ fs.mkdirSync(cacheDir, { recursive: true })
193
+ fs.writeFileSync(versionFile, VERSION)
192
194
 
193
- console.log(`Installed to ${binDir}`);
195
+ console.log(`Installed to ${binDir}`)
194
196
  }
195
197
 
196
198
  // Verify binary exists
197
199
  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);
200
+ console.error(`Error: Binary not found at ${binaryPath}`)
201
+ console.error('Try deleting ~/.cache/claude-view/ and running again.')
202
+ process.exit(1)
201
203
  }
202
204
 
203
205
  // Set STATIC_DIR so the server finds the frontend assets
204
- const env = { ...process.env, STATIC_DIR: distDir };
206
+ const env = { ...process.env, STATIC_DIR: distDir }
207
+
208
+ // Set SIDECAR_DIR so the server can spawn the Agent SDK sidecar
209
+ const sidecarDir = path.join(binDir, 'sidecar')
210
+ if (fs.existsSync(sidecarDir)) {
211
+ // Install sidecar deps if needed (first run after download)
212
+ const nodeModules = path.join(sidecarDir, 'node_modules')
213
+ if (!fs.existsSync(nodeModules)) {
214
+ console.log('Installing sidecar dependencies...')
215
+ execSync('npm install --omit=dev', { cwd: sidecarDir, stdio: 'inherit' })
216
+ }
217
+ env.SIDECAR_DIR = sidecarDir
218
+ }
205
219
 
206
220
  // Run the server, forwarding signals and exit code.
207
221
  //
@@ -211,29 +225,29 @@ async function main() {
211
225
  // the Rust server's graceful shutdown to see two SIGINTs from one Ctrl+C.
212
226
  // - SIGTERM/SIGHUP: Forward. These may be sent to our PID only (e.g. from
213
227
  // `kill` or a process manager), so the child wouldn't get them otherwise.
214
- const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit", env });
228
+ const child = spawn(binaryPath, process.argv.slice(2), { stdio: 'inherit', env })
215
229
 
216
230
  // Prevent Node from exiting on SIGINT before the child does.
217
- const ignoreSigint = () => {};
218
- process.on("SIGINT", ignoreSigint);
231
+ const ignoreSigint = () => {}
232
+ process.on('SIGINT', ignoreSigint)
219
233
 
220
234
  // Forward SIGTERM/SIGHUP — child may not receive these from process group.
221
- for (const sig of ["SIGTERM", "SIGHUP"]) {
222
- process.on(sig, () => child.kill(sig));
235
+ for (const sig of ['SIGTERM', 'SIGHUP']) {
236
+ process.on(sig, () => child.kill(sig))
223
237
  }
224
238
 
225
- child.on("exit", (code, signal) => {
239
+ child.on('exit', (code, signal) => {
226
240
  // Remove our SIGINT handler so the re-signal below uses default behavior.
227
- process.removeListener("SIGINT", ignoreSigint);
241
+ process.removeListener('SIGINT', ignoreSigint)
228
242
 
229
243
  if (signal) {
230
244
  // Child was killed by signal — re-signal ourselves so the parent shell
231
245
  // sees the correct exit status (128 + signal number).
232
- process.kill(process.pid, signal);
246
+ process.kill(process.pid, signal)
233
247
  } else {
234
- process.exit(code ?? 1);
248
+ process.exit(code ?? 1)
235
249
  }
236
- });
250
+ })
237
251
  }
238
252
 
239
- main();
253
+ main()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-view",
3
- "version": "0.8.0",
4
- "description": "Browse and search your Claude Code sessions in a beautiful local web UI",
3
+ "version": "0.11.0",
4
+ "description": "You have 10 Claude sessions running. What are they doing? Live dashboard for Claude Code monitor every session, track costs, search history, see sub-agents. One command: npx claude-view",
5
5
  "bin": {
6
6
  "claude-view": "./index.js"
7
7
  },
@@ -10,18 +10,53 @@
10
10
  "type": "git",
11
11
  "url": "https://github.com/tombelieber/claude-view"
12
12
  },
13
+ "homepage": "https://claudeview.ai",
14
+ "bugs": {
15
+ "url": "https://github.com/tombelieber/claude-view/issues"
16
+ },
13
17
  "keywords": [
14
18
  "claude",
15
19
  "claude-code",
16
- "sessions",
17
- "viewer",
18
- "browser"
20
+ "anthropic",
21
+ "ai",
22
+ "ai-agents",
23
+ "mcp",
24
+ "dashboard",
25
+ "monitoring",
26
+ "mission-control",
27
+ "developer-tools",
28
+ "cli",
29
+ "session",
30
+ "session-history",
31
+ "session-viewer",
32
+ "session-monitor",
33
+ "full-text-search",
34
+ "search",
35
+ "tantivy",
36
+ "cost-tracking",
37
+ "analytics",
38
+ "metrics",
39
+ "heatmap",
40
+ "activity-log",
41
+ "reports",
42
+ "fluency-score",
43
+ "sub-agents",
44
+ "subagents",
45
+ "agent-monitor",
46
+ "context-window",
47
+ "cache-countdown",
48
+ "real-time",
49
+ "sse",
50
+ "websocket",
51
+ "conversation-viewer",
52
+ "export",
53
+ "rust",
54
+ "react",
55
+ "axum",
56
+ "sqlite"
19
57
  ],
20
58
  "engines": {
21
59
  "node": ">=16"
22
60
  },
23
- "files": [
24
- "index.js",
25
- "README.md"
26
- ]
61
+ "files": ["index.js", "README.md"]
27
62
  }