ruby_todo 0.4.1 → 1.0.1
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/.env.template +2 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +10 -0
- data/README.md +56 -72
- data/ai_assistant_implementation.md +611 -0
- data/db/migrate/20240328_add_is_default_to_notebooks.rb +10 -0
- data/delete_notebooks.rb +20 -0
- data/implementation_steps.md +130 -0
- data/lib/ruby_todo/ai_assistant/common_query_handler.rb +378 -0
- data/lib/ruby_todo/ai_assistant/configuration_management.rb +27 -0
- data/lib/ruby_todo/ai_assistant/openai_integration.rb +333 -0
- data/lib/ruby_todo/ai_assistant/task_creation.rb +86 -0
- data/lib/ruby_todo/ai_assistant/task_management.rb +327 -0
- data/lib/ruby_todo/ai_assistant/task_search.rb +362 -0
- data/lib/ruby_todo/cli.rb +296 -146
- data/lib/ruby_todo/commands/ai_assistant.rb +449 -0
- data/lib/ruby_todo/database.rb +58 -84
- data/lib/ruby_todo/models/notebook.rb +44 -10
- data/lib/ruby_todo/version.rb +1 -1
- data/progress_ai_test.md +126 -0
- data/protectors_tasks.json +159 -0
- data/test_ai_assistant.rb +55 -0
- data/test_migration.rb +55 -0
- metadata +46 -1
data/lib/ruby_todo/cli.rb
CHANGED
@@ -7,14 +7,154 @@ require "tty-table"
|
|
7
7
|
require "time"
|
8
8
|
require "json"
|
9
9
|
require "fileutils"
|
10
|
+
require "csv"
|
10
11
|
require_relative "models/notebook"
|
11
12
|
require_relative "models/task"
|
12
13
|
require_relative "models/template"
|
13
14
|
require_relative "database"
|
15
|
+
require_relative "commands/ai_assistant"
|
14
16
|
|
15
17
|
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
|
+
|
16
154
|
class CLI < Thor
|
17
155
|
include Thor::Actions
|
156
|
+
include Export
|
157
|
+
include Import
|
18
158
|
|
19
159
|
map %w[--version -v] => :version
|
20
160
|
desc "version", "Show the Ruby Todo version"
|
@@ -203,19 +343,95 @@ module RubyTodo
|
|
203
343
|
say "Ruby Todo has been initialized successfully!".green
|
204
344
|
end
|
205
345
|
|
206
|
-
# Register subcommands
|
207
|
-
desc "notebook
|
208
|
-
|
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
|
209
352
|
|
210
|
-
desc "
|
211
|
-
|
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
|
212
372
|
|
213
|
-
desc "
|
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
|
+
# Task commands
|
429
|
+
desc "task:add [NOTEBOOK] [TITLE]", "Add a new task to a notebook"
|
214
430
|
method_option :description, type: :string, desc: "Task description"
|
215
431
|
method_option :due_date, type: :string, desc: "Due date (YYYY-MM-DD HH:MM)"
|
216
432
|
method_option :priority, type: :string, desc: "Priority (high, medium, low)"
|
217
433
|
method_option :tags, type: :string, desc: "Tags (comma-separated)"
|
218
|
-
def
|
434
|
+
def task_add(notebook_name, title)
|
219
435
|
notebook = Notebook.find_by(name: notebook_name)
|
220
436
|
unless notebook
|
221
437
|
say "Notebook '#{notebook_name}' not found".red
|
@@ -244,11 +460,11 @@ module RubyTodo
|
|
244
460
|
say "Priority: #{priority}" if priority
|
245
461
|
say "Tags: #{tags}" if tags
|
246
462
|
else
|
247
|
-
say "Error
|
463
|
+
say "Error adding task: #{task.errors.full_messages.join(", ")}".red
|
248
464
|
end
|
249
465
|
end
|
250
466
|
|
251
|
-
desc "task
|
467
|
+
desc "task:list [NOTEBOOK]", "List all tasks in a notebook"
|
252
468
|
method_option :status, type: :string, desc: "Filter by status (todo, in_progress, done, archived)"
|
253
469
|
method_option :priority, type: :string, desc: "Filter by priority (high, medium, low)"
|
254
470
|
method_option :due_soon, type: :boolean, desc: "Show only tasks due soon (within 24 hours)"
|
@@ -297,7 +513,7 @@ module RubyTodo
|
|
297
513
|
puts table.render(:ascii)
|
298
514
|
end
|
299
515
|
|
300
|
-
desc "task
|
516
|
+
desc "task:show [NOTEBOOK] [TASK_ID]", "Show detailed information about a task"
|
301
517
|
def task_show(notebook_name, task_id)
|
302
518
|
notebook = Notebook.find_by(name: notebook_name)
|
303
519
|
unless notebook
|
@@ -323,14 +539,14 @@ module RubyTodo
|
|
323
539
|
say "Updated: #{task.updated_at}"
|
324
540
|
end
|
325
541
|
|
326
|
-
desc "task
|
542
|
+
desc "task:edit [NOTEBOOK] [TASK_ID]", "Edit an existing task"
|
327
543
|
method_option :title, type: :string, desc: "New title"
|
328
544
|
method_option :description, type: :string, desc: "New description"
|
329
545
|
method_option :due_date, type: :string, desc: "New due date (YYYY-MM-DD HH:MM)"
|
330
546
|
method_option :priority, type: :string, desc: "New priority (high, medium, low)"
|
331
547
|
method_option :tags, type: :string, desc: "New tags (comma-separated)"
|
332
548
|
method_option :status, type: :string, desc: "New status (todo, in_progress, done, archived)"
|
333
|
-
def
|
549
|
+
def task_edit(notebook_name, task_id)
|
334
550
|
notebook = Notebook.find_by(name: notebook_name)
|
335
551
|
unless notebook
|
336
552
|
say "Notebook '#{notebook_name}' not found".red
|
@@ -366,8 +582,8 @@ module RubyTodo
|
|
366
582
|
end
|
367
583
|
end
|
368
584
|
|
369
|
-
desc "task
|
370
|
-
def
|
585
|
+
desc "task:move [NOTEBOOK] [TASK_ID] [STATUS]", "Move a task to a different status"
|
586
|
+
def task_move(notebook_name, task_id, status)
|
371
587
|
notebook = Notebook.find_by(name: notebook_name)
|
372
588
|
unless notebook
|
373
589
|
say "Notebook '#{notebook_name}' not found".red
|
@@ -387,8 +603,8 @@ module RubyTodo
|
|
387
603
|
end
|
388
604
|
end
|
389
605
|
|
390
|
-
desc "task
|
391
|
-
def
|
606
|
+
desc "task:delete [NOTEBOOK] [TASK_ID]", "Delete a task"
|
607
|
+
def task_delete(notebook_name, task_id)
|
392
608
|
notebook = Notebook.find_by(name: notebook_name)
|
393
609
|
unless notebook
|
394
610
|
say "Notebook '#{notebook_name}' not found".red
|
@@ -405,9 +621,9 @@ module RubyTodo
|
|
405
621
|
say "Deleted task #{task_id}".green
|
406
622
|
end
|
407
623
|
|
408
|
-
desc "task
|
624
|
+
desc "task:search [QUERY]", "Search for tasks across all notebooks"
|
409
625
|
method_option :notebook, type: :string, desc: "Limit search to a specific notebook"
|
410
|
-
def
|
626
|
+
def task_search(query)
|
411
627
|
notebooks = if options[:notebook]
|
412
628
|
[Notebook.find_by(name: options[:notebook])].compact
|
413
629
|
else
|
@@ -579,140 +795,70 @@ module RubyTodo
|
|
579
795
|
end
|
580
796
|
end
|
581
797
|
|
582
|
-
#
|
583
|
-
map "task:
|
584
|
-
map "task:
|
585
|
-
map "task:
|
586
|
-
map "task:edit" =>
|
587
|
-
map "task:
|
588
|
-
map "task:
|
589
|
-
map "task:search" =>
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
def task_to_hash(task)
|
609
|
-
{
|
610
|
-
"title" => task.title,
|
611
|
-
"description" => task.description,
|
612
|
-
"status" => task.status,
|
613
|
-
"priority" => task.priority,
|
614
|
-
"tags" => task.tags,
|
615
|
-
"due_date" => task.due_date&.iso8601,
|
616
|
-
"created_at" => task.created_at&.iso8601,
|
617
|
-
"updated_at" => task.updated_at&.iso8601
|
618
|
-
}
|
619
|
-
end
|
620
|
-
|
621
|
-
def export_to_json(data, filename)
|
622
|
-
File.write(filename, JSON.pretty_generate(data))
|
623
|
-
end
|
624
|
-
|
625
|
-
def export_to_csv(data, filename)
|
626
|
-
require "csv"
|
627
|
-
|
628
|
-
CSV.open(filename, "wb") do |csv|
|
629
|
-
if data["notebooks"]
|
630
|
-
# Multiple notebooks export
|
631
|
-
csv << ["Notebook", "Task ID", "Title", "Description", "Status", "Priority", "Tags", "Due Date",
|
632
|
-
"Created At", "Updated At"]
|
633
|
-
|
634
|
-
data["notebooks"].each do |notebook|
|
635
|
-
notebook_name = notebook["name"]
|
636
|
-
notebook["tasks"].each_with_index do |task, index|
|
637
|
-
csv << [
|
638
|
-
notebook_name,
|
639
|
-
index + 1,
|
640
|
-
task["title"],
|
641
|
-
task["description"],
|
642
|
-
task["status"],
|
643
|
-
task["priority"],
|
644
|
-
task["tags"],
|
645
|
-
task["due_date"],
|
646
|
-
task["created_at"],
|
647
|
-
task["updated_at"]
|
648
|
-
]
|
649
|
-
end
|
650
|
-
end
|
651
|
-
else
|
652
|
-
# Single notebook export
|
653
|
-
csv << ["Task ID", "Title", "Description", "Status", "Priority", "Tags", "Due Date", "Created At",
|
654
|
-
"Updated At"]
|
655
|
-
|
656
|
-
data["tasks"].each_with_index do |task, index|
|
657
|
-
csv << [
|
658
|
-
index + 1,
|
659
|
-
task["title"],
|
660
|
-
task["description"],
|
661
|
-
task["status"],
|
662
|
-
task["priority"],
|
663
|
-
task["tags"],
|
664
|
-
task["due_date"],
|
665
|
-
task["created_at"],
|
666
|
-
task["updated_at"]
|
667
|
-
]
|
668
|
-
end
|
669
|
-
end
|
798
|
+
# Map all commands to use colon format
|
799
|
+
map "task:add" => :task_add
|
800
|
+
map "task:list" => :task_list
|
801
|
+
map "task:show" => :task_show
|
802
|
+
map "task:edit" => :task_edit
|
803
|
+
map "task:move" => :task_move
|
804
|
+
map "task:delete" => :task_delete
|
805
|
+
map "task:search" => :task_search
|
806
|
+
map "notebook:create" => :notebook_create
|
807
|
+
map "notebook:list" => :notebook_list
|
808
|
+
map "notebook:set_default" => :notebook_set_default
|
809
|
+
map "template:create" => :template_create
|
810
|
+
map "template:list" => :template_list
|
811
|
+
map "template:show" => :template_show
|
812
|
+
map "template:delete" => :template_delete
|
813
|
+
map "template:use" => :template_use
|
814
|
+
|
815
|
+
# Remove old command mappings
|
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
|
670
824
|
end
|
671
825
|
end
|
672
826
|
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
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
|
691
858
|
end
|
692
|
-
|
693
|
-
count
|
694
859
|
end
|
695
860
|
|
696
|
-
|
697
|
-
results = { notebooks: 0, tasks: 0 }
|
698
|
-
|
699
|
-
data["notebooks"].each do |notebook_data|
|
700
|
-
notebook_name = notebook_data["name"]
|
701
|
-
notebook = Notebook.find_by(name: notebook_name)
|
702
|
-
|
703
|
-
unless notebook
|
704
|
-
notebook = Notebook.create(name: notebook_name)
|
705
|
-
results[:notebooks] += 1 if notebook.persisted?
|
706
|
-
end
|
707
|
-
|
708
|
-
if notebook.persisted?
|
709
|
-
tasks_count = import_tasks(notebook, notebook_data["tasks"])
|
710
|
-
results[:tasks] += tasks_count
|
711
|
-
end
|
712
|
-
end
|
713
|
-
|
714
|
-
results
|
715
|
-
end
|
861
|
+
private
|
716
862
|
|
717
863
|
def display_notebook_stats(notebook)
|
718
864
|
stats = notebook.statistics
|
@@ -856,5 +1002,9 @@ module RubyTodo
|
|
856
1002
|
|
857
1003
|
text.length > length ? "#{text[0...length]}..." : text
|
858
1004
|
end
|
1005
|
+
|
1006
|
+
def ai_command
|
1007
|
+
@ai_command ||= AIAssistantCommand.new
|
1008
|
+
end
|
859
1009
|
end
|
860
1010
|
end
|