Class: RubynCode::Skills::PackManager
- Inherits:
-
Object
- Object
- RubynCode::Skills::PackManager
- Defined in:
- lib/rubyn_code/skills/pack_manager.rb
Overview
Manages local installation, removal, and updates of skill packs with ETag caching and offline fallback support.
Installed packs live under ~/.rubyn-code/skill-packs/<pack-name>/. A manifest.json in each pack directory records metadata for listing, version tracking, and ETag-based conditional updates.
Constant Summary collapse
- PACKS_DIR =
File.join(Config::Defaults::HOME_DIR, 'skill-packs')
- MANIFEST_FILE =
'manifest.json'- ETAG_CACHE_FILE =
'.etags.json'- SAFE_NAME_RE =
/\A[a-zA-Z0-9_-]+\z/
Instance Method Summary collapse
-
#all_pack_dirs ⇒ Array<String>
Return all installed pack directories (for skill loader integration).
-
#initialize(packs_dir: PACKS_DIR) ⇒ PackManager
constructor
A new instance of PackManager.
-
#install(pack_data, etag: nil) ⇒ Hash
Install a pack from registry response data.
-
#installed ⇒ Array<Hash>
List all installed packs.
-
#installed?(name) ⇒ Boolean
Check if a pack is installed.
-
#pack_skills_dir(name) ⇒ String?
Return the skills directory for a pack (for catalog integration).
-
#remove(name) ⇒ Boolean
Remove an installed pack with path traversal protection.
-
#update(name, registry) ⇒ Symbol
Update a single installed pack using ETag-based conditional fetch.
-
#update_all(registry) ⇒ Hash<String, Symbol>
Update all installed packs.
Constructor Details
#initialize(packs_dir: PACKS_DIR) ⇒ PackManager
Returns a new instance of PackManager.
20 21 22 |
# File 'lib/rubyn_code/skills/pack_manager.rb', line 20 def initialize(packs_dir: PACKS_DIR) @packs_dir = packs_dir end |
Instance Method Details
#all_pack_dirs ⇒ Array<String>
Return all installed pack directories (for skill loader integration).
133 134 135 |
# File 'lib/rubyn_code/skills/pack_manager.rb', line 133 def all_pack_dirs installed.map { |pack| pack_path(pack[:name]) }.select { |d| File.directory?(d) } end |
#install(pack_data, etag: nil) ⇒ Hash
Install a pack from registry response data.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/rubyn_code/skills/pack_manager.rb', line 31 def install(pack_data, etag: nil) name = fetch_key(pack_data, :name) raise ArgumentError, 'Pack data must include a name' if name.nil? || name.empty? validate_name!(name) pack_dir = pack_path(name) FileUtils.mkdir_p(pack_dir) write_files(pack_dir, pack_data) write_manifest(pack_dir, pack_data, etag: etag) store_etag(name, etag) if etag manifest(name) end |
#installed ⇒ Array<Hash>
List all installed packs.
102 103 104 105 106 107 108 109 110 |
# File 'lib/rubyn_code/skills/pack_manager.rb', line 102 def installed return [] unless File.directory?(@packs_dir) Dir.children(@packs_dir) .select { |d| File.directory?(File.join(@packs_dir, d)) } .reject { |d| d.start_with?('.') } .filter_map { |d| manifest(d) } .sort_by { |m| m[:name] } end |
#installed?(name) ⇒ Boolean
Check if a pack is installed.
116 117 118 119 |
# File 'lib/rubyn_code/skills/pack_manager.rb', line 116 def installed?(name) manifest_path = File.join(@packs_dir, name.to_s, MANIFEST_FILE) File.exist?(manifest_path) end |
#pack_skills_dir(name) ⇒ String?
Return the skills directory for a pack (for catalog integration).
125 126 127 128 |
# File 'lib/rubyn_code/skills/pack_manager.rb', line 125 def pack_skills_dir(name) dir = pack_path(name) File.directory?(dir) ? dir : nil end |
#remove(name) ⇒ Boolean
Remove an installed pack with path traversal protection.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/rubyn_code/skills/pack_manager.rb', line 82 def remove(name) validate_name!(name) pack_dir = pack_path(name) return false unless File.directory?(pack_dir) # Verify the resolved path is within packs_dir to prevent traversal real_pack = File.realpath(pack_dir) real_base = File.realpath(@packs_dir) unless real_pack.start_with?("#{real_base}/") raise ArgumentError, "Pack directory is outside the skill-packs directory" end FileUtils.rm_rf(pack_dir) remove_etag(name) true end |
#update(name, registry) ⇒ Symbol
Update a single installed pack using ETag-based conditional fetch. Returns :updated, :up_to_date, or :not_installed.
52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/rubyn_code/skills/pack_manager.rb', line 52 def update(name, registry) validate_name!(name) return :not_installed unless installed?(name) cached_etag = load_etag(name) result = registry.fetch_pack(name, etag: cached_etag) return :up_to_date if result[:not_modified] install(result[:data], etag: result[:etag]) :updated end |
#update_all(registry) ⇒ Hash<String, Symbol>
Update all installed packs. Returns a hash of { name => status }.
69 70 71 72 73 74 75 76 |
# File 'lib/rubyn_code/skills/pack_manager.rb', line 69 def update_all(registry) installed.each_with_object({}) do |pack, results| name = pack[:name] results[name] = update(name, registry) rescue RegistryError => e results[name] = :"error: #{e.}" end end |