Class: PromptObjects::Env::Manager

Inherits:
Object
  • Object
show all
Defined in:
lib/prompt_objects/environment/manager.rb

Overview

Manages multiple environments in the user's data directory. Handles creation, listing, opening, archiving environments.

Constant Summary collapse

DEFAULT_BASE_DIR =
File.expand_path("~/.prompt_objects")
ENVIRONMENTS_DIR =
"environments"
ARCHIVE_DIR =
"archive"
CONFIG_FILE =
"config.yml"
DEV_ENV_NAME =
"_development"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_dir: nil) ⇒ Manager

Returns a new instance of Manager.



19
20
21
# File 'lib/prompt_objects/environment/manager.rb', line 19

def initialize(base_dir: nil)
  @base_dir = base_dir || ENV.fetch("PROMPT_OBJECTS_HOME", DEFAULT_BASE_DIR)
end

Instance Attribute Details

#base_dirObject (readonly)

Returns the value of attribute base_dir.



17
18
19
# File 'lib/prompt_objects/environment/manager.rb', line 17

def base_dir
  @base_dir
end

Instance Method Details

#any_environments?Boolean

Check if any environments exist.

Returns:

  • (Boolean)


64
65
66
# File 'lib/prompt_objects/environment/manager.rb', line 64

def any_environments?
  list.any?
end

#archive(name) ⇒ String

Archive (soft delete) an environment.

Parameters:

  • name (String)

Returns:

  • (String)

    Path to archived environment

Raises:



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/prompt_objects/environment/manager.rb', line 192

def archive(name)
  raise Error, "Environment '#{name}' not found" unless environment_exists?(name)
  raise Error, "Cannot archive development environment" if name == DEV_ENV_NAME

  src = environment_path(name)

  # Mark as archived in manifest before moving
  manifest = Manifest.load_from_dir(src)
  manifest.mark_archived!
  manifest.save_to_dir(src)

  # Commit the archive marker
  Git.commit(src, "Archived environment")

  timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
  dest = File.join(archive_dir, "#{name}_#{timestamp}")

  FileUtils.mv(src, dest)
  dest
end

#archive_dirString

Path to archive directory.

Returns:

  • (String)


38
39
40
# File 'lib/prompt_objects/environment/manager.rb', line 38

def archive_dir
  File.join(@base_dir, ARCHIVE_DIR)
end

#clone(source_name, new_name) ⇒ String

Clone an environment.

Parameters:

  • source_name (String)
  • new_name (String)

Returns:

  • (String)

    Path to cloned environment

Raises:



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/prompt_objects/environment/manager.rb', line 279

def clone(source_name, new_name)
  raise Error, "Source environment '#{source_name}' not found" unless environment_exists?(source_name)
  raise Error, "Environment '#{new_name}' already exists" if environment_exists?(new_name)
  raise Error, "Invalid environment name: #{new_name}" unless valid_name?(new_name)

  src = environment_path(source_name)
  dest = environment_path(new_name)

  # Copy directory (excluding sessions.db)
  FileUtils.mkdir_p(dest)
  Dir.glob(File.join(src, "**", "*"), File::FNM_DOTMATCH).each do |path|
    relative = path.sub("#{src}/", "")
    next if relative == "." || relative == ".."
    next if relative.include?("sessions.db")
    next if relative.start_with?(".git/")

    target = File.join(dest, relative)
    if File.directory?(path)
      FileUtils.mkdir_p(target)
    else
      FileUtils.cp(path, target)
    end
  end

  # Update manifest
  manifest = Manifest.load_from_dir(dest)
  manifest.name = new_name
  manifest.instance_variable_set(:@created_at, Time.now)
  manifest.instance_variable_set(:@stats, { "total_messages" => 0, "total_sessions" => 0, "po_count" => manifest.stats["po_count"] })
  manifest.save_to_dir(dest)

  # Initialize fresh git repo
  init_git(dest)

  dest
end

#config_pathString

Path to global config file.

Returns:

  • (String)


44
45
46
# File 'lib/prompt_objects/environment/manager.rb', line 44

def config_path
  File.join(@base_dir, CONFIG_FILE)
end

#create(name:, template: nil, description: nil) ⇒ String

Create a new environment from a template.

Parameters:

  • name (String)

    Environment name

  • template (String, nil) (defaults to: nil)

    Template name (from templates/)

  • description (String, nil) (defaults to: nil)

    Environment description

Returns:

  • (String)

    Path to created environment

Raises:



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/prompt_objects/environment/manager.rb', line 122

def create(name:, template: nil, description: nil)
  raise Error, "Environment '#{name}' already exists" if environment_exists?(name)
  raise Error, "Invalid environment name: #{name}" unless valid_name?(name)

  env_path = environment_path(name)
  FileUtils.mkdir_p(env_path)
  FileUtils.mkdir_p(File.join(env_path, "objects"))
  FileUtils.mkdir_p(File.join(env_path, "primitives"))
  FileUtils.mkdir_p(File.join(env_path, "services"))

  # Copy template if specified
  template_manifest = copy_template(env_path, template) if template

  # Create manifest
  manifest = Manifest.new(
    name: name,
    description: description || template_manifest&.dig("description"),
    icon: template_manifest&.dig("icon") || "📦",
    color: template_manifest&.dig("color") || "#4A90D9",
    default_po: template_manifest&.dig("default_po"),
    dependencies: template_manifest&.dig("dependencies"),
    llm: template_manifest&.dig("llm")
  )
  manifest.save_to_dir(env_path)

  # Create .gitignore
  create_gitignore(env_path)

  # Initialize git repo
  init_git(env_path)

  env_path
end

#create_dev_environmentString

Create the development environment (for --dev flag). Uses minimal template to ensure at least one PO exists for bootstrapping.

Returns:

  • (String)

    Path to dev environment



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/prompt_objects/environment/manager.rb', line 159

def create_dev_environment
  return environment_path(DEV_ENV_NAME) if environment_exists?(DEV_ENV_NAME)

  env_path = environment_path(DEV_ENV_NAME)
  FileUtils.mkdir_p(env_path)
  FileUtils.mkdir_p(File.join(env_path, "objects"))
  FileUtils.mkdir_p(File.join(env_path, "primitives"))

  # Copy minimal template objects for bootstrapping
  copy_template(env_path, "minimal")

  manifest = Manifest.new(
    name: DEV_ENV_NAME,
    description: "Development environment",
    icon: "🔧"
  )
  manifest.save_to_dir(env_path)

  create_gitignore(env_path)
  init_git(env_path)

  env_path
end

#default_environmentString?

Get default environment name from config.

Returns:

  • (String, nil)


318
319
320
# File 'lib/prompt_objects/environment/manager.rb', line 318

def default_environment
  global_config["default_environment"]
end

#delete_archived(archived_name) ⇒ Object

Permanently delete an archived environment.

Parameters:

  • archived_name (String)

Raises:



252
253
254
255
256
257
# File 'lib/prompt_objects/environment/manager.rb', line 252

def delete_archived(archived_name)
  path = File.join(archive_dir, archived_name)
  raise Error, "Archived environment not found: #{archived_name}" unless Dir.exist?(path)

  FileUtils.rm_rf(path)
end

#destroy(name) ⇒ Object

Permanently delete an ACTIVE environment (its directory and all data). Clears the default if this was it. For a recoverable flow, archive first.

Parameters:

  • name (String)

Raises:



262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/prompt_objects/environment/manager.rb', line 262

def destroy(name)
  raise Error, "Environment '#{name}' not found" unless environment_exists?(name)
  raise Error, "Cannot delete the development environment" if name == DEV_ENV_NAME

  FileUtils.rm_rf(environment_path(name))

  if default_environment == name
    config = global_config
    config.delete("default_environment")
    save_global_config(config)
  end
end

#dev_environment_pathString

Get path to development environment.

Returns:

  • (String)


185
186
187
# File 'lib/prompt_objects/environment/manager.rb', line 185

def dev_environment_path
  create_dev_environment
end

#environment_exists?(name) ⇒ Boolean

Check if an environment exists.

Parameters:

  • name (String)

Returns:

  • (Boolean)


94
95
96
97
# File 'lib/prompt_objects/environment/manager.rb', line 94

def environment_exists?(name)
  path = environment_path(name)
  Dir.exist?(path) && File.exist?(File.join(path, Env::Manifest::FILENAME))
end

#environment_path(name) ⇒ String

Get path to an environment.

Parameters:

  • name (String)

Returns:

  • (String)


102
103
104
# File 'lib/prompt_objects/environment/manager.rb', line 102

def environment_path(name)
  File.join(environments_dir, name)
end

#environments_dirString

Path to environments directory.

Returns:

  • (String)


32
33
34
# File 'lib/prompt_objects/environment/manager.rb', line 32

def environments_dir
  File.join(@base_dir, ENVIRONMENTS_DIR)
end

#first_run?Boolean

Check if first-run wizard should be shown.

Returns:

  • (Boolean)


70
71
72
# File 'lib/prompt_objects/environment/manager.rb', line 70

def first_run?
  !any_environments?
end

#global_configHash

Load global config.

Returns:

  • (Hash)


50
51
52
53
54
# File 'lib/prompt_objects/environment/manager.rb', line 50

def global_config
  return {} unless File.exist?(config_path)

  YAML.safe_load(File.read(config_path)) || {}
end

#listArray<String>

List all environment names.

Returns:

  • (Array<String>)


76
77
78
79
80
81
82
83
# File 'lib/prompt_objects/environment/manager.rb', line 76

def list
  return [] unless Dir.exist?(environments_dir)

  Dir.children(environments_dir)
     .select { |name| environment_exists?(name) }
     .reject { |name| name.start_with?("_") } # Hide dev environments
     .sort
end

#list_archivedArray<String>

List archived environments.

Returns:

  • (Array<String>)


215
216
217
218
219
# File 'lib/prompt_objects/environment/manager.rb', line 215

def list_archived
  return [] unless Dir.exist?(archive_dir)

  Dir.children(archive_dir).sort
end

#list_with_manifestsArray<Manifest>

List all environments with their manifests.

Returns:



87
88
89
# File 'lib/prompt_objects/environment/manager.rb', line 87

def list_with_manifests
  list.map { |name| manifest_for(name) }.compact
end

#manifest_for(name) ⇒ Manifest?

Get manifest for an environment.

Parameters:

  • name (String)

Returns:



109
110
111
112
113
114
115
# File 'lib/prompt_objects/environment/manager.rb', line 109

def manifest_for(name)
  return nil unless environment_exists?(name)

  Manifest.load_from_dir(environment_path(name))
rescue StandardError
  nil
end

#restore(archived_name, restore_as: nil) ⇒ String

Restore an archived environment.

Parameters:

  • archived_name (String)

    Name with timestamp suffix

  • restore_as (String, nil) (defaults to: nil)

    New name (defaults to original name)

Returns:

  • (String)

    Path to restored environment

Raises:



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/prompt_objects/environment/manager.rb', line 225

def restore(archived_name, restore_as: nil)
  src = File.join(archive_dir, archived_name)
  raise Error, "Archived environment not found: #{archived_name}" unless Dir.exist?(src)

  # Extract original name (remove timestamp suffix)
  original_name = archived_name.sub(/_\d{8}_\d{6}$/, "")
  new_name = restore_as || original_name

  raise Error, "Environment '#{new_name}' already exists" if environment_exists?(new_name)

  dest = environment_path(new_name)
  FileUtils.mv(src, dest)

  # Update manifest: clear archived_at, update name if needed
  manifest = Manifest.load_from_dir(dest)
  manifest.archived_at = nil
  manifest.name = new_name if new_name != original_name
  manifest.save_to_dir(dest)

  # Commit the restore
  Git.commit(dest, "Restored environment#{new_name != original_name ? " as '#{new_name}'" : ""}")

  dest
end

#save_global_config(config) ⇒ Object

Save global config.

Parameters:

  • config (Hash)


58
59
60
# File 'lib/prompt_objects/environment/manager.rb', line 58

def save_global_config(config)
  File.write(config_path, config.to_yaml)
end

#set_default_environment(name) ⇒ Object

Set default environment.

Parameters:

  • name (String)

Raises:



324
325
326
327
328
329
330
# File 'lib/prompt_objects/environment/manager.rb', line 324

def set_default_environment(name)
  raise Error, "Environment '#{name}' not found" unless environment_exists?(name)

  config = global_config
  config["default_environment"] = name
  save_global_config(config)
end

#setup!Object

Ensure base directory structure exists.



24
25
26
27
28
# File 'lib/prompt_objects/environment/manager.rb', line 24

def setup!
  FileUtils.mkdir_p(environments_dir)
  FileUtils.mkdir_p(archive_dir)
  ensure_global_config
end