caruso 0.5.4
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 +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +146 -0
- data/CHANGELOG.md +213 -0
- data/CLAUDE.md +276 -0
- data/IMPROVEMENTS.md +337 -0
- data/LICENSE.txt +21 -0
- data/README.md +326 -0
- data/Rakefile +71 -0
- data/bin/caruso +6 -0
- data/caruso.gemspec +43 -0
- data/lib/caruso/adapter.rb +110 -0
- data/lib/caruso/cli.rb +532 -0
- data/lib/caruso/config_manager.rb +190 -0
- data/lib/caruso/fetcher.rb +248 -0
- data/lib/caruso/marketplace_registry.rb +102 -0
- data/lib/caruso/version.rb +5 -0
- data/lib/caruso.rb +22 -0
- data/reference/marketplace.md +433 -0
- data/reference/plugins.md +391 -0
- data/reference/plugins_reference.md +376 -0
- metadata +176 -0
data/lib/caruso/cli.rb
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require_relative "../caruso"
|
|
5
|
+
|
|
6
|
+
module Caruso
|
|
7
|
+
class Marketplace < Thor
|
|
8
|
+
desc "add URL", "Add a marketplace"
|
|
9
|
+
method_option :ref, type: :string, desc: "Git branch or tag to checkout"
|
|
10
|
+
def add(url)
|
|
11
|
+
config_manager = load_config
|
|
12
|
+
|
|
13
|
+
# Determine source type
|
|
14
|
+
source = "git"
|
|
15
|
+
if url.match?(%r{\Ahttps://github\.com/[^/]+/[^/]+}) || url.match?(%r{\A[^/]+/[^/]+\z})
|
|
16
|
+
source = "github"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Initialize fetcher and clone repository (cache dir is URL-based)
|
|
20
|
+
fetcher = Caruso::Fetcher.new(url, ref: options[:ref])
|
|
21
|
+
|
|
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})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Read marketplace name from marketplace.json
|
|
28
|
+
marketplace_name = fetcher.extract_marketplace_name
|
|
29
|
+
|
|
30
|
+
config_manager.add_marketplace(marketplace_name, url, source: source, ref: options[:ref])
|
|
31
|
+
|
|
32
|
+
puts "Added marketplace '#{marketplace_name}' from #{url}"
|
|
33
|
+
puts " Cached at: #{fetcher.cache_dir}"
|
|
34
|
+
puts " Ref: #{options[:ref]}" if options[:ref]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
desc "list", "List configured marketplaces"
|
|
38
|
+
def list
|
|
39
|
+
config_manager = load_config
|
|
40
|
+
marketplaces = config_manager.list_marketplaces
|
|
41
|
+
|
|
42
|
+
if marketplaces.empty?
|
|
43
|
+
puts "No marketplaces configured."
|
|
44
|
+
else
|
|
45
|
+
puts "Configured Marketplaces:"
|
|
46
|
+
marketplaces.each do |name, details|
|
|
47
|
+
puts " - #{name}: #{details['url']} (Ref: #{details['ref'] || 'HEAD'})"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "remove NAME", "Remove a marketplace"
|
|
53
|
+
def remove(name)
|
|
54
|
+
config_manager = load_config
|
|
55
|
+
|
|
56
|
+
# Remove from config
|
|
57
|
+
config_manager.remove_marketplace(name)
|
|
58
|
+
|
|
59
|
+
# Remove from registry
|
|
60
|
+
registry = Caruso::MarketplaceRegistry.new
|
|
61
|
+
marketplace = registry.get_marketplace(name)
|
|
62
|
+
if marketplace
|
|
63
|
+
cache_dir = marketplace["install_location"]
|
|
64
|
+
registry.remove_marketplace(name)
|
|
65
|
+
|
|
66
|
+
# Inform about cache directory
|
|
67
|
+
if Dir.exist?(cache_dir)
|
|
68
|
+
puts "Cache directory still exists at: #{cache_dir}"
|
|
69
|
+
puts "Run 'rm -rf #{cache_dir}' to delete it if desired."
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
puts "Removed marketplace '#{name}'"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
desc "info NAME", "Show marketplace information"
|
|
77
|
+
def info(name)
|
|
78
|
+
registry = Caruso::MarketplaceRegistry.new
|
|
79
|
+
marketplace = registry.get_marketplace(name)
|
|
80
|
+
|
|
81
|
+
unless marketplace
|
|
82
|
+
puts "Error: Marketplace '#{name}' not found in registry."
|
|
83
|
+
available = registry.list_marketplaces.keys
|
|
84
|
+
puts "Available marketplaces: #{available.join(', ')}" unless available.empty?
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
puts "Marketplace: #{name}"
|
|
89
|
+
puts " Source: #{marketplace['source']}" if marketplace['source']
|
|
90
|
+
puts " URL: #{marketplace['url']}"
|
|
91
|
+
puts " Location: #{marketplace['install_location']}"
|
|
92
|
+
puts " Last Updated: #{marketplace['last_updated']}"
|
|
93
|
+
puts " Ref: #{marketplace['ref']}" if marketplace['ref']
|
|
94
|
+
|
|
95
|
+
# Check if directory actually exists
|
|
96
|
+
if Dir.exist?(marketplace['install_location'])
|
|
97
|
+
puts " Status: ✓ Cached locally"
|
|
98
|
+
else
|
|
99
|
+
puts " Status: ✗ Cache directory missing"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
desc "update [NAME]", "Update marketplace metadata (updates all if no name given)"
|
|
104
|
+
def update(name = nil)
|
|
105
|
+
config_manager = load_config
|
|
106
|
+
marketplaces = config_manager.list_marketplaces
|
|
107
|
+
|
|
108
|
+
if name
|
|
109
|
+
# 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
|
+
|
|
115
|
+
marketplace_details = config_manager.get_marketplace_details(name)
|
|
116
|
+
unless marketplace_details
|
|
117
|
+
puts "Error: Marketplace '#{name}' not found."
|
|
118
|
+
puts "Available marketplaces: #{marketplaces.keys.join(', ')}"
|
|
119
|
+
return
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
puts "Updating marketplace '#{name}'..."
|
|
123
|
+
begin
|
|
124
|
+
fetcher = Caruso::Fetcher.new(marketplace_details["url"], marketplace_name: name, ref: marketplace_details["ref"])
|
|
125
|
+
fetcher.update_cache
|
|
126
|
+
puts "Updated marketplace '#{name}'"
|
|
127
|
+
rescue StandardError => e
|
|
128
|
+
puts "Error updating marketplace: #{e.message}"
|
|
129
|
+
end
|
|
130
|
+
else
|
|
131
|
+
# 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
|
+
|
|
137
|
+
puts "Updating all marketplaces..."
|
|
138
|
+
success_count = 0
|
|
139
|
+
error_count = 0
|
|
140
|
+
|
|
141
|
+
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
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
puts "\nUpdated #{success_count} marketplace(s)" + (error_count.positive? ? " (#{error_count} failed)" : "")
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def load_config
|
|
160
|
+
manager = Caruso::ConfigManager.new
|
|
161
|
+
manager.load
|
|
162
|
+
manager
|
|
163
|
+
rescue Caruso::Error => e
|
|
164
|
+
puts "Error: #{e.message}"
|
|
165
|
+
exit 1
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
class Plugin < Thor
|
|
170
|
+
desc "install PLUGIN_NAME", "Install a plugin (format: plugin@marketplace or just plugin)"
|
|
171
|
+
def install(plugin_ref)
|
|
172
|
+
config_manager = load_config
|
|
173
|
+
target_dir = config_manager.full_target_path
|
|
174
|
+
ide = config_manager.ide
|
|
175
|
+
|
|
176
|
+
plugin_name, marketplace_name = plugin_ref.split("@")
|
|
177
|
+
|
|
178
|
+
marketplaces = config_manager.list_marketplaces
|
|
179
|
+
|
|
180
|
+
marketplace_url = nil
|
|
181
|
+
|
|
182
|
+
if marketplace_name
|
|
183
|
+
marketplace_details = config_manager.get_marketplace_details(marketplace_name)
|
|
184
|
+
unless marketplace_details
|
|
185
|
+
puts "Error: Marketplace '#{marketplace_name}' not found. Add it with 'caruso marketplace add <url>'."
|
|
186
|
+
puts "Available marketplaces: #{marketplaces.keys.join(', ')}" unless marketplaces.empty?
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
marketplace_url = marketplace_details["url"]
|
|
190
|
+
elsif marketplaces.empty?
|
|
191
|
+
# Try to find plugin in any configured marketplace
|
|
192
|
+
# Or default to the first one if only one exists
|
|
193
|
+
puts "Error: No marketplaces configured. Add one with 'caruso marketplace add <url>'."
|
|
194
|
+
return
|
|
195
|
+
elsif marketplaces.size == 1
|
|
196
|
+
marketplace_name = marketplaces.keys.first
|
|
197
|
+
marketplace_url = marketplaces.values.first["url"]
|
|
198
|
+
puts "Using default marketplace: #{marketplace_name}"
|
|
199
|
+
else
|
|
200
|
+
puts "Error: Multiple marketplaces configured. Please specify which one to use: plugin@marketplace"
|
|
201
|
+
puts "Available marketplaces: #{marketplaces.keys.join(', ')}"
|
|
202
|
+
return
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
puts "Installing #{plugin_name} from #{marketplace_name}..."
|
|
206
|
+
|
|
207
|
+
begin
|
|
208
|
+
fetcher = Caruso::Fetcher.new(marketplace_url, marketplace_name: marketplace_name)
|
|
209
|
+
files = fetcher.fetch(plugin_name)
|
|
210
|
+
rescue Caruso::PluginNotFoundError => e
|
|
211
|
+
puts "Error: #{e.message}"
|
|
212
|
+
puts "Available plugins: #{e.available_plugins.join(', ')}" unless e.available_plugins.empty?
|
|
213
|
+
return
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
if files.empty?
|
|
217
|
+
puts "No steering files found for #{plugin_name}."
|
|
218
|
+
return
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
adapter = Caruso::Adapter.new(
|
|
222
|
+
files,
|
|
223
|
+
target_dir: target_dir,
|
|
224
|
+
agent: ide.to_sym,
|
|
225
|
+
marketplace_name: marketplace_name,
|
|
226
|
+
plugin_name: plugin_name
|
|
227
|
+
)
|
|
228
|
+
created_filenames = adapter.adapt
|
|
229
|
+
|
|
230
|
+
# Convert filenames to relative paths from project root
|
|
231
|
+
created_files = created_filenames.map { |f| File.join(config_manager.target_dir, f) }
|
|
232
|
+
|
|
233
|
+
# Use composite key for uniqueness
|
|
234
|
+
plugin_key = "#{plugin_name}@#{marketplace_name}"
|
|
235
|
+
config_manager.add_plugin(plugin_key, created_files, marketplace_name: marketplace_name)
|
|
236
|
+
puts "Installed #{plugin_name}!"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
desc "uninstall PLUGIN_NAME", "Uninstall a plugin"
|
|
240
|
+
def uninstall(plugin_ref)
|
|
241
|
+
config_manager = load_config
|
|
242
|
+
|
|
243
|
+
# Handle both "plugin" and "plugin@marketplace" formats
|
|
244
|
+
# If just "plugin", we need to find the full key
|
|
245
|
+
plugin_key = plugin_ref
|
|
246
|
+
unless plugin_ref.include?("@")
|
|
247
|
+
installed = config_manager.list_plugins
|
|
248
|
+
matches = installed.keys.select { |k| k.start_with?("#{plugin_ref}@") }
|
|
249
|
+
if matches.size == 1
|
|
250
|
+
plugin_key = matches.first
|
|
251
|
+
elsif matches.size > 1
|
|
252
|
+
puts "Error: Multiple plugins match '#{plugin_ref}'. Please specify marketplace: #{matches.join(', ')}"
|
|
253
|
+
return
|
|
254
|
+
elsif !installed.key?(plugin_ref) # Check exact match just in case
|
|
255
|
+
puts "Plugin #{plugin_ref} is not installed."
|
|
256
|
+
return
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
unless config_manager.plugin_installed?(plugin_key)
|
|
261
|
+
puts "Plugin #{plugin_key} is not installed."
|
|
262
|
+
return
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
puts "Removing #{plugin_key}..."
|
|
266
|
+
files_to_remove = config_manager.remove_plugin(plugin_key)
|
|
267
|
+
|
|
268
|
+
files_to_remove.each do |file|
|
|
269
|
+
full_path = File.join(config_manager.project_dir, file)
|
|
270
|
+
if File.exist?(full_path)
|
|
271
|
+
File.delete(full_path)
|
|
272
|
+
puts " Deleted #{file}"
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
puts "Uninstalled #{plugin_key}."
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
desc "list", "List available and installed plugins"
|
|
280
|
+
def list
|
|
281
|
+
config_manager = load_config
|
|
282
|
+
marketplaces = config_manager.list_marketplaces
|
|
283
|
+
installed = config_manager.list_plugins
|
|
284
|
+
|
|
285
|
+
if marketplaces.empty?
|
|
286
|
+
puts "No marketplaces configured. Use 'caruso marketplace add <url>' to get started."
|
|
287
|
+
return
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
marketplaces.each do |name, details|
|
|
291
|
+
puts "\nMarketplace: #{name} (#{details['url']})"
|
|
292
|
+
begin
|
|
293
|
+
fetcher = Caruso::Fetcher.new(details["url"], marketplace_name: name, ref: details["ref"])
|
|
294
|
+
available = fetcher.list_available_plugins
|
|
295
|
+
|
|
296
|
+
available.each do |plugin|
|
|
297
|
+
plugin_key = "#{plugin[:name]}@#{name}"
|
|
298
|
+
status = installed.key?(plugin_key) ? "[Installed]" : ""
|
|
299
|
+
puts " - #{plugin[:name]} #{status}"
|
|
300
|
+
puts " #{plugin[:description]}"
|
|
301
|
+
end
|
|
302
|
+
rescue StandardError => e
|
|
303
|
+
puts " Error fetching marketplace: #{e.message}"
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
desc "update PLUGIN_NAME", "Update a plugin to the latest version"
|
|
309
|
+
method_option :all, type: :boolean, aliases: "-a", desc: "Update all installed plugins"
|
|
310
|
+
def update(plugin_ref = nil)
|
|
311
|
+
config_manager = load_config
|
|
312
|
+
installed_plugins = config_manager.list_plugins
|
|
313
|
+
|
|
314
|
+
if options[:all]
|
|
315
|
+
# Update all plugins
|
|
316
|
+
if installed_plugins.empty?
|
|
317
|
+
puts "No plugins installed."
|
|
318
|
+
return
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
puts "Updating all plugins..."
|
|
322
|
+
success_count = 0
|
|
323
|
+
error_count = 0
|
|
324
|
+
|
|
325
|
+
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
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
puts "\nUpdated #{success_count} plugin(s)" + (error_count.positive? ? " (#{error_count} failed)" : "")
|
|
337
|
+
else
|
|
338
|
+
# Update single plugin
|
|
339
|
+
unless plugin_ref
|
|
340
|
+
puts "Error: Please specify a plugin name (plugin@marketplace) or use --all to update all plugins."
|
|
341
|
+
return
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Resolve key
|
|
345
|
+
plugin_key = plugin_ref
|
|
346
|
+
unless plugin_ref.include?("@")
|
|
347
|
+
matches = installed_plugins.keys.select { |k| k.start_with?("#{plugin_ref}@") }
|
|
348
|
+
if matches.size == 1
|
|
349
|
+
plugin_key = matches.first
|
|
350
|
+
elsif matches.size > 1
|
|
351
|
+
puts "Error: Multiple plugins match '#{plugin_ref}'. Please specify marketplace: #{matches.join(', ')}"
|
|
352
|
+
return
|
|
353
|
+
elsif !installed_plugins.key?(plugin_ref)
|
|
354
|
+
puts "Error: Plugin '#{plugin_ref}' is not installed."
|
|
355
|
+
puts "Use 'caruso plugin install #{plugin_ref}' to install it."
|
|
356
|
+
return
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
plugin_data = installed_plugins[plugin_key]
|
|
361
|
+
unless plugin_data
|
|
362
|
+
puts "Error: Plugin '#{plugin_key}' is not installed."
|
|
363
|
+
return
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
puts "Updating #{plugin_key}..."
|
|
367
|
+
begin
|
|
368
|
+
update_single_plugin(plugin_key, plugin_data, config_manager)
|
|
369
|
+
puts "Updated #{plugin_key}!"
|
|
370
|
+
rescue StandardError => e
|
|
371
|
+
puts "Error updating plugin: #{e.message}"
|
|
372
|
+
exit 1
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
desc "outdated", "Show plugins with available updates"
|
|
378
|
+
def outdated
|
|
379
|
+
config_manager = load_config
|
|
380
|
+
target_dir = config_manager.full_target_path
|
|
381
|
+
|
|
382
|
+
installed_plugins = config_manager.list_plugins
|
|
383
|
+
|
|
384
|
+
if installed_plugins.empty?
|
|
385
|
+
puts "No plugins installed."
|
|
386
|
+
return
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
puts "Checking for updates..."
|
|
390
|
+
outdated_plugins = []
|
|
391
|
+
|
|
392
|
+
marketplaces = config_manager.list_marketplaces
|
|
393
|
+
|
|
394
|
+
installed_plugins.each do |key, plugin_data|
|
|
395
|
+
marketplace_name = plugin_data["marketplace"]
|
|
396
|
+
next unless marketplace_name
|
|
397
|
+
|
|
398
|
+
marketplace_details = config_manager.get_marketplace_details(marketplace_name)
|
|
399
|
+
next unless marketplace_details
|
|
400
|
+
|
|
401
|
+
begin
|
|
402
|
+
fetcher = Caruso::Fetcher.new(marketplace_details["url"], marketplace_name: marketplace_name, ref: marketplace_details["ref"])
|
|
403
|
+
# For now, we'll just report that updates might be available
|
|
404
|
+
# Full version comparison would require version tracking in marketplace.json
|
|
405
|
+
outdated_plugins << {
|
|
406
|
+
name: key,
|
|
407
|
+
current_version: "unknown", # Version tracking not fully implemented yet
|
|
408
|
+
marketplace: marketplace_name
|
|
409
|
+
}
|
|
410
|
+
rescue StandardError
|
|
411
|
+
# Skip plugins with inaccessible marketplaces
|
|
412
|
+
next
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
if outdated_plugins.empty?
|
|
417
|
+
puts "All plugins are up to date."
|
|
418
|
+
else
|
|
419
|
+
puts "\nPlugins installed:"
|
|
420
|
+
outdated_plugins.each do |plugin|
|
|
421
|
+
puts " - #{plugin[:name]} (version: #{plugin[:current_version]})"
|
|
422
|
+
end
|
|
423
|
+
puts "\nRun 'caruso plugin update --all' to update all plugins."
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
private
|
|
428
|
+
|
|
429
|
+
def update_single_plugin(plugin_key, plugin_data, config_manager)
|
|
430
|
+
marketplace_name = plugin_data["marketplace"]
|
|
431
|
+
unless marketplace_name
|
|
432
|
+
raise "No marketplace information found for #{plugin_key}"
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
marketplace_details = config_manager.get_marketplace_details(marketplace_name)
|
|
436
|
+
unless marketplace_details
|
|
437
|
+
raise "Marketplace '#{marketplace_name}' not found in config"
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Update marketplace cache first
|
|
441
|
+
fetcher = Caruso::Fetcher.new(marketplace_details["url"], marketplace_name: marketplace_name, ref: marketplace_details["ref"])
|
|
442
|
+
fetcher.update_cache
|
|
443
|
+
|
|
444
|
+
# Parse plugin name from key (plugin@marketplace)
|
|
445
|
+
plugin_name = plugin_key.split("@").first
|
|
446
|
+
|
|
447
|
+
# Fetch latest plugin files
|
|
448
|
+
files = fetcher.fetch(plugin_name)
|
|
449
|
+
|
|
450
|
+
if files.empty?
|
|
451
|
+
raise "No steering files found for #{plugin_name}"
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Adapt files to target IDE
|
|
455
|
+
adapter = Caruso::Adapter.new(
|
|
456
|
+
files,
|
|
457
|
+
target_dir: config_manager.full_target_path,
|
|
458
|
+
agent: config_manager.ide.to_sym,
|
|
459
|
+
marketplace_name: marketplace_name,
|
|
460
|
+
plugin_name: plugin_name
|
|
461
|
+
)
|
|
462
|
+
created_filenames = adapter.adapt
|
|
463
|
+
|
|
464
|
+
# Convert filenames to relative paths from project root
|
|
465
|
+
created_files = created_filenames.map { |f| File.join(config_manager.target_dir, f) }
|
|
466
|
+
|
|
467
|
+
# Cleanup: Delete files that are no longer present
|
|
468
|
+
old_files = config_manager.get_installed_files(plugin_key)
|
|
469
|
+
files_to_delete = old_files - created_files
|
|
470
|
+
files_to_delete.each do |file|
|
|
471
|
+
full_path = File.join(config_manager.project_dir, file)
|
|
472
|
+
if File.exist?(full_path)
|
|
473
|
+
File.delete(full_path)
|
|
474
|
+
puts " Deleted obsolete file: #{file}"
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# Update plugin in config
|
|
479
|
+
config_manager.add_plugin(plugin_key, created_files, marketplace_name: marketplace_name)
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def load_config
|
|
483
|
+
manager = Caruso::ConfigManager.new
|
|
484
|
+
manager.load
|
|
485
|
+
manager
|
|
486
|
+
rescue Caruso::Error => e
|
|
487
|
+
puts "Error: #{e.message}"
|
|
488
|
+
exit 1
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
class CLI < Thor
|
|
493
|
+
desc "init [PATH]", "Initialize Caruso in a directory"
|
|
494
|
+
method_option :ide, required: true, desc: "Target IDE (currently: cursor)"
|
|
495
|
+
def init(path = ".")
|
|
496
|
+
config_manager = Caruso::ConfigManager.new(path)
|
|
497
|
+
|
|
498
|
+
begin
|
|
499
|
+
config = config_manager.init(ide: options[:ide])
|
|
500
|
+
|
|
501
|
+
puts "✓ Initialized Caruso for #{config['ide']}"
|
|
502
|
+
puts " Project directory: #{config_manager.project_dir}"
|
|
503
|
+
puts " Target directory: #{config['target_dir']}"
|
|
504
|
+
puts ""
|
|
505
|
+
puts "Created files:"
|
|
506
|
+
puts " ✓ caruso.json (commit this)"
|
|
507
|
+
puts " ✓ .caruso.local.json (add to .gitignore)"
|
|
508
|
+
puts ""
|
|
509
|
+
puts "Recommended .gitignore entries:"
|
|
510
|
+
puts " .caruso.local.json"
|
|
511
|
+
puts " #{config['target_dir']}/caruso/"
|
|
512
|
+
rescue ArgumentError => e
|
|
513
|
+
puts "Error: #{e.message}"
|
|
514
|
+
exit 1
|
|
515
|
+
rescue Caruso::Error => e
|
|
516
|
+
puts "Error: #{e.message}"
|
|
517
|
+
exit 1
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
desc "marketplace SUBCOMMAND", "Manage marketplaces"
|
|
522
|
+
subcommand "marketplace", Marketplace
|
|
523
|
+
|
|
524
|
+
desc "plugin SUBCOMMAND", "Manage plugins"
|
|
525
|
+
subcommand "plugin", Plugin
|
|
526
|
+
|
|
527
|
+
desc "version", "Print version"
|
|
528
|
+
def version
|
|
529
|
+
puts "Caruso v#{Caruso::VERSION}"
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|