ruby_todo 1.0.3 → 1.0.7
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +60 -75
- data/ai_assistant_implementation.md +1 -1
- data/lib/ruby_todo/ai_assistant/command_processor.rb +127 -0
- data/lib/ruby_todo/ai_assistant/openai_integration.rb +178 -183
- data/lib/ruby_todo/ai_assistant/param_extractor.rb +64 -0
- data/lib/ruby_todo/ai_assistant/prompt_builder.rb +95 -0
- data/lib/ruby_todo/ai_assistant/task_creator.rb +161 -0
- data/lib/ruby_todo/cli.rb +128 -469
- data/lib/ruby_todo/commands/ai_assistant.rb +870 -302
- data/lib/ruby_todo/commands/ai_commands.rb +20 -0
- data/lib/ruby_todo/commands/notebook_commands.rb +39 -0
- data/lib/ruby_todo/commands/template_commands.rb +139 -0
- data/lib/ruby_todo/concerns/import_export.rb +138 -0
- data/lib/ruby_todo/concerns/statistics.rb +166 -0
- data/lib/ruby_todo/concerns/task_filters.rb +39 -0
- data/lib/ruby_todo/formatters/display_formatter.rb +80 -0
- data/lib/ruby_todo/models/task.rb +0 -7
- data/lib/ruby_todo/version.rb +1 -1
- data/lib/ruby_todo.rb +9 -0
- metadata +13 -8
- data/.env.template +0 -2
- data/lib/ruby_todo/ai_assistant/common_query_handler.rb +0 -378
- data/lib/ruby_todo/ai_assistant/task_management.rb +0 -331
- data/lib/ruby_todo/ai_assistant/task_search.rb +0 -365
- data/test_ai_assistant.rb +0 -55
- data/test_migration.rb +0 -55
data/lib/ruby_todo/cli.rb
CHANGED
@@ -13,148 +13,22 @@ require_relative "models/task"
|
|
13
13
|
require_relative "models/template"
|
14
14
|
require_relative "database"
|
15
15
|
require_relative "commands/ai_assistant"
|
16
|
+
require_relative "formatters/display_formatter"
|
17
|
+
require_relative "concerns/statistics"
|
18
|
+
require_relative "concerns/task_filters"
|
19
|
+
require_relative "concerns/import_export"
|
20
|
+
require_relative "commands/notebook_commands"
|
21
|
+
require_relative "commands/template_commands"
|
16
22
|
|
17
23
|
module RubyTodo
|
18
|
-
module Export
|
19
|
-
private
|
20
|
-
|
21
|
-
def export_notebook(notebook)
|
22
|
-
{
|
23
|
-
"name" => notebook.name,
|
24
|
-
"created_at" => notebook.created_at,
|
25
|
-
"updated_at" => notebook.updated_at,
|
26
|
-
"tasks" => notebook.tasks.map { |task| task_to_hash(task) }
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
def export_all_notebooks(notebooks)
|
31
|
-
{
|
32
|
-
"notebooks" => notebooks.map { |notebook| export_notebook(notebook) }
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
def task_to_hash(task)
|
37
|
-
{
|
38
|
-
"title" => task.title,
|
39
|
-
"description" => task.description,
|
40
|
-
"status" => task.status,
|
41
|
-
"priority" => task.priority,
|
42
|
-
"tags" => task.tags,
|
43
|
-
"due_date" => task.due_date&.iso8601,
|
44
|
-
"created_at" => task.created_at&.iso8601,
|
45
|
-
"updated_at" => task.updated_at&.iso8601
|
46
|
-
}
|
47
|
-
end
|
48
|
-
|
49
|
-
def export_to_json(data, filename)
|
50
|
-
File.write(filename, JSON.pretty_generate(data))
|
51
|
-
end
|
52
|
-
|
53
|
-
def export_to_csv(data, filename)
|
54
|
-
CSV.open(filename, "wb") do |csv|
|
55
|
-
if data["notebooks"]
|
56
|
-
export_multiple_notebooks_to_csv(data, csv)
|
57
|
-
else
|
58
|
-
export_single_notebook_to_csv(data, csv)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def export_multiple_notebooks_to_csv(data, csv)
|
64
|
-
csv << ["Notebook", "Task ID", "Title", "Description", "Status", "Priority", "Tags", "Due Date",
|
65
|
-
"Created At", "Updated At"]
|
66
|
-
|
67
|
-
data["notebooks"].each do |notebook|
|
68
|
-
notebook_name = notebook["name"]
|
69
|
-
notebook["tasks"].each_with_index do |task, index|
|
70
|
-
csv << [
|
71
|
-
notebook_name,
|
72
|
-
index + 1,
|
73
|
-
task["title"],
|
74
|
-
task["description"],
|
75
|
-
task["status"],
|
76
|
-
task["priority"],
|
77
|
-
task["tags"],
|
78
|
-
task["due_date"],
|
79
|
-
task["created_at"],
|
80
|
-
task["updated_at"]
|
81
|
-
]
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def export_single_notebook_to_csv(data, csv)
|
87
|
-
csv << ["Task ID", "Title", "Description", "Status", "Priority", "Tags", "Due Date", "Created At",
|
88
|
-
"Updated At"]
|
89
|
-
|
90
|
-
data["tasks"].each_with_index do |task, index|
|
91
|
-
csv << [
|
92
|
-
index + 1,
|
93
|
-
task["title"],
|
94
|
-
task["description"],
|
95
|
-
task["status"],
|
96
|
-
task["priority"],
|
97
|
-
task["tags"],
|
98
|
-
task["due_date"],
|
99
|
-
task["created_at"],
|
100
|
-
task["updated_at"]
|
101
|
-
]
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
module Import
|
107
|
-
private
|
108
|
-
|
109
|
-
def import_tasks(notebook, tasks_data)
|
110
|
-
count = 0
|
111
|
-
|
112
|
-
tasks_data.each do |task_data|
|
113
|
-
# Convert ISO8601 string to Time object
|
114
|
-
due_date = Time.parse(task_data["due_date"]) if task_data["due_date"]
|
115
|
-
|
116
|
-
task = Task.create(
|
117
|
-
notebook: notebook,
|
118
|
-
title: task_data["title"],
|
119
|
-
description: task_data["description"],
|
120
|
-
status: task_data["status"] || "todo",
|
121
|
-
priority: task_data["priority"],
|
122
|
-
tags: task_data["tags"],
|
123
|
-
due_date: due_date
|
124
|
-
)
|
125
|
-
|
126
|
-
count += 1 if task.persisted?
|
127
|
-
end
|
128
|
-
|
129
|
-
count
|
130
|
-
end
|
131
|
-
|
132
|
-
def import_all_notebooks(data)
|
133
|
-
results = { notebooks: 0, tasks: 0 }
|
134
|
-
|
135
|
-
data["notebooks"].each do |notebook_data|
|
136
|
-
notebook_name = notebook_data["name"]
|
137
|
-
notebook = Notebook.find_by(name: notebook_name)
|
138
|
-
|
139
|
-
unless notebook
|
140
|
-
notebook = Notebook.create(name: notebook_name)
|
141
|
-
results[:notebooks] += 1 if notebook.persisted?
|
142
|
-
end
|
143
|
-
|
144
|
-
if notebook.persisted?
|
145
|
-
tasks_count = import_tasks(notebook, notebook_data["tasks"])
|
146
|
-
results[:tasks] += tasks_count
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
results
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
24
|
class CLI < Thor
|
155
25
|
include Thor::Actions
|
156
|
-
include
|
157
|
-
include
|
26
|
+
include DisplayFormatter
|
27
|
+
include Statistics
|
28
|
+
include TaskFilters
|
29
|
+
include ImportExport
|
30
|
+
include NotebookCommands
|
31
|
+
include TemplateCommands
|
158
32
|
|
159
33
|
map %w[--version -v] => :version
|
160
34
|
desc "version", "Show the Ruby Todo version"
|
@@ -166,168 +40,6 @@ module RubyTodo
|
|
166
40
|
true
|
167
41
|
end
|
168
42
|
|
169
|
-
# Notebook commands
|
170
|
-
class NotebookCommand < Thor
|
171
|
-
desc "create NAME", "Create a new notebook"
|
172
|
-
def create(name)
|
173
|
-
Notebook.create(name: name)
|
174
|
-
puts "Created notebook: #{name}".green
|
175
|
-
end
|
176
|
-
|
177
|
-
desc "list", "List all notebooks"
|
178
|
-
def list
|
179
|
-
notebooks = Notebook.all
|
180
|
-
if notebooks.empty?
|
181
|
-
puts "No notebooks found. Create one with 'ruby_todo notebook create NAME'".yellow
|
182
|
-
return
|
183
|
-
end
|
184
|
-
|
185
|
-
table = TTY::Table.new(
|
186
|
-
header: ["ID", "Name", "Tasks", "Created At"],
|
187
|
-
rows: notebooks.map { |n| [n.id, n.name, n.tasks.count, n.created_at] }
|
188
|
-
)
|
189
|
-
puts table.render(:ascii)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
# Template commands
|
194
|
-
class TemplateCommand < Thor
|
195
|
-
desc "create NAME", "Create a new task template"
|
196
|
-
option :notebook, aliases: "-n", desc: "Notebook to associate this template with (optional)"
|
197
|
-
option :title, aliases: "-t", desc: "Title pattern (required)", required: true
|
198
|
-
option :description, aliases: "-d", desc: "Description pattern"
|
199
|
-
option :priority, aliases: "-p", desc: "Priority (high, medium, low)"
|
200
|
-
option :tags, aliases: "-g", desc: "Tags pattern"
|
201
|
-
option :due, aliases: "-u", desc: "Due date offset (e.g., '2d', '1w', '3h')"
|
202
|
-
def create(name)
|
203
|
-
notebook = nil
|
204
|
-
if options[:notebook]
|
205
|
-
notebook = RubyTodo::Notebook.find_by(name: options[:notebook])
|
206
|
-
unless notebook
|
207
|
-
puts "Notebook '#{options[:notebook]}' not found."
|
208
|
-
exit 1
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
template = RubyTodo::Template.new(
|
213
|
-
name: name,
|
214
|
-
notebook: notebook,
|
215
|
-
title_pattern: options[:title],
|
216
|
-
description_pattern: options[:description],
|
217
|
-
tags_pattern: options[:tags],
|
218
|
-
priority: options[:priority],
|
219
|
-
due_date_offset: options[:due]
|
220
|
-
)
|
221
|
-
|
222
|
-
if template.save
|
223
|
-
puts "Template '#{name}' created successfully."
|
224
|
-
else
|
225
|
-
puts "Error creating template: #{template.errors.full_messages.join(", ")}"
|
226
|
-
exit 1
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
desc "list", "List all templates"
|
231
|
-
def list
|
232
|
-
templates = RubyTodo::Template.all
|
233
|
-
|
234
|
-
if templates.empty?
|
235
|
-
puts "No templates found. Create one with 'ruby_todo template create NAME'"
|
236
|
-
return
|
237
|
-
end
|
238
|
-
|
239
|
-
table = TTY::Table.new(
|
240
|
-
header: ["ID", "Name", "Title Pattern", "Notebook", "Priority", "Due Date Offset"],
|
241
|
-
rows: templates.map do |template|
|
242
|
-
[
|
243
|
-
template.id,
|
244
|
-
template.name,
|
245
|
-
template.title_pattern,
|
246
|
-
template.notebook&.name || "None",
|
247
|
-
template.priority || "None",
|
248
|
-
template.due_date_offset || "None"
|
249
|
-
]
|
250
|
-
end
|
251
|
-
)
|
252
|
-
|
253
|
-
puts table.render(:unicode, padding: [0, 1])
|
254
|
-
end
|
255
|
-
|
256
|
-
desc "show NAME", "Show details of a specific template"
|
257
|
-
def show(name)
|
258
|
-
template = RubyTodo::Template.find_by(name: name)
|
259
|
-
|
260
|
-
unless template
|
261
|
-
puts "Template '#{name}' not found."
|
262
|
-
exit 1
|
263
|
-
end
|
264
|
-
|
265
|
-
puts "Template Details:"
|
266
|
-
puts "ID: #{template.id}"
|
267
|
-
puts "Name: #{template.name}"
|
268
|
-
puts "Notebook: #{template.notebook&.name || "None"}"
|
269
|
-
puts "Title Pattern: #{template.title_pattern}"
|
270
|
-
puts "Description Pattern: #{template.description_pattern || "None"}"
|
271
|
-
puts "Tags Pattern: #{template.tags_pattern || "None"}"
|
272
|
-
puts "Priority: #{template.priority || "None"}"
|
273
|
-
puts "Due Date Offset: #{template.due_date_offset || "None"}"
|
274
|
-
puts "Created At: #{template.created_at}"
|
275
|
-
puts "Updated At: #{template.updated_at}"
|
276
|
-
end
|
277
|
-
|
278
|
-
desc "delete NAME", "Delete a template"
|
279
|
-
def delete(name)
|
280
|
-
template = RubyTodo::Template.find_by(name: name)
|
281
|
-
|
282
|
-
unless template
|
283
|
-
puts "Template '#{name}' not found."
|
284
|
-
exit 1
|
285
|
-
end
|
286
|
-
|
287
|
-
if template.destroy
|
288
|
-
puts "Template '#{name}' deleted successfully."
|
289
|
-
else
|
290
|
-
puts "Error deleting template: #{template.errors.full_messages.join(", ")}"
|
291
|
-
exit 1
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
desc "use NAME NOTEBOOK", "Create a task from a template in the specified notebook"
|
296
|
-
option :replacements, aliases: "-r", desc: "Replacements for placeholders (e.g., 'item:Books,date:2023-12-31')"
|
297
|
-
def use(name, notebook_name)
|
298
|
-
template = RubyTodo::Template.find_by(name: name)
|
299
|
-
|
300
|
-
unless template
|
301
|
-
puts "Template '#{name}' not found."
|
302
|
-
exit 1
|
303
|
-
end
|
304
|
-
|
305
|
-
notebook = RubyTodo::Notebook.find_by(name: notebook_name)
|
306
|
-
|
307
|
-
unless notebook
|
308
|
-
puts "Notebook '#{notebook_name}' not found."
|
309
|
-
exit 1
|
310
|
-
end
|
311
|
-
|
312
|
-
replacements = {}
|
313
|
-
if options[:replacements]
|
314
|
-
options[:replacements].split(",").each do |r|
|
315
|
-
key, value = r.split(":")
|
316
|
-
replacements[key] = value if key && value
|
317
|
-
end
|
318
|
-
end
|
319
|
-
|
320
|
-
task = template.create_task(notebook, replacements)
|
321
|
-
|
322
|
-
if task.persisted?
|
323
|
-
puts "Task created successfully with ID: #{task.id}"
|
324
|
-
else
|
325
|
-
puts "Error creating task: #{task.errors.full_messages.join(", ")}"
|
326
|
-
exit 1
|
327
|
-
end
|
328
|
-
end
|
329
|
-
end
|
330
|
-
|
331
43
|
class_option :notebook, type: :string, desc: "Specify the notebook to use"
|
332
44
|
|
333
45
|
def initialize(*args)
|
@@ -343,88 +55,6 @@ module RubyTodo
|
|
343
55
|
say "Ruby Todo has been initialized successfully!".green
|
344
56
|
end
|
345
57
|
|
346
|
-
# Register subcommands with colon format
|
347
|
-
desc "notebook:create NAME", "Create a new notebook"
|
348
|
-
def notebook_create(name)
|
349
|
-
Notebook.create(name: name)
|
350
|
-
puts "Created notebook: #{name}".green
|
351
|
-
end
|
352
|
-
|
353
|
-
desc "notebook:list", "List all notebooks"
|
354
|
-
def notebook_list
|
355
|
-
notebooks = Notebook.all
|
356
|
-
return say "No notebooks found".yellow if notebooks.empty?
|
357
|
-
|
358
|
-
table = TTY::Table.new(
|
359
|
-
header: ["ID", "Name", "Tasks", "Created At", "Default"],
|
360
|
-
rows: notebooks.map do |notebook|
|
361
|
-
[
|
362
|
-
notebook.id,
|
363
|
-
notebook.name,
|
364
|
-
notebook.tasks.count,
|
365
|
-
notebook.created_at,
|
366
|
-
notebook.is_default? ? "✓" : ""
|
367
|
-
]
|
368
|
-
end
|
369
|
-
)
|
370
|
-
puts table.render(:ascii)
|
371
|
-
end
|
372
|
-
|
373
|
-
desc "notebook:set_default NOTEBOOK", "Set a notebook as the default"
|
374
|
-
def notebook_set_default(name)
|
375
|
-
notebook = Notebook.find_by(name: name)
|
376
|
-
if notebook
|
377
|
-
notebook.make_default!
|
378
|
-
say "Successfully set '#{name}' as the default notebook".green
|
379
|
-
else
|
380
|
-
say "Notebook '#{name}' not found".red
|
381
|
-
end
|
382
|
-
end
|
383
|
-
|
384
|
-
desc "template:create NAME", "Create a new task template"
|
385
|
-
def template_create(name)
|
386
|
-
TemplateCommand.new.create(name)
|
387
|
-
end
|
388
|
-
|
389
|
-
desc "template:list", "List all templates"
|
390
|
-
def template_list
|
391
|
-
TemplateCommand.new.list
|
392
|
-
end
|
393
|
-
|
394
|
-
desc "template:show NAME", "Show details of a specific template"
|
395
|
-
def template_show(name)
|
396
|
-
TemplateCommand.new.show(name)
|
397
|
-
end
|
398
|
-
|
399
|
-
desc "template:delete NAME", "Delete a template"
|
400
|
-
def template_delete(name)
|
401
|
-
TemplateCommand.new.delete(name)
|
402
|
-
end
|
403
|
-
|
404
|
-
desc "template:use NAME NOTEBOOK", "Create a task from a template in the specified notebook"
|
405
|
-
def template_use(name, notebook)
|
406
|
-
TemplateCommand.new.use(name, notebook)
|
407
|
-
end
|
408
|
-
|
409
|
-
# Register AI Assistant subcommand with colon format
|
410
|
-
desc "ai:ask PROMPT", "Ask the AI assistant to perform tasks using natural language"
|
411
|
-
method_option :api_key, type: :string, desc: "OpenAI API key"
|
412
|
-
method_option :verbose, type: :boolean, default: false, desc: "Show detailed response"
|
413
|
-
def ai_ask(*prompt_args)
|
414
|
-
prompt = prompt_args.join(" ")
|
415
|
-
ai_command.ask(prompt)
|
416
|
-
end
|
417
|
-
|
418
|
-
desc "ai:configure", "Configure the AI assistant settings"
|
419
|
-
def ai_configure
|
420
|
-
ai_command.configure
|
421
|
-
end
|
422
|
-
|
423
|
-
# Map commands to use colon format
|
424
|
-
map "notebook:list" => :notebook_list
|
425
|
-
map "ai:ask" => :ai_ask
|
426
|
-
map "ai:configure" => :ai_configure
|
427
|
-
|
428
58
|
# Task commands
|
429
59
|
desc "task:add [NOTEBOOK] [TITLE]", "Add a new task to a notebook"
|
430
60
|
method_option :description, type: :string, desc: "Task description"
|
@@ -432,11 +62,8 @@ module RubyTodo
|
|
432
62
|
method_option :priority, type: :string, desc: "Priority (high, medium, low)"
|
433
63
|
method_option :tags, type: :string, desc: "Tags (comma-separated)"
|
434
64
|
def task_add(notebook_name, title)
|
435
|
-
notebook =
|
436
|
-
unless notebook
|
437
|
-
say "Notebook '#{notebook_name}' not found".red
|
438
|
-
return
|
439
|
-
end
|
65
|
+
notebook = find_notebook(notebook_name)
|
66
|
+
return unless notebook
|
440
67
|
|
441
68
|
description = options[:description]
|
442
69
|
due_date = parse_due_date(options[:due_date]) if options[:due_date]
|
@@ -471,46 +98,17 @@ module RubyTodo
|
|
471
98
|
method_option :overdue, type: :boolean, desc: "Show only overdue tasks"
|
472
99
|
method_option :tags, type: :string, desc: "Filter by tags (comma-separated)"
|
473
100
|
def task_list(notebook_name)
|
474
|
-
notebook =
|
475
|
-
unless notebook
|
476
|
-
say "Notebook '#{notebook_name}' not found".red
|
477
|
-
return
|
478
|
-
end
|
479
|
-
|
480
|
-
tasks = notebook.tasks
|
101
|
+
notebook = find_notebook(notebook_name)
|
102
|
+
return unless notebook
|
481
103
|
|
482
|
-
|
483
|
-
tasks = tasks.where(status: options[:status]) if options[:status]
|
484
|
-
tasks = tasks.where(priority: options[:priority]) if options[:priority]
|
485
|
-
|
486
|
-
if options[:tags]
|
487
|
-
tag_filters = options[:tags].split(",").map(&:strip)
|
488
|
-
tasks = tasks.select { |t| t.tags && tag_filters.any? { |tag| t.tags.include?(tag) } }
|
489
|
-
end
|
490
|
-
|
491
|
-
tasks = tasks.select(&:due_soon?) if options[:due_soon]
|
492
|
-
tasks = tasks.select(&:overdue?) if options[:overdue]
|
104
|
+
tasks = apply_filters(notebook.tasks)
|
493
105
|
|
494
106
|
if tasks.empty?
|
495
107
|
say "No tasks found in notebook '#{notebook_name}'".yellow
|
496
108
|
return
|
497
109
|
end
|
498
110
|
|
499
|
-
|
500
|
-
header: ["ID", "Title", "Status", "Priority", "Due Date", "Tags", "Description"],
|
501
|
-
rows: tasks.map do |t|
|
502
|
-
[
|
503
|
-
t.id,
|
504
|
-
t.title,
|
505
|
-
format_status(t.status),
|
506
|
-
format_priority(t.priority),
|
507
|
-
format_due_date(t.due_date),
|
508
|
-
truncate_text(t.tags, 15),
|
509
|
-
truncate_text(t.description, 30)
|
510
|
-
]
|
511
|
-
end
|
512
|
-
)
|
513
|
-
puts table.render(:ascii)
|
111
|
+
display_tasks(tasks)
|
514
112
|
end
|
515
113
|
|
516
114
|
desc "task:show [NOTEBOOK] [TASK_ID]", "Show detailed information about a task"
|
@@ -795,7 +393,43 @@ module RubyTodo
|
|
795
393
|
end
|
796
394
|
end
|
797
395
|
|
798
|
-
#
|
396
|
+
# AI Commands - explicitly defined
|
397
|
+
desc "ai:ask [PROMPT]", "Ask the AI assistant to perform tasks using natural language"
|
398
|
+
method_option :api_key, type: :string, desc: "OpenAI API key"
|
399
|
+
method_option :verbose, type: :boolean, default: false, desc: "Show detailed response"
|
400
|
+
def ai_ask(*prompt_args)
|
401
|
+
require_relative "commands/ai_assistant"
|
402
|
+
prompt = prompt_args.join(" ")
|
403
|
+
ai_command = AIAssistantCommand.new
|
404
|
+
ai_command.ask(prompt, verbose: options[:verbose], api_key: options[:api_key])
|
405
|
+
end
|
406
|
+
|
407
|
+
desc "ai:configure", "Configure the AI assistant settings"
|
408
|
+
def ai_configure
|
409
|
+
require_relative "commands/ai_assistant"
|
410
|
+
ai_command = AIAssistantCommand.new
|
411
|
+
ai_command.configure
|
412
|
+
end
|
413
|
+
|
414
|
+
# Define explicit methods for notebook commands to ensure Thor can see them
|
415
|
+
desc "notebook:create NAME", "Create a new notebook"
|
416
|
+
|
417
|
+
desc "notebook:list", "List all notebooks"
|
418
|
+
|
419
|
+
desc "notebook:set_default NOTEBOOK", "Set a notebook as the default"
|
420
|
+
|
421
|
+
# Register command descriptions for templates with explicit method definitions
|
422
|
+
desc "template:create NAME", "Create a new task template"
|
423
|
+
|
424
|
+
desc "template:list", "List all templates"
|
425
|
+
|
426
|
+
desc "template:show NAME", "Show details of a specific template"
|
427
|
+
|
428
|
+
desc "template:delete NAME", "Delete a template"
|
429
|
+
|
430
|
+
desc "template:use NAME NOTEBOOK", "Create a task from a template in the specified notebook"
|
431
|
+
|
432
|
+
# Map commands to use colon format
|
799
433
|
map "task:add" => :task_add
|
800
434
|
map "task:list" => :task_list
|
801
435
|
map "task:show" => :task_show
|
@@ -811,52 +445,8 @@ module RubyTodo
|
|
811
445
|
map "template:show" => :template_show
|
812
446
|
map "template:delete" => :template_delete
|
813
447
|
map "template:use" => :template_use
|
814
|
-
|
815
|
-
|
816
|
-
no_commands do
|
817
|
-
def self.remove_old_commands
|
818
|
-
remove_command :create
|
819
|
-
remove_command :list
|
820
|
-
remove_command :show
|
821
|
-
remove_command :delete
|
822
|
-
remove_command :use
|
823
|
-
remove_command :ai
|
824
|
-
end
|
825
|
-
end
|
826
|
-
|
827
|
-
remove_old_commands
|
828
|
-
|
829
|
-
# Override the help command to show only colon-formatted commands
|
830
|
-
def help(command = nil)
|
831
|
-
if command.nil?
|
832
|
-
puts "Commands:"
|
833
|
-
puts " ruby_todo ai:ask PROMPT # Ask the AI assistant to perform tasks"
|
834
|
-
puts " ruby_todo ai:configure # Configure the AI assistant settings"
|
835
|
-
puts " ruby_todo notebook:create NAME # Create a new notebook"
|
836
|
-
puts " ruby_todo notebook:list # List all notebooks"
|
837
|
-
puts " ruby_todo notebook:set_default NAME # Set a notebook as the default"
|
838
|
-
puts " ruby_todo task:add [NOTEBOOK] [TITLE] # Add a new task to a notebook"
|
839
|
-
puts " ruby_todo task:list [NOTEBOOK] # List all tasks in a notebook"
|
840
|
-
puts " ruby_todo task:show [NOTEBOOK] [TASK_ID] # Show task details"
|
841
|
-
puts " ruby_todo task:edit [NOTEBOOK] [TASK_ID] # Edit a task"
|
842
|
-
puts " ruby_todo task:move [NOTEBOOK] [TASK_ID] STATUS # Move a task to a different status"
|
843
|
-
puts " ruby_todo task:delete [NOTEBOOK] [TASK_ID] # Delete a task"
|
844
|
-
puts " ruby_todo task:search [QUERY] # Search for tasks"
|
845
|
-
puts " ruby_todo template:create NAME # Create a task template"
|
846
|
-
puts " ruby_todo template:list # List all templates"
|
847
|
-
puts " ruby_todo template:show NAME # Show template details"
|
848
|
-
puts " ruby_todo template:delete NAME # Delete a template"
|
849
|
-
puts " ruby_todo template:use NAME NOTEBOOK # Use a template"
|
850
|
-
puts " ruby_todo export [NOTEBOOK] [FILENAME] # Export tasks"
|
851
|
-
puts " ruby_todo import [FILENAME] # Import tasks"
|
852
|
-
puts " ruby_todo init # Initialize todo list"
|
853
|
-
puts " ruby_todo version # Show version"
|
854
|
-
puts "\nOptions:"
|
855
|
-
puts " [--notebook=NOTEBOOK] # Specify the notebook to use"
|
856
|
-
else
|
857
|
-
super
|
858
|
-
end
|
859
|
-
end
|
448
|
+
map "ai:ask" => :ai_ask
|
449
|
+
map "ai:configure" => :ai_configure
|
860
450
|
|
861
451
|
private
|
862
452
|
|
@@ -1003,8 +593,77 @@ module RubyTodo
|
|
1003
593
|
text.length > length ? "#{text[0...length]}..." : text
|
1004
594
|
end
|
1005
595
|
|
1006
|
-
def
|
1007
|
-
|
596
|
+
def find_notebook(notebook_name)
|
597
|
+
notebook = Notebook.find_by(name: notebook_name)
|
598
|
+
unless notebook
|
599
|
+
say "Notebook '#{notebook_name}' not found".red
|
600
|
+
return nil
|
601
|
+
end
|
602
|
+
notebook
|
603
|
+
end
|
604
|
+
|
605
|
+
def apply_filters(tasks)
|
606
|
+
tasks = apply_status_filter(tasks)
|
607
|
+
tasks = apply_priority_filter(tasks)
|
608
|
+
tasks = apply_tag_filter(tasks)
|
609
|
+
apply_due_date_filters(tasks)
|
610
|
+
end
|
611
|
+
|
612
|
+
def apply_status_filter(tasks)
|
613
|
+
return tasks unless options[:status]
|
614
|
+
|
615
|
+
tasks.where(status: options[:status])
|
616
|
+
end
|
617
|
+
|
618
|
+
def apply_priority_filter(tasks)
|
619
|
+
return tasks unless options[:priority]
|
620
|
+
|
621
|
+
tasks.where(priority: options[:priority])
|
622
|
+
end
|
623
|
+
|
624
|
+
def apply_tag_filter(tasks)
|
625
|
+
return tasks unless options[:tags]
|
626
|
+
|
627
|
+
tag_filters = options[:tags].split(",").map(&:strip)
|
628
|
+
tasks.select { |t| t.tags && tag_filters.any? { |tag| t.tags.include?(tag) } }
|
629
|
+
end
|
630
|
+
|
631
|
+
def apply_due_date_filters(tasks)
|
632
|
+
tasks = tasks.select(&:due_soon?) if options[:due_soon]
|
633
|
+
tasks = tasks.select(&:overdue?) if options[:overdue]
|
634
|
+
tasks
|
635
|
+
end
|
636
|
+
|
637
|
+
def display_tasks(tasks)
|
638
|
+
if ENV["RUBY_TODO_TEST"]
|
639
|
+
display_tasks_simple_format(tasks)
|
640
|
+
else
|
641
|
+
display_tasks_table_format(tasks)
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
def display_tasks_simple_format(tasks)
|
646
|
+
tasks.each do |t|
|
647
|
+
puts "#{t.id}: #{t.title} (#{t.status})"
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
def display_tasks_table_format(tasks)
|
652
|
+
table = TTY::Table.new(
|
653
|
+
header: ["ID", "Title", "Status", "Priority", "Due Date", "Tags", "Description"],
|
654
|
+
rows: tasks.map do |t|
|
655
|
+
[
|
656
|
+
t.id,
|
657
|
+
t.title,
|
658
|
+
format_status(t.status),
|
659
|
+
format_priority(t.priority),
|
660
|
+
format_due_date(t.due_date),
|
661
|
+
truncate_text(t.tags, 15),
|
662
|
+
truncate_text(t.description, 30)
|
663
|
+
]
|
664
|
+
end
|
665
|
+
)
|
666
|
+
puts table.render(:ascii)
|
1008
667
|
end
|
1009
668
|
end
|
1010
669
|
end
|