bone-agent 1.3.3 → 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 +17 -0
- package/config.yaml.example +5 -2
- package/package.json +1 -1
- package/prompts/main/communication_style.md +1 -1
- package/prompts/main/dream.md +23 -9
- package/prompts/main/skills.md +3 -0
- package/prompts/micro/communication_style.md +1 -1
- package/prompts/micro/skills.md +1 -0
- package/src/core/agentic.py +138 -38
- package/src/core/chat_manager.py +19 -6
- package/src/core/config_manager.py +8 -1
- package/src/core/cron.py +0 -4
- 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 +46 -2
- package/src/llm/prompts.py +12 -7
- package/src/llm/providers.py +3 -1
- package/src/llm/token_tracker.py +15 -0
- 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 +5 -1
- 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 +54 -3
- package/src/tools/helpers/plugin_manifest.py +99 -70
- package/src/tools/review_sub_agent.py +2 -1
- package/src/tools/rg_search.py +24 -7
- package/src/tools/search_plugins.py +140 -72
- package/src/tools/shell.py +3 -3
- package/src/ui/commands.py +355 -33
- package/src/ui/displays.py +26 -1
- package/src/ui/main.py +0 -4
- package/src/ui/tool_confirmation.py +16 -5
- package/src/utils/editor.py +88 -39
- package/src/utils/settings.py +6 -2
- package/src/utils/validation.py +10 -0
package/src/ui/commands.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import Optional
|
|
|
8
8
|
from llm import config
|
|
9
9
|
|
|
10
10
|
from core.config_manager import ConfigManager as ConfigManagerClass
|
|
11
|
-
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
|
|
12
12
|
from ui.banner import display_startup_banner
|
|
13
13
|
from core.agentic import SubAgentPanel
|
|
14
14
|
from ui.setting_selector import SettingSelector, SettingCategory, SettingOption
|
|
@@ -591,12 +591,15 @@ def _handle_clear(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
591
591
|
conv_cache_creation = chat_manager.token_tracker.conv_cache_creation_tokens
|
|
592
592
|
if conv_cache_read > 0 or conv_cache_creation > 0:
|
|
593
593
|
total_cached = conv_cache_read + conv_cache_creation
|
|
594
|
-
|
|
594
|
+
cache_activity_read_pct = (
|
|
595
595
|
conv_cache_read / total_cached * 100
|
|
596
596
|
) if total_cached > 0 else 0
|
|
597
|
+
cache_coverage_pct = (
|
|
598
|
+
conv_cache_read / conv_in * 100
|
|
599
|
+
) if conv_in > 0 else 0
|
|
597
600
|
console.print(f" Cache read: {conv_cache_read:,} tokens")
|
|
598
601
|
console.print(f" Cache write: {conv_cache_creation:,} tokens")
|
|
599
|
-
console.print(f" ({
|
|
602
|
+
console.print(f" ({cache_coverage_pct:.0f}% input cached, {cache_activity_read_pct:.0f}% cache reads)")
|
|
600
603
|
|
|
601
604
|
# Display cost — combined actual + estimated, with config-based fallback
|
|
602
605
|
tracker_conv = chat_manager.token_tracker
|
|
@@ -665,11 +668,12 @@ def _open_provider_editor(chat_manager, console, provider):
|
|
|
665
668
|
min_val=0.0, step=0.01,
|
|
666
669
|
))
|
|
667
670
|
|
|
668
|
-
|
|
671
|
+
provider_label = config.get_provider_display_name(provider)
|
|
672
|
+
category = SettingCategory(title=f"{provider_label} Settings", settings=settings)
|
|
669
673
|
|
|
670
674
|
selector = SettingSelector(
|
|
671
675
|
categories=[category],
|
|
672
|
-
title=f"Configure {
|
|
676
|
+
title=f"Configure {provider_label}",
|
|
673
677
|
)
|
|
674
678
|
|
|
675
679
|
changes = selector.run()
|
|
@@ -742,7 +746,8 @@ def _handle_provider(chat_manager, console, debug_mode_container, args, cron_sch
|
|
|
742
746
|
# Validate provider name
|
|
743
747
|
if provider not in config.get_providers():
|
|
744
748
|
console.print(f"[red]Error: Unknown provider '{provider}'[/red]")
|
|
745
|
-
|
|
749
|
+
available = ', '.join(config.get_provider_display_name(prov) for prov in config.get_providers())
|
|
750
|
+
console.print(f"[dim]Available providers: {available}[/dim]")
|
|
746
751
|
return CommandResult(status="handled")
|
|
747
752
|
|
|
748
753
|
# Switch directly to the named provider
|
|
@@ -756,7 +761,7 @@ def _handle_provider(chat_manager, console, debug_mode_container, args, cron_sch
|
|
|
756
761
|
|
|
757
762
|
cfg = config.get_provider_config(provider)
|
|
758
763
|
model = cfg.get('model') or cfg.get('api_model') or ''
|
|
759
|
-
label =
|
|
764
|
+
label = config.get_provider_display_name(provider)
|
|
760
765
|
if model:
|
|
761
766
|
label += f" ({model})"
|
|
762
767
|
console.print(f"[green]Switched to {label}[/green]")
|
|
@@ -772,7 +777,7 @@ def _handle_provider(chat_manager, console, debug_mode_container, args, cron_sch
|
|
|
772
777
|
for prov in config.get_providers():
|
|
773
778
|
cfg = config.get_provider_config(prov)
|
|
774
779
|
model = cfg.get('model') or cfg.get('api_model') or ''
|
|
775
|
-
entry = {"value": prov, "text":
|
|
780
|
+
entry = {"value": prov, "text": config.get_provider_display_name(prov)}
|
|
776
781
|
if model:
|
|
777
782
|
entry["description"] = model[:40]
|
|
778
783
|
provider_options.append(entry)
|
|
@@ -1092,18 +1097,38 @@ def _handle_usage(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
1092
1097
|
console.print(f" Output tokens: {tracker.total_completion_tokens:,}")
|
|
1093
1098
|
console.print(f" Total tokens: {tracker.total_tokens:,}")
|
|
1094
1099
|
|
|
1095
|
-
# Display cache token breakdown
|
|
1096
|
-
|
|
1097
|
-
|
|
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:
|
|
1098
1109
|
total_cached = tracker.total_cache_read_tokens + tracker.total_cache_creation_tokens
|
|
1099
|
-
|
|
1110
|
+
cache_activity_read_pct = (
|
|
1100
1111
|
tracker.total_cache_read_tokens
|
|
1101
1112
|
/ total_cached * 100
|
|
1102
1113
|
) if total_cached > 0 else 0
|
|
1114
|
+
cache_coverage_pct = (
|
|
1115
|
+
tracker.total_cache_read_tokens
|
|
1116
|
+
/ tracker.total_prompt_tokens * 100
|
|
1117
|
+
) if tracker.total_prompt_tokens > 0 else 0
|
|
1103
1118
|
console.print()
|
|
1104
|
-
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
|
+
)
|
|
1105
1123
|
console.print(f" Cache read: {tracker.total_cache_read_tokens:,} tokens")
|
|
1106
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]")
|
|
1107
1132
|
console.print()
|
|
1108
1133
|
|
|
1109
1134
|
|
|
@@ -2184,8 +2209,8 @@ def _handle_obsidian(chat_manager, console, debug_mode_container, args, cron_sch
|
|
|
2184
2209
|
return CommandResult(status="handled")
|
|
2185
2210
|
|
|
2186
2211
|
|
|
2187
|
-
def
|
|
2188
|
-
"""Persist
|
|
2212
|
+
def _persist_tool_visibility(console):
|
|
2213
|
+
"""Persist tool and skill visibility state to config file.
|
|
2189
2214
|
|
|
2190
2215
|
Returns True on success, False on failure.
|
|
2191
2216
|
"""
|
|
@@ -2194,6 +2219,7 @@ def _persist_disabled_tools(console):
|
|
|
2194
2219
|
if "TOOL_SETTINGS" not in cfg_data:
|
|
2195
2220
|
cfg_data["TOOL_SETTINGS"] = {}
|
|
2196
2221
|
cfg_data["TOOL_SETTINGS"]["disabled_tools"] = list(tool_settings.disabled_tools)
|
|
2222
|
+
cfg_data["TOOL_SETTINGS"]["hidden_skills"] = list(tool_settings.hidden_skills)
|
|
2197
2223
|
config_manager.save(cfg_data)
|
|
2198
2224
|
return True
|
|
2199
2225
|
except Exception as e:
|
|
@@ -2202,18 +2228,23 @@ def _persist_disabled_tools(console):
|
|
|
2202
2228
|
|
|
2203
2229
|
|
|
2204
2230
|
def _handle_tools(chat_manager, console, debug_mode_container, args, cron_scheduler=None):
|
|
2205
|
-
"""Handle /tools command —
|
|
2231
|
+
"""Handle /tools command — manage tool availability and skill discovery visibility.
|
|
2206
2232
|
|
|
2207
|
-
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.
|
|
2208
2235
|
Subcommands:
|
|
2209
|
-
list — show all tools
|
|
2210
|
-
enable <name> — enable a
|
|
2211
|
-
disable <name> — disable a
|
|
2212
|
-
|
|
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)
|
|
2213
2242
|
disable-group <key> — disable all tools in a group
|
|
2214
2243
|
"""
|
|
2244
|
+
from core.skills import iter_skill_summaries, validate_skill_name
|
|
2215
2245
|
from ui.setting_selector import SettingOption, SettingCategory, SettingSelector
|
|
2216
2246
|
from tools.helpers.base import ToolRegistry, TOOL_GROUPS
|
|
2247
|
+
from tools.helpers.plugin_manifest import plugin_manifest
|
|
2217
2248
|
|
|
2218
2249
|
# Text subcommands
|
|
2219
2250
|
if args:
|
|
@@ -2221,17 +2252,27 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2221
2252
|
|
|
2222
2253
|
if args_clean.lower() in ("list", "status"):
|
|
2223
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)
|
|
2224
2257
|
disabled = ToolRegistry.get_disabled()
|
|
2225
|
-
|
|
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
|
+
)
|
|
2226
2269
|
console.print()
|
|
2227
2270
|
|
|
2228
|
-
# Build reverse lookup: tool_name -> group_label
|
|
2229
2271
|
tool_to_group = {}
|
|
2230
2272
|
for gkey, gdef in TOOL_GROUPS.items():
|
|
2231
2273
|
for tname in gdef["tools"]:
|
|
2232
2274
|
tool_to_group.setdefault(tname, []).append(gdef["label"])
|
|
2233
2275
|
|
|
2234
|
-
# Group tools for display
|
|
2235
2276
|
current_group = None
|
|
2236
2277
|
for t in all_tools:
|
|
2237
2278
|
groups = tool_to_group.get(t.name, [])
|
|
@@ -2243,6 +2284,18 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2243
2284
|
status = "[red]off[/red]" if is_off else "[green]on[/green] "
|
|
2244
2285
|
console.print(f" {status} {t.name}")
|
|
2245
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
|
+
|
|
2246
2299
|
console.print()
|
|
2247
2300
|
console.print("[dim]Groups:[/dim] " + ", ".join(
|
|
2248
2301
|
f"[bold]{k}[/bold] ({v['label']})" for k, v in TOOL_GROUPS.items()
|
|
@@ -2281,17 +2334,17 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2281
2334
|
|
|
2282
2335
|
# Sync and persist
|
|
2283
2336
|
tool_settings.disabled_tools = sorted(ToolRegistry.get_disabled())
|
|
2284
|
-
|
|
2337
|
+
_persist_tool_visibility(console)
|
|
2285
2338
|
console.print()
|
|
2286
2339
|
return CommandResult(status="handled")
|
|
2287
2340
|
|
|
2288
|
-
# Single tool operations
|
|
2341
|
+
# Single tool/plugin operations
|
|
2289
2342
|
if action in ("enable", "disable"):
|
|
2290
|
-
# Match case-insensitively against registered tools
|
|
2291
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()})
|
|
2292
2345
|
matched = all_registered_lower.get(target.lower())
|
|
2293
2346
|
if not matched:
|
|
2294
|
-
console.print(f"[red]Unknown tool: {target}[/red]")
|
|
2347
|
+
console.print(f"[red]Unknown tool or plugin: {target}[/red]")
|
|
2295
2348
|
console.print(f"[dim]Run [bold #5F9EA0]/tools list[/bold #5F9EA0] to see all tools.[/dim]")
|
|
2296
2349
|
return CommandResult(status="handled")
|
|
2297
2350
|
|
|
@@ -2305,17 +2358,44 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2305
2358
|
tool_settings.disabled_tools.append(matched)
|
|
2306
2359
|
console.print(f"[yellow]Disabled: {matched}[/yellow]")
|
|
2307
2360
|
|
|
2308
|
-
|
|
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)
|
|
2309
2387
|
console.print()
|
|
2310
2388
|
return CommandResult(status="handled")
|
|
2311
2389
|
|
|
2312
2390
|
console.print(f"[red]Unknown subcommand: {args}[/red]")
|
|
2313
|
-
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>]")
|
|
2314
2392
|
return CommandResult(status="handled")
|
|
2315
2393
|
|
|
2316
2394
|
# No args — interactive toggle UI, organized by groups
|
|
2317
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()}
|
|
2318
2397
|
disabled = ToolRegistry.get_disabled()
|
|
2398
|
+
hidden_skills = set(tool_settings.hidden_skills)
|
|
2319
2399
|
|
|
2320
2400
|
categories = []
|
|
2321
2401
|
for gkey, gdef in TOOL_GROUPS.items():
|
|
@@ -2355,10 +2435,36 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2355
2435
|
input_type="boolean",
|
|
2356
2436
|
on_text="ON",
|
|
2357
2437
|
off_text="OFF",
|
|
2358
|
-
description=f"Modes: {modes}",
|
|
2359
2438
|
))
|
|
2360
2439
|
categories.append(SettingCategory(title="Other", settings=other_options))
|
|
2361
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
|
+
|
|
2362
2468
|
selector = SettingSelector(
|
|
2363
2469
|
categories=categories,
|
|
2364
2470
|
title="Tool Settings",
|
|
@@ -2377,7 +2483,17 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2377
2483
|
# Apply changes
|
|
2378
2484
|
newly_disabled = []
|
|
2379
2485
|
newly_enabled = []
|
|
2486
|
+
newly_hidden_skills = []
|
|
2487
|
+
newly_visible_skills = []
|
|
2380
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
|
+
|
|
2381
2497
|
if not enabled and name not in disabled:
|
|
2382
2498
|
ToolRegistry.disable(name)
|
|
2383
2499
|
newly_disabled.append(name)
|
|
@@ -2385,10 +2501,13 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2385
2501
|
ToolRegistry.enable(name)
|
|
2386
2502
|
newly_enabled.append(name)
|
|
2387
2503
|
|
|
2388
|
-
# Sync tool_settings.disabled_tools to be the full current disabled set
|
|
2389
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)
|
|
2390
2509
|
|
|
2391
|
-
|
|
2510
|
+
_persist_tool_visibility(console)
|
|
2392
2511
|
|
|
2393
2512
|
# Summary
|
|
2394
2513
|
change_lines = []
|
|
@@ -2396,6 +2515,10 @@ def _handle_tools(chat_manager, console, debug_mode_container, args, cron_schedu
|
|
|
2396
2515
|
change_lines.append(f" [yellow]Disabled:[/yellow] {name}")
|
|
2397
2516
|
for name in newly_enabled:
|
|
2398
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}")
|
|
2399
2522
|
|
|
2400
2523
|
if change_lines:
|
|
2401
2524
|
total_enabled = len(ToolRegistry.get_all())
|
|
@@ -2461,6 +2584,12 @@ def _handle_cd(chat_manager, console, debug_mode_container, args, cron_scheduler
|
|
|
2461
2584
|
except Exception:
|
|
2462
2585
|
pass
|
|
2463
2586
|
|
|
2587
|
+
# Rebuild system prompt so project root stays current
|
|
2588
|
+
try:
|
|
2589
|
+
chat_manager.update_system_prompt()
|
|
2590
|
+
except Exception:
|
|
2591
|
+
pass
|
|
2592
|
+
|
|
2464
2593
|
return CommandResult(status="handled")
|
|
2465
2594
|
|
|
2466
2595
|
|
|
@@ -2515,6 +2644,198 @@ def _handle_prompt(chat_manager, console, debug_mode_container, args, cron_sched
|
|
|
2515
2644
|
return CommandResult(status="handled")
|
|
2516
2645
|
|
|
2517
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
|
+
|
|
2518
2839
|
def _handle_obsidian_init(console, obsidian_settings):
|
|
2519
2840
|
"""Handle /obsidian init — scaffold project folder structure in vault."""
|
|
2520
2841
|
if not obsidian_settings.is_active():
|
|
@@ -2741,6 +3062,7 @@ _COMMAND_HANDLERS = {
|
|
|
2741
3062
|
"/setup": _handle_setup,
|
|
2742
3063
|
"/cron": _handle_cron,
|
|
2743
3064
|
"/prompt": _handle_prompt,
|
|
3065
|
+
"/skills": _handle_skills,
|
|
2744
3066
|
}
|
|
2745
3067
|
|
|
2746
3068
|
|
package/src/ui/displays.py
CHANGED
|
@@ -22,7 +22,7 @@ def show_provider_table(current_provider: str, console):
|
|
|
22
22
|
else:
|
|
23
23
|
status = "✅" if cfg.get("api_key") else "❌ (set API key)"
|
|
24
24
|
active = " [green](active)[/green]" if provider == current_provider else ""
|
|
25
|
-
table.add_row(
|
|
25
|
+
table.add_row(config.get_provider_display_name(provider), status, f"{model[:40]}{active}")
|
|
26
26
|
|
|
27
27
|
console.print(table)
|
|
28
28
|
|
|
@@ -66,6 +66,7 @@ def show_help_table(console):
|
|
|
66
66
|
table.add_row("[bold #5F9EA0]/cd[/bold #5F9EA0] [path]", "Change working directory (no args to show current)")
|
|
67
67
|
table.add_row("[bold #5F9EA0]/edit[/bold #5F9EA0], [bold #5F9EA0]/e[/bold #5F9EA0]", "Open editor for multi-line input")
|
|
68
68
|
table.add_row("[bold #5F9EA0]/review[/bold #5F9EA0] [args], [bold #5F9EA0]/r[/bold #5F9EA0]", "Code review git changes (e.g. /review --staged, /review main..HEAD)")
|
|
69
|
+
table.add_row("[bold #5F9EA0]/skills[/bold #5F9EA0] [list|add|modify|remove|use]", "Manage reusable prompt skills")
|
|
69
70
|
table.add_row("[bold #5F9EA0]/obsidian[/bold #5F9EA0] [set|enable|disable|status|init]", "Manage vault integration, scaffold project folders")
|
|
70
71
|
table.add_row("[bold #5F9EA0]/tools[/bold #5F9EA0] [list|enable|disable|enable-group|disable-group]", "Toggle tools or groups (e.g. file_ops, task_mgmt)")
|
|
71
72
|
table.add_row("[bold #5F9EA0]/setup[/bold #5F9EA0]", "Re-run the first-run setup wizard")
|
|
@@ -142,6 +143,30 @@ def show_cron_help_table(console):
|
|
|
142
143
|
console.print("")
|
|
143
144
|
|
|
144
145
|
|
|
146
|
+
def show_skills_help_table(console):
|
|
147
|
+
"""Display skills command help table.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
console: Rich Console instance for output.
|
|
151
|
+
"""
|
|
152
|
+
console.print("")
|
|
153
|
+
table = Table(show_header=True, box=box.SIMPLE_HEAD)
|
|
154
|
+
table.add_column("Command", no_wrap=True)
|
|
155
|
+
table.add_column("Description")
|
|
156
|
+
|
|
157
|
+
table.add_row("[bold #5F9EA0]/skills list[/bold #5F9EA0]", "List skills")
|
|
158
|
+
table.add_row("[bold #5F9EA0]/skills add[/bold #5F9EA0] <name>", "Create a skill in your editor")
|
|
159
|
+
table.add_row("[bold #5F9EA0]/skills edit[/bold #5F9EA0] <name>", "Edit an existing skill")
|
|
160
|
+
table.add_row("[bold #5F9EA0]/skills modify[/bold #5F9EA0] <name> <prompt>", "Replace a skill")
|
|
161
|
+
table.add_row("[bold #5F9EA0]/skills show[/bold #5F9EA0] <name>", "Show a skill")
|
|
162
|
+
table.add_row("[bold #5F9EA0]/skills load[/bold #5F9EA0] <name>", "Load a skill into this chat")
|
|
163
|
+
table.add_row("[bold #5F9EA0]/skills remove[/bold #5F9EA0] <name>", "Delete a skill")
|
|
164
|
+
table.add_row("[bold #5F9EA0]/skills dir[/bold #5F9EA0]", "Show the skills directory")
|
|
165
|
+
|
|
166
|
+
console.print(Panel(table, title="[bold #5F9EA0]Skills[/bold #5F9EA0]", border_style="grey23", padding=(0, 2)))
|
|
167
|
+
console.print("")
|
|
168
|
+
|
|
169
|
+
|
|
145
170
|
def show_config_overview(chat_manager, console, debug_mode_container, current_provider):
|
|
146
171
|
"""Display comprehensive configuration overview.
|
|
147
172
|
|
package/src/ui/main.py
CHANGED
|
@@ -35,7 +35,6 @@ from core.agentic import agentic_answer
|
|
|
35
35
|
from utils.settings import MonokaiDarkBGStyle, left_align_headings
|
|
36
36
|
from utils.paths import REPO_ROOT, RG_EXE_PATH
|
|
37
37
|
from exceptions import BoneAgentError
|
|
38
|
-
from tools.loader import load_all_tools
|
|
39
38
|
|
|
40
39
|
# Console setup
|
|
41
40
|
console = Console(theme=Theme({
|
|
@@ -382,9 +381,6 @@ def main():
|
|
|
382
381
|
"""Main interactive chat loop."""
|
|
383
382
|
|
|
384
383
|
# Load all tools (built-in and user tools)
|
|
385
|
-
# This populates the ToolRegistry with all decorated tools
|
|
386
|
-
load_all_tools()
|
|
387
|
-
|
|
388
384
|
# Check for config.yaml — run setup wizard on first run
|
|
389
385
|
from ui.setup_wizard import is_first_run, run_wizard as _run_setup_wizard
|
|
390
386
|
|