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
@@ -1,102 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "prompt_builder"
|
4
|
+
|
5
|
+
# Modules for OpenAI integration and prompt building
|
3
6
|
module RubyTodo
|
7
|
+
# Documentation for available commands in the CLI
|
4
8
|
module OpenAIDocumentation
|
5
9
|
CLI_DOCUMENTATION = <<~DOCS
|
6
|
-
Available
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
2. Search tasks:
|
16
|
-
ruby_todo task:search SEARCH_TERM
|
17
|
-
- Searches for tasks containing the search term
|
18
|
-
|
19
|
-
3. Show task details:
|
20
|
-
ruby_todo task:show NOTEBOOK TASK_ID
|
21
|
-
- Shows detailed information about a specific task
|
22
|
-
|
23
|
-
4. Add task:
|
24
|
-
ruby_todo task:add [NOTEBOOK] [TITLE]
|
25
|
-
- Add a new task to a notebook
|
26
|
-
- Interactive prompts for title, description, priority, due date, and tags
|
27
|
-
|
28
|
-
5. Edit task:
|
29
|
-
ruby_todo task:edit [NOTEBOOK] [TASK_ID]
|
30
|
-
- Edit an existing task's details
|
31
|
-
|
32
|
-
6. Delete task:
|
33
|
-
ruby_todo task:delete [NOTEBOOK] [TASK_ID]
|
34
|
-
- Delete a task from a notebook
|
35
|
-
|
36
|
-
7. Move task:
|
37
|
-
ruby_todo task:move [NOTEBOOK] [TASK_ID] [STATUS]
|
38
|
-
- Move a task to a different status
|
39
|
-
- STATUS can be: todo, in_progress, done, archived
|
40
|
-
|
41
|
-
Notebook Management:
|
42
|
-
8. List notebooks:
|
43
|
-
ruby_todo notebook:list
|
44
|
-
- List all notebooks
|
45
|
-
|
46
|
-
9. Create notebook:
|
47
|
-
ruby_todo notebook:create NAME
|
48
|
-
- Create a new notebook
|
49
|
-
|
50
|
-
10. Set default notebook:
|
51
|
-
ruby_todo notebook:set_default NAME
|
52
|
-
- Set a notebook as the default
|
53
|
-
|
54
|
-
Template Management:
|
55
|
-
11. List templates:
|
56
|
-
ruby_todo template:list
|
57
|
-
- List all templates
|
58
|
-
|
59
|
-
12. Show template:
|
60
|
-
ruby_todo template:show NAME
|
61
|
-
- Show details of a specific template
|
62
|
-
|
63
|
-
13. Create template:
|
64
|
-
ruby_todo template:create NAME --title TITLE
|
65
|
-
- Create a new task template
|
66
|
-
|
67
|
-
14. Delete template:
|
68
|
-
ruby_todo template:delete NAME
|
69
|
-
- Delete a template
|
70
|
-
|
71
|
-
15. Use template:
|
72
|
-
ruby_todo template:use NAME NOTEBOOK
|
73
|
-
- Create a task from a template in the specified notebook
|
74
|
-
|
75
|
-
Other Commands:
|
76
|
-
16. Export tasks:
|
77
|
-
ruby_todo export [NOTEBOOK] [FILENAME]
|
78
|
-
- Export tasks from a notebook to a JSON file
|
79
|
-
|
80
|
-
17. Import tasks:
|
81
|
-
ruby_todo import [FILENAME]
|
82
|
-
- Import tasks from a JSON or CSV file
|
83
|
-
|
84
|
-
18. Show statistics:
|
85
|
-
ruby_todo stats [NOTEBOOK]
|
86
|
-
- Show statistics for a notebook or all notebooks
|
87
|
-
|
88
|
-
19. Initialize:
|
89
|
-
ruby_todo init
|
90
|
-
- Initialize a new todo list
|
91
|
-
|
92
|
-
Note: All commands use colons (e.g., 'task:list', 'notebook:list').
|
93
|
-
Available statuses: todo, in_progress, done, archived
|
10
|
+
Available commands:
|
11
|
+
- task:add [notebook] [title] --description [desc] --priority [high|medium|low] --tags [tags]
|
12
|
+
- task:list [notebook] [--status status] [--priority priority]
|
13
|
+
- task:move [notebook] [task_id] [status] - Move to todo/in_progress/done/archived
|
14
|
+
- task:delete [notebook] [task_id]
|
15
|
+
- notebook:create [name]
|
16
|
+
- notebook:list
|
17
|
+
- stats [notebook]
|
94
18
|
DOCS
|
95
19
|
end
|
96
20
|
|
97
|
-
|
21
|
+
# Core prompt building functionality
|
22
|
+
module OpenAIPromptBuilderCore
|
98
23
|
include OpenAIDocumentation
|
99
24
|
|
25
|
+
def format_task_for_prompt(task)
|
26
|
+
"#{task[:title]} (Status: #{task[:status]})"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Module for building prompts for OpenAI
|
31
|
+
module OpenAIPromptBuilder
|
32
|
+
include OpenAIPromptBuilderCore
|
33
|
+
include OpenAIAdvancedPromptBuilder
|
34
|
+
|
100
35
|
private
|
101
36
|
|
102
37
|
def build_messages(prompt, context)
|
@@ -105,8 +40,8 @@ module RubyTodo
|
|
105
40
|
{ role: "user", content: prompt }
|
106
41
|
]
|
107
42
|
|
108
|
-
say "\nSystem prompt:\n#{messages.first[:content]}\n" if options[:verbose]
|
109
|
-
say "\nUser prompt:\n#{prompt}\n" if options[:verbose]
|
43
|
+
say "\nSystem prompt:\n#{messages.first[:content]}\n" if @options && @options[:verbose]
|
44
|
+
say "\nUser prompt:\n#{prompt}\n" if @options && @options[:verbose]
|
110
45
|
|
111
46
|
messages
|
112
47
|
end
|
@@ -182,7 +117,8 @@ module RubyTodo
|
|
182
117
|
default_notebook = RubyTodo::Notebook.default_notebook&.name || "YourNotebook"
|
183
118
|
|
184
119
|
prompt = "\n\nEven if no tasks match a search or if your request isn't about task movement, "
|
185
|
-
prompt += "I still need you to return a JSON response with commands and explanation.
|
120
|
+
prompt += "I still need you to return a JSON response with commands and explanation."
|
121
|
+
prompt += "The following examples use the current default notebook '#{default_notebook}'."
|
186
122
|
prompt += "\n\nExample JSON Response:"
|
187
123
|
prompt += "\n```json"
|
188
124
|
prompt += "\n{"
|
@@ -203,135 +139,194 @@ module RubyTodo
|
|
203
139
|
"Task #{task[:task_id]} in notebook '#{task[:notebook]}': " \
|
204
140
|
"#{task[:title]} (Status: #{task[:status]})"
|
205
141
|
end
|
142
|
+
|
143
|
+
def enrich_context_with_tasks(context)
|
144
|
+
# Create a string representation of the context
|
145
|
+
build_enriched_context(context)
|
146
|
+
end
|
206
147
|
end
|
207
148
|
|
208
|
-
|
209
|
-
|
210
|
-
|
149
|
+
# Module for handling context and prompt preparation
|
150
|
+
module OpenAIContextBuilding
|
151
|
+
private
|
211
152
|
|
212
|
-
def
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
say "Sending request to OpenAI..." if options[:verbose]
|
153
|
+
def build_prompt_context(context)
|
154
|
+
# Format the context for the AI
|
155
|
+
notebooks = context[:notebooks]
|
156
|
+
message_context = "Current context:\n"
|
217
157
|
|
218
|
-
|
158
|
+
if notebooks.empty?
|
159
|
+
message_context += "No notebooks found.\n"
|
160
|
+
else
|
161
|
+
notebooks.each do |notebook|
|
162
|
+
message_context += "Notebook: #{notebook[:name]}\n"
|
163
|
+
message_context += format_tasks_for_context(notebook[:tasks])
|
164
|
+
end
|
165
|
+
end
|
219
166
|
|
220
|
-
|
167
|
+
message_context
|
168
|
+
end
|
221
169
|
|
222
|
-
|
170
|
+
def format_tasks_for_context(tasks)
|
171
|
+
context = ""
|
172
|
+
if tasks.empty?
|
173
|
+
context += " No tasks in this notebook.\n"
|
174
|
+
else
|
175
|
+
tasks.each do |task|
|
176
|
+
context += " Task ID: #{task[:id]}, Title: #{task[:title]}, Status: #{task[:status]}"
|
177
|
+
context += ", Tags: #{task[:tags]}" if task[:tags]
|
178
|
+
context += "\n"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
context
|
182
|
+
end
|
223
183
|
|
224
|
-
|
184
|
+
def build_available_commands
|
185
|
+
CLI_DOCUMENTATION
|
186
|
+
end
|
225
187
|
|
226
|
-
|
188
|
+
def prepare_system_message(message_context, available_commands)
|
189
|
+
system_message = "You are a task management assistant that generates ruby_todo CLI commands. "
|
190
|
+
system_message += "Your role is to analyze user requests and generate the appropriate ruby_todo command(s). "
|
191
|
+
system_message += "You should respond to ALL types of requests, not just task movement requests."
|
192
|
+
system_message += "\n\nHere are the available commands and their usage:\n"
|
193
|
+
system_message += available_commands
|
194
|
+
system_message += "\nBased on the user's request, generate command(s) that follow these formats exactly."
|
195
|
+
system_message += "\n\nStatus Mapping:"
|
196
|
+
system_message += "\n- 'pending' maps to 'todo'"
|
197
|
+
system_message += "\n- 'in progress' maps to 'in_progress'"
|
198
|
+
system_message += "\nPlease use the mapped status values in your commands."
|
199
|
+
system_message += "\n\n#{message_context}"
|
200
|
+
system_message
|
201
|
+
end
|
227
202
|
|
228
|
-
|
203
|
+
def build_user_message(prompt)
|
204
|
+
prompt
|
229
205
|
end
|
206
|
+
end
|
230
207
|
|
231
|
-
|
208
|
+
# Module for handling OpenAI API responses
|
209
|
+
module OpenAIResponseHandling
|
210
|
+
def handle_openai_response(response)
|
211
|
+
# Extract the response content
|
212
|
+
response_content = response.dig("choices", 0, "message", "content")
|
213
|
+
|
214
|
+
# Parse the JSON response
|
215
|
+
parse_openai_response_content(response_content)
|
216
|
+
end
|
232
217
|
|
233
|
-
def
|
218
|
+
def handle_openai_error(error)
|
219
|
+
# Create a default error response
|
234
220
|
{
|
235
|
-
|
236
|
-
|
237
|
-
temperature: 0.7,
|
238
|
-
max_tokens: 500
|
221
|
+
"explanation" => "Error: #{error.message}",
|
222
|
+
"commands" => []
|
239
223
|
}
|
240
224
|
end
|
241
225
|
|
242
|
-
def
|
243
|
-
|
244
|
-
|
245
|
-
|
226
|
+
def parse_openai_response_content(content)
|
227
|
+
# Extract JSON from the content (it might be wrapped in ```json blocks)
|
228
|
+
json_match = content.match(/```json\n(.+?)\n```/m) || content.match(/\{.+\}/m)
|
229
|
+
|
230
|
+
if json_match
|
231
|
+
# Parse the JSON
|
232
|
+
begin
|
233
|
+
json_content = json_match[0].gsub(/```json\n|```/, "")
|
234
|
+
JSON.parse(json_content)
|
235
|
+
rescue JSON::ParserError
|
236
|
+
# Try a more direct approach
|
237
|
+
extract_command_explanation(content)
|
238
|
+
end
|
246
239
|
else
|
247
|
-
|
240
|
+
# Fallback to direct extraction
|
241
|
+
extract_command_explanation(content)
|
248
242
|
end
|
249
|
-
|
243
|
+
rescue JSON::ParserError
|
244
|
+
nil
|
250
245
|
end
|
251
246
|
|
252
|
-
def
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
parsed_response["commands"].each_with_index do |cmd, i|
|
257
|
-
say "Command #{i + 1}: '#{cmd}'"
|
258
|
-
end
|
259
|
-
say "=== END RESPONSE DETAILS ===\n"
|
260
|
-
end
|
247
|
+
def extract_command_explanation(content)
|
248
|
+
# Extract commands
|
249
|
+
commands = []
|
250
|
+
command_matches = content.scan(/`([^`]+)`/)
|
261
251
|
|
262
|
-
|
263
|
-
|
252
|
+
command_matches.each do |match|
|
253
|
+
command = match[0].strip
|
254
|
+
commands << command unless command.empty?
|
255
|
+
end
|
264
256
|
|
265
|
-
|
266
|
-
|
257
|
+
# Extract explanation
|
258
|
+
explanation = content.gsub(/```json\n|```|`([^`]+)`/, "").strip
|
267
259
|
|
268
|
-
|
260
|
+
{
|
261
|
+
"commands" => commands,
|
262
|
+
"explanation" => explanation
|
263
|
+
}
|
269
264
|
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Module for OpenAI API interaction
|
268
|
+
module OpenAIApiInteraction
|
269
|
+
include OpenAIResponseHandling
|
270
|
+
include OpenAIContextBuilding
|
270
271
|
|
271
|
-
def
|
272
|
-
#
|
273
|
-
|
272
|
+
def query_openai(prompt, context, api_key)
|
273
|
+
# Build the context for the AI
|
274
|
+
message_context = build_prompt_context(context)
|
274
275
|
|
275
|
-
|
276
|
+
# Extract available commands from CLI documentation
|
277
|
+
available_commands = build_available_commands
|
276
278
|
|
277
|
-
#
|
278
|
-
|
279
|
+
# Prepare system message with context and available commands
|
280
|
+
system_message = prepare_system_message(message_context, available_commands)
|
279
281
|
|
280
|
-
#
|
281
|
-
|
282
|
+
# Build user message
|
283
|
+
user_message = build_user_message(prompt)
|
282
284
|
|
283
|
-
|
284
|
-
|
285
|
-
handle_json_parse_error(content, e)
|
285
|
+
# Configure and make OpenAI API call
|
286
|
+
make_openai_api_call(system_message, user_message, api_key)
|
286
287
|
end
|
287
288
|
|
288
|
-
|
289
|
-
# Remove markdown formatting if present
|
290
|
-
json_content = content.gsub(/```(?:json)?\n(.*?)\n```/m, '\1')
|
291
|
-
# Strip any leading/trailing whitespace, braces are required
|
292
|
-
json_content = json_content.strip
|
293
|
-
# Add braces if they're missing
|
294
|
-
json_content = "{#{json_content}}" unless json_content.start_with?("{") && json_content.end_with?("}")
|
289
|
+
private
|
295
290
|
|
296
|
-
|
297
|
-
|
291
|
+
def make_openai_api_call(system_message, user_message, api_key)
|
292
|
+
# Prepare the messages for the API call
|
293
|
+
messages = [
|
294
|
+
{ role: "system", content: system_message },
|
295
|
+
{ role: "user", content: user_message }
|
296
|
+
]
|
298
297
|
|
299
|
-
|
300
|
-
|
301
|
-
if !result.key?("commands") || !result["commands"].is_a?(Array)
|
302
|
-
say "Warning: AI response missing 'commands' array. Adding empty array.".yellow if options[:verbose]
|
303
|
-
result["commands"] = []
|
304
|
-
end
|
298
|
+
# Initialize the OpenAI client
|
299
|
+
client = OpenAI::Client.new(access_token: api_key)
|
305
300
|
|
306
|
-
|
307
|
-
|
308
|
-
|
301
|
+
# Make the API call
|
302
|
+
begin
|
303
|
+
response = client.chat(parameters: {
|
304
|
+
model: "gpt-4o-mini",
|
305
|
+
messages: messages,
|
306
|
+
temperature: 0.2,
|
307
|
+
max_tokens: 1000
|
308
|
+
})
|
309
|
+
|
310
|
+
# Handle the response
|
311
|
+
handle_openai_response(response)
|
312
|
+
rescue OpenAI::Error => e
|
313
|
+
handle_openai_error(e)
|
309
314
|
end
|
310
315
|
end
|
316
|
+
end
|
311
317
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
if commands.any?
|
319
|
-
say "Extracted #{commands.size} commands from text response".yellow if options[:verbose]
|
320
|
-
return {
|
321
|
-
"commands" => commands,
|
322
|
-
"explanation" => "Commands extracted from non-JSON response."
|
323
|
-
}
|
324
|
-
end
|
318
|
+
# Base OpenAI integration module
|
319
|
+
module OpenAIIntegration
|
320
|
+
include OpenAIDocumentation
|
321
|
+
include OpenAIPromptBuilder
|
322
|
+
include OpenAIApiInteraction
|
325
323
|
|
326
|
-
|
327
|
-
|
324
|
+
# System prompt for OpenAI requests
|
325
|
+
SYSTEM_PROMPT = <<~PROMPT
|
326
|
+
You are an AI assistant for the Ruby Todo CLI application. Your role is to help users manage their tasks and notebooks using natural language.
|
328
327
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
commands << cmd.strip
|
333
|
-
end
|
334
|
-
commands
|
335
|
-
end
|
328
|
+
Your responses should be formatted as JSON with commands and explanations.
|
329
|
+
Always return valid JSON that can be parsed.
|
330
|
+
PROMPT
|
336
331
|
end
|
337
332
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyTodo
|
4
|
+
module AIAssistant
|
5
|
+
# Helper module for parameter extraction
|
6
|
+
module ParamExtractor
|
7
|
+
# Helper method to extract task parameters
|
8
|
+
def extract_task_params(params, cli_args)
|
9
|
+
# Description
|
10
|
+
extract_description_param(params, cli_args)
|
11
|
+
|
12
|
+
# Priority
|
13
|
+
if params =~ /--priority\s+(\w+)/
|
14
|
+
cli_args.push("--priority", Regexp.last_match(1))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Tags
|
18
|
+
extract_tags_param(params, cli_args)
|
19
|
+
|
20
|
+
# Due date
|
21
|
+
extract_due_date_param(params, cli_args)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Helper to extract description parameter
|
25
|
+
def extract_description_param(params, cli_args)
|
26
|
+
if params =~ /--description\s+"([^"]+)"/
|
27
|
+
cli_args.push("--description", Regexp.last_match(1))
|
28
|
+
# Using a different approach to avoid duplicate branch
|
29
|
+
elsif params.match?(/--description\s+'([^']+)'/)
|
30
|
+
desc = params.match(/--description\s+'([^']+)'/)[1]
|
31
|
+
cli_args.push("--description", desc)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Helper to extract tags parameter
|
36
|
+
def extract_tags_param(params, cli_args)
|
37
|
+
case params
|
38
|
+
when /--tags\s+"([^"]+)"/
|
39
|
+
cli_args.push("--tags", Regexp.last_match(1))
|
40
|
+
# Using a different approach to avoid duplicate branch
|
41
|
+
when /--tags\s+'([^']+)'/
|
42
|
+
tags = params.match(/--tags\s+'([^']+)'/)[1]
|
43
|
+
cli_args.push("--tags", tags)
|
44
|
+
when /--tags\s+([^-\s][^-]*)/
|
45
|
+
cli_args.push("--tags", Regexp.last_match(1).strip)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Helper to extract due date parameter
|
50
|
+
def extract_due_date_param(params, cli_args)
|
51
|
+
case params
|
52
|
+
when /--due_date\s+"([^"]+)"/
|
53
|
+
cli_args.push("--due_date", Regexp.last_match(1))
|
54
|
+
# Using a different approach to avoid duplicate branch
|
55
|
+
when /--due_date\s+'([^']+)'/
|
56
|
+
due_date = params.match(/--due_date\s+'([^']+)'/)[1]
|
57
|
+
cli_args.push("--due_date", due_date)
|
58
|
+
when /--due_date\s+([^-\s][^-]*)/
|
59
|
+
cli_args.push("--due_date", Regexp.last_match(1).strip)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyTodo
|
4
|
+
# Module for building advanced prompts for OpenAI
|
5
|
+
module OpenAIAdvancedPromptBuilder
|
6
|
+
# Extract task-related information from the prompt
|
7
|
+
def extract_task_info_from_prompt(prompt)
|
8
|
+
info = {
|
9
|
+
notebook: nil,
|
10
|
+
title: nil,
|
11
|
+
priority: nil,
|
12
|
+
tags: nil
|
13
|
+
}
|
14
|
+
|
15
|
+
# Extract notebook name
|
16
|
+
if prompt =~ /(?:in|to)\s+(?:(?:the|my)\s+)?(?:notebook\s+)?["']?([^"'\s]+(?:\s+[^"'\s]+)*)["']?/i
|
17
|
+
info[:notebook] = Regexp.last_match(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Extract task title
|
21
|
+
task_title_regex = /
|
22
|
+
(?:titled|called|named)\s+["']([^"']+)["']|
|
23
|
+
(?:add|create)\s+(?:a\s+)?(?:task|to-?do)\s+(?:to|for|about)\s+["']?([^"']+?)["']?(?:\s+to|\s+in|$)
|
24
|
+
/xi
|
25
|
+
|
26
|
+
if prompt =~ task_title_regex
|
27
|
+
info[:title] = Regexp.last_match(1) || Regexp.last_match(2)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Extract priority
|
31
|
+
if prompt =~ /priority\s+(high|medium|low)/i
|
32
|
+
info[:priority] = Regexp.last_match(1)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Extract tags
|
36
|
+
if prompt =~ /tags?\s+["']?([^"']+)["']?/i
|
37
|
+
info[:tags] = Regexp.last_match(1)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set defaults for missing information
|
41
|
+
info[:notebook] ||= "test_notebook" # Default notebook for tests
|
42
|
+
info[:title] ||= "Task from prompt" # Default title
|
43
|
+
|
44
|
+
info
|
45
|
+
end
|
46
|
+
|
47
|
+
# Build a more detailed context with task information
|
48
|
+
def build_enriched_context(context)
|
49
|
+
rich_context = "Current context:\n"
|
50
|
+
|
51
|
+
if context && context[:notebooks] && !context[:notebooks].empty?
|
52
|
+
rich_context += "Available notebooks:\n"
|
53
|
+
context[:notebooks].each do |notebook|
|
54
|
+
rich_context += "- #{notebook[:name]}#{notebook[:is_default] ? " (default)" : ""}\n"
|
55
|
+
end
|
56
|
+
else
|
57
|
+
rich_context += "No notebooks available.\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
if context && context[:tasks] && !context[:tasks].empty?
|
61
|
+
rich_context += "\nRecent tasks:\n"
|
62
|
+
context[:tasks].each do |task|
|
63
|
+
task[:notebook] || "unknown"
|
64
|
+
status = task[:status] || "todo"
|
65
|
+
priority = task[:priority] ? " [priority: #{task[:priority]}]" : ""
|
66
|
+
tags = task[:tags] ? " [tags: #{task[:tags]}]" : ""
|
67
|
+
|
68
|
+
rich_context += "- Task #{task[:id]}: #{task[:title]} (#{status})#{priority}#{tags}\n"
|
69
|
+
end
|
70
|
+
else
|
71
|
+
rich_context += "\nNo tasks available.\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
rich_context
|
75
|
+
end
|
76
|
+
|
77
|
+
# Build system prompt for specialized task types
|
78
|
+
def build_task_creation_prompt(context)
|
79
|
+
<<~PROMPT
|
80
|
+
You are an AI assistant for the Ruby Todo CLI application.
|
81
|
+
Your role is to help users manage their tasks and notebooks using natural language.
|
82
|
+
|
83
|
+
Available commands:
|
84
|
+
- task:add [notebook] [title] --description [description] --tags [comma,separated,tags] - Create a new task
|
85
|
+
- task:move [notebook] [task_id] [status] - Move a task to a new status (todo/in_progress/done/archived)
|
86
|
+
|
87
|
+
IMPORTANT: When creating tasks, the exact format must be:
|
88
|
+
task:add "notebook_name" "task_title" --description "description" --priority level --tags "tags"
|
89
|
+
|
90
|
+
Current context:
|
91
|
+
#{context.to_json}
|
92
|
+
PROMPT
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|