cognautic-cli 1.1.1__py3-none-any.whl → 1.1.2__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.
cognautic/__init__.py CHANGED
@@ -2,6 +2,6 @@
2
2
  Cognautic CLI - A Python-based CLI AI coding agent
3
3
  """
4
4
 
5
- __version__ = "1.1.1"
6
- __author__ = "cognautic"
7
- __email__ = "team@cognautic.com"
5
+ __version__ = "1.1.2"
6
+ __author__ = "Cognautic"
7
+ __email__ = "cognautic@gmail.com"
cognautic/ai_engine.py CHANGED
@@ -779,20 +779,274 @@ class AIEngine:
779
779
  if max_tokens == 0 or max_tokens == -1:
780
780
  max_tokens = None # Treat 0 or -1 as unlimited
781
781
 
782
- # Generate response and process with tools
783
- response = await ai_provider.generate_response(
784
- messages=messages,
785
- model=model,
786
- max_tokens=max_tokens or 16384, # Use large default if None
787
- temperature=config.get("temperature", 0.7),
788
- )
789
-
790
- # Process response with tools
791
- async for chunk in self._process_response_with_tools(
792
- response, project_path, messages, ai_provider, model, config
782
+ # Stream response with real-time tool detection and execution
783
+ async for chunk in self._stream_with_live_tools(
784
+ ai_provider, messages, model, max_tokens, config, project_path
793
785
  ):
794
786
  yield chunk
795
787
 
788
+ async def _stream_with_live_tools(
789
+ self,
790
+ ai_provider,
791
+ messages: list,
792
+ model: str,
793
+ max_tokens: int,
794
+ config: dict,
795
+ project_path: str,
796
+ recursion_depth: int = 0,
797
+ ):
798
+ """Stream AI response with real-time tool detection and execution"""
799
+ import re
800
+ import json
801
+
802
+ # Prevent infinite recursion
803
+ MAX_RECURSION_DEPTH = 10
804
+ if recursion_depth >= MAX_RECURSION_DEPTH:
805
+ yield "\n⚠️ **Warning:** Maximum continuation depth reached.\n"
806
+ return
807
+
808
+ # Buffer to accumulate streaming response
809
+ buffer = ""
810
+ yielded_length = 0
811
+ in_json_block = False
812
+ json_block_start = -1
813
+ has_executed_tools = False
814
+ tool_results = []
815
+
816
+ # Check if provider supports streaming
817
+ if not hasattr(ai_provider, 'generate_response_stream'):
818
+ # Fall back to non-streaming
819
+ response = await ai_provider.generate_response(
820
+ messages=messages,
821
+ model=model,
822
+ max_tokens=max_tokens or 16384,
823
+ temperature=config.get("temperature", 0.7),
824
+ )
825
+ async for chunk in self._process_response_with_tools(
826
+ response, project_path, messages, ai_provider, model, config
827
+ ):
828
+ yield chunk
829
+ return
830
+
831
+ # Stream the response
832
+ try:
833
+ async for chunk in ai_provider.generate_response_stream(
834
+ messages=messages,
835
+ model=model,
836
+ max_tokens=max_tokens or 16384,
837
+ temperature=config.get("temperature", 0.7),
838
+ ):
839
+ if not chunk:
840
+ continue
841
+
842
+ buffer += chunk
843
+
844
+ # Check for JSON block markers
845
+ if not in_json_block:
846
+ # Look for start of JSON block
847
+ json_start_match = re.search(r'```json\s*\n', buffer[yielded_length:])
848
+ if json_start_match:
849
+ # Found start of JSON block
850
+ in_json_block = True
851
+ json_block_start = yielded_length + json_start_match.start()
852
+
853
+ # Yield everything BEFORE the JSON block
854
+ if json_block_start > yielded_length:
855
+ text_to_yield = buffer[yielded_length:json_block_start]
856
+ text_to_yield = self._clean_model_syntax(text_to_yield)
857
+ if text_to_yield.strip():
858
+ yield text_to_yield
859
+ yielded_length = json_block_start
860
+ else:
861
+ # No JSON block yet, yield new content (keep small buffer)
862
+ if len(buffer) > yielded_length + 10:
863
+ text_to_yield = buffer[yielded_length:-10]
864
+ text_to_yield = self._clean_model_syntax(text_to_yield)
865
+ if text_to_yield:
866
+ yield text_to_yield
867
+ yielded_length = len(buffer) - 10
868
+ else:
869
+ # Inside JSON block, look for the end
870
+ json_end_match = re.search(r'\n```', buffer[json_block_start:])
871
+ if json_end_match:
872
+ # Found end of JSON block
873
+ json_block_end = json_block_start + json_end_match.end()
874
+
875
+ # Extract the complete JSON block
876
+ json_block = buffer[json_block_start:json_block_end]
877
+
878
+ # Parse and execute the tool call immediately
879
+ json_pattern = r'```json\s*\n(.*?)\n```'
880
+ match = re.search(json_pattern, json_block, re.DOTALL)
881
+ if match:
882
+ try:
883
+ tool_call = json.loads(match.group(1).strip())
884
+
885
+ # Execute tool and yield result immediately
886
+ async for tool_result in self._execute_single_tool_live(
887
+ tool_call, project_path, tool_results
888
+ ):
889
+ yield tool_result
890
+
891
+ has_executed_tools = True
892
+
893
+ except json.JSONDecodeError as je:
894
+ yield f"\n⚠️ **JSON Parse Warning:** {str(je)}\n"
895
+
896
+ # Update state
897
+ in_json_block = False
898
+ yielded_length = json_block_end
899
+ json_block_start = -1
900
+
901
+ # Yield any remaining content
902
+ if yielded_length < len(buffer):
903
+ remaining = buffer[yielded_length:]
904
+ if not in_json_block:
905
+ remaining = self._clean_model_syntax(remaining)
906
+ if remaining.strip():
907
+ yield remaining
908
+
909
+ # Check if end_response was called
910
+ has_end_response = any(r.get("type") == "control" and r.get("message") == "end_response" for r in tool_results)
911
+
912
+ # Don't auto-continue if:
913
+ # 1. end_response was explicitly called
914
+ # 2. No tools were executed
915
+ # 3. Max recursion depth reached
916
+ if has_end_response or not has_executed_tools or recursion_depth >= MAX_RECURSION_DEPTH:
917
+ return
918
+
919
+ # Only continue if the auto-continuation manager says so
920
+ if self.auto_continuation.should_continue(tool_results, has_end_response):
921
+ yield "\n\n🔄 **Auto-continuing...**\n\n"
922
+
923
+ # Add assistant's response to messages
924
+ messages.append({"role": "assistant", "content": buffer})
925
+
926
+ # Generate continuation
927
+ final_response = await self.auto_continuation.generate_continuation(
928
+ ai_provider=ai_provider,
929
+ messages=messages,
930
+ tool_results=tool_results,
931
+ model=model,
932
+ config=config
933
+ )
934
+
935
+ # Recursively stream continuation
936
+ async for chunk in self._stream_with_live_tools(
937
+ ai_provider, messages, model, max_tokens, config, project_path, recursion_depth + 1
938
+ ):
939
+ yield chunk
940
+
941
+ except Exception as e:
942
+ import traceback
943
+ yield f"\n❌ **Streaming Error:** {str(e)}\n"
944
+ yield f"\n**Traceback:**\n{traceback.format_exc()}\n"
945
+
946
+ async def _execute_single_tool_live(self, tool_call: dict, project_path: str, tool_results: list):
947
+ """Execute a single tool call and yield the result immediately"""
948
+ try:
949
+ tool_name = tool_call.get("tool_code")
950
+ args = tool_call.get("args", {})
951
+
952
+ # Check for response control tool
953
+ if tool_name == "response_control":
954
+ operation = args.get("operation", "end_response")
955
+ if operation == "end_response":
956
+ yield "\n✅ **Response Completed**\n"
957
+ tool_results.append({"type": "control", "message": "end_response"})
958
+ return
959
+
960
+ # Execute the tool (reuse existing tool execution logic)
961
+ if tool_name == "command_runner":
962
+ cmd_args = args.copy()
963
+ if "operation" not in cmd_args:
964
+ cmd_args["operation"] = "run_command"
965
+ if "cwd" not in cmd_args:
966
+ cmd_args["cwd"] = project_path or "."
967
+
968
+ result = await self.tool_registry.execute_tool(
969
+ "command_runner", user_id="ai_engine", **cmd_args
970
+ )
971
+
972
+ if result.success:
973
+ command = args.get("command", "unknown")
974
+ operation = cmd_args.get("operation", "run_command")
975
+
976
+ if operation == "run_async_command":
977
+ process_id = result.data.get("process_id", "unknown")
978
+ pid = result.data.get("pid", "unknown")
979
+ yield f"\n✅ **Tool Used:** command_runner (background)\n⚡ **Command:** {command}\n🔄 **Status:** Running in background\n📋 **Process ID:** {process_id} (PID: {pid})\n💡 **Tip:** Use `/ct {process_id}` to terminate\n"
980
+ tool_results.append({"type": "command", "command": command, "success": True})
981
+ else:
982
+ stdout = result.data.get("stdout", "") if isinstance(result.data, dict) else str(result.data)
983
+ stderr = result.data.get("stderr", "") if isinstance(result.data, dict) else ""
984
+
985
+ full_output = stdout
986
+ if stderr and stderr.strip():
987
+ full_output += f"\n[stderr]\n{stderr}"
988
+
989
+ display_output = full_output
990
+ if len(full_output) > 10000:
991
+ display_output = full_output[:10000] + f"\n\n... [Output truncated]"
992
+
993
+ yield f"\n✅ **Tool Used:** command_runner\n⚡ **Command:** {command}\n📄 **Output:**\n```\n{display_output}\n```\n"
994
+ tool_results.append({"type": "command", "command": command, "output": full_output, "success": True})
995
+ else:
996
+ yield f"\n❌ **Command Error:** {result.error}\n"
997
+ tool_results.append({"type": "error", "error": result.error, "success": False})
998
+
999
+ elif tool_name == "file_operations":
1000
+ file_path = args.get("file_path")
1001
+ if file_path and project_path:
1002
+ from pathlib import Path
1003
+ path = Path(file_path)
1004
+ if not path.is_absolute():
1005
+ args["file_path"] = str(Path(project_path) / file_path)
1006
+
1007
+ result = await self.tool_registry.execute_tool(
1008
+ "file_operations", user_id="ai_engine", **args
1009
+ )
1010
+
1011
+ if result.success:
1012
+ operation = args.get("operation")
1013
+ file_path = args.get("file_path")
1014
+ yield f"\n✅ **Tool Used:** file_operations\n📄 **Operation:** {operation} on {file_path}\n"
1015
+ tool_results.append({"type": "file_op", "operation": operation, "success": True})
1016
+ else:
1017
+ yield f"\n❌ **File Error:** {result.error}\n"
1018
+ tool_results.append({"type": "error", "error": result.error, "success": False})
1019
+
1020
+ elif tool_name == "web_search":
1021
+ result = await self.tool_registry.execute_tool(
1022
+ "web_search", user_id="ai_engine", **args
1023
+ )
1024
+
1025
+ if result.success:
1026
+ operation = args.get("operation", "search_web")
1027
+ yield f"\n✅ **Tool Used:** web_search ({operation})\n"
1028
+ tool_results.append({"type": "web_search", "operation": operation, "success": True})
1029
+ else:
1030
+ yield f"\n❌ **Web Search Error:** {result.error}\n"
1031
+ tool_results.append({"type": "error", "error": result.error, "success": False})
1032
+
1033
+ elif tool_name == "file_reader":
1034
+ result = await self.tool_registry.execute_tool(
1035
+ "file_reader", user_id="ai_engine", **args
1036
+ )
1037
+
1038
+ if result.success:
1039
+ operation = args.get("operation")
1040
+ yield f"\n✅ **Tool Used:** file_reader ({operation})\n"
1041
+ tool_results.append({"type": "file_read", "operation": operation, "success": True})
1042
+ else:
1043
+ yield f"\n❌ **File Reader Error:** {result.error}\n"
1044
+ tool_results.append({"type": "error", "error": result.error, "success": False})
1045
+
1046
+ except Exception as e:
1047
+ yield f"\n❌ **Tool Error:** {str(e)}\n"
1048
+ tool_results.append({"type": "error", "error": str(e), "success": False})
1049
+
796
1050
  def _parse_alternative_tool_calls(self, response: str):
797
1051
  """Parse tool calls from alternative formats (e.g., GPT-OSS with special tokens)"""
798
1052
  import re
@@ -919,9 +1173,10 @@ class AIEngine:
919
1173
  if text_before_tools:
920
1174
  yield text_before_tools + "\n"
921
1175
 
922
- # Process all tool calls first, collect results
1176
+ # Process and execute tool calls LIVE - show results immediately
923
1177
  tool_results = []
924
1178
  should_end_response = False
1179
+ has_executed_tools = False
925
1180
 
926
1181
  # Combine standard JSON matches and alternative format tool calls
927
1182
  all_tool_calls = []
@@ -947,14 +1202,13 @@ class AIEngine:
947
1202
  operation = args.get("operation", "end_response")
948
1203
  if operation == "end_response":
949
1204
  should_end_response = True
950
- tool_results.append(
951
- {
952
- "type": "control",
953
- "display": f"\n✅ **Response Completed**\n",
954
- }
955
- )
1205
+ yield "\n✅ **Response Completed**\n"
1206
+ tool_results.append({"type": "control", "message": "end_response"})
956
1207
  continue
957
1208
 
1209
+ # Mark that we've executed tools
1210
+ has_executed_tools = True
1211
+
958
1212
  # Execute the tool
959
1213
  if tool_name == "command_runner":
960
1214
  cmd_args = args.copy()
@@ -968,38 +1222,56 @@ class AIEngine:
968
1222
  )
969
1223
  if result.success:
970
1224
  command = args.get("command", "unknown")
971
- stdout = (
972
- result.data.get("stdout", "")
973
- if isinstance(result.data, dict)
974
- else str(result.data)
975
- )
976
- stderr = result.data.get("stderr", "") if isinstance(result.data, dict) else ""
977
-
978
- # Combine stdout and stderr for full output
979
- full_output = stdout
980
- if stderr and stderr.strip():
981
- full_output += f"\n[stderr]\n{stderr}"
1225
+ operation = cmd_args.get("operation", "run_command")
982
1226
 
983
- # Truncate very long outputs for display (but keep full output in data)
984
- display_output = full_output
985
- if len(full_output) > 10000:
986
- display_output = full_output[:10000] + f"\n\n... [Output truncated - {len(full_output)} total characters]"
987
-
988
- tool_results.append(
989
- {
1227
+ # Check if this is an async command
1228
+ if operation == "run_async_command":
1229
+ # Background command - show process ID
1230
+ process_id = result.data.get("process_id", "unknown")
1231
+ pid = result.data.get("pid", "unknown")
1232
+ display_msg = f"\n✅ **Tool Used:** command_runner (background)\n⚡ **Command:** {command}\n🔄 **Status:** Running in background\n📋 **Process ID:** {process_id} (PID: {pid})\n💡 **Tip:** Use `/ct {process_id}` to terminate this process\n"
1233
+ yield display_msg
1234
+ tool_results.append({
1235
+ "type": "command",
1236
+ "command": command,
1237
+ "output": f"Background process started: {process_id}",
1238
+ "success": True
1239
+ })
1240
+ else:
1241
+ # Synchronous command - show output
1242
+ stdout = (
1243
+ result.data.get("stdout", "")
1244
+ if isinstance(result.data, dict)
1245
+ else str(result.data)
1246
+ )
1247
+ stderr = result.data.get("stderr", "") if isinstance(result.data, dict) else ""
1248
+
1249
+ # Combine stdout and stderr for full output
1250
+ full_output = stdout
1251
+ if stderr and stderr.strip():
1252
+ full_output += f"\n[stderr]\n{stderr}"
1253
+
1254
+ # Truncate very long outputs for display (but keep full output in data)
1255
+ display_output = full_output
1256
+ if len(full_output) > 10000:
1257
+ display_output = full_output[:10000] + f"\n\n... [Output truncated - {len(full_output)} total characters]"
1258
+
1259
+ display_msg = f"\n✅ **Tool Used:** command_runner\n⚡ **Command:** {command}\n📄 **Output:**\n```\n{display_output}\n```\n"
1260
+ yield display_msg
1261
+ tool_results.append({
990
1262
  "type": "command",
991
1263
  "command": command,
992
1264
  "output": full_output,
993
- "display": f"\n✅ **Tool Used:** command_runner\n⚡ **Command:** {command}\n📄 **Output:**\n```\n{display_output}\n```\n",
994
- }
995
- )
1265
+ "success": True
1266
+ })
996
1267
  else:
997
- tool_results.append(
998
- {
999
- "type": "error",
1000
- "display": f"\n❌ **Command Error:** {result.error}\n",
1001
- }
1002
- )
1268
+ error_msg = f"\n❌ **Command Error:** {result.error}\n"
1269
+ yield error_msg
1270
+ tool_results.append({
1271
+ "type": "error",
1272
+ "error": result.error,
1273
+ "success": False
1274
+ })
1003
1275
 
1004
1276
  elif tool_name == "file_operations":
1005
1277
  # Prepend project_path to relative file paths
@@ -1042,14 +1314,14 @@ class AIEngine:
1042
1314
  total = result.data.get("total_lines", 0)
1043
1315
  line_info = f" (lines {start}-{end} of {total})"
1044
1316
 
1045
- tool_results.append(
1046
- {
1047
- "type": "file_read",
1048
- "file_path": file_path,
1049
- "content": content, # Full content for AI processing
1050
- "display": f"\n✅ **Tool Used:** file_operations\n📄 **Operation:** {operation} on {file_path}{line_info}\n📄 **Result:**\n```\n{display_content}\n```\n",
1051
- }
1052
- )
1317
+ display_msg = f"\n✅ **Tool Used:** file_operations\n📄 **Operation:** {operation} on {file_path}{line_info}\n📄 **Result:**\n```\n{display_content}\n```\n"
1318
+ yield display_msg
1319
+ tool_results.append({
1320
+ "type": "file_read",
1321
+ "file_path": file_path,
1322
+ "content": content,
1323
+ "success": True
1324
+ })
1053
1325
  elif operation == "write_file_lines":
1054
1326
  # Show line range info for write_file_lines
1055
1327
  line_info = ""
@@ -1058,26 +1330,17 @@ class AIEngine:
1058
1330
  end = result.data.get("end_line", 1)
1059
1331
  lines_written = result.data.get("lines_written", 0)
1060
1332
  line_info = f" (lines {start}-{end}, {lines_written} lines written)"
1061
- tool_results.append(
1062
- {
1063
- "type": "file_op",
1064
- "display": f"\n✅ **Tool Used:** file_operations\n📄 **Operation:** {operation} on {file_path}{line_info}\n",
1065
- }
1066
- )
1333
+ display_msg = f"\n✅ **Tool Used:** file_operations\n📄 **Operation:** {operation} on {file_path}{line_info}\n"
1334
+ yield display_msg
1335
+ tool_results.append({"type": "file_op", "operation": operation, "success": True})
1067
1336
  else:
1068
- tool_results.append(
1069
- {
1070
- "type": "file_op",
1071
- "display": f"\n✅ **Tool Used:** file_operations\n📄 **Operation:** {operation} on {file_path}\n",
1072
- }
1073
- )
1337
+ display_msg = f"\n✅ **Tool Used:** file_operations\n📄 **Operation:** {operation} on {file_path}\n"
1338
+ yield display_msg
1339
+ tool_results.append({"type": "file_op", "operation": operation, "success": True})
1074
1340
  else:
1075
- tool_results.append(
1076
- {
1077
- "type": "error",
1078
- "display": f"\n❌ **File Error:** {result.error}\n",
1079
- }
1080
- )
1341
+ error_msg = f"\n❌ **File Error:** {result.error}\n"
1342
+ yield error_msg
1343
+ tool_results.append({"type": "error", "error": result.error, "success": False})
1081
1344
 
1082
1345
  elif tool_name == "web_search":
1083
1346
  operation = args.get("operation", "search_web")
@@ -1100,14 +1363,13 @@ class AIEngine:
1100
1363
  )
1101
1364
  result_text += f" 🔗 {item.get('url', 'No URL')}\n"
1102
1365
 
1103
- tool_results.append(
1104
- {
1105
- "type": "web_search",
1106
- "query": query,
1107
- "results": search_results,
1108
- "display": result_text,
1109
- }
1110
- )
1366
+ yield result_text
1367
+ tool_results.append({
1368
+ "type": "web_search",
1369
+ "query": query,
1370
+ "results": search_results,
1371
+ "success": True
1372
+ })
1111
1373
 
1112
1374
  elif operation == "fetch_url_content":
1113
1375
  url = args.get("url", "unknown")
@@ -1135,14 +1397,8 @@ class AIEngine:
1135
1397
  f"**Content:**\n```\n{display_content}\n```\n"
1136
1398
  )
1137
1399
 
1138
- tool_results.append(
1139
- {
1140
- "type": "web_fetch",
1141
- "url": url,
1142
- "content": content, # Full content for AI processing
1143
- "display": result_text,
1144
- }
1145
- )
1400
+ yield result_text
1401
+ tool_results.append({"type": "web_fetch", "url": url, "content": content, "success": True})
1146
1402
 
1147
1403
  elif operation == "parse_documentation":
1148
1404
  url = args.get("url", "unknown")
@@ -1162,14 +1418,8 @@ class AIEngine:
1162
1418
  f"\n• {section.get('title', 'Untitled')}\n"
1163
1419
  )
1164
1420
 
1165
- tool_results.append(
1166
- {
1167
- "type": "web_docs",
1168
- "url": url,
1169
- "doc_data": doc_data,
1170
- "display": result_text,
1171
- }
1172
- )
1421
+ yield result_text
1422
+ tool_results.append({"type": "web_docs", "url": url, "doc_data": doc_data, "success": True})
1173
1423
 
1174
1424
  elif operation == "get_api_docs":
1175
1425
  api_name = args.get("api_name", "unknown")
@@ -1187,53 +1437,35 @@ class AIEngine:
1187
1437
  result_text += f"📚 **API:** {api_name}\n"
1188
1438
  result_text += f"❌ {api_data.get('message', 'Documentation not found')}\n"
1189
1439
 
1190
- tool_results.append(
1191
- {
1192
- "type": "web_api_docs",
1193
- "api_name": api_name,
1194
- "api_data": api_data,
1195
- "display": result_text,
1196
- }
1197
- )
1440
+ yield result_text
1441
+ tool_results.append({"type": "web_api_docs", "api_name": api_name, "api_data": api_data, "success": True})
1198
1442
  else:
1199
- tool_results.append(
1200
- {
1201
- "type": "web_search",
1202
- "display": f"\n✅ **Tool Used:** web_search ({operation})\n",
1203
- }
1204
- )
1443
+ display_msg = f"\n✅ **Tool Used:** web_search ({operation})\n"
1444
+ yield display_msg
1445
+ tool_results.append({"type": "web_search", "operation": operation, "success": True})
1205
1446
  else:
1206
- tool_results.append(
1207
- {
1208
- "type": "error",
1209
- "display": f"\n❌ **Web Search Error:** {result.error}\n",
1210
- }
1211
- )
1447
+ error_msg = f"\n❌ **Web Search Error:** {result.error}\n"
1448
+ yield error_msg
1449
+ tool_results.append({"type": "error", "error": result.error, "success": False})
1212
1450
 
1213
1451
  except json.JSONDecodeError as e:
1214
- tool_results.append(
1215
- {
1216
- "type": "error",
1217
- "display": f"\n❌ **JSON Parse Error:** {str(e)}\n",
1218
- }
1219
- )
1452
+ error_msg = f"\n❌ **JSON Parse Error:** {str(e)}\n"
1453
+ yield error_msg
1454
+ tool_results.append({"type": "error", "error": str(e), "success": False})
1220
1455
  except Exception as e:
1221
- tool_results.append(
1222
- {"type": "error", "display": f"\n❌ **Tool Error:** {str(e)}\n"}
1223
- )
1224
-
1225
- # Now yield the tool results
1226
- if tool_results:
1227
- # Show all tool results
1228
- for tool_result in tool_results:
1229
- yield tool_result["display"]
1456
+ error_msg = f"\n❌ **Tool Error:** {str(e)}\n"
1457
+ yield error_msg
1458
+ tool_results.append({"type": "error", "error": str(e), "success": False})
1230
1459
 
1231
- # Check if we should end the response (end_response tool was called)
1232
- if should_end_response:
1233
- return
1460
+ # Tool results have already been yielded immediately during execution
1461
+ # Now check if we should continue or end
1462
+
1463
+ # Check if we should end the response (end_response tool was called)
1464
+ if should_end_response:
1465
+ return
1234
1466
 
1235
- # Use auto-continuation manager to determine if we should continue
1236
- if self.auto_continuation.should_continue(tool_results, should_end_response) and recursion_depth < MAX_RECURSION_DEPTH:
1467
+ # Use auto-continuation manager to determine if we should continue
1468
+ if has_executed_tools and self.auto_continuation.should_continue(tool_results, should_end_response) and recursion_depth < MAX_RECURSION_DEPTH:
1237
1469
  # Show continuation indicator
1238
1470
  yield "\n🔄 **Auto-continuing...**\n"
1239
1471
 
@@ -1389,7 +1621,11 @@ CRITICAL BEHAVIOR REQUIREMENTS:
1389
1621
  - PROVIDE COMPREHENSIVE SOLUTIONS: Don't stop after creating just one file - complete the entire functional project.
1390
1622
  - BE PROACTIVE: Anticipate what files and functionality are needed and create them all without asking for permission for each step.
1391
1623
  - EXPLORATION IS OPTIONAL: You may explore the workspace with 'ls' or 'pwd' if needed, but this is NOT required before creating new files. If the user asks you to BUILD or CREATE something, prioritize creating the files immediately.
1392
- - ALWAYS USE end_response TOOL: When you have completed ALL tasks, ALWAYS call the end_response tool to prevent unnecessary continuation
1624
+ - ALWAYS USE end_response TOOL: When you have completed ALL tasks, you MUST call the end_response tool to signal completion. This prevents unnecessary auto-continuation.
1625
+ - AUTO-CONTINUATION: The system will automatically continue ONLY when:
1626
+ * You created files but haven't run necessary commands yet (e.g., npm install after creating package.json)
1627
+ * There were errors that need to be handled
1628
+ * Otherwise, you MUST explicitly call end_response when done
1393
1629
  - NEVER RE-READ SAME FILE: If a file was truncated in the output, use read_file_lines to read the specific truncated section, DO NOT re-read the entire file
1394
1630
 
1395
1631
  WORKSPACE EXPLORATION RULES (CRITICAL - ALWAYS CHECK FIRST):
@@ -1569,7 +1805,7 @@ For multiple files, use separate tool calls:
1569
1805
  }
1570
1806
  ```
1571
1807
 
1572
- For commands (always explore first):
1808
+ For commands:
1573
1809
 
1574
1810
  ```json
1575
1811
  {
@@ -1580,16 +1816,44 @@ For commands (always explore first):
1580
1816
  }
1581
1817
  ```
1582
1818
 
1819
+ For LONG commands (npm install, servers), use run_async_command to run in background:
1820
+
1583
1821
  ```json
1584
1822
  {
1585
1823
  "tool_code": "command_runner",
1586
1824
  "args": {
1587
- "command": "pwd"
1825
+ "operation": "run_async_command",
1826
+ "command": "npm install"
1827
+ }
1828
+ }
1829
+ ```
1830
+
1831
+ For file reading and grep:
1832
+
1833
+ ```json
1834
+ {
1835
+ "tool_code": "file_reader",
1836
+ "args": {
1837
+ "operation": "read_file",
1838
+ "file_path": "src/App.js"
1839
+ }
1840
+ }
1841
+ ```
1842
+
1843
+ ```json
1844
+ {
1845
+ "tool_code": "file_reader",
1846
+ "args": {
1847
+ "operation": "grep_search",
1848
+ "pattern": "function.*Component",
1849
+ "search_path": "src",
1850
+ "file_pattern": "*.js",
1851
+ "recursive": true
1588
1852
  }
1589
1853
  }
1590
1854
  ```
1591
1855
 
1592
- For web search (when you need external information):
1856
+ For web search:
1593
1857
 
1594
1858
  ```json
1595
1859
  {
@@ -1672,11 +1936,11 @@ When user explicitly asks for RESEARCH:
1672
1936
  The tools will execute automatically and show results. Keep explanatory text BRIEF.
1673
1937
 
1674
1938
  Available tools:
1675
- - file_operations: Create, read, write, delete files and directories
1676
- - command_runner: Execute shell commands
1939
+ - file_operations: Create, read, write, delete files
1940
+ - file_reader: Read files, grep search, list directories
1941
+ - command_runner: Execute shell commands (use run_async_command for long tasks)
1677
1942
  - web_search: Search the web for information
1678
- - code_analysis: Analyze code files
1679
- - response_control: Control response continuation (use end_response to stop auto-continuation)
1943
+ - response_control: Use end_response when task is complete
1680
1944
 
1681
1945
  RESPONSE CONTINUATION (CRITICAL - ALWAYS USE end_response):
1682
1946
  - By default, after executing tools, the AI will automatically continue to complete the task
@@ -43,8 +43,60 @@ class AutoContinuationManager:
43
43
  if self.iteration_count >= self.max_iterations:
44
44
  return False
45
45
 
46
- # Continue if there were tool executions
47
- if tool_results:
46
+ # Don't continue if no tools were executed
47
+ if not tool_results:
48
+ return False
49
+
50
+ # Check if there were any errors
51
+ has_errors = any(r.get('type') == 'error' for r in tool_results)
52
+
53
+ # Check what types of tools were executed
54
+ has_file_ops = any(r.get('type') in ['file_op', 'file_write', 'file_read'] for r in tool_results)
55
+ has_commands = any(r.get('type') == 'command' for r in tool_results)
56
+ has_background_commands = any(
57
+ r.get('type') == 'command' and 'background' in str(r.get('command', ''))
58
+ for r in tool_results
59
+ )
60
+
61
+ # Check if commands were exploratory (ls, pwd, dir, etc.)
62
+ exploratory_commands = ['ls', 'pwd', 'dir', 'cd', 'find', 'tree', 'cat', 'head', 'tail']
63
+ has_exploratory_commands = any(
64
+ r.get('type') == 'command' and
65
+ any(cmd in str(r.get('command', '')).lower() for cmd in exploratory_commands)
66
+ for r in tool_results
67
+ )
68
+
69
+ # Smart continuation logic:
70
+ # 1. If there are errors, continue to let AI handle them
71
+ if has_errors:
72
+ self.iteration_count += 1
73
+ return True
74
+
75
+ # 2. If only file operations were done, likely need to run commands next
76
+ if has_file_ops and not has_commands:
77
+ self.iteration_count += 1
78
+ return True
79
+
80
+ # 3. If only exploratory commands (ls, pwd, etc.), continue to do actual work
81
+ if has_exploratory_commands and not has_file_ops:
82
+ self.iteration_count += 1
83
+ return True
84
+
85
+ # 4. If background commands were started AND files created, task likely complete
86
+ if has_background_commands and has_file_ops:
87
+ return False
88
+
89
+ # 5. If only background commands (no files), don't continue (they're running)
90
+ if has_background_commands and not has_file_ops:
91
+ return False
92
+
93
+ # 6. If regular commands executed with file ops, task likely complete
94
+ if has_commands and has_file_ops and not has_exploratory_commands:
95
+ return False
96
+
97
+ # 7. Default: Continue if we're early in the process
98
+ # This ensures AI completes the full task
99
+ if self.iteration_count < 3:
48
100
  self.iteration_count += 1
49
101
  return True
50
102
 
cognautic/cli.py CHANGED
@@ -8,8 +8,12 @@ import logging
8
8
  import os
9
9
  import readline
10
10
  import signal
11
- import sys
12
11
  from pathlib import Path
12
+ import sys
13
+ import subprocess
14
+ from prompt_toolkit import PromptSession
15
+ from prompt_toolkit.key_binding import KeyBindings
16
+ from prompt_toolkit.formatted_text import HTML
13
17
  from rich.console import Console
14
18
  from rich.panel import Panel
15
19
  from rich.text import Text
@@ -83,7 +87,7 @@ def signal_handler(signum, frame):
83
87
 
84
88
 
85
89
  @click.group(invoke_without_command=True)
86
- @click.version_option(version="1.1.1", prog_name="Cognautic CLI")
90
+ @click.version_option(version="1.1.2", prog_name="Cognautic CLI")
87
91
  @click.pass_context
88
92
  def main(ctx):
89
93
  """Cognautic CLI - AI-powered development assistant"""
@@ -193,6 +197,7 @@ def chat(provider, model, project_path, websocket_port, session):
193
197
 
194
198
  try:
195
199
  console.print("💡 Type '/help' for commands, 'exit' to quit")
200
+ console.print("💡 Press Shift+Tab to toggle Terminal mode")
196
201
  if project_path:
197
202
  console.print(f"📁 Working in: {project_path}")
198
203
 
@@ -238,21 +243,81 @@ def chat(provider, model, project_path, websocket_port, session):
238
243
  # Show current workspace
239
244
  console.print(f"📁 Workspace: {current_workspace}")
240
245
 
246
+ # Terminal mode state
247
+ terminal_mode = [False] # Use list to make it mutable in nested function
248
+
249
+ # Setup key bindings for Shift+Tab toggle
250
+ bindings = KeyBindings()
251
+
252
+ @bindings.add('s-tab') # Shift+Tab
253
+ def toggle_mode(event):
254
+ terminal_mode[0] = not terminal_mode[0]
255
+ mode_name = "🖥️ Terminal" if terminal_mode[0] else "💬 Chat"
256
+ # Clear current line and show mode switch message
257
+ event.app.current_buffer.text = ''
258
+ event.app.exit(result='__MODE_TOGGLE__')
259
+
260
+ # Create prompt session
261
+ session = PromptSession(key_bindings=bindings)
262
+
263
+ console.print("[dim]💡 Press Shift+Tab to toggle between Chat and Terminal modes[/dim]\n")
264
+
241
265
  while True:
242
266
  try:
243
- # Show current workspace in prompt
267
+ # Show current workspace and mode in prompt
244
268
  workspace_info = f" [{Path(current_workspace).name}]" if current_workspace else ""
269
+ mode_indicator = "🖥️ " if terminal_mode[0] else ""
245
270
 
246
- # Use readline delimiters for non-printing characters to fix text reordering
247
- # \001 and \002 tell readline that the enclosed characters don't take up space
248
- # This prevents cursor position issues when editing
249
- # ANSI color codes: \033[1;36m = bold cyan, \033[0m = reset
250
- prompt = f"\001\033[1;36m\002You{workspace_info}:\001\033[0m\002 "
251
- user_input = input(prompt)
271
+ prompt_text = HTML(f'<ansibrightcyan><b>{mode_indicator}You{workspace_info}:</b></ansibrightcyan> ')
272
+ user_input = await session.prompt_async(prompt_text)
273
+
274
+ # Handle mode toggle
275
+ if user_input == '__MODE_TOGGLE__':
276
+ mode_name = "🖥️ Terminal" if terminal_mode[0] else "💬 Chat"
277
+ console.print(f"[bold yellow]Switched to {mode_name} mode[/bold yellow]")
278
+ console.print("[dim]Press Shift+Tab to toggle modes[/dim]")
279
+ continue
252
280
 
253
281
  if user_input.lower() in ['exit', 'quit']:
254
282
  break
255
283
 
284
+ # Handle terminal mode
285
+ if terminal_mode[0]:
286
+ if not user_input.strip():
287
+ continue
288
+
289
+ # Execute command in terminal mode
290
+ try:
291
+ # Change to current workspace directory
292
+ original_dir = os.getcwd()
293
+ os.chdir(current_workspace)
294
+
295
+ # Run command and capture output
296
+ result = subprocess.run(
297
+ user_input,
298
+ shell=True,
299
+ capture_output=True,
300
+ text=True,
301
+ cwd=current_workspace
302
+ )
303
+
304
+ # Show output
305
+ if result.stdout:
306
+ console.print(result.stdout, end='')
307
+ if result.stderr:
308
+ console.print(f"[red]{result.stderr}[/red]", end='')
309
+
310
+ # Show exit code if non-zero
311
+ if result.returncode != 0:
312
+ console.print(f"[yellow]Exit code: {result.returncode}[/yellow]")
313
+
314
+ # Change back to original directory
315
+ os.chdir(original_dir)
316
+
317
+ except Exception as e:
318
+ console.print(f"[red]Error executing command: {str(e)}[/red]")
319
+
320
+ continue
256
321
 
257
322
  # Handle slash commands
258
323
  if user_input.startswith('/'):
@@ -1022,6 +1087,58 @@ async def handle_slash_command(command, config_manager, ai_engine, context):
1022
1087
  console.print("💬 Chat cleared")
1023
1088
  return True
1024
1089
 
1090
+ elif cmd == "ps" or cmd == "processes":
1091
+ # List all running background processes
1092
+ try:
1093
+ result = await ai_engine.tool_registry.execute_tool(
1094
+ "command_runner",
1095
+ operation="check_process_status",
1096
+ user_id="ai_engine"
1097
+ )
1098
+
1099
+ if result.success and result.data:
1100
+ processes = result.data.get('processes', [])
1101
+ if not processes:
1102
+ console.print("📋 No background processes running", style="dim")
1103
+ else:
1104
+ console.print(f"\n📋 Running Processes ({len(processes)}):", style="bold")
1105
+ for proc in processes:
1106
+ status_color = "green" if proc['status'] == 'running' else "yellow"
1107
+ console.print(f" • PID: {proc['process_id']} - {proc['command'][:50]}... [{proc['status']}]", style=status_color)
1108
+ console.print(f" Running for: {proc['running_time']:.1f}s", style="dim")
1109
+ console.print("\n💡 Use /ct <process_id> to terminate a process\n", style="dim")
1110
+ else:
1111
+ console.print("❌ Failed to get process list", style="red")
1112
+ except Exception as e:
1113
+ console.print(f"❌ Error: {str(e)}", style="red")
1114
+
1115
+ return True
1116
+
1117
+ elif cmd == "ct" or cmd == "cancel":
1118
+ # Cancel/terminate a background process
1119
+ if len(parts) < 2:
1120
+ console.print("❌ Usage: /ct <process_id>", style="red")
1121
+ console.print("💡 Tip: Process IDs are shown when commands run in background", style="dim")
1122
+ return True
1123
+
1124
+ process_id = parts[1]
1125
+ try:
1126
+ result = await ai_engine.tool_registry.execute_tool(
1127
+ "command_runner",
1128
+ operation="kill_process",
1129
+ process_id=process_id,
1130
+ user_id="ai_engine"
1131
+ )
1132
+
1133
+ if result.success:
1134
+ console.print(f"✅ Process {process_id} terminated successfully", style="green")
1135
+ else:
1136
+ console.print(f"❌ Failed to terminate process: {result.error}", style="red")
1137
+ except Exception as e:
1138
+ console.print(f"❌ Error: {str(e)}", style="red")
1139
+
1140
+ return True
1141
+
1025
1142
  elif cmd == "exit" or cmd == "quit":
1026
1143
  return False
1027
1144
 
@@ -1033,6 +1150,7 @@ def show_help():
1033
1150
  """Show help information"""
1034
1151
  help_text = Text()
1035
1152
  help_text.append("Available commands:\n", style="bold")
1153
+ help_text.append("• Press Shift+Tab - Toggle between Chat and Terminal modes\n", style="bold yellow")
1036
1154
  help_text.append("• /help - Show this help message\n")
1037
1155
  help_text.append("• /workspace <path> or /ws <path> - Change working directory\n")
1038
1156
  help_text.append("• /setup - Run interactive setup wizard\n")
@@ -1050,6 +1168,8 @@ def show_help():
1050
1168
  help_text.append(" - /rules remove workspace <index> - Remove a workspace rule\n", style="dim")
1051
1169
  help_text.append(" - /rules clear global|workspace - Clear all rules of a type\n", style="dim")
1052
1170
  help_text.append("• /speed [instant|fast|normal|slow|<number>] - Set typing speed for AI responses\n")
1171
+ help_text.append("• /ps or /processes - List all running background processes\n")
1172
+ help_text.append("• /ct <process_id> or /cancel <process_id> - Terminate a background process\n")
1053
1173
  help_text.append("• /clear - Clear chat screen\n")
1054
1174
  help_text.append("• /exit or /quit - Exit chat session\n")
1055
1175
  help_text.append("• exit or quit - Exit chat session\n")
@@ -0,0 +1,218 @@
1
+ """
2
+ File reader tool for reading and searching files
3
+ """
4
+
5
+ import os
6
+ import re
7
+ from typing import List, Dict, Any
8
+ from pathlib import Path
9
+
10
+ from .base import BaseTool, ToolResult, PermissionLevel
11
+
12
+
13
+ class FileReaderTool(BaseTool):
14
+ """Tool for reading files and searching content"""
15
+
16
+ def __init__(self):
17
+ super().__init__(
18
+ name="file_reader",
19
+ description="Read file contents and search in files",
20
+ permission_level=PermissionLevel.READ_ONLY
21
+ )
22
+
23
+ def get_capabilities(self) -> List[str]:
24
+ return [
25
+ "read_file",
26
+ "grep_search",
27
+ "list_directory"
28
+ ]
29
+
30
+ async def execute(self, operation: str, **kwargs) -> ToolResult:
31
+ """Execute file reader operation"""
32
+
33
+ operations = {
34
+ 'read_file': self._read_file,
35
+ 'grep_search': self._grep_search,
36
+ 'list_directory': self._list_directory
37
+ }
38
+
39
+ if operation not in operations:
40
+ return ToolResult(
41
+ success=False,
42
+ error=f"Unknown operation: {operation}"
43
+ )
44
+
45
+ try:
46
+ result = await operations[operation](**kwargs)
47
+ return ToolResult(success=True, data=result)
48
+ except Exception as e:
49
+ return ToolResult(success=False, error=str(e))
50
+
51
+ async def _read_file(
52
+ self,
53
+ file_path: str,
54
+ start_line: int = None,
55
+ end_line: int = None,
56
+ max_lines: int = 1000
57
+ ) -> Dict[str, Any]:
58
+ """Read file contents"""
59
+
60
+ try:
61
+ path = Path(file_path).expanduser().resolve()
62
+
63
+ if not path.exists():
64
+ raise FileNotFoundError(f"File not found: {file_path}")
65
+
66
+ if not path.is_file():
67
+ raise ValueError(f"Not a file: {file_path}")
68
+
69
+ with open(path, 'r', encoding='utf-8', errors='ignore') as f:
70
+ lines = f.readlines()
71
+
72
+ total_lines = len(lines)
73
+
74
+ if start_line is not None or end_line is not None:
75
+ start = (start_line - 1) if start_line else 0
76
+ end = end_line if end_line else total_lines
77
+ lines = lines[start:end]
78
+
79
+ if len(lines) > max_lines:
80
+ lines = lines[:max_lines]
81
+ truncated = True
82
+ else:
83
+ truncated = False
84
+
85
+ content = ''.join(lines)
86
+
87
+ return {
88
+ 'file_path': str(path),
89
+ 'content': content,
90
+ 'total_lines': total_lines,
91
+ 'lines_returned': len(lines),
92
+ 'truncated': truncated
93
+ }
94
+
95
+ except Exception as e:
96
+ raise Exception(f"Failed to read file: {str(e)}")
97
+
98
+ async def _grep_search(
99
+ self,
100
+ pattern: str,
101
+ search_path: str,
102
+ file_pattern: str = "*",
103
+ recursive: bool = True,
104
+ case_sensitive: bool = False,
105
+ max_results: int = 100
106
+ ) -> Dict[str, Any]:
107
+ """Search for pattern in files"""
108
+
109
+ try:
110
+ search_path = Path(search_path).expanduser().resolve()
111
+
112
+ if not search_path.exists():
113
+ raise FileNotFoundError(f"Path not found: {search_path}")
114
+
115
+ results = []
116
+ flags = 0 if case_sensitive else re.IGNORECASE
117
+ regex = re.compile(pattern, flags)
118
+
119
+ if search_path.is_file():
120
+ files_to_search = [search_path]
121
+ else:
122
+ if recursive:
123
+ files_to_search = list(search_path.rglob(file_pattern))
124
+ else:
125
+ files_to_search = list(search_path.glob(file_pattern))
126
+
127
+ files_to_search = [f for f in files_to_search if f.is_file()]
128
+
129
+ for file_path in files_to_search:
130
+ if len(results) >= max_results:
131
+ break
132
+
133
+ try:
134
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
135
+ for line_num, line in enumerate(f, 1):
136
+ if len(results) >= max_results:
137
+ break
138
+
139
+ if regex.search(line):
140
+ results.append({
141
+ 'file': str(file_path),
142
+ 'line_number': line_num,
143
+ 'line_content': line.rstrip('\n'),
144
+ 'match': regex.search(line).group(0)
145
+ })
146
+ except Exception:
147
+ continue
148
+
149
+ return {
150
+ 'pattern': pattern,
151
+ 'search_path': str(search_path),
152
+ 'total_matches': len(results),
153
+ 'matches': results,
154
+ 'truncated': len(results) >= max_results
155
+ }
156
+
157
+ except Exception as e:
158
+ raise Exception(f"Failed to search: {str(e)}")
159
+
160
+ async def _list_directory(
161
+ self,
162
+ directory_path: str,
163
+ show_hidden: bool = False,
164
+ recursive: bool = False,
165
+ max_depth: int = 3
166
+ ) -> Dict[str, Any]:
167
+ """List directory contents"""
168
+
169
+ try:
170
+ path = Path(directory_path).expanduser().resolve()
171
+
172
+ if not path.exists():
173
+ raise FileNotFoundError(f"Directory not found: {directory_path}")
174
+
175
+ if not path.is_dir():
176
+ raise ValueError(f"Not a directory: {directory_path}")
177
+
178
+ items = []
179
+
180
+ if recursive:
181
+ for item in path.rglob('*'):
182
+ if not show_hidden and item.name.startswith('.'):
183
+ continue
184
+
185
+ try:
186
+ relative = item.relative_to(path)
187
+ depth = len(relative.parts)
188
+ if depth > max_depth:
189
+ continue
190
+ except ValueError:
191
+ continue
192
+
193
+ items.append({
194
+ 'path': str(item),
195
+ 'name': item.name,
196
+ 'type': 'directory' if item.is_dir() else 'file',
197
+ 'size': item.stat().st_size if item.is_file() else None
198
+ })
199
+ else:
200
+ for item in path.iterdir():
201
+ if not show_hidden and item.name.startswith('.'):
202
+ continue
203
+
204
+ items.append({
205
+ 'path': str(item),
206
+ 'name': item.name,
207
+ 'type': 'directory' if item.is_dir() else 'file',
208
+ 'size': item.stat().st_size if item.is_file() else None
209
+ })
210
+
211
+ return {
212
+ 'directory': str(path),
213
+ 'total_items': len(items),
214
+ 'items': items
215
+ }
216
+
217
+ except Exception as e:
218
+ raise Exception(f"Failed to list directory: {str(e)}")
@@ -48,6 +48,7 @@ class ToolRegistry:
48
48
  from .web_search import WebSearchTool
49
49
  from .code_analysis import CodeAnalysisTool
50
50
  from .response_control import ResponseControlTool
51
+ from .file_reader import FileReaderTool
51
52
 
52
53
  # Register tools
53
54
  self.register_tool(FileOperationsTool())
@@ -55,6 +56,7 @@ class ToolRegistry:
55
56
  self.register_tool(WebSearchTool())
56
57
  self.register_tool(CodeAnalysisTool())
57
58
  self.register_tool(ResponseControlTool())
59
+ self.register_tool(FileReaderTool())
58
60
 
59
61
  def register_tool(self, tool: BaseTool):
60
62
  """Register a tool"""
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognautic-cli
3
- Version: 1.1.1
3
+ Version: 1.1.2
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
- Home-page: https://github.com/cognautic/cli
6
- Author: cognautic
7
- Author-email: cognautic <cognautic@gmail.com>
8
- Maintainer-email: cognautic <cognautic@gmail.com>
5
+ Home-page: https://github.com/cognautic/cognautic-cli
6
+ Author: Cognautic
7
+ Author-email: Cognautic <cognautic@gmail.com>
8
+ Maintainer-email: Cognautic <cognautic@gmail.com>
9
9
  License: Proprietary - All Rights Reserved
10
10
  Project-URL: Homepage, https://github.com/cognautic/cli
11
11
  Project-URL: Documentation, https://cognautic.vercel.app/cognautic-cli.html
@@ -43,6 +43,7 @@ Requires-Dist: anthropic>=0.7.0
43
43
  Requires-Dist: google-generativeai>=0.3.0
44
44
  Requires-Dist: together>=0.2.0
45
45
  Requires-Dist: nest-asyncio>=1.5.0
46
+ Requires-Dist: prompt-toolkit>=3.0.0
46
47
  Provides-Extra: tools
47
48
  Requires-Dist: gitpython>=3.1.0; extra == "tools"
48
49
  Requires-Dist: pyyaml>=6.0; extra == "tools"
@@ -78,7 +79,7 @@ Cognautic CLI is a Python-based command-line interface that brings AI-powered de
78
79
 
79
80
  | Property | Value |
80
81
  |----------|-------|
81
- | **Developer** | cognautic |
82
+ | **Developer** | Cognautic |
82
83
  | **Written in** | Python |
83
84
  | **Operating system** | Cross-platform |
84
85
  | **Type** | AI Development Tool |
@@ -597,8 +598,4 @@ You: /model claude-3-sonnet-20240229
597
598
 
598
599
  ## License
599
600
 
600
- Proprietary - All Rights Reserved
601
-
602
- © 2024 cognautic
603
-
604
- For licensing inquiries, contact: cognautic@gmail.com
601
+ MIT
@@ -1,7 +1,7 @@
1
- cognautic/__init__.py,sha256=91wlZSHl8xT0VU8eNejpMjcnpciKtuVm8QCSy9Tal10,140
2
- cognautic/ai_engine.py,sha256=kMnbZXZWnE8n9ndbItfoJmcjzxvRDgvHeMgXMwj6bNI,92428
3
- cognautic/auto_continuation.py,sha256=1YxdXJCm3cNaHer4c8Had49VepAnUrYcsU5nVYwQTIA,6773
4
- cognautic/cli.py,sha256=VEdEgMeDTNh2uTeDdTMUcLBUpDEmqzXSqXh__npIw9c,50741
1
+ cognautic/__init__.py,sha256=Wn_G_YckyznLr_f_1-MsYNMuCyHtHomSp2qYHWxiEqE,141
2
+ cognautic/ai_engine.py,sha256=W9liShtN7KL8jnDPcSR6BeqRT9xmtBBQnIx-FJTyuag,106097
3
+ cognautic/auto_continuation.py,sha256=-jJU17fdo6Ww26wtLxO8PkQ5itsARmrNlCFroMgCZSM,9032
4
+ cognautic/cli.py,sha256=Leutgyw3WKLovSsRwxsGPGKeeoAnumDxSnxN7CCWUW4,56518
5
5
  cognautic/config.py,sha256=mrkvBA2gz_i9PuER6K75RxVeLBcYbjwNYBV2NskFQVk,9115
6
6
  cognautic/file_tagger.py,sha256=DBsBIggtI0pGdbxY9XOMHtVG1k9RYCihqrAEfM68xGI,8532
7
7
  cognautic/memory.py,sha256=5oLWmkaIy5rEJ1yJ4xldJIzDfvvAKqkxz0PWk53heis,15208
@@ -14,12 +14,13 @@ cognautic/tools/base.py,sha256=PtEMTyntWwhO-SFtkMSSnhR0TXrCWqUy5Tg_yPIldCk,1590
14
14
  cognautic/tools/code_analysis.py,sha256=rWS-yz0yzmF33IEHhJsN22j4e26WDDa7Fv30PF3zBhk,15387
15
15
  cognautic/tools/command_runner.py,sha256=-C3VaSlFyZOiytxizspnz-PDONdrfut4rOXh3Vbbb6M,10032
16
16
  cognautic/tools/file_operations.py,sha256=_p_e5ZQq3Q8TGL21cRyIDoaeuDshz3_-h_C5kMLnc68,14073
17
- cognautic/tools/registry.py,sha256=5ykVeH43YcdJ8YcmwtBrtmNM2jzh55-yxWXfVqJ0ltw,3862
17
+ cognautic/tools/file_reader.py,sha256=IAi4mCnauIghlWiBshTp_ZYJ_XyWqpnEJ7gJuD4NxvA,7435
18
+ cognautic/tools/registry.py,sha256=Dk_tgj2FyLerha2k92PIgLfLlOw0vTsgzw3t3_3W4Hw,3955
18
19
  cognautic/tools/response_control.py,sha256=rb9h5mYi80uc-izUkuCcaUXOeonigX-zFvzeUlTBwcE,1480
19
20
  cognautic/tools/web_search.py,sha256=tXHy1oB_33_peopJu3Xc7k7_NWuZNfLGfXzv9w7p9bM,13863
20
- cognautic_cli-1.1.1.dist-info/licenses/LICENSE,sha256=N_qTILhnbmexBombE-njuJguCu9u2b1gwCvOVKnFWno,1066
21
- cognautic_cli-1.1.1.dist-info/METADATA,sha256=6HxmJ-r_kYQWb5cXihk3WQKJGp3QHxA-JljeysScWco,17538
22
- cognautic_cli-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- cognautic_cli-1.1.1.dist-info/entry_points.txt,sha256=QoF70RcDm_QElmIC7pkmhbFsWhuWSbxQksGaFXKBs8Q,49
24
- cognautic_cli-1.1.1.dist-info/top_level.txt,sha256=zikDi43HL2zOV9cF_PAv6RBhJruTjSk6AMkIOTX12do,10
25
- cognautic_cli-1.1.1.dist-info/RECORD,,
21
+ cognautic_cli-1.1.2.dist-info/licenses/LICENSE,sha256=N_qTILhnbmexBombE-njuJguCu9u2b1gwCvOVKnFWno,1066
22
+ cognautic_cli-1.1.2.dist-info/METADATA,sha256=K-F7985c4YQtGw55hIp6hn-SUPG8Kk-P3S9A5R2PZNk,17481
23
+ cognautic_cli-1.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ cognautic_cli-1.1.2.dist-info/entry_points.txt,sha256=QoF70RcDm_QElmIC7pkmhbFsWhuWSbxQksGaFXKBs8Q,49
25
+ cognautic_cli-1.1.2.dist-info/top_level.txt,sha256=zikDi43HL2zOV9cF_PAv6RBhJruTjSk6AMkIOTX12do,10
26
+ cognautic_cli-1.1.2.dist-info/RECORD,,