raggiecode 0.2.1__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 (93) hide show
  1. Agent/__init__.py +0 -0
  2. Agent/agent.py +891 -0
  3. Agent/chat_history_db.py +1500 -0
  4. Agent/command.py +49 -0
  5. Agent/config.py +46 -0
  6. Agent/effort_levels.py +33 -0
  7. Agent/git_manager.py +727 -0
  8. Agent/tools.py +35 -0
  9. Commands/__init__.py +18 -0
  10. Commands/effort.py +42 -0
  11. Commands/global_todo.py +23 -0
  12. Commands/help.py +22 -0
  13. Commands/reasoning.py +24 -0
  14. Commands/redo.py +11 -0
  15. Commands/reindex.py +27 -0
  16. Commands/shell.py +28 -0
  17. Commands/stream.py +24 -0
  18. Commands/undo.py +13 -0
  19. Commands/unlimited_effort.py +8 -0
  20. Commands/window_size.py +29 -0
  21. RAG/__init__.py +0 -0
  22. RAG/document.py +119 -0
  23. RAG/find.py +408 -0
  24. RAG/graph.py +231 -0
  25. Tools/GetFileCodeStructure.py +43 -0
  26. Tools/GetSymbolSourceCode.py +27 -0
  27. Tools/__init__.py +39 -0
  28. Tools/ask_user.py +102 -0
  29. Tools/dispatch_subagent.py +215 -0
  30. Tools/document.py +35 -0
  31. Tools/edit_symbol.py +250 -0
  32. Tools/fuzzy_search.py +119 -0
  33. Tools/list_dir.py +51 -0
  34. Tools/read.py +49 -0
  35. Tools/read_image.py +75 -0
  36. Tools/remove.py +75 -0
  37. Tools/replace.py +305 -0
  38. Tools/search.py +41 -0
  39. Tools/shell.py +149 -0
  40. Tools/shell_kill.py +87 -0
  41. Tools/temp_background_service.py +113 -0
  42. Tools/todo_list.py +481 -0
  43. Tools/utils.py +116 -0
  44. Tools/view_changes.py +179 -0
  45. Tools/walk_call_tree.py +30 -0
  46. Tools/web_fetch.py +175 -0
  47. Tools/web_search.py +69 -0
  48. Tools/write.py +48 -0
  49. cli.py +111 -0
  50. config/__init__.py +0 -0
  51. config/coder_system_prompt.md +119 -0
  52. config/roles.json +43 -0
  53. config/tools.json +709 -0
  54. indexing/__init__.py +0 -0
  55. indexing/cli.py +128 -0
  56. indexing/code_index_sdk.py +832 -0
  57. indexing/code_indexer.py +1763 -0
  58. indexing/db_schema.py +396 -0
  59. indexing/export_to_json.py +346 -0
  60. indexing/extractors.py +189 -0
  61. indexing/file_utils.py +97 -0
  62. indexing/frontend/__init__.py +0 -0
  63. indexing/frontend/css_extractor.py +195 -0
  64. indexing/frontend/css_parser.py +387 -0
  65. indexing/frontend/css_selector_utils.py +226 -0
  66. indexing/frontend/edit_safety.py +573 -0
  67. indexing/frontend/graph.py +838 -0
  68. indexing/frontend/html_extractor.py +496 -0
  69. indexing/frontend/html_parser.py +314 -0
  70. indexing/frontend/jsx_extractor.py +1204 -0
  71. indexing/frontend/location_lookup.py +247 -0
  72. indexing/frontend/resolver.py +485 -0
  73. indexing/frontend/runtime_resolver.py +862 -0
  74. indexing/frontend/semantic_output.py +705 -0
  75. indexing/frontend/source_location.py +69 -0
  76. indexing/frontend_config.py +72 -0
  77. indexing/frontend_models.py +347 -0
  78. indexing/language_config.py +360 -0
  79. indexing/models.py +284 -0
  80. indexing/node_utils.py +1112 -0
  81. indexing/parse_worker.py +1082 -0
  82. indexing/queries.py +1542 -0
  83. indexing/sdk_examples.py +426 -0
  84. interactive.py +248 -0
  85. raggie.py +673 -0
  86. raggiecode-0.2.1.dist-info/METADATA +944 -0
  87. raggiecode-0.2.1.dist-info/RECORD +93 -0
  88. raggiecode-0.2.1.dist-info/WHEEL +5 -0
  89. raggiecode-0.2.1.dist-info/entry_points.txt +2 -0
  90. raggiecode-0.2.1.dist-info/top_level.txt +10 -0
  91. skills/__init__.py +3 -0
  92. skills/manager.py +114 -0
  93. skills/tool.py +121 -0
raggie.py ADDED
@@ -0,0 +1,673 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Raggie - AI Coding Agent
4
+ Main entry point for the Raggie coding agent.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+ from rich.console import Console
12
+
13
+ console = Console()
14
+ GREEN = "\033[32m"
15
+ DIM = "\033[2m"
16
+ RESET = "\033[0m"
17
+
18
+ from cli import parse_args
19
+ from Agent.agent import Agent
20
+ from Agent.chat_history_db import init_db, select_chat, create_chat
21
+ from Agent.config import load_keys, load_roles, save_roles, USER_CONFIG_DIR
22
+ from skills import SkillManager
23
+ from Tools import setup_toolcalls
24
+ from Commands import setup_commands
25
+
26
+
27
+ def mask_key(key):
28
+ if len(key) <= 8:
29
+ return "*" * len(key)
30
+ return key[:4] + "*" * (len(key) - 8) + key[-4:]
31
+
32
+
33
+ def handle_roles_command():
34
+ """Interactive menu to list roles and edit their base URL and model."""
35
+ while True:
36
+ roles = load_roles()
37
+ print()
38
+ print("Agent Roles")
39
+ print("-" * 60)
40
+ if not roles:
41
+ print(" (no roles defined)")
42
+ else:
43
+ for i, (name, config) in enumerate(roles.items(), 1):
44
+ model = config.get("model", "?")
45
+ base_url = config.get("base_url", "?")
46
+ prompt_src = config.get("system_prompt_file", "") or config.get("system_prompt", "")
47
+ prompt_short = (prompt_src[:50] + "...") if len(str(prompt_src)) > 50 else prompt_src
48
+ ctx_window = config.get("context_window", "?")
49
+ reasoning = "on" if config.get("reasoning", False) else "off"
50
+ streaming = "on" if config.get("stream", False) else "off"
51
+ print(f" {i}. {name}")
52
+ print(f" Model: {model}")
53
+ print(f" Base URL: {base_url}")
54
+ print(f" Context Window: {ctx_window}")
55
+ print(f" Prompt: {prompt_short}")
56
+ print(f" Reasoning: {reasoning}")
57
+ print(f" Streaming: {streaming}")
58
+ print()
59
+ print("q. Exit")
60
+ print("1. Edit role's base URL / model")
61
+ print()
62
+
63
+ try:
64
+ choice = input("Choice: ").strip()
65
+ except KeyboardInterrupt:
66
+ print()
67
+ return
68
+ except EOFError:
69
+ print()
70
+ return
71
+
72
+ if choice == "1":
73
+ if not roles:
74
+ print("No roles to edit.")
75
+ continue
76
+
77
+ if len(roles) == 1:
78
+ idx = 1
79
+ else:
80
+ try:
81
+ idx = input("Number to edit (or 0 to cancel): ").strip()
82
+ except KeyboardInterrupt:
83
+ print()
84
+ return
85
+ except EOFError:
86
+ print()
87
+ return
88
+
89
+ try:
90
+ idx = int(idx)
91
+ except ValueError:
92
+ print("Invalid number.")
93
+ continue
94
+ if idx == 0:
95
+ continue
96
+ if idx < 1 or idx > len(roles):
97
+ print("Invalid selection.")
98
+ continue
99
+
100
+ role_name = list(roles.keys())[idx - 1]
101
+ role = roles[role_name]
102
+ print(f"\nEditing '{role_name}'. Press Enter to keep current value.")
103
+
104
+ new_model = ""
105
+ new_url = ""
106
+
107
+ try:
108
+ new_model = input(f"Model [{role.get('model', '')}]: ").strip()
109
+ except KeyboardInterrupt:
110
+ print()
111
+ return
112
+ except EOFError:
113
+ print()
114
+ return
115
+
116
+ if new_model:
117
+ role['model'] = new_model
118
+
119
+ # Base URL selection from keys.json
120
+ keys = load_keys()
121
+ key_urls = list(keys.keys())
122
+ current_url = role.get('base_url', '')
123
+
124
+ if key_urls:
125
+ print(f"\nAvailable base URLs (from your keys):")
126
+ for i, url in enumerate(key_urls, 1):
127
+ marker = " (current)" if url == current_url else ""
128
+ print(f" {i}. {url}{marker}")
129
+ print(f" 0. Enter a custom URL")
130
+ print()
131
+ try:
132
+ url_choice = input(f"Select base URL (or press Enter to keep current): ").strip()
133
+ except KeyboardInterrupt:
134
+ print()
135
+ return
136
+ except EOFError:
137
+ print()
138
+ return
139
+
140
+ if url_choice == "":
141
+ pass
142
+ elif url_choice == "0":
143
+ try:
144
+ new_url = input(f"Base URL [{current_url}]: ").strip()
145
+ except KeyboardInterrupt:
146
+ print()
147
+ return
148
+ except EOFError:
149
+ print()
150
+ return
151
+ if new_url:
152
+ role['base_url'] = new_url
153
+ else:
154
+ try:
155
+ url_idx = int(url_choice)
156
+ except ValueError:
157
+ print("Invalid selection, keeping current base URL.")
158
+ url_idx = -1
159
+ if url_idx >= 1 and url_idx <= len(key_urls):
160
+ role['base_url'] = key_urls[url_idx - 1]
161
+ elif url_idx != -1:
162
+ print("Invalid selection, keeping current base URL.")
163
+ else:
164
+ try:
165
+ new_url = input(f"Base URL [{current_url}] (no keys configured, run 'raggie keys' first): ").strip()
166
+ except KeyboardInterrupt:
167
+ print()
168
+ return
169
+ except EOFError:
170
+ print()
171
+ return
172
+ if new_url:
173
+ role['base_url'] = new_url
174
+
175
+ try:
176
+ new_ctx = input(f"Context Window [{role.get('context_window', '')}]: ").strip()
177
+ except KeyboardInterrupt:
178
+ print()
179
+ return
180
+ except EOFError:
181
+ print()
182
+ return
183
+
184
+ if new_ctx:
185
+ try:
186
+ role['context_window'] = int(new_ctx)
187
+ except ValueError:
188
+ print("Invalid context window value, keeping previous.")
189
+
190
+ try:
191
+ current_reasoning = role.get('reasoning', False)
192
+ new_reasoning = input(f"Reasoning (y/n) [{'on' if current_reasoning else 'off'}]: ").strip().lower()
193
+ except KeyboardInterrupt:
194
+ print()
195
+ return
196
+ except EOFError:
197
+ print()
198
+ return
199
+
200
+ if new_reasoning in ('y', 'yes'):
201
+ role['reasoning'] = True
202
+ elif new_reasoning in ('n', 'no'):
203
+ role['reasoning'] = False
204
+
205
+ try:
206
+ current_stream = role.get('stream', False)
207
+ new_stream = input(f"Streaming (y/n) [{'on' if current_stream else 'off'}]: ").strip().lower()
208
+ except KeyboardInterrupt:
209
+ print()
210
+ return
211
+ except EOFError:
212
+ print()
213
+ return
214
+
215
+ if new_stream in ('y', 'yes'):
216
+ role['stream'] = True
217
+ elif new_stream in ('n', 'no'):
218
+ role['stream'] = False
219
+
220
+ roles[role_name] = role
221
+ save_roles(roles)
222
+ print(f"Role '{role_name}' updated.")
223
+
224
+ elif choice == "q":
225
+ break
226
+ else:
227
+ print("Invalid choice.")
228
+
229
+
230
+ def handle_keys_command():
231
+ keys_file = USER_CONFIG_DIR / "keys.json"
232
+
233
+ while True:
234
+ print()
235
+ print("API Keys")
236
+ print("-" * 40)
237
+ keys = load_keys()
238
+ if not keys:
239
+ print(" (none)")
240
+ else:
241
+ for i, (url, key) in enumerate(keys.items(), 1):
242
+ print(f" {i}. {url} -> {mask_key(key)}")
243
+ print()
244
+ print("q. Skip")
245
+ print("1. Add key")
246
+ print("2. Remove key")
247
+ print()
248
+
249
+ try:
250
+ choice = input("Choice: ").strip()
251
+ except KeyboardInterrupt:
252
+ print()
253
+ return
254
+ except EOFError:
255
+ print()
256
+ return
257
+
258
+ if choice == "1":
259
+ url = input("Base URL: ").strip()
260
+ if not url:
261
+ print("Base URL cannot be empty.")
262
+ continue
263
+ key = input("API Key: ").strip()
264
+ if not key:
265
+ print("API Key cannot be empty.")
266
+ continue
267
+
268
+ keys = load_keys()
269
+ keys[url] = key
270
+ keys_file.parent.mkdir(parents=True, exist_ok=True)
271
+ with open(keys_file, "w") as f:
272
+ json.dump(keys, f, indent=2)
273
+ print(f"Key for {url} saved.")
274
+
275
+ elif choice == "2":
276
+ if not keys:
277
+ print("No keys to remove.")
278
+ continue
279
+
280
+ try:
281
+ idx = input("Number to remove (or 0 to cancel): ").strip()
282
+ except KeyboardInterrupt:
283
+ print()
284
+ continue
285
+ except EOFError:
286
+ print()
287
+ continue
288
+ try:
289
+ idx = int(idx)
290
+ except ValueError:
291
+ print("Invalid number.")
292
+ continue
293
+
294
+ if idx == 0:
295
+ continue
296
+ if idx < 1 or idx > len(keys):
297
+ print("Invalid selection.")
298
+ continue
299
+
300
+ removed = list(keys.keys())[idx - 1]
301
+ del keys[removed]
302
+ with open(keys_file, "w") as f:
303
+ json.dump(keys, f, indent=2)
304
+ print(f"Key for {removed} removed.")
305
+
306
+ elif choice == "q":
307
+ break
308
+ else:
309
+ print("Invalid choice.")
310
+
311
+
312
+ def handle_setup_command():
313
+ """First-time setup wizard: configure API keys, then review agent roles."""
314
+ print()
315
+ print("=" * 60)
316
+ print(" Raggie Setup Wizard")
317
+ print("=" * 60)
318
+ print()
319
+ print("Step 1: Configure your API keys.")
320
+ print("You'll need at least one API key to use Raggie.")
321
+ handle_keys_command()
322
+ print()
323
+ print("Step 2: Review your agent roles.")
324
+ print("Roles define which model and base URL each agent uses.")
325
+ handle_roles_command()
326
+ print()
327
+ print("=" * 60)
328
+ print(" Setup complete! You're ready to use Raggie.")
329
+ print(" Try: raggie code .")
330
+ print("=" * 60)
331
+
332
+
333
+ def handle_skill_command(args):
334
+ """Handle skill subcommands."""
335
+ # Initialize database to ensure skills table exists
336
+ init_db()
337
+
338
+ manager = SkillManager()
339
+
340
+ # --list-all: list all skills across all roles (role arg not required)
341
+ if getattr(args, "list_all", False):
342
+ skills = manager.list_skills()
343
+ if skills:
344
+ print("All skills:")
345
+ print("-" * 60)
346
+ for skill in skills:
347
+ print(f" {skill['role']}/{skill['name']}: {skill['summary']}")
348
+ print("-" * 60)
349
+ else:
350
+ print("No skills found.")
351
+ return
352
+
353
+ # Flag-based operations (non-interactive)
354
+ if args.import_skill:
355
+ if not args.role:
356
+ print("Error: role is required when using --import-skill", file=sys.stderr)
357
+ sys.exit(1)
358
+ if not args.name:
359
+ print("Error: --name is required when using --import-skill", file=sys.stderr)
360
+ sys.exit(1)
361
+ try:
362
+ manager.import_from_markdown(args.role, args.name, args.import_skill)
363
+ print(f"Successfully imported skill '{args.name}' for role '{args.role}' from {args.import_skill}")
364
+ except Exception as e:
365
+ print(f"Error importing skill: {e}", file=sys.stderr)
366
+ sys.exit(1)
367
+ return
368
+
369
+ if args.export_skill:
370
+ if not args.role:
371
+ print("Error: role is required when using --export-skill", file=sys.stderr)
372
+ sys.exit(1)
373
+ if not args.name:
374
+ print("Error: --name is required when using --export-skill", file=sys.stderr)
375
+ sys.exit(1)
376
+ try:
377
+ manager.export_to_markdown(args.role, args.name, args.export_skill)
378
+ print(f"Successfully exported skill '{args.name}' for role '{args.role}' to {args.export_skill}")
379
+ except Exception as e:
380
+ print(f"Error exporting skill: {e}", file=sys.stderr)
381
+ sys.exit(1)
382
+ return
383
+
384
+ if getattr(args, "delete", False):
385
+ if not args.role:
386
+ print("Error: role is required when using --delete", file=sys.stderr)
387
+ sys.exit(1)
388
+ if not args.name:
389
+ print("Error: --name is required when using --delete", file=sys.stderr)
390
+ sys.exit(1)
391
+ deleted = manager.delete_skill(args.role, args.name)
392
+ if deleted:
393
+ print(f"Successfully deleted skill '{args.name}' for role '{args.role}'")
394
+ else:
395
+ print(f"No skill '{args.name}' found for role '{args.role}'")
396
+ return
397
+
398
+ if args.show and args.name:
399
+ # Show specific skill (non-interactive)
400
+ if not args.role:
401
+ print("Error: role is required", file=sys.stderr)
402
+ sys.exit(1)
403
+ skill = manager.get_skill(args.role, args.name)
404
+ if skill:
405
+ print(f"Skill '{args.name}' for role '{args.role}':")
406
+ print("-" * 60)
407
+ print(skill)
408
+ print("-" * 60)
409
+ else:
410
+ print(f"No skill '{args.name}' found for role '{args.role}'")
411
+ return
412
+
413
+ # Interactive mode: no flags, or --show without --name
414
+ _skill_interactive_menu(manager, args.role)
415
+
416
+
417
+ def _skill_interactive_menu(manager, role_filter=None):
418
+ """Interactive menu for managing skills."""
419
+ while True:
420
+ if role_filter:
421
+ skills = manager.list_skills_by_role(role_filter)
422
+ header = f"Skills for role '{role_filter}'"
423
+ else:
424
+ skills = manager.list_skills()
425
+ header = "All Skills"
426
+
427
+ print()
428
+ print(header)
429
+ print("-" * 60)
430
+ if not skills:
431
+ print(" (no skills found)")
432
+ else:
433
+ for i, skill in enumerate(skills, 1):
434
+ r = skill['role']
435
+ n = skill['name']
436
+ s = skill['summary']
437
+ if role_filter:
438
+ print(f" {i}. {n}: {s}")
439
+ else:
440
+ print(f" {i}. {r}/{n}: {s}")
441
+ print()
442
+ print("q. Exit")
443
+ print("1. View skill content")
444
+ print("2. Delete skill")
445
+ print("3. Export skill to file")
446
+ print("4. Import skill from file")
447
+ print("5. List all skills (all roles)")
448
+ print()
449
+
450
+ try:
451
+ choice = input("Choice: ").strip()
452
+ except KeyboardInterrupt:
453
+ print()
454
+ return
455
+ except EOFError:
456
+ print()
457
+ return
458
+
459
+ if choice == "q":
460
+ break
461
+
462
+ elif choice == "1":
463
+ # View skill content
464
+ if not skills:
465
+ print("No skills to view.")
466
+ continue
467
+ try:
468
+ idx = input("Number to view (or 0 to cancel): ").strip()
469
+ except (KeyboardInterrupt, EOFError):
470
+ print()
471
+ return
472
+ try:
473
+ idx = int(idx)
474
+ except ValueError:
475
+ print("Invalid number.")
476
+ continue
477
+ if idx == 0:
478
+ continue
479
+ if idx < 1 or idx > len(skills):
480
+ print("Invalid selection.")
481
+ continue
482
+ skill = skills[idx - 1]
483
+ content = manager.get_skill(skill['role'], skill['name'])
484
+ print()
485
+ print(f"Skill '{skill['name']}' for role '{skill['role']}':")
486
+ print("=" * 60)
487
+ print(content or "(empty)")
488
+ print("=" * 60)
489
+
490
+ elif choice == "2":
491
+ # Delete skill
492
+ if not skills:
493
+ print("No skills to delete.")
494
+ continue
495
+ try:
496
+ idx = input("Number to delete (or 0 to cancel): ").strip()
497
+ except (KeyboardInterrupt, EOFError):
498
+ print()
499
+ return
500
+ try:
501
+ idx = int(idx)
502
+ except ValueError:
503
+ print("Invalid number.")
504
+ continue
505
+ if idx == 0:
506
+ continue
507
+ if idx < 1 or idx > len(skills):
508
+ print("Invalid selection.")
509
+ continue
510
+ skill = skills[idx - 1]
511
+ try:
512
+ confirm = input(f"Delete '{skill['role']}/{skill['name']}'? (y/n): ").strip().lower()
513
+ except (KeyboardInterrupt, EOFError):
514
+ print()
515
+ return
516
+ if confirm == 'y' or confirm == 'yes':
517
+ manager.delete_skill(skill['role'], skill['name'])
518
+ print(f"Deleted '{skill['role']}/{skill['name']}'.")
519
+ else:
520
+ print("Cancelled.")
521
+
522
+ elif choice == "3":
523
+ # Export skill to file
524
+ if not skills:
525
+ print("No skills to export.")
526
+ continue
527
+ try:
528
+ idx = input("Number to export (or 0 to cancel): ").strip()
529
+ except (KeyboardInterrupt, EOFError):
530
+ print()
531
+ return
532
+ try:
533
+ idx = int(idx)
534
+ except ValueError:
535
+ print("Invalid number.")
536
+ continue
537
+ if idx == 0:
538
+ continue
539
+ if idx < 1 or idx > len(skills):
540
+ print("Invalid selection.")
541
+ continue
542
+ skill = skills[idx - 1]
543
+ try:
544
+ file_path = input(f"Export to file [skill-{skill['name']}.md]: ").strip()
545
+ except (KeyboardInterrupt, EOFError):
546
+ print()
547
+ return
548
+ if not file_path:
549
+ file_path = f"skill-{skill['name']}.md"
550
+ try:
551
+ manager.export_to_markdown(skill['role'], skill['name'], file_path)
552
+ print(f"Exported '{skill['role']}/{skill['name']}' to {file_path}")
553
+ except Exception as e:
554
+ print(f"Error exporting: {e}")
555
+
556
+ elif choice == "4":
557
+ # Import skill from file
558
+ try:
559
+ role = input(f"Role [{role_filter or 'code'}]: ").strip()
560
+ except (KeyboardInterrupt, EOFError):
561
+ print()
562
+ return
563
+ if not role:
564
+ role = role_filter or "code"
565
+ try:
566
+ name = input("Skill name: ").strip()
567
+ except (KeyboardInterrupt, EOFError):
568
+ print()
569
+ return
570
+ if not name:
571
+ print("Skill name is required.")
572
+ continue
573
+ try:
574
+ file_path = input("File path: ").strip()
575
+ except (KeyboardInterrupt, EOFError):
576
+ print()
577
+ return
578
+ if not file_path:
579
+ print("File path is required.")
580
+ continue
581
+ try:
582
+ manager.import_from_markdown(role, name, file_path)
583
+ print(f"Imported skill '{name}' for role '{role}' from {file_path}")
584
+ except Exception as e:
585
+ print(f"Error importing: {e}")
586
+
587
+ elif choice == "5":
588
+ # Switch to all-roles view
589
+ role_filter = None
590
+
591
+ else:
592
+ print("Invalid choice.")
593
+
594
+
595
+ def main():
596
+ args = parse_args()
597
+
598
+ # Handle skill command
599
+ if getattr(args, "command", None) == "skill":
600
+ handle_skill_command(args)
601
+ return
602
+
603
+ # Handle roles command
604
+ if getattr(args, "command", None) == "roles":
605
+ handle_roles_command()
606
+ return
607
+
608
+ # Handle keys command
609
+ if getattr(args, "command", None) == "keys":
610
+ handle_keys_command()
611
+ return
612
+
613
+ # Handle setup command
614
+ if getattr(args, "command", None) == "setup":
615
+ handle_setup_command()
616
+ return
617
+
618
+ # Resolve and prepare project directory
619
+ project_dir = Path(args.project_dir).resolve()
620
+ if not project_dir.exists():
621
+ project_dir.mkdir(parents=True, exist_ok=True)
622
+ os.chdir(str(project_dir))
623
+
624
+ # Initialize database
625
+ init_db()
626
+
627
+ # Get or create chat
628
+ # Auto-detect interactive mode: if no prompt provided, use interactive mode
629
+ interactive_mode = not args.prompt
630
+
631
+ if interactive_mode:
632
+ # In interactive mode, let user select chat
633
+ chat_id = select_chat(args.role)
634
+ if chat_id is None:
635
+ chat_id = create_chat(args.role)
636
+ else:
637
+ # In non-interactive mode, use latest chat or create new one
638
+ from Agent.chat_history_db import list_chats
639
+ chats = list_chats(args.role)
640
+ if chats:
641
+ chat_id = chats[0]['id'] # Use most recent chat
642
+ else:
643
+ chat_id = create_chat(args.role)
644
+
645
+ # Create agent with the selected chat
646
+ try:
647
+ agent = Agent(args.role, chat_id=chat_id, debug=args.debug)
648
+ except Exception as e:
649
+ print(f"\nError: {e}", file=sys.stderr)
650
+ print("\nRun 'raggie setup' to configure your roles and API keys.", file=sys.stderr)
651
+ sys.exit(1)
652
+
653
+ # Setup tools and commands
654
+ setup_toolcalls(agent.tool_registry)
655
+ setup_commands(agent.command_registry)
656
+
657
+ # Run agent
658
+ if interactive_mode:
659
+ from interactive import run_interactive
660
+ run_interactive(agent, args.role)
661
+ else:
662
+ if not args.prompt:
663
+ print("Error: prompt is required in non-interactive mode")
664
+ from cli import _create_agent_parser
665
+ _create_agent_parser().print_help()
666
+ sys.exit(1)
667
+
668
+ from interactive import run_non_interactive
669
+ run_non_interactive(agent, args.prompt, effort=getattr(args, 'effort', None))
670
+
671
+
672
+ if __name__ == "__main__":
673
+ main()