Class: Clacky::Server::ProjectManager
- Inherits:
-
Object
- Object
- Clacky::Server::ProjectManager
- Defined in:
- lib/clacky/server/project_manager.rb
Overview
ProjectManager handles CRUD for "projects" — named groups that sessions can be assigned to, analogous to ChatGPT / Codex Projects.
Storage: ~/.clacky/projects.json Each project entry shape:
{
id: String (8-char hex),
name: String,
description: String | null,
color: String | null (e.g. "#6366f1"),
icon: String | null (e.g. "folder", "code"),
working_dir: String | null (absolute path; new sessions inherit this),
created_at: ISO8601,
updated_at: ISO8601
}
Thread-safety: a Mutex guards every read/write.
Constant Summary collapse
- PROJECTS_FILE =
File.join(Dir.home, ".clacky", "projects.json")
Instance Method Summary collapse
-
#all ⇒ Object
Return all projects sorted by created_at ascending (oldest first).
-
#create(name:, description: nil, color: nil, icon: nil, working_dir: nil) ⇒ Object
Create a new project.
-
#delete(id) ⇒ Object
Delete a project by id.
-
#find(id) ⇒ Object
Find a single project by id.
-
#initialize(projects_file: nil) ⇒ ProjectManager
constructor
A new instance of ProjectManager.
-
#update(id, name: :__unset, description: :__unset, color: :__unset, icon: :__unset, working_dir: :__unset) ⇒ Object
Update an existing project.
Constructor Details
#initialize(projects_file: nil) ⇒ ProjectManager
Returns a new instance of ProjectManager.
30 31 32 33 34 |
# File 'lib/clacky/server/project_manager.rb', line 30 def initialize(projects_file: nil) @projects_file = projects_file || PROJECTS_FILE @mutex = Mutex.new @cache = nil end |
Instance Method Details
#all ⇒ Object
Return all projects sorted by created_at ascending (oldest first).
37 38 39 |
# File 'lib/clacky/server/project_manager.rb', line 37 def all @mutex.synchronize { load_projects.dup } end |
#create(name:, description: nil, color: nil, icon: nil, working_dir: nil) ⇒ Object
Create a new project. Returns the created project hash. Required: name. Optional: description, color, icon, working_dir.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/clacky/server/project_manager.rb', line 48 def create(name:, description: nil, color: nil, icon: nil, working_dir: nil) raise ArgumentError, "name is required" if name.to_s.strip.empty? now = Time.now.iso8601 project = { id: SecureRandom.hex(4), name: name.to_s.strip, description: optional_str(description), color: optional_str(color), icon: optional_str(icon), working_dir: optional_str(working_dir), created_at: now, updated_at: now } @mutex.synchronize do projects = load_projects projects << project save_projects(projects) end project end |
#delete(id) ⇒ Object
Delete a project by id. Returns true if found and deleted, false otherwise. NOTE: caller is responsible for clearing project_id on orphaned sessions.
99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/clacky/server/project_manager.rb', line 99 def delete(id) @mutex.synchronize do projects = load_projects before = projects.size projects.reject! { |p| p[:id] == id.to_s } return false if projects.size == before save_projects(projects) true end end |
#find(id) ⇒ Object
Find a single project by id. Returns nil if not found.
42 43 44 |
# File 'lib/clacky/server/project_manager.rb', line 42 def find(id) @mutex.synchronize { load_projects.find { |p| p[:id] == id.to_s } } end |
#update(id, name: :__unset, description: :__unset, color: :__unset, icon: :__unset, working_dir: :__unset) ⇒ Object
Update an existing project. Only explicitly passed (non-sentinel) keys are changed. Pass description: nil or color: nil or icon: nil or working_dir: nil to clear those fields. Returns updated project hash, or nil if not found.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/clacky/server/project_manager.rb', line 75 def update(id, name: :__unset, description: :__unset, color: :__unset, icon: :__unset, working_dir: :__unset) @mutex.synchronize do projects = load_projects project = projects.find { |p| p[:id] == id.to_s } return nil unless project unless name == :__unset raise ArgumentError, "name cannot be empty" if name.to_s.strip.empty? project[:name] = name.to_s.strip end project[:description] = optional_str(description) unless description == :__unset project[:color] = optional_str(color) unless color == :__unset project[:icon] = optional_str(icon) unless icon == :__unset project[:working_dir] = optional_str(working_dir) unless working_dir == :__unset project[:updated_at] = Time.now.iso8601 save_projects(projects) project.dup end end |