bone-agent 1.3.2 → 1.4.0
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.
- package/README.md +19 -2
- package/config.yaml.example +13 -2
- package/package.json +3 -2
- package/prompts/main/ask_questions.md +31 -0
- package/prompts/main/batch_independent_calls.md +5 -0
- package/prompts/main/casual_interactions.md +11 -0
- package/prompts/main/code_references.md +8 -0
- package/prompts/main/communication_style.md +12 -0
- package/prompts/main/context_reliability.md +12 -0
- package/prompts/main/conversational_tool_calling.md +15 -0
- package/prompts/main/dream.md +50 -0
- package/prompts/main/editing_pattern.md +13 -0
- package/prompts/main/error_handling.md +6 -0
- package/prompts/main/exploration_pattern.md +21 -0
- package/prompts/main/intro.md +1 -0
- package/prompts/main/obsidian.md +16 -0
- package/prompts/main/obsidian_project.md +79 -0
- package/prompts/main/professional_objectivity.md +3 -0
- package/prompts/main/skills.md +3 -0
- package/prompts/main/targeted_searching.md +10 -0
- package/prompts/main/task_lists_pattern.md +8 -0
- package/prompts/main/temp_folder.md +9 -0
- package/prompts/main/think_before_acting.md +10 -0
- package/prompts/main/tone_and_style.md +4 -0
- package/prompts/main/tool_preferences.md +24 -0
- package/prompts/main/trust_subagent_context.md +21 -0
- package/prompts/main/when_to_use_sub_agent.md +7 -0
- package/prompts/micro/ask_questions.md +1 -0
- package/prompts/micro/batch_independent_calls.md +1 -0
- package/prompts/micro/casual_interactions.md +1 -0
- package/prompts/micro/code_references.md +1 -0
- package/prompts/micro/communication_style.md +1 -0
- package/prompts/micro/context_reliability.md +1 -0
- package/prompts/micro/conversational_tool_calling.md +1 -0
- package/prompts/micro/editing_pattern.md +1 -0
- package/prompts/micro/error_handling.md +1 -0
- package/prompts/micro/exploration_pattern.md +1 -0
- package/prompts/micro/intro.md +1 -0
- package/prompts/micro/obsidian.md +4 -0
- package/prompts/micro/obsidian_project.md +5 -0
- package/prompts/micro/professional_objectivity.md +1 -0
- package/prompts/micro/skills.md +1 -0
- package/prompts/micro/targeted_searching.md +1 -0
- package/prompts/micro/task_lists_pattern.md +1 -0
- package/prompts/micro/temp_folder.md +1 -0
- package/prompts/micro/think_before_acting.md +5 -0
- package/prompts/micro/tone_and_style.md +1 -0
- package/prompts/micro/tool_preferences.md +1 -0
- package/prompts/micro/trust_subagent_context.md +1 -0
- package/prompts/micro/when_to_use_sub_agent.md +1 -0
- package/src/core/agentic.py +134 -106
- package/src/core/chat_manager.py +60 -12
- package/src/core/config_manager.py +14 -1
- package/src/core/cron.py +57 -6
- package/src/core/memory.py +3 -90
- package/src/core/metadata.py +75 -0
- package/src/core/skills.py +463 -0
- package/src/core/sub_agent.py +93 -43
- package/src/core/tool_feedback.py +87 -76
- package/src/llm/client.py +7 -2
- package/src/llm/codex_provider.py +350 -0
- package/src/llm/config.py +74 -4
- package/src/llm/prompts.py +261 -502
- package/src/llm/providers.py +28 -7
- package/src/llm/token_tracker.py +32 -1
- package/src/tools/__init__.py +24 -85
- package/src/tools/create_file.py +1 -1
- package/src/tools/directory.py +1 -1
- package/src/tools/edit.py +13 -7
- package/src/tools/file_reader.py +1 -1
- package/src/tools/helpers/__init__.py +1 -7
- package/src/tools/helpers/base.py +65 -16
- package/src/tools/helpers/loader.py +2 -88
- package/src/tools/helpers/path_resolver.py +70 -13
- package/src/tools/helpers/plugin_manifest.py +99 -70
- package/src/tools/review_sub_agent.py +2 -1
- package/src/tools/rg_search.py +119 -35
- package/src/tools/search_plugins.py +140 -72
- package/src/tools/shell.py +3 -3
- package/src/ui/commands.py +470 -33
- package/src/ui/displays.py +27 -1
- package/src/ui/main.py +1 -4
- package/src/ui/tool_confirmation.py +16 -5
- package/src/utils/editor.py +88 -39
- package/src/utils/settings.py +25 -4
- package/src/utils/user_message_logger.py +120 -0
- package/src/utils/validation.py +10 -0
package/src/ui/commands.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Command routing and help display."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import re
|
|
5
|
+
import subprocess
|
|
4
6
|
from dataclasses import dataclass
|
|
5
7
|
from typing import Optional
|
|
6
8
|
from llm import config
|
|
7
9
|
|
|
8
10
|
from core.config_manager import ConfigManager as ConfigManagerClass
|
|
9
|
-
from ui.displays import show_help_table, show_cron_help_table
|
|
11
|
+
from ui.displays import show_help_table, show_cron_help_table, show_skills_help_table
|
|
10
12
|
from ui.banner import display_startup_banner
|
|
11
13
|
from core.agentic import SubAgentPanel
|
|
12
14
|
from ui.setting_selector import SettingSelector, SettingCategory, SettingOption
|
|
@@ -155,6 +157,9 @@ def _cron_remove(console, sub_args, cron_config, notify_scheduler):
|
|
|
155
157
|
if not job_id:
|
|
156
158
|
console.print("[red]Usage: /cron remove <id>[/red]")
|
|
157
159
|
return CommandResult(status="handled")
|
|
160
|
+
if job_id == "dream":
|
|
161
|
+
console.print("[red]The 'dream' job is managed by DREAM_SETTINGS.enabled in config.yaml and cannot be removed.[/red]")
|
|
162
|
+
return CommandResult(status="handled")
|
|
158
163
|
if cron_config.remove_job(job_id):
|
|
159
164
|
notify_scheduler()
|
|
160
165
|
console.print(f"[green]Removed cron job '{job_id}'[/green]")
|
|
@@ -171,6 +176,9 @@ def _cron_toggle(console, sub_args, cron_config, notify_scheduler, enable):
|
|
|
171
176
|
if not job_id:
|
|
172
177
|
console.print(f"[red]Usage: /cron {verb} <id>[/red]")
|
|
173
178
|
return CommandResult(status="handled")
|
|
179
|
+
if not enable and job_id == "dream":
|
|
180
|
+
console.print("[red]The 'dream' job is managed by DREAM_SETTINGS.enabled in config.yaml and cannot be disabled via /cron.[/red]")
|
|
181
|
+
return CommandResult(status="handled")
|
|
174
182
|
if job_id in cron_config.jobs:
|
|
175
183
|
cron_config.update_job(job_id, enabled=enable)
|
|
176
184
|
notify_scheduler()
|
|
@@ -381,6 +389,12 @@ def _handle_config(chat_manager, console, debug_mode_container, args, cron_sched
|
|
|
381
389
|
{"value": "danger", "text": "DANGER"},
|
|
382
390
|
],
|
|
383
391
|
),
|
|
392
|
+
SettingOption(
|
|
393
|
+
key="memory_enabled", text="Memory",
|
|
394
|
+
value=config.MEMORY_SETTINGS.get("enabled", True),
|
|
395
|
+
input_type="boolean",
|
|
396
|
+
on_text="ON", off_text="OFF",
|
|
397
|
+
),
|
|
384
398
|
]
|
|
385
399
|
|
|
386
400
|
# Build status bar settings
|
|
@@ -480,6 +494,10 @@ def _handle_config(chat_manager, console, debug_mode_container, args, cron_sched
|
|
|
480
494
|
console.print("[bold red on default] Dangerous git commands are still blocked.[/bold red on default]")
|
|
481
495
|
console.print("[bold yellow on default] Use at your own risk![/bold yellow on default]")
|
|
482
496
|
console.print()
|
|
497
|
+
elif key == "memory_enabled":
|
|
498
|
+
config.update_memory_settings({"enabled": value})
|
|
499
|
+
state = "enabled" if value else "disabled"
|
|
500
|
+
change_lines.append(f" Memory: {state}")
|
|
483
501
|
elif key == "compact_trigger_tokens":
|
|
484
502
|
context_settings.compact_trigger_tokens = int(value)
|
|
485
503
|
change_lines.append(f" Compaction Threshold: {value:,} tokens")
|
|
@@ -531,6 +549,17 @@ def _handle_config(chat_manager, console, debug_mode_container, args, cron_sched
|
|
|
531
549
|
except Exception as e:
|
|
532
550
|
console.print(f"[red]Failed to save status bar settings: {e}[/red]")
|
|
533
551
|
|
|
552
|
+
# Persist memory setting to config
|
|
553
|
+
if "memory_enabled" in changes:
|
|
554
|
+
try:
|
|
555
|
+
cfg_data = config_manager.load(force_reload=True)
|
|
556
|
+
if "MEMORY_SETTINGS" not in cfg_data:
|
|
557
|
+
cfg_data["MEMORY_SETTINGS"] = {}
|
|
558
|
+
cfg_data["MEMORY_SETTINGS"]["enabled"] = changes["memory_enabled"]
|
|
559
|
+
config_manager.save(cfg_data)
|
|
560
|
+
except Exception as e:
|
|
561
|
+
console.print(f"[red]Failed to save memory settings: {e}[/red]")
|
|
562
|
+
|
|
534
563
|
# Display summary
|
|
535
564
|
console.print(f"[green]Settings updated:[/green]")
|
|
536
565
|
for line in change_lines:
|
|
@@ -561,12 +590,16 @@ def _handle_clear(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
561
590
|
conv_cache_read = chat_manager.token_tracker.conv_cache_read_tokens
|
|
562
591
|
conv_cache_creation = chat_manager.token_tracker.conv_cache_creation_tokens
|
|
563
592
|
if conv_cache_read > 0 or conv_cache_creation > 0:
|
|
564
|
-
|
|
593
|
+
total_cached = conv_cache_read + conv_cache_creation
|
|
594
|
+
cache_activity_read_pct = (
|
|
595
|
+
conv_cache_read / total_cached * 100
|
|
596
|
+
) if total_cached > 0 else 0
|
|
597
|
+
cache_coverage_pct = (
|
|
565
598
|
conv_cache_read / conv_in * 100
|
|
566
599
|
) if conv_in > 0 else 0
|
|
567
600
|
console.print(f" Cache read: {conv_cache_read:,} tokens")
|
|
568
601
|
console.print(f" Cache write: {conv_cache_creation:,} tokens")
|
|
569
|
-
console.print(f" ({
|
|
602
|
+
console.print(f" ({cache_coverage_pct:.0f}% input cached, {cache_activity_read_pct:.0f}% cache reads)")
|
|
570
603
|
|
|
571
604
|
# Display cost — combined actual + estimated, with config-based fallback
|
|
572
605
|
tracker_conv = chat_manager.token_tracker
|
|
@@ -635,11 +668,12 @@ def _open_provider_editor(chat_manager, console, provider):
|
|
|
635
668
|
min_val=0.0, step=0.01,
|
|
636
669
|
))
|
|
637
670
|
|
|
638
|
-
|
|
671
|
+
provider_label = config.get_provider_display_name(provider)
|
|
672
|
+
category = SettingCategory(title=f"{provider_label} Settings", settings=settings)
|
|
639
673
|
|
|
640
674
|
selector = SettingSelector(
|
|
641
675
|
categories=[category],
|
|
642
|
-
title=f"Configure {
|
|
676
|
+
title=f"Configure {provider_label}",
|
|
643
677
|
)
|
|
644
678
|
|
|
645
679
|
changes = selector.run()
|
|
@@ -712,7 +746,8 @@ def _handle_provider(chat_manager, console, debug_mode_container, args, cron_sch
|
|
|
712
746
|
# Validate provider name
|
|
713
747
|
if provider not in config.get_providers():
|
|
714
748
|
console.print(f"[red]Error: Unknown provider '{provider}'[/red]")
|
|
715
|
-
|
|
749
|
+
available = ', '.join(config.get_provider_display_name(prov) for prov in config.get_providers())
|
|
750
|
+
console.print(f"[dim]Available providers: {available}[/dim]")
|
|
716
751
|
return CommandResult(status="handled")
|
|
717
752
|
|
|
718
753
|
# Switch directly to the named provider
|
|
@@ -726,7 +761,7 @@ def _handle_provider(chat_manager, console, debug_mode_container, args, cron_sch
|
|
|
726
761
|
|
|
727
762
|
cfg = config.get_provider_config(provider)
|
|
728
763
|
model = cfg.get('model') or cfg.get('api_model') or ''
|
|
729
|
-
label =
|
|
764
|
+
label = config.get_provider_display_name(provider)
|
|
730
765
|
if model:
|
|
731
766
|
label += f" ({model})"
|
|
732
767
|
console.print(f"[green]Switched to {label}[/green]")
|
|
@@ -742,7 +777,7 @@ def _handle_provider(chat_manager, console, debug_mode_container, args, cron_sch
|
|
|
742
777
|
for prov in config.get_providers():
|
|
743
778
|
cfg = config.get_provider_config(prov)
|
|
744
779
|
model = cfg.get('model') or cfg.get('api_model') or ''
|
|
745
|
-
entry = {"value": prov, "text":
|
|
780
|
+
entry = {"value": prov, "text": config.get_provider_display_name(prov)}
|
|
746
781
|
if model:
|
|
747
782
|
entry["description"] = model[:40]
|
|
748
783
|
provider_options.append(entry)
|
|
@@ -1062,17 +1097,38 @@ def _handle_usage(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
1062
1097
|
console.print(f" Output tokens: {tracker.total_completion_tokens:,}")
|
|
1063
1098
|
console.print(f" Total tokens: {tracker.total_tokens:,}")
|
|
1064
1099
|
|
|
1065
|
-
# Display cache token breakdown
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1100
|
+
# Display cache token breakdown when cache tokens were recorded.
|
|
1101
|
+
# Codex can report an explicit 0 cached_tokens value, which is still useful
|
|
1102
|
+
# confirmation that prompt-cache usage data is flowing through.
|
|
1103
|
+
show_cache = (
|
|
1104
|
+
tracker.total_cache_read_tokens > 0
|
|
1105
|
+
or tracker.total_cache_creation_tokens > 0
|
|
1106
|
+
or current_provider == "codex"
|
|
1107
|
+
)
|
|
1108
|
+
if show_cache:
|
|
1109
|
+
total_cached = tracker.total_cache_read_tokens + tracker.total_cache_creation_tokens
|
|
1110
|
+
cache_activity_read_pct = (
|
|
1111
|
+
tracker.total_cache_read_tokens
|
|
1112
|
+
/ total_cached * 100
|
|
1113
|
+
) if total_cached > 0 else 0
|
|
1114
|
+
cache_coverage_pct = (
|
|
1069
1115
|
tracker.total_cache_read_tokens
|
|
1070
1116
|
/ tracker.total_prompt_tokens * 100
|
|
1071
1117
|
) if tracker.total_prompt_tokens > 0 else 0
|
|
1072
1118
|
console.print()
|
|
1073
|
-
console.print(
|
|
1119
|
+
console.print(
|
|
1120
|
+
f"[#5F9EA0]Input Cache ({cache_coverage_pct:.0f}% input cached, "
|
|
1121
|
+
f"{cache_activity_read_pct:.0f}% cache reads):[/#5F9EA0]"
|
|
1122
|
+
)
|
|
1074
1123
|
console.print(f" Cache read: {tracker.total_cache_read_tokens:,} tokens")
|
|
1075
1124
|
console.print(f" Cache write: {tracker.total_cache_creation_tokens:,} tokens")
|
|
1125
|
+
if current_provider == "codex" and total_cached == 0:
|
|
1126
|
+
if tracker.last_cache_metrics_reported is False:
|
|
1127
|
+
keys = ", ".join(tracker.last_usage_keys) if tracker.last_usage_keys else "none"
|
|
1128
|
+
console.print(" [dim]Codex did not report any cache-token fields in the last usage payload.[/dim]")
|
|
1129
|
+
console.print(f" [dim]Last usage keys: {keys}[/dim]")
|
|
1130
|
+
elif tracker.last_cache_metrics_reported is None:
|
|
1131
|
+
console.print(" [dim]No usage payload has been recorded yet for this Codex session.[/dim]")
|
|
1076
1132
|
console.print()
|
|
1077
1133
|
|
|
1078
1134
|
|
|
@@ -2153,8 +2209,8 @@ def _handle_obsidian(chat_manager, console, debug_mode_container, args, cron_sch
|
|
|
2153
2209
|
return CommandResult(status="handled")
|
|
2154
2210
|
|
|
2155
2211
|
|
|
2156
|
-
def
|
|
2157
|
-
"""Persist
|
|
2212
|
+
def _persist_tool_visibility(console):
|
|
2213
|
+
"""Persist tool and skill visibility state to config file.
|
|
2158
2214
|
|
|
2159
2215
|
Returns True on success, False on failure.
|
|
2160
2216
|
"""
|
|
@@ -2163,6 +2219,7 @@ def _persist_disabled_tools(console):
|
|
|
2163
2219
|
if "TOOL_SETTINGS" not in cfg_data:
|
|
2164
2220
|
cfg_data["TOOL_SETTINGS"] = {}
|
|
2165
2221
|
cfg_data["TOOL_SETTINGS"]["disabled_tools"] = list(tool_settings.disabled_tools)
|
|
2222
|
+
cfg_data["TOOL_SETTINGS"]["hidden_skills"] = list(tool_settings.hidden_skills)
|
|
2166
2223
|
config_manager.save(cfg_data)
|
|
2167
2224
|
return True
|
|
2168
2225
|
except Exception as e:
|
|
@@ -2171,18 +2228,23 @@ def _persist_disabled_tools(console):
|
|
|
2171
2228
|
|
|
2172
2229
|
|
|
2173
2230
|
def _handle_tools(chat_manager, console, debug_mode_container, args, cron_scheduler=None):
|
|
2174
|
-
"""Handle /tools command —
|
|
2231
|
+
"""Handle /tools command — manage tool availability and skill discovery visibility.
|
|
2175
2232
|
|
|
2176
|
-
No args: Launch interactive SettingSelector with tools grouped by category
|
|
2233
|
+
No args: Launch interactive SettingSelector with tools/plugins grouped by category
|
|
2234
|
+
and a skills section for discovery visibility.
|
|
2177
2235
|
Subcommands:
|
|
2178
|
-
list — show all tools
|
|
2179
|
-
enable <name> — enable a
|
|
2180
|
-
disable <name> — disable a
|
|
2181
|
-
|
|
2236
|
+
list — show all tools, plugins, and skills with status
|
|
2237
|
+
enable <name> — enable a core tool or plugin
|
|
2238
|
+
disable <name> — disable a core tool or plugin
|
|
2239
|
+
show-skill <name> — make a skill visible in discovery surfaces
|
|
2240
|
+
hide-skill <name> — hide a skill from discovery surfaces
|
|
2241
|
+
enable-group <key> — enable all tools in a group (e.g. file_ops, task_mgmt)
|
|
2182
2242
|
disable-group <key> — disable all tools in a group
|
|
2183
2243
|
"""
|
|
2244
|
+
from core.skills import iter_skill_summaries, validate_skill_name
|
|
2184
2245
|
from ui.setting_selector import SettingOption, SettingCategory, SettingSelector
|
|
2185
2246
|
from tools.helpers.base import ToolRegistry, TOOL_GROUPS
|
|
2247
|
+
from tools.helpers.plugin_manifest import plugin_manifest
|
|
2186
2248
|
|
|
2187
2249
|
# Text subcommands
|
|
2188
2250
|
if args:
|
|
@@ -2190,17 +2252,27 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2190
2252
|
|
|
2191
2253
|
if args_clean.lower() in ("list", "status"):
|
|
2192
2254
|
all_tools = sorted(ToolRegistry._tools.values(), key=lambda t: t.name)
|
|
2255
|
+
plugin_defs = sorted(plugin_manifest.get_all(), key=lambda t: t.name)
|
|
2256
|
+
skills = sorted(iter_skill_summaries(), key=lambda s: s.name)
|
|
2193
2257
|
disabled = ToolRegistry.get_disabled()
|
|
2194
|
-
|
|
2258
|
+
hidden_skills = set(tool_settings.hidden_skills)
|
|
2259
|
+
disabled_tools = {t.name for t in all_tools if t.name in disabled}
|
|
2260
|
+
console.print(
|
|
2261
|
+
f"[bold #5F9EA0]Tools: {len(all_tools) - len(disabled_tools)} enabled, {len(disabled_tools)} disabled[/bold #5F9EA0]"
|
|
2262
|
+
)
|
|
2263
|
+
console.print(
|
|
2264
|
+
f"[bold #5F9EA0]User plugins: {sum(1 for p in plugin_defs if p.name not in disabled)} enabled, {sum(1 for p in plugin_defs if p.name in disabled)} disabled[/bold #5F9EA0]"
|
|
2265
|
+
)
|
|
2266
|
+
console.print(
|
|
2267
|
+
f"[bold #5F9EA0]Skills: {len(skills) - len(hidden_skills)} visible, {len(hidden_skills)} hidden[/bold #5F9EA0]"
|
|
2268
|
+
)
|
|
2195
2269
|
console.print()
|
|
2196
2270
|
|
|
2197
|
-
# Build reverse lookup: tool_name -> group_label
|
|
2198
2271
|
tool_to_group = {}
|
|
2199
2272
|
for gkey, gdef in TOOL_GROUPS.items():
|
|
2200
2273
|
for tname in gdef["tools"]:
|
|
2201
2274
|
tool_to_group.setdefault(tname, []).append(gdef["label"])
|
|
2202
2275
|
|
|
2203
|
-
# Group tools for display
|
|
2204
2276
|
current_group = None
|
|
2205
2277
|
for t in all_tools:
|
|
2206
2278
|
groups = tool_to_group.get(t.name, [])
|
|
@@ -2212,6 +2284,18 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2212
2284
|
status = "[red]off[/red]" if is_off else "[green]on[/green] "
|
|
2213
2285
|
console.print(f" {status} {t.name}")
|
|
2214
2286
|
|
|
2287
|
+
console.print()
|
|
2288
|
+
console.print(" [bold]User plugins[/bold]")
|
|
2289
|
+
for plugin in plugin_defs:
|
|
2290
|
+
status = "[red]off[/red]" if plugin.name in disabled else "[green]on[/green] "
|
|
2291
|
+
console.print(f" {status} {plugin.name}")
|
|
2292
|
+
|
|
2293
|
+
console.print()
|
|
2294
|
+
console.print(" [bold]Skills[/bold]")
|
|
2295
|
+
for skill in skills:
|
|
2296
|
+
status = "[red]hidden[/red]" if skill.name in hidden_skills else "[green]visible[/green]"
|
|
2297
|
+
console.print(f" {status} {skill.name}")
|
|
2298
|
+
|
|
2215
2299
|
console.print()
|
|
2216
2300
|
console.print("[dim]Groups:[/dim] " + ", ".join(
|
|
2217
2301
|
f"[bold]{k}[/bold] ({v['label']})" for k, v in TOOL_GROUPS.items()
|
|
@@ -2250,17 +2334,17 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2250
2334
|
|
|
2251
2335
|
# Sync and persist
|
|
2252
2336
|
tool_settings.disabled_tools = sorted(ToolRegistry.get_disabled())
|
|
2253
|
-
|
|
2337
|
+
_persist_tool_visibility(console)
|
|
2254
2338
|
console.print()
|
|
2255
2339
|
return CommandResult(status="handled")
|
|
2256
2340
|
|
|
2257
|
-
# Single tool operations
|
|
2341
|
+
# Single tool/plugin operations
|
|
2258
2342
|
if action in ("enable", "disable"):
|
|
2259
|
-
# Match case-insensitively against registered tools
|
|
2260
2343
|
all_registered_lower = {t.name.lower(): t.name for t in ToolRegistry._tools.values()}
|
|
2344
|
+
all_registered_lower.update({t.name.lower(): t.name for t in plugin_manifest.get_all()})
|
|
2261
2345
|
matched = all_registered_lower.get(target.lower())
|
|
2262
2346
|
if not matched:
|
|
2263
|
-
console.print(f"[red]Unknown tool: {target}[/red]")
|
|
2347
|
+
console.print(f"[red]Unknown tool or plugin: {target}[/red]")
|
|
2264
2348
|
console.print(f"[dim]Run [bold #5F9EA0]/tools list[/bold #5F9EA0] to see all tools.[/dim]")
|
|
2265
2349
|
return CommandResult(status="handled")
|
|
2266
2350
|
|
|
@@ -2274,17 +2358,44 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2274
2358
|
tool_settings.disabled_tools.append(matched)
|
|
2275
2359
|
console.print(f"[yellow]Disabled: {matched}[/yellow]")
|
|
2276
2360
|
|
|
2277
|
-
|
|
2361
|
+
_persist_tool_visibility(console)
|
|
2362
|
+
console.print()
|
|
2363
|
+
return CommandResult(status="handled")
|
|
2364
|
+
|
|
2365
|
+
if action in ("show-skill", "hide-skill"):
|
|
2366
|
+
try:
|
|
2367
|
+
skill_name = validate_skill_name(target)
|
|
2368
|
+
except Exception as e:
|
|
2369
|
+
console.print(f"[red]{e}[/red]")
|
|
2370
|
+
return CommandResult(status="handled")
|
|
2371
|
+
|
|
2372
|
+
known_skills = {skill.name for skill in iter_skill_summaries()}
|
|
2373
|
+
if skill_name not in known_skills:
|
|
2374
|
+
console.print(f"[red]Unknown skill: {skill_name}[/red]")
|
|
2375
|
+
return CommandResult(status="handled")
|
|
2376
|
+
|
|
2377
|
+
if action == "show-skill":
|
|
2378
|
+
tool_settings.hidden_skills = [n for n in tool_settings.hidden_skills if n != skill_name]
|
|
2379
|
+
console.print(f"[green]Skill visible in discovery: {skill_name}[/green]")
|
|
2380
|
+
else:
|
|
2381
|
+
if skill_name not in tool_settings.hidden_skills:
|
|
2382
|
+
tool_settings.hidden_skills.append(skill_name)
|
|
2383
|
+
console.print(f"[yellow]Skill hidden from discovery: {skill_name}[/yellow]")
|
|
2384
|
+
|
|
2385
|
+
tool_settings.hidden_skills = sorted(set(tool_settings.hidden_skills))
|
|
2386
|
+
_persist_tool_visibility(console)
|
|
2278
2387
|
console.print()
|
|
2279
2388
|
return CommandResult(status="handled")
|
|
2280
2389
|
|
|
2281
2390
|
console.print(f"[red]Unknown subcommand: {args}[/red]")
|
|
2282
|
-
console.print("Usage: [bold #5F9EA0]/tools[/bold #5F9EA0] [list | enable <name> | disable <name> | enable-group <key> | disable-group <key>]")
|
|
2391
|
+
console.print("Usage: [bold #5F9EA0]/tools[/bold #5F9EA0] [list | enable <name> | disable <name> | show-skill <name> | hide-skill <name> | enable-group <key> | disable-group <key>]")
|
|
2283
2392
|
return CommandResult(status="handled")
|
|
2284
2393
|
|
|
2285
2394
|
# No args — interactive toggle UI, organized by groups
|
|
2286
2395
|
all_tools_map = {t.name: t for t in ToolRegistry._tools.values()}
|
|
2396
|
+
plugin_tools = {t.name: t for t in plugin_manifest.get_all()}
|
|
2287
2397
|
disabled = ToolRegistry.get_disabled()
|
|
2398
|
+
hidden_skills = set(tool_settings.hidden_skills)
|
|
2288
2399
|
|
|
2289
2400
|
categories = []
|
|
2290
2401
|
for gkey, gdef in TOOL_GROUPS.items():
|
|
@@ -2324,10 +2435,36 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2324
2435
|
input_type="boolean",
|
|
2325
2436
|
on_text="ON",
|
|
2326
2437
|
off_text="OFF",
|
|
2327
|
-
description=f"Modes: {modes}",
|
|
2328
2438
|
))
|
|
2329
2439
|
categories.append(SettingCategory(title="Other", settings=other_options))
|
|
2330
2440
|
|
|
2441
|
+
plugin_options = []
|
|
2442
|
+
for name in sorted(plugin_tools):
|
|
2443
|
+
is_off = name in disabled
|
|
2444
|
+
plugin_options.append(SettingOption(
|
|
2445
|
+
key=name,
|
|
2446
|
+
text=name,
|
|
2447
|
+
value=not is_off,
|
|
2448
|
+
input_type="boolean",
|
|
2449
|
+
on_text="ON",
|
|
2450
|
+
off_text="OFF",
|
|
2451
|
+
))
|
|
2452
|
+
if plugin_options:
|
|
2453
|
+
categories.append(SettingCategory(title="User plugins", settings=plugin_options))
|
|
2454
|
+
|
|
2455
|
+
skill_options = []
|
|
2456
|
+
for skill in sorted(iter_skill_summaries(), key=lambda s: s.name):
|
|
2457
|
+
skill_options.append(SettingOption(
|
|
2458
|
+
key=f"skill:{skill.name}",
|
|
2459
|
+
text=skill.name,
|
|
2460
|
+
value=skill.name not in hidden_skills,
|
|
2461
|
+
input_type="boolean",
|
|
2462
|
+
on_text="VISIBLE",
|
|
2463
|
+
off_text="HIDDEN",
|
|
2464
|
+
))
|
|
2465
|
+
if skill_options:
|
|
2466
|
+
categories.append(SettingCategory(title="Skills", settings=skill_options))
|
|
2467
|
+
|
|
2331
2468
|
selector = SettingSelector(
|
|
2332
2469
|
categories=categories,
|
|
2333
2470
|
title="Tool Settings",
|
|
@@ -2346,7 +2483,17 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2346
2483
|
# Apply changes
|
|
2347
2484
|
newly_disabled = []
|
|
2348
2485
|
newly_enabled = []
|
|
2486
|
+
newly_hidden_skills = []
|
|
2487
|
+
newly_visible_skills = []
|
|
2349
2488
|
for name, enabled in changes.items():
|
|
2489
|
+
if name.startswith("skill:"):
|
|
2490
|
+
skill_name = name.split(":", 1)[1]
|
|
2491
|
+
if enabled and skill_name in hidden_skills:
|
|
2492
|
+
newly_visible_skills.append(skill_name)
|
|
2493
|
+
elif not enabled and skill_name not in hidden_skills:
|
|
2494
|
+
newly_hidden_skills.append(skill_name)
|
|
2495
|
+
continue
|
|
2496
|
+
|
|
2350
2497
|
if not enabled and name not in disabled:
|
|
2351
2498
|
ToolRegistry.disable(name)
|
|
2352
2499
|
newly_disabled.append(name)
|
|
@@ -2354,10 +2501,13 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2354
2501
|
ToolRegistry.enable(name)
|
|
2355
2502
|
newly_enabled.append(name)
|
|
2356
2503
|
|
|
2357
|
-
# Sync tool_settings.disabled_tools to be the full current disabled set
|
|
2358
2504
|
tool_settings.disabled_tools = sorted(ToolRegistry.get_disabled())
|
|
2505
|
+
next_hidden_skills = set(hidden_skills)
|
|
2506
|
+
next_hidden_skills.update(newly_hidden_skills)
|
|
2507
|
+
next_hidden_skills.difference_update(newly_visible_skills)
|
|
2508
|
+
tool_settings.hidden_skills = sorted(next_hidden_skills)
|
|
2359
2509
|
|
|
2360
|
-
|
|
2510
|
+
_persist_tool_visibility(console)
|
|
2361
2511
|
|
|
2362
2512
|
# Summary
|
|
2363
2513
|
change_lines = []
|
|
@@ -2365,6 +2515,10 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2365
2515
|
change_lines.append(f" [yellow]Disabled:[/yellow] {name}")
|
|
2366
2516
|
for name in newly_enabled:
|
|
2367
2517
|
change_lines.append(f" [green]Enabled:[/green] {name}")
|
|
2518
|
+
for name in newly_hidden_skills:
|
|
2519
|
+
change_lines.append(f" [yellow]Hidden skill:[/yellow] {name}")
|
|
2520
|
+
for name in newly_visible_skills:
|
|
2521
|
+
change_lines.append(f" [green]Visible skill:[/green] {name}")
|
|
2368
2522
|
|
|
2369
2523
|
if change_lines:
|
|
2370
2524
|
total_enabled = len(ToolRegistry.get_all())
|
|
@@ -2430,9 +2584,258 @@ def _handle_cd(chat_manager, console, debug_mode_container, args, cron_scheduler
|
|
|
2430
2584
|
except Exception:
|
|
2431
2585
|
pass
|
|
2432
2586
|
|
|
2587
|
+
# Rebuild system prompt so project root stays current
|
|
2588
|
+
try:
|
|
2589
|
+
chat_manager.update_system_prompt()
|
|
2590
|
+
except Exception:
|
|
2591
|
+
pass
|
|
2592
|
+
|
|
2433
2593
|
return CommandResult(status="handled")
|
|
2434
2594
|
|
|
2435
2595
|
|
|
2596
|
+
def _handle_prompt(chat_manager, console, debug_mode_container, args, cron_scheduler=None):
|
|
2597
|
+
"""Handle /prompt command — show/swap prompt variants."""
|
|
2598
|
+
from utils.settings import prompt_settings
|
|
2599
|
+
from llm.prompts import _variant_available, _list_variants
|
|
2600
|
+
|
|
2601
|
+
cfg_manager = config_manager
|
|
2602
|
+
|
|
2603
|
+
if not args or args.strip() == "list":
|
|
2604
|
+
variants = _list_variants()
|
|
2605
|
+
current = prompt_settings.variant
|
|
2606
|
+
console.print()
|
|
2607
|
+
console.print(f"[bold #5F9EA0]Prompt Variants[/bold #5F9EA0] (current: [bold]{current}[/bold])")
|
|
2608
|
+
console.print()
|
|
2609
|
+
for v in variants:
|
|
2610
|
+
marker = "[bold green]active[/bold green]" if v == current else ""
|
|
2611
|
+
console.print(f" [bold]{v}[/bold] {marker}")
|
|
2612
|
+
console.print()
|
|
2613
|
+
console.print("[dim]Switch with: [bold #5F9EA0]/prompt main[/bold #5F9EA0] or [bold #5F9EA0]/prompt micro[/bold #5F9EA0][/dim]")
|
|
2614
|
+
return CommandResult(status="handled")
|
|
2615
|
+
|
|
2616
|
+
# Single arg: variant name to switch to
|
|
2617
|
+
target = args.strip().lower()
|
|
2618
|
+
|
|
2619
|
+
if not _variant_available(target):
|
|
2620
|
+
variants = _list_variants()
|
|
2621
|
+
console.print(f"[red]Unknown variant: '{target}'[/red]")
|
|
2622
|
+
console.print(f"[dim]Available: {', '.join(variants)}[/dim]")
|
|
2623
|
+
return CommandResult(status="handled")
|
|
2624
|
+
|
|
2625
|
+
# Update settings
|
|
2626
|
+
prompt_settings.variant = target
|
|
2627
|
+
|
|
2628
|
+
# Persist to config
|
|
2629
|
+
try:
|
|
2630
|
+
cfg_data = cfg_manager.load(force_reload=True)
|
|
2631
|
+
if "PROMPT_SETTINGS" not in cfg_data:
|
|
2632
|
+
cfg_data["PROMPT_SETTINGS"] = {}
|
|
2633
|
+
cfg_data["PROMPT_SETTINGS"]["variant"] = target
|
|
2634
|
+
cfg_manager.save(cfg_data)
|
|
2635
|
+
except Exception as e:
|
|
2636
|
+
console.print(f"[red]Failed to save variant to config: {e}[/red]")
|
|
2637
|
+
console.print("[yellow]Variant applied for this session only — it will revert on restart.[/yellow]")
|
|
2638
|
+
|
|
2639
|
+
# Rebuild system prompt in-place (no restart)
|
|
2640
|
+
chat_manager.update_system_prompt(variant=target)
|
|
2641
|
+
console.print(f"[green]Switched to '{target}' variant[/green]")
|
|
2642
|
+
console.print("[dim]System prompt rebuilt in-place.[/dim]")
|
|
2643
|
+
|
|
2644
|
+
return CommandResult(status="handled")
|
|
2645
|
+
|
|
2646
|
+
|
|
2647
|
+
def _print_skills_usage(console):
|
|
2648
|
+
show_skills_help_table(console)
|
|
2649
|
+
|
|
2650
|
+
|
|
2651
|
+
def _skills_list(console, query=None):
|
|
2652
|
+
from datetime import datetime
|
|
2653
|
+
from core.skills import get_skills_dir, list_skills
|
|
2654
|
+
|
|
2655
|
+
skills = list_skills(query=query)
|
|
2656
|
+
if not skills:
|
|
2657
|
+
console.print("[dim]No skills found.[/dim]")
|
|
2658
|
+
console.print(f"[dim]Directory: {get_skills_dir()}[/dim]")
|
|
2659
|
+
console.print("[dim]Create one with: [bold #5F9EA0]/skills add frontend_design[/bold #5F9EA0][/dim]")
|
|
2660
|
+
return
|
|
2661
|
+
|
|
2662
|
+
table = Table(show_header=True, box=box.SIMPLE_HEAD)
|
|
2663
|
+
table.add_column("Skill", no_wrap=True)
|
|
2664
|
+
table.add_column("Preview")
|
|
2665
|
+
table.add_column("Modified", no_wrap=True)
|
|
2666
|
+
for skill in skills:
|
|
2667
|
+
modified = datetime.fromtimestamp(skill.modified).strftime("%Y-%m-%d %H:%M")
|
|
2668
|
+
table.add_row(f"[bold]{skill.name}[/bold]", skill.preview, modified)
|
|
2669
|
+
console.print(table)
|
|
2670
|
+
console.print("[dim]Load with: [bold #5F9EA0]/skills load <name>[/bold #5F9EA0][/dim]")
|
|
2671
|
+
|
|
2672
|
+
|
|
2673
|
+
def _open_skill_editor(console, debug_mode_container, initial_content):
|
|
2674
|
+
from utils.editor import open_editor_for_content
|
|
2675
|
+
|
|
2676
|
+
return open_editor_for_content(
|
|
2677
|
+
console,
|
|
2678
|
+
initial_content=initial_content,
|
|
2679
|
+
debug_mode=debug_mode_container["debug"],
|
|
2680
|
+
)
|
|
2681
|
+
|
|
2682
|
+
|
|
2683
|
+
def _write_skill_from_editor(console, debug_mode_container, name, initial_content, *, overwrite, verb):
|
|
2684
|
+
from core.skills import write_skill
|
|
2685
|
+
|
|
2686
|
+
success, content = _open_skill_editor(console, debug_mode_container, initial_content)
|
|
2687
|
+
if not success or not content or not content.strip():
|
|
2688
|
+
console.print("[dim]Cancelled.[/dim]")
|
|
2689
|
+
return None
|
|
2690
|
+
path = write_skill(name, content, overwrite=overwrite)
|
|
2691
|
+
console.print(f"[green]{verb} skill '{name}'.[/green] [dim]{path}[/dim]")
|
|
2692
|
+
return path
|
|
2693
|
+
|
|
2694
|
+
|
|
2695
|
+
def _handle_skills(chat_manager, console, debug_mode_container, args, cron_sched=None):
|
|
2696
|
+
"""Handle /skills command — manage reusable prompt snippets."""
|
|
2697
|
+
from core.skills import (
|
|
2698
|
+
SkillError,
|
|
2699
|
+
activate_skill,
|
|
2700
|
+
get_skills_dir,
|
|
2701
|
+
read_skill,
|
|
2702
|
+
remove_skill,
|
|
2703
|
+
validate_skill_name,
|
|
2704
|
+
write_skill,
|
|
2705
|
+
)
|
|
2706
|
+
|
|
2707
|
+
if not args or not args.strip():
|
|
2708
|
+
_skills_list(console)
|
|
2709
|
+
return CommandResult(status="handled")
|
|
2710
|
+
|
|
2711
|
+
args_clean = args.strip()
|
|
2712
|
+
parts = args_clean.split(maxsplit=2)
|
|
2713
|
+
subcmd = parts[0].lower()
|
|
2714
|
+
|
|
2715
|
+
try:
|
|
2716
|
+
if subcmd in ("help", "-h", "--help"):
|
|
2717
|
+
_print_skills_usage(console)
|
|
2718
|
+
return CommandResult(status="handled")
|
|
2719
|
+
|
|
2720
|
+
if subcmd in ("list", "ls"):
|
|
2721
|
+
list_parts = args_clean.split(maxsplit=1)
|
|
2722
|
+
query = list_parts[1] if len(list_parts) > 1 else None
|
|
2723
|
+
_skills_list(console, query=query)
|
|
2724
|
+
return CommandResult(status="handled")
|
|
2725
|
+
|
|
2726
|
+
if subcmd == "dir":
|
|
2727
|
+
console.print(str(get_skills_dir()))
|
|
2728
|
+
return CommandResult(status="handled")
|
|
2729
|
+
|
|
2730
|
+
if subcmd == "show":
|
|
2731
|
+
if len(parts) < 2:
|
|
2732
|
+
console.print("[red]Usage: /skills show <name>[/red]")
|
|
2733
|
+
return CommandResult(status="handled")
|
|
2734
|
+
name = validate_skill_name(parts[1])
|
|
2735
|
+
content = read_skill(name, strip_heading=False)
|
|
2736
|
+
console.print(Markdown(left_align_headings(content), code_theme=MonokaiDarkBGStyle, justify="left"))
|
|
2737
|
+
return CommandResult(status="handled")
|
|
2738
|
+
|
|
2739
|
+
if subcmd in ("load", "use"):
|
|
2740
|
+
if len(parts) < 2:
|
|
2741
|
+
console.print(f"[red]Usage: /skills {subcmd} <name>[/red]")
|
|
2742
|
+
return CommandResult(status="handled")
|
|
2743
|
+
name = validate_skill_name(parts[1])
|
|
2744
|
+
tokens = activate_skill(chat_manager, name, read_skill(name))
|
|
2745
|
+
console.print(f"[green]Activated skill '{name}' for this chat.[/green] [dim](~{tokens:,} tokens)[/dim]")
|
|
2746
|
+
return CommandResult(status="handled")
|
|
2747
|
+
|
|
2748
|
+
if subcmd in ("remove", "rm", "delete"):
|
|
2749
|
+
if len(parts) < 2:
|
|
2750
|
+
console.print("[red]Usage: /skills remove <name>[/red]")
|
|
2751
|
+
return CommandResult(status="handled")
|
|
2752
|
+
name = validate_skill_name(parts[1])
|
|
2753
|
+
from rich.prompt import Confirm
|
|
2754
|
+
if not Confirm.ask(f"Remove skill '{name}'?", default=False):
|
|
2755
|
+
console.print("[dim]Cancelled.[/dim]")
|
|
2756
|
+
return CommandResult(status="handled")
|
|
2757
|
+
remove_skill(name)
|
|
2758
|
+
console.print(f"[green]Removed skill '{name}'.[/green]")
|
|
2759
|
+
return CommandResult(status="handled")
|
|
2760
|
+
|
|
2761
|
+
if subcmd == "edit":
|
|
2762
|
+
if len(parts) < 2:
|
|
2763
|
+
console.print("[red]Usage: /skills edit <name>[/red]")
|
|
2764
|
+
return CommandResult(status="handled")
|
|
2765
|
+
name = validate_skill_name(parts[1])
|
|
2766
|
+
try:
|
|
2767
|
+
initial_content = read_skill(name, strip_heading=False)
|
|
2768
|
+
except SkillError:
|
|
2769
|
+
initial_content = f"# {name}\n\n"
|
|
2770
|
+
_write_skill_from_editor(
|
|
2771
|
+
console,
|
|
2772
|
+
debug_mode_container,
|
|
2773
|
+
name,
|
|
2774
|
+
initial_content,
|
|
2775
|
+
overwrite=True,
|
|
2776
|
+
verb="Saved",
|
|
2777
|
+
)
|
|
2778
|
+
return CommandResult(status="handled")
|
|
2779
|
+
|
|
2780
|
+
if subcmd in ("add", "create", "new"):
|
|
2781
|
+
if len(parts) < 2:
|
|
2782
|
+
console.print(f"[red]Usage: /skills {subcmd} <name> [prompt][/red]")
|
|
2783
|
+
return CommandResult(status="handled")
|
|
2784
|
+
name = validate_skill_name(parts[1])
|
|
2785
|
+
if len(parts) >= 3:
|
|
2786
|
+
path = write_skill(name, parts[2], overwrite=False)
|
|
2787
|
+
console.print(f"[green]Created skill '{name}'.[/green] [dim]{path}[/dim]")
|
|
2788
|
+
return CommandResult(status="handled")
|
|
2789
|
+
_write_skill_from_editor(
|
|
2790
|
+
console,
|
|
2791
|
+
debug_mode_container,
|
|
2792
|
+
name,
|
|
2793
|
+
f"# {name}\n\n",
|
|
2794
|
+
overwrite=False,
|
|
2795
|
+
verb="Created",
|
|
2796
|
+
)
|
|
2797
|
+
return CommandResult(status="handled")
|
|
2798
|
+
|
|
2799
|
+
if subcmd == "modify":
|
|
2800
|
+
cmd_parts = args_clean.split(maxsplit=2)
|
|
2801
|
+
if len(cmd_parts) < 2:
|
|
2802
|
+
console.print("[red]Usage: /skills modify <name> [prompt][/red]")
|
|
2803
|
+
return CommandResult(status="handled")
|
|
2804
|
+
name = validate_skill_name(cmd_parts[1])
|
|
2805
|
+
if len(cmd_parts) < 3:
|
|
2806
|
+
_write_skill_from_editor(
|
|
2807
|
+
console,
|
|
2808
|
+
debug_mode_container,
|
|
2809
|
+
name,
|
|
2810
|
+
read_skill(name, strip_heading=False),
|
|
2811
|
+
overwrite=True,
|
|
2812
|
+
verb="Updated",
|
|
2813
|
+
)
|
|
2814
|
+
return CommandResult(status="handled")
|
|
2815
|
+
read_skill(name)
|
|
2816
|
+
path = write_skill(name, cmd_parts[2], overwrite=True)
|
|
2817
|
+
console.print(f"[green]Updated skill '{name}'.[/green] [dim]{path}[/dim]")
|
|
2818
|
+
return CommandResult(status="handled")
|
|
2819
|
+
|
|
2820
|
+
if len(parts) >= 2:
|
|
2821
|
+
name = validate_skill_name(parts[0])
|
|
2822
|
+
_, body = args_clean.split(maxsplit=1)
|
|
2823
|
+
path = write_skill(name, body, overwrite=False)
|
|
2824
|
+
console.print(f"[green]Created skill '{name}'.[/green] [dim]{path}[/dim]")
|
|
2825
|
+
return CommandResult(status="handled")
|
|
2826
|
+
|
|
2827
|
+
console.print("[red]Usage: /skills add <name> [prompt][/red]")
|
|
2828
|
+
console.print("[dim]Run [bold #5F9EA0]/skills help[/bold #5F9EA0] for all commands.[/dim]")
|
|
2829
|
+
return CommandResult(status="handled")
|
|
2830
|
+
|
|
2831
|
+
except SkillError as e:
|
|
2832
|
+
console.print(f"[red]{e}[/red]")
|
|
2833
|
+
return CommandResult(status="handled")
|
|
2834
|
+
except Exception as e:
|
|
2835
|
+
console.print(f"[red]Skills command failed: {e}[/red]")
|
|
2836
|
+
return CommandResult(status="handled")
|
|
2837
|
+
|
|
2838
|
+
|
|
2436
2839
|
def _handle_obsidian_init(console, obsidian_settings):
|
|
2437
2840
|
"""Handle /obsidian init — scaffold project folder structure in vault."""
|
|
2438
2841
|
if not obsidian_settings.is_active():
|
|
@@ -2658,9 +3061,35 @@ _COMMAND_HANDLERS = {
|
|
|
2658
3061
|
"/cd": _handle_cd,
|
|
2659
3062
|
"/setup": _handle_setup,
|
|
2660
3063
|
"/cron": _handle_cron,
|
|
3064
|
+
"/prompt": _handle_prompt,
|
|
3065
|
+
"/skills": _handle_skills,
|
|
2661
3066
|
}
|
|
2662
3067
|
|
|
2663
3068
|
|
|
3069
|
+
def _handle_shell_command(console, command):
|
|
3070
|
+
"""Execute a shell command prefixed with : and display output."""
|
|
3071
|
+
from utils.settings import tool_settings
|
|
3072
|
+
try:
|
|
3073
|
+
result = subprocess.run(
|
|
3074
|
+
["/bin/sh", "-c", command], capture_output=True, text=True,
|
|
3075
|
+
encoding="utf-8", errors="replace", timeout=tool_settings.command_timeout_sec,
|
|
3076
|
+
)
|
|
3077
|
+
output = ((result.stdout or "") + (result.stderr or "")).strip() or "(no output)"
|
|
3078
|
+
lines = output.splitlines()
|
|
3079
|
+
if len(lines) > 200:
|
|
3080
|
+
output = "\n".join(lines[:100]) + f"\n\n... ({len(lines) - 200} lines omitted) ...\n\n" + "\n".join(lines[-100:])
|
|
3081
|
+
console.print()
|
|
3082
|
+
if result.returncode != 0:
|
|
3083
|
+
console.print(f"[red]exit code: {result.returncode}[/red]")
|
|
3084
|
+
console.print(output)
|
|
3085
|
+
console.print()
|
|
3086
|
+
except subprocess.TimeoutExpired:
|
|
3087
|
+
console.print(f"[red]Command timed out after {tool_settings.command_timeout_sec}s[/red]")
|
|
3088
|
+
except Exception as e:
|
|
3089
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
3090
|
+
return CommandResult(status="handled")
|
|
3091
|
+
|
|
3092
|
+
|
|
2664
3093
|
def process_command(chat_manager, user_input, console, debug_mode_container, cron_scheduler=None):
|
|
2665
3094
|
"""Process command and optionally return replacement content.
|
|
2666
3095
|
|
|
@@ -2681,6 +3110,14 @@ def process_command(chat_manager, user_input, console, debug_mode_container, cro
|
|
|
2681
3110
|
cmd = parts[0].lower()
|
|
2682
3111
|
args = parts[1] if len(parts) > 1 else None
|
|
2683
3112
|
|
|
3113
|
+
# Shell command prefix (:command)
|
|
3114
|
+
if user_input.startswith(":"):
|
|
3115
|
+
shell_cmd = user_input[1:].strip()
|
|
3116
|
+
if shell_cmd:
|
|
3117
|
+
result = _handle_shell_command(console, shell_cmd)
|
|
3118
|
+
return (result.status, result.replacement_input)
|
|
3119
|
+
return ("handled", None)
|
|
3120
|
+
|
|
2684
3121
|
# Look up handler in registry
|
|
2685
3122
|
handler = _COMMAND_HANDLERS.get(cmd)
|
|
2686
3123
|
if handler:
|