caruso 0.5.4 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c64aeb60f2cf2507b6b3cad6756d9730775931e2b059384f3c43febceec1aec3
4
- data.tar.gz: ba8c515ffa913873789826bb4f704445106cc8cfbf7cbedb39ab50f68289ef83
3
+ metadata.gz: 718b4ebf40d78b4314d5ba6869ccf65d4e7ba02ada56c0c9b98e5de973d538c7
4
+ data.tar.gz: c6fcc0b7f7e45b6cd5e9235e954ea0c1cdc5d49fd729f0c6268e135ef7ac6bce
5
5
  SHA512:
6
- metadata.gz: d81e5ab8c88206fb14523eb0a906dfd1cb4fe98e2154580242d9cd63e7d73127497d623adb0fcf5b17c32d77cf9c1091f1bd3fab49eeeef9de1fa367d149fac8
7
- data.tar.gz: 52d8eb203ad04f396b66a00b54e5235bc3f609ea394036e7d76adaf16bc9a46bc77017901a4836f3ba2451ff905f91edea747b2845c86330b32b96a38012f981
6
+ metadata.gz: 76693951a7604897ad48efaa2e81570165e483ab20e651ff757d009a4c179f21a7738f9912229dd768205736ee076fbf2f865e1b7549385ce4c320132674c08a
7
+ data.tar.gz: 691886bb54d63379e80df0663b0825f89b2dd7ece512fc2e859b90c7e09cc830c63c3f6c2cad4fe2dc6f83974eec03d728cb1c150240cf424821cb6948d2b7f4
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 3.0
2
+ TargetRubyVersion: 3.2
3
3
  NewCops: enable
4
4
  SuggestExtensions: false
5
5
  Exclude:
@@ -31,6 +31,7 @@ Metrics/MethodLength:
31
31
  Exclude:
32
32
  - 'spec/**/*'
33
33
  - 'lib/caruso/cli.rb' # CLI command methods can be longer
34
+ - 'lib/caruso/fetcher.rb' # Marketplace loading is complex
34
35
 
35
36
  # Line length - modern screens can handle more
36
37
  Layout/LineLength:
@@ -38,6 +39,7 @@ Layout/LineLength:
38
39
  Exclude:
39
40
  - 'spec/**/*'
40
41
  - '*.gemspec'
42
+ - 'lib/caruso/cli.rb' # CLI methods can have longer conditional lines
41
43
 
42
44
  # Allow longer parameter lists for complex methods
43
45
  Metrics/ParameterLists:
@@ -56,6 +58,8 @@ Metrics/ClassLength:
56
58
  Max: 150
57
59
  Exclude:
58
60
  - 'spec/**/*'
61
+ - 'lib/caruso/cli.rb' # CLI class has many Thor commands
62
+ - 'lib/caruso/fetcher.rb' # Fetcher handles multiple sources
59
63
 
60
64
  # Prefer descriptive block parameter names
61
65
  Lint/UnusedBlockArgument:
@@ -140,6 +144,7 @@ Metrics/PerceivedComplexity:
140
144
  Max: 12
141
145
  Exclude:
142
146
  - 'lib/caruso/cli.rb'
147
+ - 'lib/caruso/fetcher.rb' # Security validation requires complex checks
143
148
 
144
149
  # Duplicate branches are acceptable for error handling with different contexts
145
150
  Lint/DuplicateBranch:
data/AGENTS.md ADDED
@@ -0,0 +1,276 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Mission
6
+
7
+ Caruso is a Ruby gem CLI that bridges the gap between AI coding assistants. It fetches "steering documentation" (commands, agents, skills) from Claude Code Marketplaces and converts them to formats compatible with other IDEs, currently Cursor.
8
+
9
+ ## Source of Truth: Claude Code Documentation
10
+
11
+ **IMPORTANT:** The official Claude Code marketplace and plugin specifications are located in `/Users/philipp/code/caruso/reference/`:
12
+
13
+ - `marketplace.md` - Marketplace structure and specification
14
+ - `plugins.md` - Plugin format and configuration
15
+ - `plugins_reference.md` - Component configuration fields and metadata
16
+
17
+ **These reference documents are the authoritative source for:**
18
+ - Marketplace.json schema and plugin metadata format
19
+ - Component configuration fields (`commands`, `agents`, `skills`, `hooks`, `mcpServers`)
20
+ - Expected directory structures and file patterns
21
+ - Metadata requirements and validation rules
22
+
23
+ When implementing features or fixing bugs related to marketplace compatibility, **always consult these reference files first**. If the implementation conflicts with the reference docs, the reference docs are correct and the code should be updated to match.
24
+
25
+ ## Development Commands
26
+
27
+ ### Build and Install
28
+ ```bash
29
+ # Build the gem
30
+ gem build caruso.gemspec
31
+
32
+ # Install locally
33
+ gem install caruso-*.gem
34
+
35
+ # Verify installation
36
+ caruso version
37
+ ```
38
+
39
+ ### Testing
40
+ ```bash
41
+ # Run offline tests only (default)
42
+ bundle exec rake spec
43
+ # or
44
+ bundle exec rspec
45
+
46
+ # Run all tests including live marketplace integration
47
+ bundle exec rake spec:all
48
+ # or
49
+ RUN_LIVE_TESTS=1 bundle exec rspec
50
+
51
+ # Run only live tests
52
+ bundle exec rake spec:live
53
+
54
+ # Run specific test file
55
+ bundle exec rspec spec/integration/plugin_spec.rb
56
+
57
+ # Run specific test
58
+ bundle exec rspec spec/integration/plugin_spec.rb:42
59
+ ```
60
+
61
+ **Important:** Live tests (tagged with `:live`) interact with real marketplaces (anthropics/skills) and require network access. They can be slow (~7 minutes). Marketplace cache is stored in `~/.caruso/marketplaces/`. Integration tests set `CARUSO_TESTING_SKIP_CLONE=true` to skip Git cloning for fast offline testing.
62
+
63
+ ### Linting
64
+ ```bash
65
+ bundle exec rubocop
66
+ ```
67
+
68
+ ### Version Management
69
+ ```bash
70
+ # Bump patch version (0.1.3 → 0.1.4)
71
+ bundle exec rake bump:patch
72
+
73
+ # Bump minor version (0.1.4 → 0.2.0)
74
+ bundle exec rake bump:minor
75
+
76
+ # Bump major version (0.1.4 → 1.0.0)
77
+ bundle exec rake bump:major
78
+ ```
79
+
80
+ ## Architecture
81
+
82
+ ### Core Pipeline: Fetch → Adapt → Track
83
+
84
+ Caruso follows a three-stage pipeline for plugin management:
85
+
86
+ 1. **Fetch** (`Fetcher`) - Clones Git repositories, resolves marketplace.json, finds plugin markdown files
87
+ 2. **Adapt** (`Adapter`) - Converts Claude Code markdown to target IDE format with metadata injection
88
+ 3. **Track** (`ConfigManager`) - Records installations in `caruso.json` and `.caruso.local.json`
89
+
90
+ ### Key Components
91
+
92
+ #### ConfigManager (`lib/caruso/config_manager.rb`)
93
+ Manages configuration and state. Splits data between:
94
+
95
+ **1. Project Config (`caruso.json`)**
96
+ - `ide`: Target IDE (currently only "cursor" supported)
97
+ - `target_dir`: Where to write converted files (`.cursor/rules` for Cursor)
98
+ - `marketplaces`: Name → URL mapping
99
+ - `plugins`: Name → metadata (marketplace source)
100
+ - `version`: Config schema version
101
+
102
+ **2. Local Config (`.caruso.local.json`)**
103
+ - `installed_files`: Plugin Name → Array of file paths
104
+ - `initialized_at`: Timestamp
105
+
106
+ Must run `caruso init --ide=cursor` before other commands. ConfigManager handles loading/saving both files and ensures `.caruso.local.json` is gitignored.
107
+
108
+ #### MarketplaceRegistry (`lib/caruso/marketplace_registry.rb`)
109
+ Manages persistent marketplace metadata registry at `~/.caruso/known_marketplaces.json`. Contains:
110
+ - `source`: Marketplace type (git, github, url, local, directory)
111
+ - `url`: Original marketplace URL
112
+ - `install_location`: Local cache path (e.g., `~/.caruso/marketplaces/skills/`)
113
+ - `last_updated`: ISO8601 timestamp of last update
114
+ - `ref`: Optional Git ref/branch/tag for pinning
115
+
116
+ **Key features:**
117
+ - **Schema validation**: Validates required fields and timestamp format on load
118
+ - **Corruption handling**: Backs up corrupted registry to `.corrupted.<timestamp>` and continues with empty registry
119
+ - **Timestamp tracking**: Updates `last_updated` when marketplace cache is refreshed
120
+ - **Source type tracking**: Enables future support for multiple marketplace sources
121
+
122
+ This registry enables persistent tracking of marketplace state across reboots, unlike the previous `/tmp` approach.
123
+
124
+ #### Fetcher (`lib/caruso/fetcher.rb`)
125
+ Resolves and fetches plugins from marketplaces. Supports:
126
+ - GitHub repos: `https://github.com/owner/repo`
127
+ - Git URLs: Any Git-cloneable URL
128
+ - Local paths: `./path/to/marketplace` or `./path/to/marketplace.json`
129
+
130
+ **Key behavior:**
131
+ - Clones Git repos to `~/.caruso/marketplaces/<marketplace-name>/` (persistent across reboots)
132
+ - Registers marketplace metadata in `~/.caruso/known_marketplaces.json` (via MarketplaceRegistry)
133
+ - Supports Git ref/branch pinning for version control
134
+ - Reads `marketplace.json` to find available plugins
135
+ - Supports custom component paths: plugins can specify `commands`, `agents`, `skills` arrays pointing to non-standard locations
136
+ - Scans standard directories: `{commands,agents,skills}/**/*.md`
137
+ - Excludes README.md and LICENSE.md files
138
+ - **Custom paths supplement (not replace) default directories** - this is critical
139
+ - Detects SSH authentication errors and provides helpful error messages
140
+
141
+ **marketplace.json structure:**
142
+ ```json
143
+ {
144
+ "plugins": [
145
+ {
146
+ "name": "document-skills",
147
+ "description": "Work with documents",
148
+ "source": "./document-skills",
149
+ "skills": ["./document-skills/xlsx", "./document-skills/pdf"]
150
+ }
151
+ ]
152
+ }
153
+ ```
154
+
155
+ The `commands`, `agents`, and `skills` fields accept:
156
+ - String: `"./custom/path"`
157
+ - Array: `["./path1", "./path2"]`
158
+
159
+ Both files and directories are supported. Fetcher recursively finds all `.md` files.
160
+
161
+ #### Adapter (`lib/caruso/adapter.rb`)
162
+ Converts Claude Code markdown files to target IDE format. For Cursor:
163
+ - Renames `.md` → `.mdc`
164
+ - Injects YAML frontmatter with required Cursor metadata:
165
+ - `globs: []` - Enables semantic search (Apply Intelligently)
166
+ - `alwaysApply: false` - Prevents auto-application to every chat
167
+ - `description` - Preserved from original or generated
168
+ - Preserves existing frontmatter if present, adds missing fields
169
+ - Handles special case: `SKILL.md` → named after parent directory to avoid collisions
170
+
171
+ Returns array of created filenames (not full paths) for manifest tracking.
172
+
173
+ #### CLI (`lib/caruso/cli.rb`)
174
+ Thor-based CLI with nested commands:
175
+ - `caruso init [PATH] --ide=cursor`
176
+ - `caruso marketplace add URL [--ref=BRANCH]` - Add marketplace with optional Git ref pinning (name comes from marketplace.json)
177
+ - `caruso marketplace list` - List configured marketplaces
178
+ - `caruso marketplace remove NAME` - Remove marketplace from manifest and registry
179
+ - `caruso marketplace update [NAME]` - Update marketplace cache (all if no name given)
180
+ - `caruso marketplace info NAME` - Show detailed marketplace information from registry
181
+ - `caruso plugin install|uninstall|list|update|outdated`
182
+
183
+ **Important patterns:**
184
+ - All commands except `init` require existing `caruso.json` (enforced by `load_config` helper)
185
+ - Plugin install format: `plugin@marketplace` or just `plugin` (if only one marketplace configured)
186
+ - Update commands refresh marketplace cache (git pull) before fetching latest plugin files
187
+ - Marketplace add eagerly clones repos unless `CARUSO_TESTING_SKIP_CLONE` env var is set (used in tests)
188
+ - **Marketplace names always come from marketplace.json `name` field (required)** - no custom names allowed
189
+ - Errors use descriptive messages with suggestions (e.g., "use 'caruso marketplace add <url>'")
190
+
191
+ ### Data Flow Example
192
+
193
+ User runs: `caruso plugin install document-skills@skills`
194
+
195
+ 1. **CLI** parses command, loads config from `caruso.json`
196
+ 2. **ConfigManager** looks up marketplace "skills" URL
197
+ 3. **Fetcher** clones/updates marketplace repo to `~/.caruso/marketplaces/skills/`
198
+ 4. **Fetcher** registers/updates marketplace metadata in MarketplaceRegistry
199
+ 5. **Fetcher** reads `marketplace.json`, finds document-skills plugin
200
+ 6. **Fetcher** scans standard directories + custom paths from `skills: [...]` array
201
+ 7. **Fetcher** returns list of `.md` file paths
202
+ 8. **Adapter** converts each file: adds frontmatter, renames to `.mdc`, writes to `.cursor/rules/caruso/`
203
+ 9. **Adapter** returns created filenames
204
+ 10. **ConfigManager** records plugin in `caruso.json` and files in `.caruso.local.json`
205
+ 11. **CLI** prints success message
206
+
207
+ ### Testing Architecture
208
+
209
+ Uses **Aruba** for CLI integration testing. Test structure:
210
+ - `spec/unit/` - Direct class testing (ConfigManager, Fetcher logic)
211
+ - `spec/integration/` - Full CLI workflow tests via Aruba subprocess execution
212
+
213
+ **Aruba helpers in spec_helper.rb:**
214
+ - `init_caruso(ide: "cursor")` - Runs init command with success assertion
215
+ - `add_marketplace(url, name)` - Adds marketplace with success assertion
216
+ - `config_file`, `manifest_file`, `load_config`, `load_manifest` - File access helpers
217
+ - `mdc_files` - Glob for `.cursor/rules/*.mdc` files
218
+
219
+ **Critical testing pattern:**
220
+ ```ruby
221
+ run_command("caruso plugin install foo@bar")
222
+ expect(last_command_started).to be_successfully_executed # Always verify success first!
223
+ manifest = load_manifest # Then access results
224
+ ```
225
+
226
+ **Why this matters:** If command fails, manifest might not exist (nil). Always assert success before accessing command results to prevent confusing test failures.
227
+
228
+ **Live tests:**
229
+ - Tagged with `:live` metadata
230
+ - Run only when `RUN_LIVE_TESTS=1` environment variable set
231
+ - Interact with real anthropics/skills marketplace
232
+ - Cache cleared once at test suite start for performance
233
+ - Use `sleep` for timestamp resolution (not `Timecop`) because Caruso runs as subprocess
234
+
235
+ **Timecop limitation:** Cannot mock time in subprocesses. When testing timestamp updates in plugin reinstall scenarios, use `sleep 1.1` (ISO8601 has second precision) instead of `Timecop.travel`.
236
+
237
+ ## Marketplace Compatibility
238
+
239
+ Caruso supports the Claude Code marketplace specification with custom component paths:
240
+
241
+ - Standard structure: `{commands,agents,skills}/**/*.md`
242
+ - Custom paths: `"commands": ["./custom/path"]` in marketplace.json
243
+ - Both string and array formats supported
244
+ - Custom paths **supplement** defaults (they don't replace)
245
+
246
+ Example: anthropics/skills marketplace uses custom paths:
247
+ ```json
248
+ {
249
+ "name": "document-skills",
250
+ "skills": ["./document-skills/xlsx", "./document-skills/pdf"]
251
+ }
252
+ ```
253
+
254
+ Fetcher will scan both:
255
+ 1. `./document-skills/skills/**/*.md` (default)
256
+ 2. `./document-skills/xlsx/**/*.md` (custom)
257
+ 3. `./document-skills/pdf/**/*.md` (custom)
258
+
259
+ Results are deduplicated with `.uniq`.
260
+
261
+ ## Release Process
262
+
263
+ 1. Run tests: `bundle exec rake spec:all`
264
+ 2. Bump version: `bundle exec rake bump:patch` (or minor/major)
265
+ 3. Update CHANGELOG.md with release notes
266
+ 4. Commit: `git commit -m "chore: Bump version to X.Y.Z"`
267
+ 5. Tag: `git tag -a vX.Y.Z -m "Release version X.Y.Z"`
268
+ 6. Build: `gem build caruso.gemspec`
269
+ 7. Install and test: `gem install caruso-X.Y.Z.gem && caruso version`
270
+ 8. Push: `git push origin main --tags`
271
+
272
+ Version is managed in `lib/caruso/version.rb`.
273
+
274
+ # Memory
275
+ - The goal is a clean, correct, consistent implementation. Never implement fallbacks that hide errors or engage in defensive programming.
276
+ - Treat the vendor directory .cursor/rules/caruso/ as a build artifact
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2025-11-24
11
+
12
+ ### Security
13
+ - **CRITICAL**: Addressed "Uncontrolled data used in path expression" vulnerabilities (CodeQL)
14
+ - Introduced `Caruso::SafeFile` for secure file reading with strict path sanitization
15
+ - Introduced `Caruso::SafeDir` for secure directory operations (globbing, existence checks)
16
+ - Replaced all vulnerable `File` and `Dir` calls in `Adapter` and `Fetcher` with safe alternatives
17
+ - Removed redundant string-based path validation in favor of robust `Pathname` canonicalization
18
+
19
+ ### Changed
20
+ - `Adapter` now strictly validates file existence and raises errors instead of silently skipping invalid files
21
+ - `Fetcher` now filters glob results to ensure they remain within trusted plugin directories
22
+
10
23
  ## [0.5.3] - 2025-11-23
11
24
 
12
25
  ### Changed
data/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ AGENTS.md
data/README.md CHANGED
@@ -15,24 +15,27 @@ Enable Cursor to consume Claude Code plugins from marketplaces by converting the
15
15
 
16
16
  ## Installation
17
17
 
18
- ### Option 1: Install from Source (Recommended)
18
+ ### Install from RubyGems
19
19
 
20
- Clone the repository and build the gem:
20
+ ```bash
21
+ gem install caruso
22
+ ```
23
+
24
+ Verify the installation:
21
25
 
22
26
  ```bash
23
- git clone https://github.com/pcomans/caruso.git
24
- cd caruso
25
- gem build caruso.gemspec
26
- gem install caruso-*.gem
27
+ caruso version
27
28
  ```
28
29
 
29
- ### Option 2: Using `specific_install`
30
+ ### Install from Source (Development)
30
31
 
31
- If you have the `specific_install` gem, you can install directly from GitHub:
32
+ For development or testing unreleased features:
32
33
 
33
34
  ```bash
34
- gem install specific_install
35
- gem specific_install -l https://github.com/pcomans/caruso.git
35
+ git clone https://github.com/pcomans/caruso.git
36
+ cd caruso
37
+ gem build caruso.gemspec
38
+ gem install caruso-*.gem
36
39
  ```
37
40
 
38
41
  ## Usage
data/SECURITY.md ADDED
@@ -0,0 +1,98 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ We release patches for security vulnerabilities in the following versions:
6
+
7
+ | Version | Supported |
8
+ | ------- | ------------------ |
9
+ | 0.5.x | :white_check_mark: |
10
+ | < 0.5 | :x: |
11
+
12
+ ## Reporting a Vulnerability
13
+
14
+ We take the security of Caruso seriously. If you discover a security vulnerability, please follow these steps:
15
+
16
+ ### 1. **Do Not** Open a Public Issue
17
+
18
+ Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.
19
+
20
+ ### 2. Report Privately
21
+
22
+ Please report security vulnerabilities using GitHub's private vulnerability reporting:
23
+
24
+ - Go to the [Security tab](https://github.com/pcomans/caruso/security)
25
+ - Click "Report a vulnerability"
26
+ - Fill out the form with details about the vulnerability
27
+
28
+ ### 3. Include Details
29
+
30
+ Please include as much of the following information as possible:
31
+
32
+ - Type of vulnerability (e.g., command injection, path traversal, etc.)
33
+ - Step-by-step instructions to reproduce the issue
34
+ - Proof of concept or exploit code (if possible)
35
+ - Impact of the vulnerability
36
+ - Suggested fix (if you have one)
37
+
38
+ ### 4. What to Expect
39
+
40
+ - **Acknowledgment**: We'll acknowledge your report within 48 hours
41
+ - **Updates**: We'll keep you informed about our progress
42
+ - **Fix Timeline**: We aim to release a fix within 7-14 days for critical vulnerabilities
43
+ - **Credit**: With your permission, we'll credit you in the security advisory
44
+
45
+ ## Security Considerations
46
+
47
+ When using Caruso, keep these security practices in mind:
48
+
49
+ ### Marketplace Sources
50
+
51
+ - Only add marketplaces from trusted sources
52
+ - Review plugin code before installation when possible
53
+ - Be cautious with marketplaces requiring authentication
54
+
55
+ ### Git Credentials
56
+
57
+ - Caruso uses Git to clone marketplace repositories
58
+ - Ensure your Git credentials are properly secured
59
+ - Use SSH keys or personal access tokens instead of passwords
60
+
61
+ ### File Permissions
62
+
63
+ - Caruso writes files to `.cursor/rules/caruso/`
64
+ - Ensure proper file permissions in your project directory
65
+ - Review generated files before committing to version control
66
+
67
+ ### Dependencies
68
+
69
+ - Keep Caruso updated to the latest version
70
+ - Regularly update Ruby and gem dependencies
71
+ - Run `gem update caruso` to get security patches
72
+
73
+ ## Scope
74
+
75
+ This security policy applies to:
76
+
77
+ - The Caruso gem and CLI tool
78
+ - Official marketplace repositories maintained by this project
79
+ - Documentation and examples in this repository
80
+
81
+ It does not cover:
82
+
83
+ - Third-party marketplaces or plugins
84
+ - User-created custom plugins
85
+ - Vulnerabilities in Ruby itself or system dependencies
86
+
87
+ ## Security Updates
88
+
89
+ Security updates will be announced through:
90
+
91
+ - GitHub Security Advisories
92
+ - Release notes in CHANGELOG.md
93
+ - RubyGems.org security alerts
94
+
95
+ ## Additional Resources
96
+
97
+ - [RubyGems Security Guide](https://guides.rubygems.org/security/)
98
+ - [OWASP Top 10](https://owasp.org/www-project-top-ten/)
data/caruso.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "A tool to fetch Claude Code plugins and adapt them into Cursor Rules or other agent contexts."
13
13
  spec.homepage = "https://github.com/pcomans/caruso"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 3.0.0"
15
+ spec.required_ruby_version = ">= 3.2.0"
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = "https://github.com/pcomans/caruso"
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "fileutils"
4
4
  require "yaml"
5
+ require_relative "safe_file"
6
+ require_relative "path_sanitizer"
5
7
 
6
8
  module Caruso
7
9
  class Adapter
@@ -19,7 +21,8 @@ module Caruso
19
21
  def adapt
20
22
  created_files = []
21
23
  files.each do |file_path|
22
- content = File.read(file_path)
24
+ content = SafeFile.read(file_path)
25
+
23
26
  adapted_content = inject_metadata(content, file_path)
24
27
  created_file = save_file(file_path, adapted_content)
25
28
  created_files << created_file
data/lib/caruso/cli.rb CHANGED
@@ -20,8 +20,10 @@ module Caruso
20
20
  fetcher = Caruso::Fetcher.new(url, ref: options[:ref])
21
21
 
22
22
  # For Git repos, clone/update the cache (skip in test mode to allow fake URLs)
23
- if (source == "github" || url.match?(/\Ahttps?:/) || url.match?(%r{[^/]+/[^/]+})) && !ENV["CARUSO_TESTING_SKIP_CLONE"]
24
- fetcher.clone_git_repo({"url" => url, "source" => source})
23
+ # Fixed ReDoS: Use anchored regex and limit input length to prevent catastrophic backtracking
24
+ is_owner_repo = url.length < 256 && url.match?(%r{\A[^/]+/[^/]+\z})
25
+ if (source == "github" || url.match?(/\Ahttps?:/) || is_owner_repo) && !ENV["CARUSO_TESTING_SKIP_CLONE"]
26
+ fetcher.clone_git_repo({ "url" => url, "source" => source })
25
27
  end
26
28
 
27
29
  # Read marketplace name from marketplace.json
@@ -86,14 +88,14 @@ module Caruso
86
88
  end
87
89
 
88
90
  puts "Marketplace: #{name}"
89
- puts " Source: #{marketplace['source']}" if marketplace['source']
91
+ puts " Source: #{marketplace['source']}" if marketplace["source"]
90
92
  puts " URL: #{marketplace['url']}"
91
93
  puts " Location: #{marketplace['install_location']}"
92
94
  puts " Last Updated: #{marketplace['last_updated']}"
93
- puts " Ref: #{marketplace['ref']}" if marketplace['ref']
95
+ puts " Ref: #{marketplace['ref']}" if marketplace["ref"]
94
96
 
95
97
  # Check if directory actually exists
96
- if Dir.exist?(marketplace['install_location'])
98
+ if Dir.exist?(marketplace["install_location"])
97
99
  puts " Status: ✓ Cached locally"
98
100
  else
99
101
  puts " Status: ✗ Cache directory missing"
@@ -105,12 +107,12 @@ module Caruso
105
107
  config_manager = load_config
106
108
  marketplaces = config_manager.list_marketplaces
107
109
 
110
+ if marketplaces.empty?
111
+ puts "No marketplaces configured. Use 'caruso marketplace add <url>' to get started."
112
+ return
113
+ end
108
114
  if name
109
115
  # Update specific marketplace
110
- if marketplaces.empty?
111
- puts "No marketplaces configured. Use 'caruso marketplace add <url>' to get started."
112
- return
113
- end
114
116
 
115
117
  marketplace_details = config_manager.get_marketplace_details(name)
116
118
  unless marketplace_details
@@ -121,7 +123,8 @@ module Caruso
121
123
 
122
124
  puts "Updating marketplace '#{name}'..."
123
125
  begin
124
- fetcher = Caruso::Fetcher.new(marketplace_details["url"], marketplace_name: name, ref: marketplace_details["ref"])
126
+ fetcher = Caruso::Fetcher.new(marketplace_details["url"], marketplace_name: name,
127
+ ref: marketplace_details["ref"])
125
128
  fetcher.update_cache
126
129
  puts "Updated marketplace '#{name}'"
127
130
  rescue StandardError => e
@@ -129,25 +132,19 @@ module Caruso
129
132
  end
130
133
  else
131
134
  # Update all marketplaces
132
- if marketplaces.empty?
133
- puts "No marketplaces configured. Use 'caruso marketplace add <url>' to get started."
134
- return
135
- end
136
135
 
137
136
  puts "Updating all marketplaces..."
138
137
  success_count = 0
139
138
  error_count = 0
140
139
 
141
140
  marketplaces.each do |marketplace_name, details|
142
- begin
143
- puts " Updating #{marketplace_name}..."
144
- fetcher = Caruso::Fetcher.new(details["url"], marketplace_name: marketplace_name, ref: details["ref"])
145
- fetcher.update_cache
146
- success_count += 1
147
- rescue StandardError => e
148
- puts " Error updating #{marketplace_name}: #{e.message}"
149
- error_count += 1
150
- end
141
+ puts " Updating #{marketplace_name}..."
142
+ fetcher = Caruso::Fetcher.new(details["url"], marketplace_name: marketplace_name, ref: details["ref"])
143
+ fetcher.update_cache
144
+ success_count += 1
145
+ rescue StandardError => e
146
+ puts " Error updating #{marketplace_name}: #{e.message}"
147
+ error_count += 1
151
148
  end
152
149
 
153
150
  puts "\nUpdated #{success_count} marketplace(s)" + (error_count.positive? ? " (#{error_count} failed)" : "")
@@ -323,14 +320,12 @@ module Caruso
323
320
  error_count = 0
324
321
 
325
322
  installed_plugins.each do |key, plugin_data|
326
- begin
327
- puts " Updating #{key}..."
328
- update_single_plugin(key, plugin_data, config_manager)
329
- success_count += 1
330
- rescue StandardError => e
331
- puts " Error updating #{key}: #{e.message}"
332
- error_count += 1
333
- end
323
+ puts " Updating #{key}..."
324
+ update_single_plugin(key, plugin_data, config_manager)
325
+ success_count += 1
326
+ rescue StandardError => e
327
+ puts " Error updating #{key}: #{e.message}"
328
+ error_count += 1
334
329
  end
335
330
 
336
331
  puts "\nUpdated #{success_count} plugin(s)" + (error_count.positive? ? " (#{error_count} failed)" : "")
@@ -377,7 +372,7 @@ module Caruso
377
372
  desc "outdated", "Show plugins with available updates"
378
373
  def outdated
379
374
  config_manager = load_config
380
- target_dir = config_manager.full_target_path
375
+ config_manager.full_target_path
381
376
 
382
377
  installed_plugins = config_manager.list_plugins
383
378
 
@@ -389,7 +384,7 @@ module Caruso
389
384
  puts "Checking for updates..."
390
385
  outdated_plugins = []
391
386
 
392
- marketplaces = config_manager.list_marketplaces
387
+ config_manager.list_marketplaces
393
388
 
394
389
  installed_plugins.each do |key, plugin_data|
395
390
  marketplace_name = plugin_data["marketplace"]
@@ -399,7 +394,8 @@ module Caruso
399
394
  next unless marketplace_details
400
395
 
401
396
  begin
402
- fetcher = Caruso::Fetcher.new(marketplace_details["url"], marketplace_name: marketplace_name, ref: marketplace_details["ref"])
397
+ Caruso::Fetcher.new(marketplace_details["url"], marketplace_name: marketplace_name,
398
+ ref: marketplace_details["ref"])
403
399
  # For now, we'll just report that updates might be available
404
400
  # Full version comparison would require version tracking in marketplace.json
405
401
  outdated_plugins << {
@@ -438,7 +434,8 @@ module Caruso
438
434
  end
439
435
 
440
436
  # Update marketplace cache first
441
- fetcher = Caruso::Fetcher.new(marketplace_details["url"], marketplace_name: marketplace_name, ref: marketplace_details["ref"])
437
+ fetcher = Caruso::Fetcher.new(marketplace_details["url"], marketplace_name: marketplace_name,
438
+ ref: marketplace_details["ref"])
442
439
  fetcher.update_cache
443
440
 
444
441
  # Parse plugin name from key (plugin@marketplace)