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),
pinned_at: String | null (ISO8601; set when pinned, nil when not),
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, pinned: :__unset) ⇒ Object
Update an existing project.
Constructor Details
#initialize(projects_file: nil) ⇒ ProjectManager
Returns a new instance of ProjectManager.
31 32 33 34 35 |
# File 'lib/clacky/server/project_manager.rb', line 31 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).
38 39 40 |
# File 'lib/clacky/server/project_manager.rb', line 38 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.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/clacky/server/project_manager.rb', line 49 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.
102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/clacky/server/project_manager.rb', line 102 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.
43 44 45 |
# File 'lib/clacky/server/project_manager.rb', line 43 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, pinned: :__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. Pass pinned: true to pin (sets pinned_at to now), pinned: false to unpin. Returns updated project hash, or nil if not found.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/clacky/server/project_manager.rb', line 77 def update(id, name: :__unset, description: :__unset, color: :__unset, icon: :__unset, working_dir: :__unset, pinned: :__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[:pinned_at] = pinned == true ? Time.now.iso8601 : nil unless pinned == :__unset project[:updated_at] = Time.now.iso8601 save_projects(projects) project.dup end end |