cognautic-cli 1.1.3__tar.gz → 1.1.4__tar.gz
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.
Potentially problematic release.
This version of cognautic-cli might be problematic. Click here for more details.
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/PKG-INFO +2 -2
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/__init__.py +1 -1
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/ai_engine.py +161 -15
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/auto_continuation.py +88 -93
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/cli.py +45 -46
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/tools/__init__.py +5 -1
- cognautic_cli-1.1.4/cognautic/tools/code_navigation.py +642 -0
- cognautic_cli-1.1.4/cognautic/tools/directory_context.py +340 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/tools/registry.py +4 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic_cli.egg-info/PKG-INFO +2 -2
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic_cli.egg-info/SOURCES.txt +3 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/pyproject.toml +2 -2
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/setup.py +1 -1
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/LICENSE +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/README.md +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/config.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/file_tagger.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/memory.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/provider_endpoints.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/rules.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/tools/base.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/tools/code_analysis.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/tools/command_runner.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/tools/file_operations.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/tools/file_reader.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/tools/response_control.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/tools/web_search.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/utils.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic/websocket_server.py +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic_cli.egg-info/dependency_links.txt +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic_cli.egg-info/entry_points.txt +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic_cli.egg-info/requires.txt +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/cognautic_cli.egg-info/top_level.txt +0 -0
- {cognautic_cli-1.1.3 → cognautic_cli-1.1.4}/setup.cfg +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cognautic-cli
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.4
|
|
4
4
|
Summary: A Python-based CLI AI coding agent that provides agentic development capabilities with multi-provider AI support and real-time interaction
|
|
5
5
|
Home-page: https://github.com/cognautic/cognautic-cli
|
|
6
6
|
Author: Cognautic
|
|
7
7
|
Author-email: Cognautic <cognautic@gmail.com>
|
|
8
8
|
Maintainer-email: Cognautic <cognautic@gmail.com>
|
|
9
|
-
License:
|
|
9
|
+
License-Expression: MIT
|
|
10
10
|
Project-URL: Homepage, https://github.com/cognautic/cli
|
|
11
11
|
Project-URL: Documentation, https://cognautic.vercel.app/cognautic-cli.html
|
|
12
12
|
Project-URL: Repository, https://github.com/cognautic/cli.git
|
|
@@ -960,7 +960,7 @@ class AIEngine:
|
|
|
960
960
|
return
|
|
961
961
|
|
|
962
962
|
# Only continue if the auto-continuation manager says so
|
|
963
|
-
if self.auto_continuation.should_continue(tool_results, has_end_response):
|
|
963
|
+
if self.auto_continuation.should_continue(tool_results, has_end_response, buffer):
|
|
964
964
|
yield "... \n"
|
|
965
965
|
|
|
966
966
|
# Add assistant's response to messages
|
|
@@ -968,7 +968,7 @@ class AIEngine:
|
|
|
968
968
|
|
|
969
969
|
# Build continuation prompt
|
|
970
970
|
continuation_prompt = self.auto_continuation.build_continuation_prompt(
|
|
971
|
-
tool_results
|
|
971
|
+
tool_results, buffer
|
|
972
972
|
)
|
|
973
973
|
|
|
974
974
|
# Add continuation prompt to messages
|
|
@@ -2255,6 +2255,79 @@ For web search:
|
|
|
2255
2255
|
}
|
|
2256
2256
|
```
|
|
2257
2257
|
|
|
2258
|
+
For directory context and project structure:
|
|
2259
|
+
|
|
2260
|
+
```json
|
|
2261
|
+
{
|
|
2262
|
+
"tool_code": "directory_context",
|
|
2263
|
+
"args": {
|
|
2264
|
+
"operation": "get_directory_summary"
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
```
|
|
2268
|
+
|
|
2269
|
+
```json
|
|
2270
|
+
{
|
|
2271
|
+
"tool_code": "directory_context",
|
|
2272
|
+
"args": {
|
|
2273
|
+
"operation": "list_directory_tree",
|
|
2274
|
+
"max_depth": 3
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
```
|
|
2278
|
+
|
|
2279
|
+
```json
|
|
2280
|
+
{
|
|
2281
|
+
"tool_code": "directory_context",
|
|
2282
|
+
"args": {
|
|
2283
|
+
"operation": "get_project_structure"
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
```
|
|
2287
|
+
|
|
2288
|
+
For code navigation (jump to definition, find references, search symbols):
|
|
2289
|
+
|
|
2290
|
+
```json
|
|
2291
|
+
{
|
|
2292
|
+
"tool_code": "code_navigation",
|
|
2293
|
+
"args": {
|
|
2294
|
+
"operation": "jump_to_definition",
|
|
2295
|
+
"symbol": "MyClass"
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
```
|
|
2299
|
+
|
|
2300
|
+
```json
|
|
2301
|
+
{
|
|
2302
|
+
"tool_code": "code_navigation",
|
|
2303
|
+
"args": {
|
|
2304
|
+
"operation": "find_references",
|
|
2305
|
+
"symbol": "myFunction"
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
```
|
|
2309
|
+
|
|
2310
|
+
```json
|
|
2311
|
+
{
|
|
2312
|
+
"tool_code": "code_navigation",
|
|
2313
|
+
"args": {
|
|
2314
|
+
"operation": "search_symbols",
|
|
2315
|
+
"query": "handler",
|
|
2316
|
+
"symbol_type": "function"
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
```
|
|
2320
|
+
|
|
2321
|
+
```json
|
|
2322
|
+
{
|
|
2323
|
+
"tool_code": "code_navigation",
|
|
2324
|
+
"args": {
|
|
2325
|
+
"operation": "list_symbols",
|
|
2326
|
+
"file_path": "src/main.py"
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
```
|
|
2330
|
+
|
|
2258
2331
|
EXAMPLE WORKFLOWS:
|
|
2259
2332
|
|
|
2260
2333
|
When user asks to ADD FEATURE to existing project (e.g., "add export button to cymox"):
|
|
@@ -2308,10 +2381,14 @@ Available tools:
|
|
|
2308
2381
|
- command_runner: Execute shell commands (use run_async_command for long tasks)
|
|
2309
2382
|
- web_search: Search the web for information
|
|
2310
2383
|
- response_control: Use end_response when task is complete
|
|
2384
|
+
- directory_context: Get detailed directory structure and project information
|
|
2385
|
+
- code_navigation: Jump to definition, find references, search symbols in code
|
|
2386
|
+
|
|
2387
|
+
RESPONSE CONTINUATION (CRITICAL - SYSTEM WILL AUTO-CONTINUE):
|
|
2388
|
+
- ⚠️ IMPORTANT: The system will AUTOMATICALLY continue your response after EVERY message
|
|
2389
|
+
- This means you will KEEP GETTING called until you explicitly call end_response
|
|
2390
|
+
- YOU MUST call end_response when you finish ALL work, or you'll loop forever:
|
|
2311
2391
|
|
|
2312
|
-
RESPONSE CONTINUATION (CRITICAL - ALWAYS USE end_response):
|
|
2313
|
-
- By default, after executing tools, the AI will automatically continue to complete the task
|
|
2314
|
-
- YOU MUST ALWAYS use the response_control tool when you finish ALL work:
|
|
2315
2392
|
```json
|
|
2316
2393
|
{
|
|
2317
2394
|
"tool_code": "response_control",
|
|
@@ -2320,16 +2397,21 @@ RESPONSE CONTINUATION (CRITICAL - ALWAYS USE end_response):
|
|
|
2320
2397
|
}
|
|
2321
2398
|
}
|
|
2322
2399
|
```
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
*
|
|
2326
|
-
*
|
|
2327
|
-
*
|
|
2328
|
-
|
|
2329
|
-
*
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2400
|
+
|
|
2401
|
+
- HOW IT WORKS:
|
|
2402
|
+
* You respond → System auto-continues → You respond again → System auto-continues → ...
|
|
2403
|
+
* This loop ONLY stops when you call end_response
|
|
2404
|
+
* If you say "I will use a tool" but don't execute it, system will auto-continue and remind you
|
|
2405
|
+
* If you execute tools, system will auto-continue for next steps
|
|
2406
|
+
* If you do nothing, system will still auto-continue
|
|
2407
|
+
|
|
2408
|
+
- WHEN TO USE end_response (THE ONLY WAY TO STOP):
|
|
2409
|
+
* ✅ After completing ALL requested work
|
|
2410
|
+
* ✅ After providing final summary/instructions
|
|
2411
|
+
* ✅ When task is 100% complete
|
|
2412
|
+
* ❌ NEVER use it if task is incomplete - system will continue for you
|
|
2413
|
+
|
|
2414
|
+
- CRITICAL: If you forget end_response, you'll keep getting called in an infinite loop!
|
|
2333
2415
|
|
|
2334
2416
|
REMEMBER:
|
|
2335
2417
|
1. Use tools to actually perform actions, don't just provide code examples!
|
|
@@ -2459,6 +2541,70 @@ When creating or modifying files:
|
|
|
2459
2541
|
if project_path:
|
|
2460
2542
|
prompt += f"\n\nCurrent project path: {project_path}"
|
|
2461
2543
|
prompt += "\nYou can analyze and modify files in this project."
|
|
2544
|
+
|
|
2545
|
+
# Add directory context automatically
|
|
2546
|
+
try:
|
|
2547
|
+
from pathlib import Path
|
|
2548
|
+
project_dir = Path(project_path)
|
|
2549
|
+
if project_dir.exists() and project_dir.is_dir():
|
|
2550
|
+
prompt += "\n\n" + "=" * 80
|
|
2551
|
+
prompt += "\n📁 CURRENT DIRECTORY CONTENTS (AUTO-PROVIDED FOR CONTEXT)"
|
|
2552
|
+
prompt += "\n" + "=" * 80
|
|
2553
|
+
|
|
2554
|
+
# List immediate directory contents
|
|
2555
|
+
contents = []
|
|
2556
|
+
files = []
|
|
2557
|
+
dirs = []
|
|
2558
|
+
|
|
2559
|
+
try:
|
|
2560
|
+
for item in sorted(project_dir.iterdir()):
|
|
2561
|
+
if item.name.startswith('.') and item.name not in ['.gitignore', '.env.example']:
|
|
2562
|
+
continue # Skip hidden files except important ones
|
|
2563
|
+
|
|
2564
|
+
if item.is_dir():
|
|
2565
|
+
# Count items in directory
|
|
2566
|
+
try:
|
|
2567
|
+
item_count = len(list(item.iterdir()))
|
|
2568
|
+
dirs.append(f" 📂 {item.name}/ ({item_count} items)")
|
|
2569
|
+
except (PermissionError, OSError):
|
|
2570
|
+
dirs.append(f" 📂 {item.name}/")
|
|
2571
|
+
else:
|
|
2572
|
+
# Get file size
|
|
2573
|
+
try:
|
|
2574
|
+
size = item.stat().st_size
|
|
2575
|
+
if size < 1024:
|
|
2576
|
+
size_str = f"{size}B"
|
|
2577
|
+
elif size < 1024 * 1024:
|
|
2578
|
+
size_str = f"{size/1024:.1f}KB"
|
|
2579
|
+
else:
|
|
2580
|
+
size_str = f"{size/(1024*1024):.1f}MB"
|
|
2581
|
+
files.append(f" 📄 {item.name} ({size_str})")
|
|
2582
|
+
except (PermissionError, OSError):
|
|
2583
|
+
files.append(f" 📄 {item.name}")
|
|
2584
|
+
|
|
2585
|
+
prompt += f"\n\nDirectory: {project_path}"
|
|
2586
|
+
prompt += f"\n\nDirectories ({len(dirs)}):"
|
|
2587
|
+
if dirs:
|
|
2588
|
+
prompt += "\n" + "\n".join(dirs)
|
|
2589
|
+
else:
|
|
2590
|
+
prompt += "\n (none)"
|
|
2591
|
+
|
|
2592
|
+
prompt += f"\n\nFiles ({len(files)}):"
|
|
2593
|
+
if files:
|
|
2594
|
+
prompt += "\n" + "\n".join(files)
|
|
2595
|
+
else:
|
|
2596
|
+
prompt += "\n (none)"
|
|
2597
|
+
|
|
2598
|
+
prompt += "\n\n" + "=" * 80
|
|
2599
|
+
prompt += "\nℹ️ This directory listing is provided automatically for your context."
|
|
2600
|
+
prompt += "\nℹ️ You can use the 'directory_context' tool for more detailed information."
|
|
2601
|
+
prompt += "\n" + "=" * 80
|
|
2602
|
+
|
|
2603
|
+
except PermissionError:
|
|
2604
|
+
prompt += "\n\n⚠️ Permission denied accessing directory contents."
|
|
2605
|
+
except Exception:
|
|
2606
|
+
# Silently fail if directory context can't be added
|
|
2607
|
+
pass
|
|
2462
2608
|
|
|
2463
2609
|
return prompt
|
|
2464
2610
|
|
|
@@ -26,129 +26,124 @@ class AutoContinuationManager:
|
|
|
26
26
|
self.iteration_count = 0
|
|
27
27
|
self.previous_tool_types = []
|
|
28
28
|
|
|
29
|
-
def should_continue(
|
|
29
|
+
def should_continue(
|
|
30
|
+
self,
|
|
31
|
+
tool_results: List[Dict[str, Any]],
|
|
32
|
+
has_end_response: bool,
|
|
33
|
+
ai_response: str = ""
|
|
34
|
+
) -> bool:
|
|
30
35
|
"""
|
|
31
36
|
Determine if AI should automatically continue
|
|
32
37
|
|
|
38
|
+
SIMPLIFIED LOGIC: Always continue EXCEPT when end_response is called
|
|
39
|
+
This ensures AI completes tasks without manual intervention
|
|
40
|
+
|
|
33
41
|
Args:
|
|
34
42
|
tool_results: List of tool execution results
|
|
35
43
|
has_end_response: Whether end_response tool was called
|
|
44
|
+
ai_response: The AI's text response (to detect promises without execution)
|
|
36
45
|
|
|
37
46
|
Returns:
|
|
38
47
|
True if should continue, False otherwise
|
|
39
48
|
"""
|
|
40
|
-
# Don't continue if end_response was explicitly called
|
|
49
|
+
# RULE 1: Don't continue if end_response was explicitly called
|
|
50
|
+
# This is the ONLY condition that stops auto-continuation
|
|
41
51
|
if has_end_response:
|
|
42
52
|
return False
|
|
43
53
|
|
|
44
|
-
# Don't continue if max iterations reached
|
|
54
|
+
# RULE 2: Don't continue if max iterations reached (safety limit)
|
|
45
55
|
if self.iteration_count >= self.max_iterations:
|
|
46
56
|
return False
|
|
47
57
|
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
self.previous_tool_types = self.previous_tool_types[-5:] # Keep only last 5
|
|
91
|
-
|
|
92
|
-
# Smart continuation logic:
|
|
93
|
-
# 1. If there are errors, continue to let AI handle them
|
|
94
|
-
if has_errors:
|
|
95
|
-
self.iteration_count += 1
|
|
96
|
-
return True
|
|
97
|
-
|
|
98
|
-
# 2. If file was read, always continue so AI can act on the content
|
|
99
|
-
if has_file_read:
|
|
100
|
-
self.iteration_count += 1
|
|
101
|
-
return True
|
|
102
|
-
|
|
103
|
-
# 3. If repeating the same operation (like web searches), don't continue to break the loop
|
|
104
|
-
if is_repeating_search and self.iteration_count > 3:
|
|
105
|
-
return False # Break the loop if repeating searches for more than 3 iterations
|
|
106
|
-
|
|
107
|
-
# 4. If only file operations were done, likely need to run commands next
|
|
108
|
-
if has_file_ops and not has_commands:
|
|
109
|
-
self.iteration_count += 1
|
|
110
|
-
return True
|
|
111
|
-
|
|
112
|
-
# 5. If only exploratory commands (ls, pwd, etc.), continue to do actual work
|
|
113
|
-
if has_exploratory_commands and not (has_file_ops or has_file_read):
|
|
114
|
-
self.iteration_count += 1
|
|
115
|
-
return True
|
|
116
|
-
|
|
117
|
-
# 6. If web search was performed, often AI should continue to create files/execute commands with the information
|
|
118
|
-
if has_web_search and not has_file_ops:
|
|
119
|
-
self.iteration_count += 1
|
|
120
|
-
return True
|
|
121
|
-
|
|
122
|
-
# 7. If background commands were started AND files created, task likely complete
|
|
123
|
-
if has_background_commands and (has_file_ops or has_file_read):
|
|
124
|
-
return False
|
|
125
|
-
|
|
126
|
-
# 8. If only background commands (no files), don't continue (they're running)
|
|
127
|
-
if has_background_commands and not (has_file_ops or has_file_read):
|
|
128
|
-
return False
|
|
129
|
-
|
|
130
|
-
# 9. If regular commands executed with file ops, task likely complete
|
|
131
|
-
if has_commands and has_file_ops and not has_exploratory_commands:
|
|
132
|
-
return False
|
|
58
|
+
# RULE 3: Check if AI said it would use tools but didn't execute them
|
|
59
|
+
# This handles the scenario where AI says "I will use X tool" but doesn't
|
|
60
|
+
if not tool_results and ai_response:
|
|
61
|
+
promise_indicators = [
|
|
62
|
+
"i will use",
|
|
63
|
+
"i can use",
|
|
64
|
+
"let me use",
|
|
65
|
+
"i'll use",
|
|
66
|
+
"using the",
|
|
67
|
+
"i will call",
|
|
68
|
+
"i'll call",
|
|
69
|
+
"let me call",
|
|
70
|
+
"i can help you with that",
|
|
71
|
+
"i will list",
|
|
72
|
+
"i will find",
|
|
73
|
+
"i will search",
|
|
74
|
+
"i will check",
|
|
75
|
+
"i will analyze",
|
|
76
|
+
"i will show",
|
|
77
|
+
"i will count"
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
ai_lower = ai_response.lower()
|
|
81
|
+
|
|
82
|
+
# Check if AI promised to use tools
|
|
83
|
+
promised_to_use_tools = any(indicator in ai_lower for indicator in promise_indicators)
|
|
84
|
+
|
|
85
|
+
# Check if AI mentioned specific tools
|
|
86
|
+
tool_mentions = [
|
|
87
|
+
"code_navigation",
|
|
88
|
+
"directory_context",
|
|
89
|
+
"file_operations",
|
|
90
|
+
"web_search",
|
|
91
|
+
"command_runner",
|
|
92
|
+
"tool"
|
|
93
|
+
]
|
|
94
|
+
mentioned_tools = any(tool in ai_lower for tool in tool_mentions)
|
|
95
|
+
|
|
96
|
+
# If AI promised to use tools or mentioned specific tools but didn't execute any
|
|
97
|
+
if promised_to_use_tools or mentioned_tools:
|
|
98
|
+
self.iteration_count += 1
|
|
99
|
+
return True
|
|
133
100
|
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
if
|
|
101
|
+
# RULE 4: If no tools were executed and no promises detected, still continue
|
|
102
|
+
# The AI might need another chance to complete the task
|
|
103
|
+
if not tool_results:
|
|
137
104
|
self.iteration_count += 1
|
|
138
105
|
return True
|
|
139
106
|
|
|
140
|
-
|
|
107
|
+
# RULE 5: If ANY tools were executed, ALWAYS continue
|
|
108
|
+
# Let the AI decide when it's done by calling end_response
|
|
109
|
+
self.iteration_count += 1
|
|
110
|
+
return True
|
|
141
111
|
|
|
142
|
-
def build_continuation_prompt(
|
|
112
|
+
def build_continuation_prompt(
|
|
113
|
+
self,
|
|
114
|
+
tool_results: List[Dict[str, Any]],
|
|
115
|
+
ai_response: str = ""
|
|
116
|
+
) -> str:
|
|
143
117
|
"""
|
|
144
118
|
Build an appropriate continuation prompt based on tool results
|
|
145
119
|
|
|
146
120
|
Args:
|
|
147
121
|
tool_results: List of tool execution results
|
|
122
|
+
ai_response: The AI's text response (to provide context)
|
|
148
123
|
|
|
149
124
|
Returns:
|
|
150
125
|
Continuation prompt string
|
|
151
126
|
"""
|
|
127
|
+
# Special case: AI said it would use tools but didn't
|
|
128
|
+
if not tool_results and ai_response:
|
|
129
|
+
return """You said you would use a tool, but you didn't actually execute it.
|
|
130
|
+
|
|
131
|
+
CRITICAL: You MUST include the actual JSON tool call in your response, not just say you will do it.
|
|
132
|
+
|
|
133
|
+
For example, if you want to use code_navigation to list symbols, you MUST include:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"tool_code": "code_navigation",
|
|
138
|
+
"args": {
|
|
139
|
+
"operation": "list_symbols",
|
|
140
|
+
"file_path": "main.py"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Now execute the tool you mentioned:"""
|
|
146
|
+
|
|
152
147
|
# Categorize tool results
|
|
153
148
|
has_file_ops = any(r.get('type') in ['file_op', 'file_write', 'file_read'] for r in tool_results)
|
|
154
149
|
has_commands = any(r.get('type') == 'command' for r in tool_results)
|
|
@@ -75,19 +75,8 @@ from .rules import RulesManager
|
|
|
75
75
|
|
|
76
76
|
console = Console()
|
|
77
77
|
|
|
78
|
-
# Global flag to stop AI response
|
|
79
|
-
stop_response = False
|
|
80
|
-
|
|
81
|
-
def signal_handler(signum, frame):
|
|
82
|
-
"""Handle Ctrl+X (SIGQUIT) to stop AI response"""
|
|
83
|
-
global stop_response
|
|
84
|
-
stop_response = True
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
78
|
@click.group(invoke_without_command=True)
|
|
90
|
-
@click.version_option(version="1.1.
|
|
79
|
+
@click.version_option(version="1.1.4", prog_name="Cognautic CLI")
|
|
91
80
|
@click.pass_context
|
|
92
81
|
def main(ctx):
|
|
93
82
|
"""Cognautic CLI - AI-powered development assistant"""
|
|
@@ -187,16 +176,16 @@ def chat(provider, model, project_path, websocket_port, session):
|
|
|
187
176
|
websocket_server = WebSocketServer(ai_engine, port=websocket_port)
|
|
188
177
|
|
|
189
178
|
async def run_chat():
|
|
190
|
-
global stop_response
|
|
191
|
-
|
|
192
|
-
# Set up Ctrl+X signal handler (SIGQUIT)
|
|
193
|
-
signal.signal(signal.SIGQUIT, signal_handler)
|
|
194
|
-
|
|
195
179
|
# Start WebSocket server
|
|
196
180
|
server_task = asyncio.create_task(websocket_server.start())
|
|
197
181
|
|
|
182
|
+
# Track last Ctrl+C time for double-tap detection
|
|
183
|
+
import time
|
|
184
|
+
last_ctrl_c_time = [0] # Use list to make it mutable in nested function
|
|
185
|
+
|
|
198
186
|
try:
|
|
199
|
-
console.print("INFO: Type '/help' for commands,
|
|
187
|
+
console.print("INFO: Type '/help' for commands, or press Ctrl+C twice to exit")
|
|
188
|
+
console.print("INFO: Press Enter to send, Alt+Enter for new line")
|
|
200
189
|
console.print("INFO: Press Shift+Tab to toggle Terminal mode")
|
|
201
190
|
if project_path:
|
|
202
191
|
console.print(f"DIR: Working in: {project_path}")
|
|
@@ -246,7 +235,7 @@ def chat(provider, model, project_path, websocket_port, session):
|
|
|
246
235
|
# Terminal mode state
|
|
247
236
|
terminal_mode = [False] # Use list to make it mutable in nested function
|
|
248
237
|
|
|
249
|
-
# Setup key bindings for Shift+Tab toggle
|
|
238
|
+
# Setup key bindings for Shift+Tab toggle and multi-line support
|
|
250
239
|
bindings = KeyBindings()
|
|
251
240
|
|
|
252
241
|
@bindings.add('s-tab') # Shift+Tab
|
|
@@ -257,8 +246,19 @@ def chat(provider, model, project_path, websocket_port, session):
|
|
|
257
246
|
event.app.current_buffer.text = ''
|
|
258
247
|
event.app.exit(result='__MODE_TOGGLE__')
|
|
259
248
|
|
|
260
|
-
#
|
|
261
|
-
|
|
249
|
+
@bindings.add('enter') # Enter key submits
|
|
250
|
+
def submit_message(event):
|
|
251
|
+
event.current_buffer.validate_and_handle()
|
|
252
|
+
|
|
253
|
+
@bindings.add('escape', 'enter') # Meta+Enter (Alt+Enter) adds new line
|
|
254
|
+
def new_line(event):
|
|
255
|
+
event.current_buffer.insert_text('\n')
|
|
256
|
+
|
|
257
|
+
# Create prompt session with multi-line support
|
|
258
|
+
session = PromptSession(
|
|
259
|
+
key_bindings=bindings,
|
|
260
|
+
multiline=True # Allow multi-line editing
|
|
261
|
+
)
|
|
262
262
|
|
|
263
263
|
console.print("[dim]INFO: Press Shift+Tab to toggle between Chat and Terminal modes[/dim]\n")
|
|
264
264
|
|
|
@@ -339,7 +339,7 @@ def chat(provider, model, project_path, websocket_port, session):
|
|
|
339
339
|
console.print(f"[yellow]Exit code: {returncode}[/yellow]")
|
|
340
340
|
|
|
341
341
|
except KeyboardInterrupt:
|
|
342
|
-
# Handle Ctrl+C - terminate the process
|
|
342
|
+
# Handle Ctrl+C - terminate the process but don't exit CLI
|
|
343
343
|
console.print("\n[yellow]^C[/yellow]")
|
|
344
344
|
process.terminate()
|
|
345
345
|
try:
|
|
@@ -419,8 +419,10 @@ def chat(provider, model, project_path, websocket_port, session):
|
|
|
419
419
|
console.print("[bold magenta]─[/bold magenta]" * 50)
|
|
420
420
|
console.print("[bold magenta]AI:[/bold magenta] ", end="")
|
|
421
421
|
full_response = ""
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
|
|
423
|
+
# Disable Ctrl+C during AI response
|
|
424
|
+
import signal
|
|
425
|
+
original_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
424
426
|
|
|
425
427
|
try:
|
|
426
428
|
async for chunk in ai_engine.process_message_stream(
|
|
@@ -430,42 +432,36 @@ def chat(provider, model, project_path, websocket_port, session):
|
|
|
430
432
|
project_path=current_workspace,
|
|
431
433
|
conversation_history=conversation_history
|
|
432
434
|
):
|
|
433
|
-
# Check if stop was requested
|
|
434
|
-
if stop_response:
|
|
435
|
-
response_stopped = True
|
|
436
|
-
console.print("\n\n[yellow]⏸️ AI response stopped by user (Ctrl+X)[/yellow]")
|
|
437
|
-
break
|
|
438
|
-
|
|
439
435
|
# Display character by character for typewriter effect
|
|
440
436
|
for char in chunk:
|
|
441
|
-
# Check stop flag during character display too
|
|
442
|
-
if stop_response:
|
|
443
|
-
response_stopped = True
|
|
444
|
-
console.print("\n\n[yellow]⏸️ AI response stopped by user (Ctrl+X)[/yellow]")
|
|
445
|
-
break
|
|
446
437
|
console.print(char, end="")
|
|
447
438
|
full_response += char
|
|
448
439
|
if typing_delay > 0:
|
|
449
440
|
await asyncio.sleep(typing_delay)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
except KeyboardInterrupt:
|
|
454
|
-
# Ctrl+C pressed - exit chat
|
|
455
|
-
break
|
|
441
|
+
finally:
|
|
442
|
+
# Re-enable Ctrl+C after AI response
|
|
443
|
+
signal.signal(signal.SIGINT, original_handler)
|
|
456
444
|
|
|
457
445
|
console.print() # New line after streaming
|
|
458
446
|
console.print("[bold magenta]─[/bold magenta]" * 50) # Border after AI response
|
|
459
447
|
|
|
460
|
-
# Add AI response to memory
|
|
448
|
+
# Add AI response to memory
|
|
461
449
|
if full_response:
|
|
462
450
|
memory_manager.add_message("assistant", full_response)
|
|
463
451
|
|
|
464
|
-
# Don't break - continue to next prompt
|
|
465
|
-
|
|
466
452
|
except KeyboardInterrupt:
|
|
467
|
-
# Ctrl+C pressed while waiting for user input
|
|
468
|
-
|
|
453
|
+
# Ctrl+C pressed while waiting for user input
|
|
454
|
+
current_time = time.time()
|
|
455
|
+
time_since_last = current_time - last_ctrl_c_time[0]
|
|
456
|
+
|
|
457
|
+
if time_since_last < 2.0: # Double tap within 2 seconds
|
|
458
|
+
console.print("\n[yellow]Exiting...[/yellow]")
|
|
459
|
+
break
|
|
460
|
+
else:
|
|
461
|
+
# First Ctrl+C - show hint
|
|
462
|
+
console.print("\n[dim]Press Ctrl+C again within 2 seconds to exit, or type 'exit'[/dim]")
|
|
463
|
+
last_ctrl_c_time[0] = current_time
|
|
464
|
+
continue
|
|
469
465
|
except Exception as e:
|
|
470
466
|
console.print(f"ERROR: {str(e)}", style="red")
|
|
471
467
|
|
|
@@ -1200,6 +1196,9 @@ def show_help():
|
|
|
1200
1196
|
"""Show help information"""
|
|
1201
1197
|
help_text = Text()
|
|
1202
1198
|
help_text.append("Available commands:\n", style="bold")
|
|
1199
|
+
help_text.append("• Press Enter - Send message\n", style="bold green")
|
|
1200
|
+
help_text.append("• Press Alt+Enter - New line (multi-line input)\n", style="bold green")
|
|
1201
|
+
help_text.append("• Press Ctrl+C twice - Exit CLI\n", style="bold yellow")
|
|
1203
1202
|
help_text.append("• Press Shift+Tab - Toggle between Chat and Terminal modes\n", style="bold yellow")
|
|
1204
1203
|
help_text.append("• /help - Show this help message\n")
|
|
1205
1204
|
help_text.append("• /workspace <path> or /ws <path> - Change working directory\n")
|
|
@@ -8,6 +8,8 @@ from .command_runner import CommandRunnerTool
|
|
|
8
8
|
from .web_search import WebSearchTool
|
|
9
9
|
from .code_analysis import CodeAnalysisTool
|
|
10
10
|
from .response_control import ResponseControlTool
|
|
11
|
+
from .directory_context import DirectoryContextTool
|
|
12
|
+
from .code_navigation import CodeNavigationTool
|
|
11
13
|
|
|
12
14
|
__all__ = [
|
|
13
15
|
'ToolRegistry',
|
|
@@ -15,5 +17,7 @@ __all__ = [
|
|
|
15
17
|
'CommandRunnerTool',
|
|
16
18
|
'WebSearchTool',
|
|
17
19
|
'CodeAnalysisTool',
|
|
18
|
-
'ResponseControlTool'
|
|
20
|
+
'ResponseControlTool',
|
|
21
|
+
'DirectoryContextTool',
|
|
22
|
+
'CodeNavigationTool'
|
|
19
23
|
]
|