nc1709 1.15.4__py3-none-any.whl → 1.18.8__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.
- nc1709/__init__.py +1 -1
- nc1709/agent/core.py +172 -19
- nc1709/agent/permissions.py +2 -2
- nc1709/agent/tools/bash_tool.py +295 -8
- nc1709/cli.py +435 -19
- nc1709/cli_ui.py +137 -52
- nc1709/conversation_logger.py +416 -0
- nc1709/llm_adapter.py +62 -4
- nc1709/plugins/agents/database_agent.py +695 -0
- nc1709/plugins/agents/django_agent.py +11 -4
- nc1709/plugins/agents/docker_agent.py +11 -4
- nc1709/plugins/agents/fastapi_agent.py +11 -4
- nc1709/plugins/agents/git_agent.py +11 -4
- nc1709/plugins/agents/nextjs_agent.py +11 -4
- nc1709/plugins/agents/ollama_agent.py +574 -0
- nc1709/plugins/agents/test_agent.py +702 -0
- nc1709/prompts/unified_prompt.py +156 -14
- nc1709/requirements_tracker.py +526 -0
- nc1709/thinking_messages.py +337 -0
- nc1709/version_check.py +6 -2
- nc1709/web/server.py +63 -3
- nc1709/web/templates/index.html +819 -140
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/METADATA +10 -7
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/RECORD +28 -22
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/WHEEL +0 -0
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/entry_points.txt +0 -0
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/licenses/LICENSE +0 -0
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/top_level.txt +0 -0
nc1709/cli.py
CHANGED
|
@@ -17,7 +17,8 @@ from .remote_client import RemoteClient, is_remote_mode
|
|
|
17
17
|
from .cli_ui import (
|
|
18
18
|
ActionSpinner, Color, Icons,
|
|
19
19
|
status, thinking, success, error, warning, info,
|
|
20
|
-
action_spinner, print_response, format_response
|
|
20
|
+
action_spinner, print_response, format_response,
|
|
21
|
+
log_action, log_output, get_terminal_width
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
# Default server URL - users connect to this server by default
|
|
@@ -96,6 +97,26 @@ try:
|
|
|
96
97
|
except ImportError:
|
|
97
98
|
HAS_COGNITIVE = False
|
|
98
99
|
|
|
100
|
+
# Import requirements tracker
|
|
101
|
+
try:
|
|
102
|
+
from .requirements_tracker import (
|
|
103
|
+
RequirementsTracker, RequirementStatus, RequirementPriority,
|
|
104
|
+
get_tracker, reset_tracker
|
|
105
|
+
)
|
|
106
|
+
HAS_REQUIREMENTS = True
|
|
107
|
+
except ImportError:
|
|
108
|
+
HAS_REQUIREMENTS = False
|
|
109
|
+
|
|
110
|
+
# Import conversation logger
|
|
111
|
+
try:
|
|
112
|
+
from .conversation_logger import (
|
|
113
|
+
ConversationLogger, init_logger, get_logger,
|
|
114
|
+
log_user, log_assistant, log_tool, log_error, log_system
|
|
115
|
+
)
|
|
116
|
+
HAS_CONVERSATION_LOGGER = True
|
|
117
|
+
except ImportError:
|
|
118
|
+
HAS_CONVERSATION_LOGGER = False
|
|
119
|
+
|
|
99
120
|
|
|
100
121
|
class NC1709CLI:
|
|
101
122
|
"""Main CLI application"""
|
|
@@ -150,9 +171,18 @@ class NC1709CLI:
|
|
|
150
171
|
api_key=self.api_key
|
|
151
172
|
)
|
|
152
173
|
# Verify connection
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
174
|
+
server_status = self.remote_client.check_status()
|
|
175
|
+
|
|
176
|
+
# Get server host for display
|
|
177
|
+
from urllib.parse import urlparse
|
|
178
|
+
parsed = urlparse(self.remote_url)
|
|
179
|
+
server_host = parsed.netloc or self.remote_url
|
|
180
|
+
|
|
181
|
+
# Store tools count for banner
|
|
182
|
+
self._remote_tools_count = server_status.get('tools_count', 17) # Default to 17
|
|
183
|
+
|
|
184
|
+
# Clean connection display (Claude Code style)
|
|
185
|
+
print(f"\n{Color.GREEN}●{Color.RESET} Connected to {Color.BOLD}{server_host}{Color.RESET}")
|
|
156
186
|
|
|
157
187
|
# Set up minimal local components (no LLM needed)
|
|
158
188
|
self.file_controller = FileController()
|
|
@@ -194,7 +224,12 @@ class NC1709CLI:
|
|
|
194
224
|
|
|
195
225
|
# Create new ID based on machine info
|
|
196
226
|
import platform
|
|
197
|
-
|
|
227
|
+
import getpass
|
|
228
|
+
try:
|
|
229
|
+
username = getpass.getuser()
|
|
230
|
+
except Exception:
|
|
231
|
+
username = os.environ.get('USER', os.environ.get('USERNAME', 'user'))
|
|
232
|
+
machine_info = f"{platform.node()}-{platform.machine()}-{username}"
|
|
198
233
|
user_id = hashlib.sha256(machine_info.encode()).hexdigest()[:16]
|
|
199
234
|
|
|
200
235
|
# Save for future sessions
|
|
@@ -676,6 +711,12 @@ class NC1709CLI:
|
|
|
676
711
|
"""
|
|
677
712
|
self._print_banner()
|
|
678
713
|
|
|
714
|
+
# Initialize conversation logger for this session
|
|
715
|
+
if HAS_CONVERSATION_LOGGER:
|
|
716
|
+
mode = "remote" if self.remote_client else "local"
|
|
717
|
+
self._conversation_logger = init_logger(mode=mode)
|
|
718
|
+
log_system("Session started", {"mode": mode, "cwd": str(Path.cwd())})
|
|
719
|
+
|
|
679
720
|
# Initialize or resume session
|
|
680
721
|
if self.session_manager:
|
|
681
722
|
if resume_session:
|
|
@@ -688,9 +729,28 @@ class NC1709CLI:
|
|
|
688
729
|
self.session_manager.start_session(project_path=str(Path.cwd()))
|
|
689
730
|
else:
|
|
690
731
|
self.session_manager.start_session(project_path=str(Path.cwd()))
|
|
691
|
-
print(f"📝 Started new session: {self.session_manager.current_session.id}")
|
|
692
732
|
|
|
693
|
-
|
|
733
|
+
# Show tools count and session ID (Claude Code style)
|
|
734
|
+
if self.remote_client:
|
|
735
|
+
tools_count = getattr(self, '_remote_tools_count', 17)
|
|
736
|
+
print(f"{Color.GREEN}●{Color.RESET} {tools_count} tools available")
|
|
737
|
+
else:
|
|
738
|
+
# Count local tools
|
|
739
|
+
tools_count = 0
|
|
740
|
+
if hasattr(self, '_local_registry') and self._local_registry:
|
|
741
|
+
tools_count = len(self._local_registry.list_names())
|
|
742
|
+
print(f"{Color.GREEN}●{Color.RESET} {tools_count} tools available")
|
|
743
|
+
|
|
744
|
+
# Show session ID
|
|
745
|
+
if HAS_CONVERSATION_LOGGER and hasattr(self, '_conversation_logger') and self._conversation_logger:
|
|
746
|
+
session_id = self._conversation_logger.session_id[:16] # Shortened
|
|
747
|
+
print(f"{Color.GREEN}●{Color.RESET} Session: {Color.DIM}{session_id}{Color.RESET}")
|
|
748
|
+
|
|
749
|
+
# Initialize file tracking for this session
|
|
750
|
+
self._session_files_created = []
|
|
751
|
+
self._session_files_modified = []
|
|
752
|
+
|
|
753
|
+
print()
|
|
694
754
|
|
|
695
755
|
# Set up prompt_toolkit with slash command completion
|
|
696
756
|
prompt_session = self._create_prompt_session()
|
|
@@ -750,6 +810,16 @@ class NC1709CLI:
|
|
|
750
810
|
self._list_sessions()
|
|
751
811
|
continue
|
|
752
812
|
|
|
813
|
+
# Conversation logs commands
|
|
814
|
+
if cmd_lower == "logs":
|
|
815
|
+
self._show_conversation_logs()
|
|
816
|
+
continue
|
|
817
|
+
|
|
818
|
+
if cmd_lower.startswith("logs "):
|
|
819
|
+
session_id = cmd[5:].strip()
|
|
820
|
+
self._show_conversation_log(session_id)
|
|
821
|
+
continue
|
|
822
|
+
|
|
753
823
|
if cmd_lower == "save":
|
|
754
824
|
if self.session_manager and self.session_manager.current_session:
|
|
755
825
|
self.session_manager.save_session(self.session_manager.current_session)
|
|
@@ -941,6 +1011,47 @@ class NC1709CLI:
|
|
|
941
1011
|
self._exit_plan_mode()
|
|
942
1012
|
continue
|
|
943
1013
|
|
|
1014
|
+
# Requirements tracking commands
|
|
1015
|
+
if cmd_lower in ["requirements", "reqs"]:
|
|
1016
|
+
self._show_requirements()
|
|
1017
|
+
continue
|
|
1018
|
+
|
|
1019
|
+
if cmd_lower.startswith("requirements init ") or cmd_lower.startswith("reqs init "):
|
|
1020
|
+
parts = cmd.split(" ", 2)
|
|
1021
|
+
name = parts[2] if len(parts) > 2 else ""
|
|
1022
|
+
self._init_requirements(name)
|
|
1023
|
+
continue
|
|
1024
|
+
|
|
1025
|
+
if cmd_lower.startswith("requirements add ") or cmd_lower.startswith("reqs add "):
|
|
1026
|
+
parts = cmd.split(" ", 2)
|
|
1027
|
+
title = parts[2] if len(parts) > 2 else ""
|
|
1028
|
+
self._add_requirement(title)
|
|
1029
|
+
continue
|
|
1030
|
+
|
|
1031
|
+
if cmd_lower.startswith("requirements done ") or cmd_lower.startswith("reqs done "):
|
|
1032
|
+
parts = cmd.split(" ", 2)
|
|
1033
|
+
req_id = parts[2] if len(parts) > 2 else ""
|
|
1034
|
+
self._complete_requirement(req_id)
|
|
1035
|
+
continue
|
|
1036
|
+
|
|
1037
|
+
if cmd_lower.startswith("requirements start ") or cmd_lower.startswith("reqs start "):
|
|
1038
|
+
parts = cmd.split(" ", 2)
|
|
1039
|
+
req_id = parts[2] if len(parts) > 2 else ""
|
|
1040
|
+
self._start_requirement(req_id)
|
|
1041
|
+
continue
|
|
1042
|
+
|
|
1043
|
+
if cmd_lower.startswith("requirements note "):
|
|
1044
|
+
parts = cmd.split(" ", 3)
|
|
1045
|
+
if len(parts) >= 4:
|
|
1046
|
+
req_id = parts[2]
|
|
1047
|
+
note = parts[3]
|
|
1048
|
+
self._add_requirement_note(req_id, note)
|
|
1049
|
+
continue
|
|
1050
|
+
|
|
1051
|
+
if cmd_lower in ["requirements all", "reqs all"]:
|
|
1052
|
+
self._show_requirements(include_completed=True)
|
|
1053
|
+
continue
|
|
1054
|
+
|
|
944
1055
|
# GitHub/PR commands
|
|
945
1056
|
if cmd_lower == "pr":
|
|
946
1057
|
self._create_pr_interactive()
|
|
@@ -1130,6 +1241,10 @@ class NC1709CLI:
|
|
|
1130
1241
|
Args:
|
|
1131
1242
|
prompt: User's prompt
|
|
1132
1243
|
"""
|
|
1244
|
+
# Log user message
|
|
1245
|
+
if HAS_CONVERSATION_LOGGER:
|
|
1246
|
+
log_user(prompt)
|
|
1247
|
+
|
|
1133
1248
|
if self.remote_client:
|
|
1134
1249
|
# Remote mode with LOCAL tool execution
|
|
1135
1250
|
# Server only provides LLM thinking, tools run on user's machine
|
|
@@ -1257,11 +1372,19 @@ class NC1709CLI:
|
|
|
1257
1372
|
tool_history = []
|
|
1258
1373
|
final_response = ""
|
|
1259
1374
|
|
|
1375
|
+
# Track files created/modified in this request
|
|
1376
|
+
files_created = []
|
|
1377
|
+
files_modified = []
|
|
1378
|
+
|
|
1260
1379
|
print() # Add spacing
|
|
1261
1380
|
|
|
1381
|
+
# Show analyzing status (Claude Code style)
|
|
1382
|
+
print(f"{Color.YELLOW}{Icons.BULLET}{Color.RESET} Analyzing request...")
|
|
1383
|
+
|
|
1262
1384
|
while iteration < max_iterations:
|
|
1263
1385
|
iteration += 1
|
|
1264
|
-
|
|
1386
|
+
if iteration > 1:
|
|
1387
|
+
thinking(f"Thinking... (iteration {iteration})")
|
|
1265
1388
|
|
|
1266
1389
|
try:
|
|
1267
1390
|
# Call remote server for LLM response (NO tool execution on server)
|
|
@@ -1282,16 +1405,23 @@ class NC1709CLI:
|
|
|
1282
1405
|
clean_response = self._clean_response_text(response)
|
|
1283
1406
|
print_response(clean_response)
|
|
1284
1407
|
|
|
1408
|
+
# Log assistant response
|
|
1409
|
+
if HAS_CONVERSATION_LOGGER:
|
|
1410
|
+
log_assistant(clean_response)
|
|
1411
|
+
|
|
1285
1412
|
# Save assistant response to session for memory
|
|
1286
1413
|
if self.session_manager:
|
|
1287
1414
|
self.session_manager.add_message("assistant", clean_response, auto_save=True)
|
|
1288
1415
|
|
|
1289
|
-
# Show
|
|
1290
|
-
if
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1416
|
+
# Show completion summary with file tree (Claude Code style)
|
|
1417
|
+
if files_created or files_modified:
|
|
1418
|
+
self._show_file_summary(files_created, files_modified)
|
|
1419
|
+
|
|
1420
|
+
# Update session-level file tracking
|
|
1421
|
+
if hasattr(self, '_session_files_created'):
|
|
1422
|
+
self._session_files_created.extend(files_created)
|
|
1423
|
+
if hasattr(self, '_session_files_modified'):
|
|
1424
|
+
self._session_files_modified.extend(files_modified)
|
|
1295
1425
|
|
|
1296
1426
|
# Flush any remaining files to index
|
|
1297
1427
|
self._flush_index_queue()
|
|
@@ -1330,16 +1460,41 @@ class NC1709CLI:
|
|
|
1330
1460
|
tool_history.append({"tool": tool_name, "target": target, "success": False})
|
|
1331
1461
|
continue
|
|
1332
1462
|
|
|
1333
|
-
# Execute tool locally
|
|
1334
|
-
|
|
1463
|
+
# Execute tool locally with Claude Code-style UI
|
|
1464
|
+
log_action(tool_name, target)
|
|
1335
1465
|
|
|
1336
1466
|
try:
|
|
1467
|
+
import time
|
|
1468
|
+
start_time = time.time()
|
|
1337
1469
|
tool_result = tool.run(**tool_params)
|
|
1470
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
1471
|
+
|
|
1338
1472
|
if tool_result.success:
|
|
1339
1473
|
result_text = tool_result.output
|
|
1340
|
-
|
|
1474
|
+
|
|
1475
|
+
# Track file operations
|
|
1476
|
+
file_path = tool_params.get("file_path") or tool_params.get("path") or target
|
|
1477
|
+
if tool_name == "Write":
|
|
1478
|
+
# Track new file creation
|
|
1479
|
+
files_created.append(file_path)
|
|
1480
|
+
# Enhanced output for Write - show line count
|
|
1481
|
+
content = tool_params.get("content", "")
|
|
1482
|
+
line_count = content.count('\n') + 1 if content else 0
|
|
1483
|
+
log_output(f"Written {line_count} lines", is_error=False)
|
|
1484
|
+
elif tool_name == "Edit":
|
|
1485
|
+
# Track file modification
|
|
1486
|
+
files_modified.append(file_path)
|
|
1487
|
+
log_output(f"File modified", is_error=False)
|
|
1488
|
+
else:
|
|
1489
|
+
# Show output in Claude Code style (truncated for readability)
|
|
1490
|
+
log_output(result_text, is_error=False)
|
|
1491
|
+
|
|
1341
1492
|
tool_history.append({"tool": tool_name, "target": target, "success": True})
|
|
1342
1493
|
|
|
1494
|
+
# Log tool call
|
|
1495
|
+
if HAS_CONVERSATION_LOGGER:
|
|
1496
|
+
log_tool(tool_name, tool_params, result_text[:1000], success=True, duration_ms=duration_ms)
|
|
1497
|
+
|
|
1343
1498
|
# Auto-index files when Read tool is used
|
|
1344
1499
|
if tool_name == "Read" and hasattr(self, '_user_id'):
|
|
1345
1500
|
self._queue_file_for_indexing(
|
|
@@ -1348,17 +1503,25 @@ class NC1709CLI:
|
|
|
1348
1503
|
)
|
|
1349
1504
|
else:
|
|
1350
1505
|
result_text = f"Error: {tool_result.error}"
|
|
1351
|
-
|
|
1506
|
+
log_output(result_text, is_error=True)
|
|
1352
1507
|
tool_history.append({"tool": tool_name, "target": target, "success": False})
|
|
1353
1508
|
|
|
1509
|
+
# Log failed tool call
|
|
1510
|
+
if HAS_CONVERSATION_LOGGER:
|
|
1511
|
+
log_tool(tool_name, tool_params, result_text, success=False, duration_ms=duration_ms)
|
|
1512
|
+
|
|
1354
1513
|
all_results.append(f"[{tool_name}({target})] {result_text}")
|
|
1355
1514
|
|
|
1356
1515
|
except Exception as e:
|
|
1357
1516
|
result_text = f"Exception: {str(e)}"
|
|
1358
|
-
|
|
1517
|
+
log_output(result_text, is_error=True)
|
|
1359
1518
|
all_results.append(f"[{tool_name}] {result_text}")
|
|
1360
1519
|
tool_history.append({"tool": tool_name, "target": target, "success": False})
|
|
1361
1520
|
|
|
1521
|
+
# Log exception
|
|
1522
|
+
if HAS_CONVERSATION_LOGGER:
|
|
1523
|
+
log_error(result_text, {"tool": tool_name, "params": tool_params})
|
|
1524
|
+
|
|
1362
1525
|
# Add assistant response and tool results to conversation
|
|
1363
1526
|
messages.append({"role": "assistant", "content": response})
|
|
1364
1527
|
messages.append({
|
|
@@ -1786,6 +1949,14 @@ class NC1709CLI:
|
|
|
1786
1949
|
{B}{M}📧 Need a key? support@lafzusa.com{R}
|
|
1787
1950
|
{C}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{R}
|
|
1788
1951
|
''')
|
|
1952
|
+
# Check for updates and show notification even on auth screen
|
|
1953
|
+
try:
|
|
1954
|
+
from .version_check import check_and_notify
|
|
1955
|
+
update_msg = check_and_notify()
|
|
1956
|
+
if update_msg:
|
|
1957
|
+
print(f"{Y}{update_msg}{R}")
|
|
1958
|
+
except Exception:
|
|
1959
|
+
pass
|
|
1789
1960
|
|
|
1790
1961
|
def _print_startup_banner(self) -> None:
|
|
1791
1962
|
"""Print startup banner (kept for compatibility)"""
|
|
@@ -1900,6 +2071,13 @@ class NC1709CLI:
|
|
|
1900
2071
|
/brain status Show cognitive system status
|
|
1901
2072
|
/brain suggest Get proactive suggestions
|
|
1902
2073
|
/brain index Index project for context awareness
|
|
2074
|
+
|
|
2075
|
+
\033[36mRequirements:\033[0m
|
|
2076
|
+
/requirements Show project requirements (/reqs shortcut)
|
|
2077
|
+
/reqs add <title> Add a new requirement
|
|
2078
|
+
/reqs start <id> Mark requirement as in-progress
|
|
2079
|
+
/reqs done <id> Mark requirement as complete
|
|
2080
|
+
/reqs all Show all requirements including completed
|
|
1903
2081
|
"""
|
|
1904
2082
|
|
|
1905
2083
|
def _show_history(self) -> None:
|
|
@@ -3024,6 +3202,244 @@ class NC1709CLI:
|
|
|
3024
3202
|
print(f"❌ Error connecting: {e}")
|
|
3025
3203
|
return 1
|
|
3026
3204
|
|
|
3205
|
+
# =========================================================================
|
|
3206
|
+
# File Summary Display
|
|
3207
|
+
# =========================================================================
|
|
3208
|
+
|
|
3209
|
+
def _show_file_summary(self, files_created: list, files_modified: list) -> None:
|
|
3210
|
+
"""Show a summary of files created/modified with file tree format.
|
|
3211
|
+
|
|
3212
|
+
Args:
|
|
3213
|
+
files_created: List of file paths that were created
|
|
3214
|
+
files_modified: List of file paths that were modified
|
|
3215
|
+
"""
|
|
3216
|
+
if not files_created and not files_modified:
|
|
3217
|
+
return
|
|
3218
|
+
|
|
3219
|
+
# Deduplicate paths
|
|
3220
|
+
created_set = set(files_created)
|
|
3221
|
+
modified_set = set(files_modified) - created_set # Don't show as modified if created
|
|
3222
|
+
|
|
3223
|
+
total_files = len(created_set) + len(modified_set)
|
|
3224
|
+
|
|
3225
|
+
# Show summary header
|
|
3226
|
+
print(f"\n{Color.GREEN}{Icons.SUCCESS}{Color.RESET} Done!")
|
|
3227
|
+
|
|
3228
|
+
if created_set:
|
|
3229
|
+
print(f"\n{Color.BOLD}Files created:{Color.RESET}")
|
|
3230
|
+
self._print_file_tree(list(created_set), "created")
|
|
3231
|
+
|
|
3232
|
+
if modified_set:
|
|
3233
|
+
print(f"\n{Color.BOLD}Files modified:{Color.RESET}")
|
|
3234
|
+
self._print_file_tree(list(modified_set), "modified")
|
|
3235
|
+
|
|
3236
|
+
print()
|
|
3237
|
+
|
|
3238
|
+
def _print_file_tree(self, file_paths: list, action: str) -> None:
|
|
3239
|
+
"""Print files in a tree format.
|
|
3240
|
+
|
|
3241
|
+
Args:
|
|
3242
|
+
file_paths: List of file paths
|
|
3243
|
+
action: Either 'created' or 'modified'
|
|
3244
|
+
"""
|
|
3245
|
+
# Get current working directory for relative paths
|
|
3246
|
+
cwd = Path.cwd()
|
|
3247
|
+
|
|
3248
|
+
# Sort and convert to relative paths where possible
|
|
3249
|
+
relative_paths = []
|
|
3250
|
+
for fp in file_paths:
|
|
3251
|
+
try:
|
|
3252
|
+
path = Path(fp)
|
|
3253
|
+
if path.is_absolute():
|
|
3254
|
+
try:
|
|
3255
|
+
rel = path.relative_to(cwd)
|
|
3256
|
+
relative_paths.append(str(rel))
|
|
3257
|
+
except ValueError:
|
|
3258
|
+
relative_paths.append(fp)
|
|
3259
|
+
else:
|
|
3260
|
+
relative_paths.append(fp)
|
|
3261
|
+
except Exception:
|
|
3262
|
+
relative_paths.append(fp)
|
|
3263
|
+
|
|
3264
|
+
relative_paths.sort()
|
|
3265
|
+
|
|
3266
|
+
# Print with tree structure
|
|
3267
|
+
for i, path in enumerate(relative_paths):
|
|
3268
|
+
is_last = (i == len(relative_paths) - 1)
|
|
3269
|
+
prefix = Icons.TREE_BRANCH if is_last else Icons.TREE_TEE
|
|
3270
|
+
color = Color.GREEN if action == "created" else Color.YELLOW
|
|
3271
|
+
print(f"{prefix} {color}{path}{Color.RESET}")
|
|
3272
|
+
|
|
3273
|
+
# =========================================================================
|
|
3274
|
+
# Conversation Logs Methods
|
|
3275
|
+
# =========================================================================
|
|
3276
|
+
|
|
3277
|
+
def _show_conversation_logs(self) -> None:
|
|
3278
|
+
"""Show recent conversation logs"""
|
|
3279
|
+
if not HAS_CONVERSATION_LOGGER:
|
|
3280
|
+
warning("Conversation logger not available")
|
|
3281
|
+
return
|
|
3282
|
+
|
|
3283
|
+
sessions = ConversationLogger.list_sessions(limit=20)
|
|
3284
|
+
|
|
3285
|
+
if not sessions:
|
|
3286
|
+
print("No conversation logs found.")
|
|
3287
|
+
print(f"Logs are stored in: ~/.nc1709/logs/")
|
|
3288
|
+
return
|
|
3289
|
+
|
|
3290
|
+
print(f"\n{Color.BOLD}Recent Conversation Logs:{Color.RESET}\n")
|
|
3291
|
+
for session in sessions:
|
|
3292
|
+
session_id = session.get("session_id", "unknown")[:20]
|
|
3293
|
+
started = session.get("started_at", "")[:16]
|
|
3294
|
+
ip = session.get("ip_address") or "local"
|
|
3295
|
+
mode = session.get("mode", "remote")
|
|
3296
|
+
count = session.get("entry_count", 0)
|
|
3297
|
+
|
|
3298
|
+
print(f" {Color.CYAN}{session_id}{Color.RESET}")
|
|
3299
|
+
print(f" Started: {started} | IP: {ip} | Mode: {mode}")
|
|
3300
|
+
print(f" Entries: {count}")
|
|
3301
|
+
print()
|
|
3302
|
+
|
|
3303
|
+
print(f"{Color.DIM}Use /logs <session_id> to view a specific log{Color.RESET}")
|
|
3304
|
+
|
|
3305
|
+
def _show_conversation_log(self, session_id: str) -> None:
|
|
3306
|
+
"""Show a specific conversation log"""
|
|
3307
|
+
if not HAS_CONVERSATION_LOGGER:
|
|
3308
|
+
warning("Conversation logger not available")
|
|
3309
|
+
return
|
|
3310
|
+
|
|
3311
|
+
session_data = ConversationLogger.load_session(session_id)
|
|
3312
|
+
|
|
3313
|
+
if not session_data:
|
|
3314
|
+
error(f"Session log not found: {session_id}")
|
|
3315
|
+
return
|
|
3316
|
+
|
|
3317
|
+
session = session_data.get("session", {})
|
|
3318
|
+
entries = session_data.get("entries", [])
|
|
3319
|
+
|
|
3320
|
+
print(f"\n{Color.BOLD}Session: {session.get('session_id')}{Color.RESET}")
|
|
3321
|
+
print(f"Started: {session.get('started_at', '')[:16]}")
|
|
3322
|
+
print(f"IP: {session.get('ip_address') or 'local'}")
|
|
3323
|
+
print(f"Mode: {session.get('mode', 'remote')}")
|
|
3324
|
+
print(f"Working Dir: {session.get('working_directory', 'unknown')}")
|
|
3325
|
+
print(f"\n{'─' * 60}\n")
|
|
3326
|
+
|
|
3327
|
+
for entry in entries:
|
|
3328
|
+
role = entry.get("role", "unknown")
|
|
3329
|
+
timestamp = entry.get("timestamp", "")[:19]
|
|
3330
|
+
content = entry.get("content", "")
|
|
3331
|
+
metadata = entry.get("metadata", {})
|
|
3332
|
+
|
|
3333
|
+
if role == "user":
|
|
3334
|
+
print(f"{Color.GREEN}[{timestamp}] User:{Color.RESET}")
|
|
3335
|
+
print(f" {content[:500]}{'...' if len(content) > 500 else ''}")
|
|
3336
|
+
elif role == "assistant":
|
|
3337
|
+
print(f"{Color.BLUE}[{timestamp}] Assistant:{Color.RESET}")
|
|
3338
|
+
print(f" {content[:500]}{'...' if len(content) > 500 else ''}")
|
|
3339
|
+
elif role == "tool":
|
|
3340
|
+
tool_name = metadata.get("tool_name", "unknown")
|
|
3341
|
+
success_str = "✓" if metadata.get("success") else "✗"
|
|
3342
|
+
duration = metadata.get("duration_ms", 0)
|
|
3343
|
+
print(f"{Color.YELLOW}[{timestamp}] Tool: {tool_name} {success_str} ({duration}ms){Color.RESET}")
|
|
3344
|
+
elif role == "error":
|
|
3345
|
+
print(f"{Color.RED}[{timestamp}] Error: {content}{Color.RESET}")
|
|
3346
|
+
elif role == "system":
|
|
3347
|
+
print(f"{Color.DIM}[{timestamp}] System: {content}{Color.RESET}")
|
|
3348
|
+
|
|
3349
|
+
print()
|
|
3350
|
+
|
|
3351
|
+
print(f"\n{Color.DIM}Total entries: {len(entries)}{Color.RESET}")
|
|
3352
|
+
|
|
3353
|
+
# =========================================================================
|
|
3354
|
+
# Requirements Tracking Methods
|
|
3355
|
+
# =========================================================================
|
|
3356
|
+
|
|
3357
|
+
def _show_requirements(self, include_completed: bool = False) -> None:
|
|
3358
|
+
"""Show project requirements"""
|
|
3359
|
+
if not HAS_REQUIREMENTS:
|
|
3360
|
+
warning("Requirements tracking module not available")
|
|
3361
|
+
return
|
|
3362
|
+
|
|
3363
|
+
tracker = get_tracker()
|
|
3364
|
+
print(tracker.format_all(verbose=True, include_completed=include_completed))
|
|
3365
|
+
|
|
3366
|
+
def _init_requirements(self, name: str) -> None:
|
|
3367
|
+
"""Initialize a new project for requirements tracking"""
|
|
3368
|
+
if not HAS_REQUIREMENTS:
|
|
3369
|
+
warning("Requirements tracking module not available")
|
|
3370
|
+
return
|
|
3371
|
+
|
|
3372
|
+
if not name:
|
|
3373
|
+
# Use current directory name
|
|
3374
|
+
name = Path.cwd().name
|
|
3375
|
+
|
|
3376
|
+
tracker = get_tracker()
|
|
3377
|
+
project = tracker.init_project(name)
|
|
3378
|
+
success(f"Initialized requirements for project: {project.name}")
|
|
3379
|
+
info(f"Storage: {tracker.storage_path}")
|
|
3380
|
+
|
|
3381
|
+
def _add_requirement(self, title: str) -> None:
|
|
3382
|
+
"""Add a new requirement"""
|
|
3383
|
+
if not HAS_REQUIREMENTS:
|
|
3384
|
+
warning("Requirements tracking module not available")
|
|
3385
|
+
return
|
|
3386
|
+
|
|
3387
|
+
if not title:
|
|
3388
|
+
warning("Please provide a requirement title")
|
|
3389
|
+
return
|
|
3390
|
+
|
|
3391
|
+
tracker = get_tracker()
|
|
3392
|
+
req = tracker.add_requirement(title)
|
|
3393
|
+
success(f"Added requirement: {req.id}")
|
|
3394
|
+
print(tracker.format_requirement(req, verbose=True))
|
|
3395
|
+
|
|
3396
|
+
def _complete_requirement(self, req_id: str) -> None:
|
|
3397
|
+
"""Mark a requirement as complete"""
|
|
3398
|
+
if not HAS_REQUIREMENTS:
|
|
3399
|
+
warning("Requirements tracking module not available")
|
|
3400
|
+
return
|
|
3401
|
+
|
|
3402
|
+
if not req_id:
|
|
3403
|
+
warning("Please provide a requirement ID")
|
|
3404
|
+
return
|
|
3405
|
+
|
|
3406
|
+
tracker = get_tracker()
|
|
3407
|
+
req = tracker.set_status(req_id.upper(), RequirementStatus.COMPLETED)
|
|
3408
|
+
if req:
|
|
3409
|
+
success(f"Completed: {req.title}")
|
|
3410
|
+
else:
|
|
3411
|
+
error(f"Requirement not found: {req_id}")
|
|
3412
|
+
|
|
3413
|
+
def _start_requirement(self, req_id: str) -> None:
|
|
3414
|
+
"""Mark a requirement as in progress"""
|
|
3415
|
+
if not HAS_REQUIREMENTS:
|
|
3416
|
+
warning("Requirements tracking module not available")
|
|
3417
|
+
return
|
|
3418
|
+
|
|
3419
|
+
if not req_id:
|
|
3420
|
+
warning("Please provide a requirement ID")
|
|
3421
|
+
return
|
|
3422
|
+
|
|
3423
|
+
tracker = get_tracker()
|
|
3424
|
+
req = tracker.set_status(req_id.upper(), RequirementStatus.IN_PROGRESS)
|
|
3425
|
+
if req:
|
|
3426
|
+
success(f"Started: {req.title}")
|
|
3427
|
+
else:
|
|
3428
|
+
error(f"Requirement not found: {req_id}")
|
|
3429
|
+
|
|
3430
|
+
def _add_requirement_note(self, req_id: str, note: str) -> None:
|
|
3431
|
+
"""Add a note to a requirement"""
|
|
3432
|
+
if not HAS_REQUIREMENTS:
|
|
3433
|
+
warning("Requirements tracking module not available")
|
|
3434
|
+
return
|
|
3435
|
+
|
|
3436
|
+
tracker = get_tracker()
|
|
3437
|
+
req = tracker.add_note(req_id.upper(), note)
|
|
3438
|
+
if req:
|
|
3439
|
+
success(f"Added note to {req_id}")
|
|
3440
|
+
else:
|
|
3441
|
+
error(f"Requirement not found: {req_id}")
|
|
3442
|
+
|
|
3027
3443
|
# =========================================================================
|
|
3028
3444
|
# Cognitive System Methods (Brain)
|
|
3029
3445
|
# =========================================================================
|