@elliotxx/claude-list 0.1.1

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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /node_modules
2
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 elliotxx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,405 @@
1
+ <div align="center">
2
+
3
+ <div>
4
+ <img src="assets/logo.svg" alt="claude-list Logo" width="128" height="128">
5
+ </div>
6
+
7
+ <h1 style="margin-top: 10px;">claude-list</h1>
8
+
9
+ CLI tool for viewing installed plugins, skills, agents, and MCP servers in Claude Code.
10
+
11
+ <div align="center">
12
+ <a href="https://github.com/elliotxx/claude-list/actions"><img alt="CI Status" src="https://img.shields.io/github/actions/workflow/status/elliotxx/claude-list?logo=github"/></a>
13
+ <a href="https://crates.io/crates/claude-list"><img alt="Crates.io" src="https://img.shields.io/crates/v/claude-list"/></a>
14
+ <a href="https://github.com/elliotxx/claude-list/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/License-MIT-yellow.svg"/></a>
15
+ <a href="https://www.rust-lang.org/"><img alt="Rust" src="https://img.shields.io/badge/Rust-1.75+-orange.svg"/></a>
16
+ </div>
17
+
18
+ <p>
19
+ <a href="#why-claude-list">Why?</a>
20
+ ◆ <a href="#quick-start">Quick Start</a>
21
+ ◆ <a href="#features">Features</a>
22
+ ◆ <a href="#installation">Installation</a>
23
+ ◆ <a href="#architecture">Architecture</a>
24
+ </p>
25
+ </div>
26
+
27
+ ---
28
+
29
+ ## Latest News 🔥
30
+
31
+ - **[2026/01]** Published to crates.io - now installable via `cargo install claude-list`
32
+ - **[2026/01]** Added Homebrew support with cargo-dist multi-platform builds
33
+ - **[2026/01]** Released v0.1.0 with compact, detailed, and JSON output modes
34
+ - **[2026/01]** Implemented 7 component parsers (plugins, skills, sessions, mcp, hooks, agents, commands)
35
+
36
+ ---
37
+
38
+ ## Why claude-list?
39
+
40
+ A Rust CLI tool that follows Unix philosophy—do one thing well. It reads your Claude Code `.claude` directory and presents key information in a clean, minimal format with modern aesthetics.
41
+
42
+ - **🎨 Clean Output** - Human-readable compact format by default
43
+ - **🔍 Detailed Views** - Version, source, and path information on demand
44
+ - **🤖 Scriptable** - JSON output for automation and integration
45
+ - **⚡ Fast** - Sub-second execution (< 0.03s)
46
+ - **🔒 Safe** - Handles missing files and partial data gracefully
47
+ - **📦 Multi-Platform** - Pre-built binaries for Linux and macOS
48
+
49
+ ---
50
+
51
+ ## Quick Start
52
+
53
+ ```bash
54
+ # Install from crates.io
55
+ cargo install claude-list
56
+
57
+ # View your Claude Code environment
58
+ claude-list
59
+ ```
60
+
61
+ **Need more details?** See [Installation](#installation) below for all installation options.
62
+
63
+ ---
64
+
65
+ ## Features
66
+
67
+ ### Output Modes
68
+
69
+ | Mode | Command | Description |
70
+ |------|---------|-------------|
71
+ | Compact | `claude-list` | Summary with counts |
72
+ | Detailed | `claude-list -l` | Full info with version, source, path |
73
+ | JSON | `claude-list --json` | Machine-readable output |
74
+
75
+ ### Filtering
76
+
77
+ Filter to show specific component types:
78
+
79
+ ```bash
80
+ claude-list --plugins # Only plugins
81
+ claude-list --skills # Only skills
82
+ claude-list --sessions # Only sessions
83
+ claude-list --mcp # Only MCP servers
84
+ claude-list --hooks # Only hooks
85
+ claude-list --agents # Only agents
86
+ claude-list --commands # Only commands
87
+
88
+ # Combine filters
89
+ claude-list --plugins --skills
90
+ ```
91
+
92
+ ### Custom Configuration Directory
93
+
94
+ ```bash
95
+ claude-list --config /path/to/.claude
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Demo
101
+
102
+ ### Compact Mode (Default)
103
+
104
+ ```
105
+ CLAUDE-LIST v0.1.0
106
+
107
+ CONFIG: /Users/user/.claude
108
+
109
+ PLUGINS 3 installed
110
+ context7
111
+ plugin_playwright
112
+ plugin_example
113
+
114
+ SKILLS 12 available
115
+ brainstorming
116
+ claude-code-guide
117
+ ...
118
+
119
+ SESSIONS 47 recorded
120
+ MCP 2 servers
121
+ test-mcp
122
+ another-mcp
123
+ ```
124
+
125
+ ### Detailed Mode (`-l`)
126
+
127
+ ```
128
+ CLAUDE-LIST v0.1.0
129
+
130
+ CONFIG: /Users/user/.claude
131
+
132
+ PLUGINS 3 installed
133
+ NAME VERSION SOURCE PATH
134
+ ------------------- ------- --------- ---------------------------------
135
+ context7 2.1.0 official /Users/user/.claude/settings.json
136
+ plugin_playwright 1.0.0 third-party /Users/user/.claude/settings.json
137
+ plugin_example 0.5.0 community /Users/user/.claude/settings.json
138
+ ```
139
+
140
+ ### JSON Mode (`--json`)
141
+
142
+ ```json
143
+ {
144
+ "version": "0.1.0",
145
+ "config_dir": "/Users/user/.claude",
146
+ "plugins": [...],
147
+ "skills": [...],
148
+ "sessions": {...},
149
+ "mcp_servers": [...],
150
+ "hooks": [...],
151
+ "agents": [...],
152
+ "commands": [...]
153
+ }
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Installation
159
+
160
+ ### Option 1: From crates.io (Recommended)
161
+
162
+ ```bash
163
+ cargo install claude-list
164
+ ```
165
+
166
+ ### Option 2: From Homebrew (macOS)
167
+
168
+ ```bash
169
+ brew tap elliotxx/tap && brew install elliotxx/tap/claude-list
170
+ ```
171
+
172
+ ### Option 3: From GitHub Releases
173
+
174
+ Download pre-built binaries from [GitHub Releases](https://github.com/elliotxx/claude-list/releases):
175
+
176
+ ```bash
177
+ # Linux (x86_64)
178
+ wget https://github.com/elliotxx/claude-list/releases/latest/download/claude-list-x86_64-unknown-linux-gnu.tar.gz
179
+ tar -xzf claude-list-x86_64-unknown-linux-gnu.tar.gz
180
+ ./claude-list
181
+
182
+ # macOS (Apple Silicon)
183
+ wget https://github.com/elliotxx/claude-list/releases/latest/download/claude-list-aarch64-apple-darwin.tar.gz
184
+ tar -xzf claude-list-aarch64-apple-darwin.tar.gz
185
+ ./claude-list
186
+ ```
187
+
188
+ ### Option 4: From Source
189
+
190
+ ```bash
191
+ git clone https://github.com/elliotxx/claude-list.git
192
+ cd claude-list
193
+ cargo build --release
194
+ cargo install --path .
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Architecture
200
+
201
+ ### System Overview
202
+
203
+ <div align="center">
204
+ <img src="https://via.placeholder.com/750x400?text=claude-list+Architecture" alt="Architecture Diagram" width="750">
205
+ </div>
206
+
207
+ ### Component Architecture
208
+
209
+ ```
210
+ ┌─────────────────────────────────────────────────────────┐
211
+ │ User Input │
212
+ │ (CLI Arguments) │
213
+ └──────────────────────┬──────────────────────────────────┘
214
+
215
+
216
+ ┌─────────────────────────────────────────────────────────┐
217
+ │ Main Entry Point (src/main.rs) │
218
+ │ • Parse CLI arguments │
219
+ │ • Dispatch to formatters │
220
+ └──────────────────────┬──────────────────────────────────┘
221
+
222
+
223
+ ┌─────────────────────────────────────────────────────────┐
224
+ │ Parser Layer (src/parsers/) │
225
+ │ • plugins.rs → Parse installed plugins │
226
+ │ • skills.rs → Parse skills │
227
+ │ • sessions.rs → Parse session history │
228
+ │ • mcp.rs → Parse MCP servers │
229
+ │ • hooks.rs → Parse hooks │
230
+ │ • agents.rs → Parse agents │
231
+ │ • commands.rs → Parse commands │
232
+ └──────────────────────┬──────────────────────────────────┘
233
+
234
+
235
+ ┌─────────────────────────────────────────────────────────┐
236
+ │ Formatter Layer (src/formatters/) │
237
+ │ • compact.rs → Human-readable summary │
238
+ │ • detailed.rs → Full info with version/source/path │
239
+ │ • json.rs → Machine-readable JSON output │
240
+ └─────────────────────────────────────────────────────────┘
241
+ ```
242
+
243
+ ### Key Design Decisions
244
+
245
+ - **Pattern Used**: Unix philosophy—single responsibility, compose simple tools
246
+ - **Technology Stack**: Rust 1.75+, clap (CLI), serde (JSON), anyhow (error handling)
247
+ - **Scalability**: Each parser is independent, easy to extend
248
+ - **Error Handling**: Graceful degradation for missing files
249
+
250
+ ### Supported Data Sources
251
+
252
+ | Component | Format | Location |
253
+ |-----------|--------|----------|
254
+ | Plugins | JSON | `.claude/plugins/installed_plugins.json` |
255
+ | Skills | YAML | `.claude/skills/*/skill.yaml` |
256
+ | MCP Servers | Directory/YAML | `.claude/mcp-servers/*/` |
257
+ | Sessions | JSON Lines | `.claude/history.jsonl` |
258
+ | Commands | Markdown | `.claude/commands/*.md` |
259
+ | Agents | Markdown | `.claude/agents/*.md` |
260
+ | Hooks | Markdown | `.claude/hooks/*.md` |
261
+
262
+ ---
263
+
264
+ ## Development
265
+
266
+ ### Prerequisites
267
+
268
+ - Rust 1.75+
269
+ - Cargo
270
+
271
+ ### Quick Start
272
+
273
+ ```bash
274
+ # Run all checks
275
+ cargo fmt && cargo clippy && cargo test
276
+
277
+ # Run tests only
278
+ cargo test
279
+
280
+ # Run integration tests
281
+ cargo test --test cli_test
282
+ ```
283
+
284
+ ### Building
285
+
286
+ ```bash
287
+ # Debug build
288
+ cargo build
289
+
290
+ # Release build
291
+ cargo build --release
292
+
293
+ # Check format
294
+ cargo fmt --check
295
+
296
+ # Run clippy
297
+ cargo clippy --all-features -- -D warnings
298
+ ```
299
+
300
+ ### Testing
301
+
302
+ ```bash
303
+ # Run all tests
304
+ cargo test --all-features
305
+
306
+ # Run with verbose output
307
+ cargo test --all-features --verbose
308
+
309
+ # Run specific test
310
+ cargo test test_name
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Publishing
316
+
317
+ Releases are automated via [cargo-dist](https://dist.clap.rs/):
318
+
319
+ 1. Push a git tag matching `x.y.z` (e.g., `0.1.1`)
320
+ 2. CI pipeline triggers automatically:
321
+ - **Plan**: Generate build manifest
322
+ - **Build**: Multi-platform builds (x86_64 Linux, x86_64/aarch64 macOS)
323
+ - **Publish**: GitHub Release + Homebrew
324
+
325
+ ### Release Process
326
+
327
+ ```bash
328
+ # 1. Update version in Cargo.toml
329
+ # Edit Cargo.toml: version = "0.1.0" → "0.1.1"
330
+
331
+ # 2. Commit version change
332
+ git add -A && git commit -m "chore: bump version to 0.1.1"
333
+
334
+ # 3. Create git tag
335
+ git tag 0.1.1
336
+
337
+ # 4. Push to GitHub (including tags)
338
+ git push && git push --tags
339
+
340
+ # 5. After CI completes, manually publish to crates.io
341
+ cargo publish
342
+ ```
343
+
344
+ > **Note**: Publishing to crates.io requires manual `cargo publish` as cargo-dist does not support automatic crates.io publishing.
345
+
346
+ ### CI Tokens Required
347
+
348
+ | Secret | Purpose |获取位置|
349
+ |--------|---------|--------|
350
+ | `CARGO_REGISTRY_TOKEN` | Publish to crates.io | [crates.io/settings/tokens](https://crates.io/settings/tokens) |
351
+ | `HOMEBREW_TAP_TOKEN` | Publish to Homebrew | [GitHub Settings](https://github.com/settings/tokens) |
352
+
353
+ ---
354
+
355
+ ## Contributing
356
+
357
+ We welcome contributions! Feel free to submit issues and pull requests.
358
+
359
+ ### Contribution Areas
360
+
361
+ - **Feature Development**: Add new parsers or formatters
362
+ - **Bug Fixes**: Fix issues and improve stability
363
+ - **Documentation**: Improve guides and examples
364
+ - **Testing**: Add tests and improve coverage
365
+
366
+ ### Quick Start for Contributors
367
+
368
+ ```bash
369
+ # Fork the repository on GitHub, then clone your fork
370
+ git clone https://github.com/YOUR_USERNAME/claude-list.git
371
+ cd claude-list
372
+
373
+ # Follow installation steps above
374
+
375
+ # Create feature branch
376
+ git checkout -b feature/your-feature-name
377
+
378
+ # Make changes, test, then commit and push
379
+ git add .
380
+ git commit -m "feat: description"
381
+ git push origin feature/your-feature-name
382
+ ```
383
+
384
+ ---
385
+
386
+ ## License
387
+
388
+ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.
389
+
390
+ ---
391
+
392
+ ## Acknowledgments
393
+
394
+ - [Claude Code](https://claude.com/claude-code) for the inspiration
395
+ - [clap](https://github.com/clap-rs/clap) for CLI argument parsing
396
+ - [cargo-dist](https://github.com/axodotdev/cargo-dist) for automated releases
397
+
398
+ ---
399
+
400
+ <div align="center">
401
+ <p>
402
+ <strong>Built with ❤️ for the Claude Code community</strong><br>
403
+ <sub>Parse and display your Claude Code environment</sub>
404
+ </p>
405
+ </div>
@@ -0,0 +1,212 @@
1
+ const { createWriteStream, existsSync, mkdirSync, mkdtemp } = require("fs");
2
+ const { join, sep } = require("path");
3
+ const { spawnSync } = require("child_process");
4
+ const { tmpdir } = require("os");
5
+
6
+ const axios = require("axios");
7
+ const rimraf = require("rimraf");
8
+ const tmpDir = tmpdir();
9
+
10
+ const error = (msg) => {
11
+ console.error(msg);
12
+ process.exit(1);
13
+ };
14
+
15
+ class Package {
16
+ constructor(platform, name, url, filename, zipExt, binaries) {
17
+ let errors = [];
18
+ if (typeof url !== "string") {
19
+ errors.push("url must be a string");
20
+ } else {
21
+ try {
22
+ new URL(url);
23
+ } catch (e) {
24
+ errors.push(e);
25
+ }
26
+ }
27
+ if (name && typeof name !== "string") {
28
+ errors.push("package name must be a string");
29
+ }
30
+ if (!name) {
31
+ errors.push("You must specify the name of your package");
32
+ }
33
+ if (binaries && typeof binaries !== "object") {
34
+ errors.push("binaries must be a string => string map");
35
+ }
36
+ if (!binaries) {
37
+ errors.push("You must specify the binaries in the package");
38
+ }
39
+
40
+ if (errors.length > 0) {
41
+ let errorMsg =
42
+ "One or more of the parameters you passed to the Binary constructor are invalid:\n";
43
+ errors.forEach((error) => {
44
+ errorMsg += error;
45
+ });
46
+ errorMsg +=
47
+ '\n\nCorrect usage: new Package("my-binary", "https://example.com/binary/download.tar.gz", {"my-binary": "my-binary"})';
48
+ error(errorMsg);
49
+ }
50
+
51
+ this.platform = platform;
52
+ this.url = url;
53
+ this.name = name;
54
+ this.filename = filename;
55
+ this.zipExt = zipExt;
56
+ this.installDirectory = join(__dirname, "node_modules", ".bin_real");
57
+ this.binaries = binaries;
58
+
59
+ if (!existsSync(this.installDirectory)) {
60
+ mkdirSync(this.installDirectory, { recursive: true });
61
+ }
62
+ }
63
+
64
+ exists() {
65
+ for (const binaryName in this.binaries) {
66
+ const binRelPath = this.binaries[binaryName];
67
+ const binPath = join(this.installDirectory, binRelPath);
68
+ if (!existsSync(binPath)) {
69
+ return false;
70
+ }
71
+ }
72
+ return true;
73
+ }
74
+
75
+ install(fetchOptions, suppressLogs = false) {
76
+ if (this.exists()) {
77
+ if (!suppressLogs) {
78
+ console.error(
79
+ `${this.name} is already installed, skipping installation.`,
80
+ );
81
+ }
82
+ return Promise.resolve();
83
+ }
84
+
85
+ if (existsSync(this.installDirectory)) {
86
+ rimraf.sync(this.installDirectory);
87
+ }
88
+
89
+ mkdirSync(this.installDirectory, { recursive: true });
90
+
91
+ if (!suppressLogs) {
92
+ console.error(`Downloading release from ${this.url}`);
93
+ }
94
+
95
+ return axios({ ...fetchOptions, url: this.url, responseType: "stream" })
96
+ .then((res) => {
97
+ return new Promise((resolve, reject) => {
98
+ mkdtemp(`${tmpDir}${sep}`, (err, directory) => {
99
+ let tempFile = join(directory, this.filename);
100
+ const sink = res.data.pipe(createWriteStream(tempFile));
101
+ sink.on("error", (err) => reject(err));
102
+ sink.on("close", () => {
103
+ if (/\.tar\.*/.test(this.zipExt)) {
104
+ const result = spawnSync("tar", [
105
+ "xf",
106
+ tempFile,
107
+ // The tarballs are stored with a leading directory
108
+ // component; we strip one component in the
109
+ // shell installers too.
110
+ "--strip-components",
111
+ "1",
112
+ "-C",
113
+ this.installDirectory,
114
+ ]);
115
+ if (result.status == 0) {
116
+ resolve();
117
+ } else if (result.error) {
118
+ reject(result.error);
119
+ } else {
120
+ reject(
121
+ new Error(
122
+ `An error occurred untarring the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
123
+ ),
124
+ );
125
+ }
126
+ } else if (this.zipExt == ".zip") {
127
+ let result;
128
+ if (this.platform.artifactName.includes("windows")) {
129
+ // Windows does not have "unzip" by default on many installations, instead
130
+ // we use Expand-Archive from powershell
131
+ result = spawnSync("powershell.exe", [
132
+ "-NoProfile",
133
+ "-NonInteractive",
134
+ "-Command",
135
+ `& {
136
+ param([string]$LiteralPath, [string]$DestinationPath)
137
+ Expand-Archive -LiteralPath $LiteralPath -DestinationPath $DestinationPath -Force
138
+ }`,
139
+ tempFile,
140
+ this.installDirectory,
141
+ ]);
142
+ } else {
143
+ result = spawnSync("unzip", [
144
+ "-q",
145
+ tempFile,
146
+ "-d",
147
+ this.installDirectory,
148
+ ]);
149
+ }
150
+
151
+ if (result.status == 0) {
152
+ resolve();
153
+ } else if (result.error) {
154
+ reject(result.error);
155
+ } else {
156
+ reject(
157
+ new Error(
158
+ `An error occurred unzipping the artifact: stdout: ${result.stdout}; stderr: ${result.stderr}`,
159
+ ),
160
+ );
161
+ }
162
+ } else {
163
+ reject(
164
+ new Error(`Unrecognized file extension: ${this.zipExt}`),
165
+ );
166
+ }
167
+ });
168
+ });
169
+ });
170
+ })
171
+ .then(() => {
172
+ if (!suppressLogs) {
173
+ console.error(`${this.name} has been installed!`);
174
+ }
175
+ })
176
+ .catch((e) => {
177
+ error(`Error fetching release: ${e.message}`);
178
+ });
179
+ }
180
+
181
+ run(binaryName, fetchOptions) {
182
+ const promise = !this.exists()
183
+ ? this.install(fetchOptions, true)
184
+ : Promise.resolve();
185
+
186
+ promise
187
+ .then(() => {
188
+ const [, , ...args] = process.argv;
189
+
190
+ const options = { cwd: process.cwd(), stdio: "inherit" };
191
+
192
+ const binRelPath = this.binaries[binaryName];
193
+ if (!binRelPath) {
194
+ error(`${binaryName} is not a known binary in ${this.name}`);
195
+ }
196
+ const binPath = join(this.installDirectory, binRelPath);
197
+ const result = spawnSync(binPath, args, options);
198
+
199
+ if (result.error) {
200
+ error(result.error);
201
+ }
202
+
203
+ process.exit(result.status);
204
+ })
205
+ .catch((e) => {
206
+ error(e.message);
207
+ process.exit(1);
208
+ });
209
+ }
210
+ }
211
+
212
+ module.exports.Package = Package;