ara-cli 0.1.13.3__py3-none-any.whl → 0.1.14.0__py3-none-any.whl

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.
Files changed (61) hide show
  1. ara_cli/__init__.py +1 -1
  2. ara_cli/ara_command_action.py +162 -112
  3. ara_cli/ara_config.py +1 -1
  4. ara_cli/ara_subcommands/convert.py +66 -2
  5. ara_cli/ara_subcommands/prompt.py +266 -106
  6. ara_cli/artefact_autofix.py +2 -2
  7. ara_cli/artefact_converter.py +152 -53
  8. ara_cli/artefact_creator.py +41 -17
  9. ara_cli/artefact_lister.py +3 -3
  10. ara_cli/artefact_models/artefact_model.py +1 -1
  11. ara_cli/artefact_models/artefact_templates.py +0 -9
  12. ara_cli/artefact_models/feature_artefact_model.py +8 -8
  13. ara_cli/artefact_reader.py +62 -43
  14. ara_cli/artefact_scan.py +39 -17
  15. ara_cli/chat.py +23 -15
  16. ara_cli/children_contribution_updater.py +737 -0
  17. ara_cli/classifier.py +34 -0
  18. ara_cli/commands/load_command.py +4 -3
  19. ara_cli/commands/load_image_command.py +1 -1
  20. ara_cli/commands/read_command.py +23 -27
  21. ara_cli/completers.py +24 -0
  22. ara_cli/error_handler.py +26 -11
  23. ara_cli/file_loaders/document_reader.py +0 -178
  24. ara_cli/file_loaders/factories/__init__.py +0 -0
  25. ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
  26. ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
  27. ara_cli/file_loaders/file_loader.py +1 -30
  28. ara_cli/file_loaders/loaders/__init__.py +0 -0
  29. ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
  30. ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
  31. ara_cli/file_loaders/readers/__init__.py +0 -0
  32. ara_cli/file_loaders/readers/docx_reader.py +49 -0
  33. ara_cli/file_loaders/readers/excel_reader.py +27 -0
  34. ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
  35. ara_cli/file_loaders/readers/odt_reader.py +59 -0
  36. ara_cli/file_loaders/readers/pdf_reader.py +54 -0
  37. ara_cli/file_loaders/readers/pptx_reader.py +104 -0
  38. ara_cli/file_loaders/tools/__init__.py +0 -0
  39. ara_cli/output_suppressor.py +53 -0
  40. ara_cli/prompt_handler.py +123 -17
  41. ara_cli/tag_extractor.py +8 -7
  42. ara_cli/version.py +1 -1
  43. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +18 -12
  44. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/RECORD +58 -45
  45. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
  46. tests/test_artefact_converter.py +1 -46
  47. tests/test_artefact_lister.py +11 -8
  48. tests/test_chat.py +4 -4
  49. tests/test_chat_givens_images.py +1 -1
  50. tests/test_children_contribution_updater.py +98 -0
  51. tests/test_document_loader_office.py +267 -0
  52. tests/test_prompt_handler.py +416 -214
  53. tests/test_setup_default_chat_prompt_mode.py +198 -0
  54. tests/test_tag_extractor.py +95 -49
  55. ara_cli/file_loaders/document_readers.py +0 -233
  56. ara_cli/file_loaders/file_loaders.py +0 -123
  57. ara_cli/file_loaders/text_file_loader.py +0 -187
  58. /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
  59. /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
  60. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
  61. {ara_cli-0.1.13.3.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
ara_cli/__init__.py CHANGED
@@ -62,4 +62,4 @@ BINARY_TYPE_MAPPING = {
62
62
  ".jpeg": "image/jpeg",
63
63
  }
64
64
 
65
- DOCUMENT_TYPE_EXTENSIONS = [".docx", ".doc", ".odt", ".pdf"]
65
+ DOCUMENT_TYPE_EXTENSIONS = [".docx", ".doc", ".odt", ".pdf", ".xlsx", ".xls", ".pptx"]
@@ -23,33 +23,44 @@ def create_action(args):
23
23
  from ara_cli.artefact_reader import ArtefactReader
24
24
  from ara_cli.artefact_fuzzy_search import find_closest_rule
25
25
 
26
- check_validity(Classifier.is_valid_classifier(args.classifier),
27
- "Invalid classifier provided. Please provide a valid classifier.")
28
- check_validity(is_valid_filename(args.parameter),
29
- "Invalid filename provided. Please provide a valid filename.")
26
+ check_validity(
27
+ Classifier.is_valid_classifier(args.classifier),
28
+ "Invalid classifier provided. Please provide a valid classifier.",
29
+ )
30
+ check_validity(
31
+ is_valid_filename(args.parameter),
32
+ "Invalid filename provided. Please provide a valid filename.",
33
+ )
30
34
 
31
35
  def handle_parent_arguments(args):
32
- parent_classifier = args.parent_classifier if hasattr(
33
- args, "parent_classifier") else None
34
- parent_name = args.parent_name if hasattr(
35
- args, "parent_name") else None
36
- rule = args.rule if hasattr(args, 'rule') else None
37
- invalid_classifier_message = "Invalid parent classifier provided. Please provide a valid classifier"
38
- invalid_name_message = "Invalid filename provided for parent. Please provide a valid filename."
36
+ parent_classifier = (
37
+ args.parent_classifier if hasattr(args, "parent_classifier") else None
38
+ )
39
+ parent_name = args.parent_name if hasattr(args, "parent_name") else None
40
+ rule = args.rule if hasattr(args, "rule") else None
41
+ invalid_classifier_message = (
42
+ "Invalid parent classifier provided. Please provide a valid classifier"
43
+ )
44
+ invalid_name_message = (
45
+ "Invalid filename provided for parent. Please provide a valid filename."
46
+ )
39
47
  if parent_classifier and parent_name and rule:
40
- check_validity(Classifier.is_valid_classifier(
41
- parent_classifier), invalid_classifier_message)
42
- check_validity(is_valid_filename(
43
- parent_name), invalid_name_message)
44
- parent_artefact = ArtefactReader.read_artefact(
45
- artefact_name=parent_name, classifier=parent_classifier)
48
+ check_validity(
49
+ Classifier.is_valid_classifier(parent_classifier),
50
+ invalid_classifier_message,
51
+ )
52
+ check_validity(is_valid_filename(parent_name), invalid_name_message)
53
+ parent_artefact = ArtefactReader().read_artefact(
54
+ artefact_name=parent_name, classifier=parent_classifier
55
+ )
46
56
  rule = find_closest_rule(parent_artefact, rule)
47
57
  return parent_classifier, parent_name, rule
48
58
  if parent_classifier and parent_name:
49
- check_validity(Classifier.is_valid_classifier(
50
- parent_classifier), invalid_classifier_message)
51
- check_validity(is_valid_filename(
52
- parent_name), invalid_name_message)
59
+ check_validity(
60
+ Classifier.is_valid_classifier(parent_classifier),
61
+ invalid_classifier_message,
62
+ )
63
+ check_validity(is_valid_filename(parent_name), invalid_name_message)
53
64
  return parent_classifier, parent_name, rule
54
65
  return None, None, None
55
66
 
@@ -70,8 +81,9 @@ def create_action(args):
70
81
  return
71
82
 
72
83
  artefact_creator = ArtefactCreator()
73
- artefact_creator.run(args.parameter, args.classifier,
74
- parent_classifier, parent_name, rule)
84
+ artefact_creator.run(
85
+ args.parameter, args.classifier, parent_classifier, parent_name, rule
86
+ )
75
87
 
76
88
 
77
89
  @handle_errors(context="delete action", error_handler=error_handler)
@@ -88,12 +100,18 @@ def rename_action(args):
88
100
  from ara_cli.classifier import Classifier
89
101
  from ara_cli.filename_validator import is_valid_filename
90
102
 
91
- check_validity(is_valid_filename(args.parameter),
92
- "Invalid filename provided. Please provide a valid filename.")
93
- check_validity(Classifier.is_valid_classifier(args.classifier),
94
- "Invalid classifier provided. Please provide a valid classifier.")
95
- check_validity(is_valid_filename(
96
- args.aspect), "Invalid new filename provided. Please provide a valid filename.")
103
+ check_validity(
104
+ is_valid_filename(args.parameter),
105
+ "Invalid filename provided. Please provide a valid filename.",
106
+ )
107
+ check_validity(
108
+ Classifier.is_valid_classifier(args.classifier),
109
+ "Invalid classifier provided. Please provide a valid classifier.",
110
+ )
111
+ check_validity(
112
+ is_valid_filename(args.aspect),
113
+ "Invalid new filename provided. Please provide a valid filename.",
114
+ )
97
115
 
98
116
  artefact_renamer = ArtefactRenamer()
99
117
  artefact_renamer.rename(args.parameter, args.aspect, args.classifier)
@@ -124,7 +142,7 @@ def list_action(args):
124
142
  include_extension=args.include_extension,
125
143
  exclude_extension=args.exclude_extension,
126
144
  include_tags=args.include_tags,
127
- exclude_tags=args.exclude_tags
145
+ exclude_tags=args.exclude_tags,
128
146
  )
129
147
 
130
148
  # Map flags to their corresponding methods
@@ -165,7 +183,7 @@ def list_tags_action(args):
165
183
  tag_extractor = TagExtractor()
166
184
  tag_groups = tag_extractor.extract_tags(
167
185
  filtered_extra_column=getattr(args, "filtered_extra_column", False),
168
- list_filter=list_filter
186
+ list_filter=list_filter,
169
187
  )
170
188
 
171
189
  if args.json:
@@ -190,71 +208,91 @@ def prompt_action(args):
190
208
  from ara_cli.classifier import Classifier
191
209
  from ara_cli.filename_validator import is_valid_filename
192
210
 
193
- check_validity(Classifier.is_valid_classifier(args.classifier),
194
- "Invalid classifier provided. Please provide a valid classifier.")
195
- check_validity(is_valid_filename(args.parameter),
196
- "Invalid filename provided. Please provide a valid filename.")
211
+ check_validity(
212
+ Classifier.is_valid_classifier(args.classifier),
213
+ "Invalid classifier provided. Please provide a valid classifier.",
214
+ )
215
+ check_validity(
216
+ is_valid_filename(args.parameter),
217
+ "Invalid filename provided. Please provide a valid filename.",
218
+ )
197
219
 
198
220
  classifier = args.classifier
199
221
  param = args.parameter
200
222
  init = args.steps
201
- write = getattr(args, 'write', False)
223
+ write = getattr(args, "write", False)
202
224
 
203
225
  def handle_init():
204
226
  from ara_cli.prompt_handler import initialize_prompt_templates
227
+
205
228
  initialize_prompt_templates(classifier, param)
206
229
 
207
230
  def handle_init_rag():
208
231
  from ara_cli.prompt_handler import initialize_prompt_templates
209
232
  from ara_cli.prompt_rag import search_and_add_relevant_files_to_prompt_givens
233
+
210
234
  initialize_prompt_templates(classifier, param)
211
235
  search_and_add_relevant_files_to_prompt_givens(classifier, param)
212
236
 
213
237
  def handle_load():
214
238
  from ara_cli.prompt_handler import load_selected_prompt_templates
239
+
215
240
  load_selected_prompt_templates(classifier, param)
216
241
 
217
242
  def handle_send():
218
243
  from ara_cli.prompt_handler import create_and_send_custom_prompt
244
+
219
245
  create_and_send_custom_prompt(classifier, param)
220
246
 
221
247
  def handle_load_and_send():
222
- from ara_cli.prompt_handler import load_selected_prompt_templates, create_and_send_custom_prompt
248
+ from ara_cli.prompt_handler import (
249
+ load_selected_prompt_templates,
250
+ create_and_send_custom_prompt,
251
+ )
252
+
223
253
  load_selected_prompt_templates(classifier, param)
224
254
  create_and_send_custom_prompt(classifier, param)
225
255
 
226
256
  def handle_extract():
227
257
  from ara_cli.prompt_extractor import extract_and_save_prompt_results
228
258
  from ara_cli.update_config_prompt import update_artefact_config_prompt_files
259
+
229
260
  extract_and_save_prompt_results(classifier, param, write=write)
230
261
  print(f"automatic update after extract")
231
- update_artefact_config_prompt_files(
232
- classifier, param, automatic_update=True)
262
+ update_artefact_config_prompt_files(classifier, param, automatic_update=True)
233
263
 
234
264
  def handle_chat():
235
265
  from ara_cli.prompt_chat import initialize_prompt_chat_mode
266
+
236
267
  chat_name = args.chat_name
237
268
  reset = args.reset
238
269
  output_mode = args.output_mode
239
270
  append_strings = args.append
240
271
  restricted = args.restricted
241
- initialize_prompt_chat_mode(classifier, param, chat_name, reset=reset,
242
- output_mode=output_mode, append_strings=append_strings, restricted=restricted)
272
+ initialize_prompt_chat_mode(
273
+ classifier,
274
+ param,
275
+ chat_name,
276
+ reset=reset,
277
+ output_mode=output_mode,
278
+ append_strings=append_strings,
279
+ restricted=restricted,
280
+ )
243
281
 
244
282
  def handle_update():
245
283
  from ara_cli.update_config_prompt import update_artefact_config_prompt_files
246
- update_artefact_config_prompt_files(
247
- classifier, param, automatic_update=True)
284
+
285
+ update_artefact_config_prompt_files(classifier, param, automatic_update=True)
248
286
 
249
287
  command_dispatcher = {
250
- 'init': handle_init,
251
- 'init-rag': handle_init_rag,
252
- 'load': handle_load,
253
- 'send': handle_send,
254
- 'load-and-send': handle_load_and_send,
255
- 'extract': handle_extract,
256
- 'chat': handle_chat,
257
- 'update': handle_update,
288
+ "init": handle_init,
289
+ "init-rag": handle_init_rag,
290
+ "load": handle_load,
291
+ "send": handle_send,
292
+ "load-and-send": handle_load_and_send,
293
+ "extract": handle_extract,
294
+ "chat": handle_chat,
295
+ "update": handle_update,
258
296
  }
259
297
 
260
298
  if init in command_dispatcher:
@@ -279,8 +317,11 @@ def chat_action(args):
279
317
  chat_file_path = join(cwd, chat_name)
280
318
 
281
319
  with suppress_stdout(output_mode):
282
- chat = Chat(chat_file_path, reset=reset) if not restricted else Chat(
283
- chat_file_path, reset=reset, enable_commands=whitelisted_commands)
320
+ chat = (
321
+ Chat(chat_file_path, reset=reset)
322
+ if not restricted
323
+ else Chat(chat_file_path, reset=reset, enable_commands=whitelisted_commands)
324
+ )
284
325
 
285
326
  if append_strings:
286
327
  chat.append_strings(append_strings)
@@ -324,21 +365,22 @@ def load_action(args):
324
365
  default_patterns = {
325
366
  "rules": "*.rules.md",
326
367
  "intention": "*.intention.md",
327
- "commands": "*.commands.md"
368
+ "commands": "*.commands.md",
328
369
  }
329
370
 
330
371
  default_pattern = default_patterns.get(template_type)
331
372
 
332
373
  if not template_name and not default_pattern:
333
374
  raise AraError(
334
- f"A template name is required for template type '{template_type}'.")
375
+ f"A template name is required for template type '{template_type}'."
376
+ )
335
377
 
336
378
  loader = TemplateLoader() # No chat instance for CLI context
337
379
  success = loader.load_template(
338
380
  template_name=template_name,
339
381
  template_type=template_type,
340
382
  chat_file_path=chat_file_path,
341
- default_pattern=default_pattern
383
+ default_pattern=default_pattern,
342
384
  )
343
385
 
344
386
  if not success:
@@ -350,8 +392,10 @@ def template_action(args):
350
392
  from ara_cli.classifier import Classifier
351
393
  from ara_cli.template_manager import TemplatePathManager
352
394
 
353
- check_validity(Classifier.is_valid_classifier(args.classifier),
354
- "Invalid classifier provided. Please provide a valid classifier.")
395
+ check_validity(
396
+ Classifier.is_valid_classifier(args.classifier),
397
+ "Invalid classifier provided. Please provide a valid classifier.",
398
+ )
355
399
 
356
400
  template_manager = TemplatePathManager()
357
401
  content = template_manager.get_template_content(args.classifier)
@@ -372,11 +416,9 @@ def fetch_templates_action(args):
372
416
 
373
417
  subdirs = ["commands", "rules", "intentions", "blueprints"]
374
418
 
375
- os.makedirs(join(prompt_templates_dir,
376
- "global-prompt-modules"), exist_ok=True)
419
+ os.makedirs(join(prompt_templates_dir, "global-prompt-modules"), exist_ok=True)
377
420
  for subdir in subdirs:
378
- target_dir = join(prompt_templates_dir,
379
- "global-prompt-modules", subdir)
421
+ target_dir = join(prompt_templates_dir, "global-prompt-modules", subdir)
380
422
  source_dir = join(global_prompt_templates_path, subdir)
381
423
  os.makedirs(target_dir, exist_ok=True)
382
424
  for item in os.listdir(source_dir):
@@ -386,7 +428,8 @@ def fetch_templates_action(args):
386
428
 
387
429
  custom_prompt_templates_subdir = config.custom_prompt_templates_subdir
388
430
  local_prompt_modules_dir = join(
389
- prompt_templates_dir, custom_prompt_templates_subdir)
431
+ prompt_templates_dir, custom_prompt_templates_subdir
432
+ )
390
433
  os.makedirs(local_prompt_modules_dir, exist_ok=True)
391
434
  for subdir in subdirs:
392
435
  os.makedirs(join(local_prompt_modules_dir, subdir), exist_ok=True)
@@ -407,14 +450,14 @@ def read_action(args):
407
450
  include_extension=args.include_extension,
408
451
  exclude_extension=args.exclude_extension,
409
452
  include_tags=args.include_tags,
410
- exclude_tags=args.exclude_tags
453
+ exclude_tags=args.exclude_tags,
411
454
  )
412
455
 
413
456
  command = ReadCommand(
414
457
  classifier=classifier,
415
458
  artefact_name=artefact_name,
416
459
  read_mode=read_mode,
417
- list_filter=list_filter
460
+ list_filter=list_filter,
418
461
  )
419
462
 
420
463
  command.execute()
@@ -432,7 +475,7 @@ def reconnect_action(args):
432
475
  artefact_name = args.parameter
433
476
  parent_classifier = args.parent_classifier
434
477
  parent_name = args.parent_name
435
- rule = args.rule if hasattr(args, 'rule') else None
478
+ rule = args.rule if hasattr(args, "rule") else None
436
479
 
437
480
  read_error_message = f"Could not connect {classifier} '{artefact_name}' to {parent_classifier} '{parent_name}'"
438
481
 
@@ -441,27 +484,27 @@ def reconnect_action(args):
441
484
  file_classifier = FileClassifier(os)
442
485
  classified_file_info = file_classifier.classify_files()
443
486
 
444
- artefact = ArtefactReader.read_artefact(
487
+ reader = ArtefactReader()
488
+ artefact = reader.read_artefact(
445
489
  artefact_name=artefact_name,
446
490
  classifier=classifier,
447
- classified_file_info=classified_file_info
491
+ classified_file_info=classified_file_info,
448
492
  )
449
493
 
450
494
  if not artefact:
451
495
  raise AraError(read_error_message)
452
496
 
453
- parent = ArtefactReader.read_artefact(
497
+ parent = reader.read_artefact(
454
498
  artefact_name=parent_name,
455
499
  classifier=parent_classifier,
456
- classified_file_info=classified_file_info
500
+ classified_file_info=classified_file_info,
457
501
  )
458
502
 
459
503
  if not parent:
460
504
  raise AraError(read_error_message)
461
505
 
462
506
  contribution = Contribution(
463
- artefact_name=parent.title,
464
- classifier=parent.artefact_type
507
+ artefact_name=parent.title, classifier=parent.artefact_type
465
508
  )
466
509
 
467
510
  if rule:
@@ -470,7 +513,7 @@ def reconnect_action(args):
470
513
  feedback_message += f" using rule '{closest_rule}'"
471
514
 
472
515
  artefact.contribution = contribution
473
- with open(artefact.file_path, 'w', encoding='utf-8') as file:
516
+ with open(artefact.file_path, "w", encoding="utf-8") as file:
474
517
  artefact_content = artefact.serialize()
475
518
  file.write(artefact_content)
476
519
 
@@ -489,18 +532,20 @@ def read_status_action(args):
489
532
  artefact_info = file_classifier.classify_files()
490
533
  artefact_info_dicts = artefact_info.get(classifier, [])
491
534
 
492
- all_artefact_names = [artefact_info["title"]
493
- for artefact_info in artefact_info_dicts]
535
+ all_artefact_names = [
536
+ artefact_info["title"] for artefact_info in artefact_info_dicts
537
+ ]
494
538
  if artefact_name not in all_artefact_names:
495
539
  suggest_close_name_matches(
496
- artefact_name, all_artefact_names, report_as_error=True)
540
+ artefact_name, all_artefact_names, report_as_error=True
541
+ )
497
542
  return
498
543
 
499
- artefact_info = next(filter(
500
- lambda x: x["title"] == artefact_name, artefact_info_dicts
501
- ))
544
+ artefact_info = next(
545
+ filter(lambda x: x["title"] == artefact_name, artefact_info_dicts)
546
+ )
502
547
 
503
- with open(artefact_info["file_path"], 'r', encoding='utf-8') as file:
548
+ with open(artefact_info["file_path"], "r", encoding="utf-8") as file:
504
549
  content = file.read()
505
550
  artefact = artefact_from_content(content)
506
551
 
@@ -524,18 +569,20 @@ def read_user_action(args):
524
569
  artefact_info = file_classifier.classify_files()
525
570
  artefact_info_dicts = artefact_info.get(classifier, [])
526
571
 
527
- all_artefact_names = [artefact_info["title"]
528
- for artefact_info in artefact_info_dicts]
572
+ all_artefact_names = [
573
+ artefact_info["title"] for artefact_info in artefact_info_dicts
574
+ ]
529
575
  if artefact_name not in all_artefact_names:
530
576
  suggest_close_name_matches(
531
- artefact_name, all_artefact_names, report_as_error=True)
577
+ artefact_name, all_artefact_names, report_as_error=True
578
+ )
532
579
  return
533
580
 
534
- artefact_info = next(filter(
535
- lambda x: x["title"] == artefact_name, artefact_info_dicts
536
- ))
581
+ artefact_info = next(
582
+ filter(lambda x: x["title"] == artefact_name, artefact_info_dicts)
583
+ )
537
584
 
538
- with open(artefact_info["file_path"], 'r', encoding='utf-8') as file:
585
+ with open(artefact_info["file_path"], "r", encoding="utf-8") as file:
539
586
  content = file.read()
540
587
  artefact = artefact_from_content(content)
541
588
 
@@ -560,38 +607,40 @@ def set_status_action(args):
560
607
  artefact_name = args.parameter
561
608
  new_status = args.new_status
562
609
 
563
- if new_status.startswith('@'):
564
- new_status = new_status.lstrip('@')
610
+ if new_status.startswith("@"):
611
+ new_status = new_status.lstrip("@")
565
612
 
566
- check_validity(new_status in status_tags,
567
- "Invalid status provided. Please provide a valid status.")
613
+ check_validity(
614
+ new_status in status_tags,
615
+ "Invalid status provided. Please provide a valid status.",
616
+ )
568
617
 
569
618
  file_classifier = FileClassifier(os)
570
619
  classified_artefacts_info = file_classifier.classify_files()
571
620
  classified_artefact_dict = classified_artefacts_info.get(classifier, [])
572
- all_artefact_names = [artefact_info["title"]
573
- for artefact_info in classified_artefact_dict]
621
+ all_artefact_names = [
622
+ artefact_info["title"] for artefact_info in classified_artefact_dict
623
+ ]
574
624
 
575
625
  if artefact_name not in all_artefact_names:
576
626
  suggest_close_name_matches(artefact_name, all_artefact_names)
577
627
  return
578
628
 
579
- artefact_info = next(filter(
580
- lambda x: x["title"] == artefact_name, classified_artefact_dict
581
- ))
629
+ artefact_info = next(
630
+ filter(lambda x: x["title"] == artefact_name, classified_artefact_dict)
631
+ )
582
632
 
583
- with open(artefact_info["file_path"], 'r', encoding='utf-8') as file:
633
+ with open(artefact_info["file_path"], "r", encoding="utf-8") as file:
584
634
  content = file.read()
585
635
  artefact = artefact_from_content(content)
586
636
 
587
637
  artefact.status = new_status
588
638
 
589
639
  serialized_content = artefact.serialize()
590
- with open(f"{artefact_info['file_path']}", 'w', encoding='utf-8') as file:
640
+ with open(f"{artefact_info['file_path']}", "w", encoding="utf-8") as file:
591
641
  file.write(serialized_content)
592
642
 
593
- print(
594
- f"Status of task '{artefact_name}' has been updated to '{new_status}'.")
643
+ print(f"Status of task '{artefact_name}' has been updated to '{new_status}'.")
595
644
 
596
645
 
597
646
  @handle_errors(context="set-user action", error_handler=error_handler)
@@ -603,24 +652,25 @@ def set_user_action(args):
603
652
  artefact_name = args.parameter
604
653
  new_user = args.new_user
605
654
 
606
- if new_user.startswith('@'):
607
- new_user = new_user.lstrip('@')
655
+ if new_user.startswith("@"):
656
+ new_user = new_user.lstrip("@")
608
657
 
609
658
  file_classifier = FileClassifier(os)
610
659
  classified_artefacts_info = file_classifier.classify_files()
611
660
  classified_artefact_dict = classified_artefacts_info.get(classifier, [])
612
- all_artefact_names = [artefact_info["title"]
613
- for artefact_info in classified_artefact_dict]
661
+ all_artefact_names = [
662
+ artefact_info["title"] for artefact_info in classified_artefact_dict
663
+ ]
614
664
 
615
665
  if artefact_name not in all_artefact_names:
616
666
  suggest_close_name_matches(artefact_name, all_artefact_names)
617
667
  return
618
668
 
619
- artefact_info = next(filter(
620
- lambda x: x["title"] == artefact_name, classified_artefact_dict
621
- ))
669
+ artefact_info = next(
670
+ filter(lambda x: x["title"] == artefact_name, classified_artefact_dict)
671
+ )
622
672
 
623
- with open(artefact_info["file_path"], 'r', encoding='utf-8') as file:
673
+ with open(artefact_info["file_path"], "r", encoding="utf-8") as file:
624
674
  content = file.read()
625
675
  artefact = artefact_from_content(content)
626
676
 
@@ -628,7 +678,7 @@ def set_user_action(args):
628
678
 
629
679
  serialized_content = artefact.serialize()
630
680
 
631
- with open(artefact_info["file_path"], 'w', encoding='utf-8') as file:
681
+ with open(artefact_info["file_path"], "w", encoding="utf-8") as file:
632
682
  file.write(serialized_content)
633
683
 
634
684
  print(f"User of task '{artefact_name}' has been updated to '{new_user}'.")
@@ -692,7 +742,7 @@ def autofix_action(args):
692
742
  single_pass=args.single_pass,
693
743
  deterministic=run_deterministic,
694
744
  non_deterministic=run_non_deterministic,
695
- classified_artefact_info=classified_artefact_info
745
+ classified_artefact_info=classified_artefact_info,
696
746
  )
697
747
 
698
748
  print("\nAutofix process completed. Please review the changes.")
@@ -704,11 +754,11 @@ def extract_action(args):
704
754
 
705
755
  filename = args.filename
706
756
  force = args.force
707
- write = getattr(args, 'write', False)
757
+ write = getattr(args, "write", False)
708
758
  command = ExtractCommand(
709
759
  file_name=filename,
710
760
  force=force,
711
761
  write=write,
712
- output=lambda msg: print(msg, file=sys.stdout)
762
+ output=lambda msg: print(msg, file=sys.stdout),
713
763
  )
714
764
  command.execute()
ara_cli/ara_config.py CHANGED
@@ -34,7 +34,7 @@ def get_default_llm_config() -> Dict[str, "LLMConfigItem"]:
34
34
  "gpt-5-web": LLMConfigItem(
35
35
  provider="openai",
36
36
  model="openai/gpt-5-search-api",
37
- temperature=1,
37
+ temperature=None,
38
38
  max_completion_tokens=16000,
39
39
  ),
40
40
  "gpt-4o": LLMConfigItem(
@@ -1,4 +1,5 @@
1
1
  import typer
2
+ from typing import Optional
2
3
 
3
4
  from ara_cli import error_handler
4
5
  from ara_cli.completers import DynamicCompleters
@@ -28,16 +29,79 @@ def register(app: typer.Typer):
28
29
  override: bool = typer.Option(
29
30
  False, "--override", help="Override existing artefact if it exists"
30
31
  ),
32
+ force: bool = typer.Option(
33
+ False,
34
+ "-f",
35
+ "--force",
36
+ help="When converting to task/issue, automatically clear children's contribution fields",
37
+ ),
38
+ json_output: bool = typer.Option(
39
+ False,
40
+ "-j",
41
+ "--json",
42
+ help="Output results as JSON (for API/non-interactive mode)",
43
+ ),
44
+ children_action: Optional[str] = typer.Option(
45
+ None,
46
+ "--children-action",
47
+ help="Non-interactive mode: Action for children (cancel/clear/reassign)",
48
+ ),
49
+ new_parent_classifier: Optional[str] = typer.Option(
50
+ None,
51
+ "--new-parent-classifier",
52
+ help="For reassign action: classifier of the new parent artefact",
53
+ autocompletion=DynamicCompleters.create_classifier_completer(),
54
+ ),
55
+ new_parent_name: Optional[str] = typer.Option(
56
+ None,
57
+ "--new-parent-name",
58
+ help="For reassign action: name of the new parent artefact",
59
+ ),
60
+ preview: bool = typer.Option(
61
+ False,
62
+ "--preview",
63
+ help="Preview children info without converting (returns JSON)",
64
+ ),
31
65
  ):
32
66
  """
33
67
  Convert an existing artefact from one classifier to another.
68
+
69
+ For API/non-interactive mode, use --json and --children-action options.
70
+ Use --preview to get children information without performing conversion.
34
71
  """
35
72
  try:
36
73
  from ara_cli.artefact_converter import AraArtefactConverter
74
+ from ara_cli.children_contribution_updater import (
75
+ ChildrenContributionUpdater,
76
+ )
77
+ import json
78
+
79
+ # Preview mode: return children info as JSON
80
+ if preview:
81
+ updater = ChildrenContributionUpdater()
82
+ info = updater.get_children_info(
83
+ artefact_name, old_classifier, new_classifier
84
+ )
85
+ print(json.dumps(info, indent=2))
86
+ return
37
87
 
38
88
  converter = AraArtefactConverter()
39
89
  converter.convert(
40
- old_classifier, artefact_name, new_classifier, merge, override
90
+ old_classifier,
91
+ artefact_name,
92
+ new_classifier,
93
+ merge,
94
+ override,
95
+ force,
96
+ children_action=children_action,
97
+ new_parent_classifier=new_parent_classifier,
98
+ new_parent_name=new_parent_name,
99
+ json_output=json_output,
41
100
  )
42
101
  except Exception as e:
43
- error_handler.handle_error(e)
102
+ if json_output:
103
+ import json
104
+
105
+ print(json.dumps({"status": "error", "message": str(e)}))
106
+ else:
107
+ error_handler.handle_error(e)